// @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;