Source: Router/extern.js

// @ts-check
/**
 * @import {ExpressRequestAuthorized, ExpressResponse} from '../types.js'
 */
/**
 * External Member Router
 *
 * Persons with type `extern` are associated with a group but are not full
 * organisation members. They share the same data structure as regular persons
 * and reuse most of the person read/update logic.
 *
 * Extern ↔ person conversions are handled transparently:
 * - Converting to extern downgrades `familyFees` → `family`.
 * - `hierarchie` and `stage` are always forced to 0 for externs.
 *
 * @swagger
 * tags:
 *   - name: Extern
 *     description: |
 *       External-member management. Externs are persons linked to a group
 *       without holding full organisational membership rights.
 *
 * components:
 *   schemas:
 *     ExternResult:
 *       type: object
 *       description: Extern record as returned by the API
 *       properties:
 *         UID:
 *           type: string
 *           format: uuid
 *           example: a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d
 *         Type:
 *           type: string
 *           enum: [extern]
 *         Display:
 *           type: string
 *           example: "Mustermann, Max"
 *         hierarchie:
 *           type: integer
 *           enum: [0]
 *         stage:
 *           type: integer
 *           enum: [0]
 *         Data:
 *           $ref: '#/components/schemas/PersonData'
 */
import express from 'express';
import { checkObjectAdmin, checkVisible, checkAdmin } from '../utils/authChecks.js';
import { requestUpdateLogger, readLogger } from '../utils/requestLogger.js';
import { validateSingleUID, validateUUID } from '../utils/uuidValidation.js';
import * as externController from './person/externController.js';

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

// ---------------------------------------------------------------------------
// Mutation routes
// ---------------------------------------------------------------------------

/**
 * @swagger
 * /api/kpe20/extern/{group}:
 *   put:
 *     summary: Create or update an extern in a group
 *     description: |
 *       Performs a full upsert: inserts a new extern when the body UID does not
 *       yet exist, or updates the existing record (including type migration from
 *       `person` → `extern`). Always sets `hierarchie = 0` and `stage = 0`.
 *       When downgrading from person the fee type is changed from `familyFees`
 *       to `family`.
 *     tags:
 *       - Extern
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: group
 *         required: true
 *         schema:
 *           type: string
 *           format: uuid
 *         description: UUID of the group to add/update the extern in
 *       - in: query
 *         name: timestamp
 *         required: false
 *         schema:
 *           type: string
 *         description: Backdate timestamp (ISO 8601 or Unix ms)
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             allOf:
 *               - $ref: '#/components/schemas/PersonData'
 *               - type: object
 *                 required: [UID, firstName, lastName, gender]
 *                 properties:
 *                   UID:
 *                     type: string
 *                     format: uuid
 *     responses:
 *       200:
 *         description: Extern created or updated
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 success:
 *                   type: boolean
 *                   example: true
 *                 result:
 *                   $ref: '#/components/schemas/ExternResult'
 *       300:
 *         description: Validation error (invalid group UID or invalid body UID format)
 */
// @ts-ignore
api.put('/:group', validateUUID(['group']), requestUpdateLogger, checkObjectAdmin, externController.createOrUpdateExtern);

/**
 * @swagger
 * /api/kpe20/extern/{UID}:
 *   post:
 *     summary: Partially update an extern's profile data
 *     description: |
 *       Merges the request body over existing Member-table data. Only supplied
 *       fields are changed. Delegates to the person update logic since externs
 *       share the same data structure.
 *     tags:
 *       - Extern
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: UID
 *         required: true
 *         schema:
 *           type: string
 *           format: uuid
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             $ref: '#/components/schemas/PersonData'
 *     responses:
 *       200:
 *         description: Extern updated successfully
 */
// @ts-ignore
api.post('/:UID', validateSingleUID, requestUpdateLogger, checkObjectAdmin, externController.updateExtern);

/**
 * @swagger
 * /api/kpe20/extern/{group}/{UID}:
 *   post:
 *     summary: Update group membership for an extern
 *     description: |
 *       Changes which group an extern belongs to, or updates membership metadata
 *       (role, stage, hierarchy). Handles migration between groups automatically.
 *     tags:
 *       - Extern
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: group
 *         required: true
 *         schema:
 *           type: string
 *           format: uuid
 *         description: Target group UUID
 *       - in: path
 *         name: UID
 *         required: true
 *         schema:
 *           type: string
 *           format: uuid
 *         description: Extern UUID
 *     responses:
 *       200:
 *         description: Membership updated successfully
 */
// @ts-ignore
api.post('/:group/:UID', validateUUID(['group', 'UID']), requestUpdateLogger, checkObjectAdmin, externController.updateGroupMembership);

/**
 * @swagger
 * /api/kpe20/extern/rebuildAccess/{UID}:
 *   post:
 *     summary: Rebuild visibility and list access for an extern
 *     description: |
 *       Rebuilds all `Visible` and list-access records for the extern and each
 *       of its `job` child objects. Equivalent to the per-person step in the
 *       maintenance visibility rebuild. Requires org-level admin.
 *     tags:
 *       - Extern
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: UID
 *         required: true
 *         schema:
 *           type: string
 *           format: uuid
 *         description: UUID of the extern
 *     responses:
 *       200:
 *         description: Rebuild completed successfully
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 success:
 *                   type: boolean
 *                   example: true
 *       403:
 *         description: Not authorised — org-level admin required
 */
// @ts-ignore
api.post('/rebuildAccess/:UID', validateSingleUID, checkAdmin, requestUpdateLogger, externController.rebuildExternAccess);

/**
 * @swagger
 * /api/kpe20/extern/{UID}:
 *   delete:
 *     summary: Delete an extern and all associated data
 *     description: |
 *       Permanently removes the extern along with child objects of type
 *       `guest`, `job`, and `entry`, plus all associated `Links` records.
 *       Triggers a WebSocket update on affected parent groups.
 *     tags:
 *       - Extern
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: UID
 *         required: true
 *         schema:
 *           type: string
 *           format: uuid
 *     responses:
 *       200:
 *         description: Extern deleted successfully
 */
// @ts-ignore
api.delete('/:UID', validateSingleUID, requestUpdateLogger, checkObjectAdmin, externController.deleteExtern);

// ---------------------------------------------------------------------------
// Read routes
// ---------------------------------------------------------------------------

/**
 * @swagger
 * /api/kpe20/extern/{UID}:
 *   get:
 *     summary: Get extern details
 *     description: |
 *       Returns the full record for an extern. Delegates to the person endpoint
 *       since externs share the same data structure.
 *     tags:
 *       - Extern
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: UID
 *         required: true
 *         schema:
 *           type: string
 *           format: uuid
 *     responses:
 *       200:
 *         description: Extern details
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 success:
 *                   type: boolean
 *                 result:
 *                   $ref: '#/components/schemas/ExternResult'
 */
// @ts-ignore
api.get('/:UID', validateSingleUID, readLogger, externController.getExtern);

/**
 * @swagger
 * /api/kpe20/extern/admin/{UID}:
 *   get:
 *     summary: Check admin rights for an extern
 *     description: Returns whether the current user has object-admin privileges for the extern.
 *     tags:
 *       - Extern
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: UID
 *         required: true
 *         schema:
 *           type: string
 *           format: uuid
 *     responses:
 *       200:
 *         description: Admin check result
 */
// @ts-ignore
api.get('/admin/:UID', validateSingleUID, externController.getExternAdmin);

/**
 * @swagger
 * /api/kpe20/extern/history/{UID}:
 *   get:
 *     summary: Get extern membership history
 *     description: Returns the historical group-membership records for an extern.
 *     tags:
 *       - Extern
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: UID
 *         required: true
 *         schema:
 *           type: string
 *           format: uuid
 *     responses:
 *       200:
 *         description: Extern history entries
 */
// @ts-ignore
api.get('/history/:UID', validateSingleUID, checkVisible, externController.getExternHistory);

/**
 * @swagger
 * /api/kpe20/extern/duplicates/{firstName}/{lastName}:
 *   get:
 *     summary: Find duplicate externs by name
 *     description: |
 *       Uses phonetic matching to find existing externs or persons whose name
 *       is similar to the given first and last name.
 *     tags:
 *       - Extern
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: firstName
 *         required: true
 *         schema:
 *           type: string
 *       - in: path
 *         name: lastName
 *         required: true
 *         schema:
 *           type: string
 *     responses:
 *       200:
 *         description: List of potential duplicates
 */
// @ts-ignore
api.get('/duplicates/:firstName/:lastName', externController.getExternDuplicates);

export default api;