// @ts-check
/**
* @import {ExpressRequestAuthorized, ExpressResponse} from '../types.js'
*/
/**
* OrgaSettings Router
*
* Endpoints for managing per-organisation settings stored in Vault.
* All routes require admin privileges (checkAdmin middleware).
*
* Mounted at: /api/kpe20/orgaSettings (see http-server.js)
*
* @swagger
* tags:
* - name: OrgaSettings
* description: >
* Per-organisation settings stored in Vault —
* domain mappings and SMTP configuration.
*
* components:
* schemas:
* DomainMap:
* type: object
* description: Map of domain names to their type (internal or external)
* additionalProperties:
* type: string
* enum: [internal, external]
* example:
* myclub: internal
* myclub.de: external
* DomainValidationError:
* type: object
* properties:
* domain:
* type: string
* example: myclub
* error:
* type: string
* example: '"myclub" is a reserved system name.'
* AppEntry:
* type: object
* required: [domain, roles, title]
* properties:
* domain:
* type: string
* description: App URL or hostname
* example: db2.app.kpe.de
* roles:
* type: array
* items:
* type: string
* description: Keycloak roles that grant access
* example: [db-admin, db-user]
* title:
* type: string
* example: KPE Mitglieder Datenbank
* description:
* type: string
* example: KPE Filesharing
* port:
* type: integer
* example: 80
* AppsMap:
* type: object
* description: Map of app identifiers to their configuration
* additionalProperties:
* $ref: '#/components/schemas/AppEntry'
* example:
* member.app:
* domain: db2.app.kpe.de
* roles: [db-admin, db-user]
* title: KPE Mitglieder Datenbank
* IconUploadResponse:
* type: object
* properties:
* success:
* type: boolean
* example: true
* iconUrl:
* type: string
* format: uri
* description: Public URL of the uploaded icon in the commtool-public bucket
* example: https://minio.commtool.org/commtool-public/abc123/icons/member.app.png
* MailSettings:
* type: object
* properties:
* host:
* type: string
* example: smtp.example.com
* port:
* type: integer
* example: 587
* user:
* type: string
* example: noreply@example.com
* password:
* type: string
* description: Masked as •••••••• when reading; pass the sentinel to keep the existing password unchanged.
* example: ••••••••
* userName:
* type: string
* example: No-Reply
* from:
* type: string
* example: noreply@example.com
* secure:
* type: boolean
* example: true
*/
import express from 'express';
import { checkAdmin } from '../utils/authChecks.js';
import { requestUpdateLogger } from '../utils/requestLogger.js';
import * as orgaSettingsController from './orgaSettings/controller.js';
/** @type {express.Express} */
const api = express();
// ── Domains ───────────────────────────────────────────────────────────────────
/**
* @swagger
* /api/kpe20/orgaSettings/domains:
* get:
* summary: Load domain settings for the current organisation
* description: >
* Returns the full domain map (domain → type) stored in Vault
* for the authenticated user's organisation.
* tags: [OrgaSettings]
* security:
* - bearerAuth: []
* responses:
* 200:
* description: Domain map retrieved successfully
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: true
* result:
* $ref: '#/components/schemas/DomainMap'
* 400:
* description: No organisation in session
* 500:
* description: Failed to load domain settings
*/
// @ts-ignore
api.get('/domains', checkAdmin, orgaSettingsController.getDomainsController);
/**
* @swagger
* /api/kpe20/orgaSettings/domains:
* put:
* summary: Save domain settings for the current organisation
* description: >
* Validates and persists the domain map for the authenticated user's
* organisation in Vault. Each entry maps a domain name to its type
* ("internal" or "external"). Validation checks format rules and
* conflicts with other organisations.
* tags: [OrgaSettings]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/DomainMap'
* example:
* myclub: internal
* myclub.de: external
* responses:
* 200:
* description: Domains saved successfully
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: true
* 400:
* description: Validation errors or missing organisation
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: false
* errors:
* type: array
* items:
* $ref: '#/components/schemas/DomainValidationError'
* 500:
* description: Failed to save domain settings
*/
// @ts-ignore
api.put('/domains', checkAdmin, requestUpdateLogger, orgaSettingsController.saveDomainsController);
// ── Apps ─────────────────────────────────────────────────────────────────────
/**
* @swagger
* /api/kpe20/orgaSettings/apps:
* get:
* summary: Load app registry for the current organisation
* description: >
* Returns all configured apps (domain, roles, title, etc.) stored in
* Vault for the authenticated user's organisation.
* tags: [OrgaSettings]
* security:
* - bearerAuth: []
* responses:
* 200:
* description: App registry retrieved successfully
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: true
* result:
* $ref: '#/components/schemas/AppsMap'
* 400:
* description: No organisation in session
* 500:
* description: Failed to load app settings
*/
// @ts-ignore
api.get('/apps', checkAdmin, orgaSettingsController.getAppsController);
/**
* @swagger
* /api/kpe20/orgaSettings/apps:
* put:
* summary: Save app registry for the current organisation
* description: >
* Persists the full app registry for the authenticated user's organisation
* in Vault. The entire object is replaced; pass the full updated map.
* tags: [OrgaSettings]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/AppsMap'
* responses:
* 200:
* description: App registry saved
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: true
* 400:
* description: Invalid request body or missing organisation
* 500:
* description: Failed to save app settings
*/
// @ts-ignore
api.post('/apps', checkAdmin, requestUpdateLogger, orgaSettingsController.saveAppsController);
/**
* @swagger
* /api/kpe20/orgaSettings/apps/{appId}/icon:
* post:
* summary: Upload an icon for an app
* description: >
* Uploads an image file (PNG, JPG, SVG, GIF or WebP) to the
* `commtool-public` MinIO bucket under `{orgId}/icons/{appId}.{ext}`
* and persists the resulting public URL in Vault under
* `apps[appId].icon` for the current organisation.
* tags: [OrgaSettings]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: appId
* required: true
* schema:
* type: string
* description: The app identifier key (e.g. member.app)
* requestBody:
* required: true
* content:
* multipart/form-data:
* schema:
* type: object
* properties:
* files:
* type: string
* format: binary
* description: Image file (PNG / JPG / SVG / GIF / WebP, max recommended 512 KB)
* responses:
* 200:
* description: Icon uploaded and URL stored in Vault
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/IconUploadResponse'
* 400:
* description: Unsupported file type, missing app or missing organisation
* 500:
* description: Upload or Vault write failed
*/
// @ts-ignore
api.post('/apps/:appId/icon', checkAdmin, requestUpdateLogger, orgaSettingsController.uploadAppIconController);
// ── Mail / SMTP ───────────────────────────────────────────────────────────────
/**
* @swagger
* /api/kpe20/orgaSettings/mail:
* get:
* summary: Load SMTP settings for the current organisation
* description: >
* Returns the SMTP configuration stored in Vault for the authenticated
* user's organisation. The `password` field is masked before being
* returned.
* tags: [OrgaSettings]
* security:
* - bearerAuth: []
* responses:
* 200:
* description: SMTP settings retrieved (password masked)
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: true
* result:
* $ref: '#/components/schemas/MailSettings'
* 400:
* description: No organisation in session
* 500:
* description: Failed to load mail settings
*/
// @ts-ignore
api.get('/mail', checkAdmin, orgaSettingsController.getMailController);
/**
* @swagger
* /api/kpe20/orgaSettings/mail:
* post:
* summary: Save SMTP settings for the current organisation
* description: >
* Validates and persists SMTP configuration for the authenticated
* user's organisation in Vault. If `password` is the mask sentinel
* (••••••••) the existing password is preserved unchanged.
* tags: [OrgaSettings]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/MailSettings'
* responses:
* 200:
* description: SMTP settings saved
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: true
* 400:
* description: Validation error (missing host/user or invalid port)
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: false
* message:
* type: string
* example: 'Field "host" is required.'
* 500:
* description: Failed to save mail settings
*/
// @ts-ignore
api.post('/mail', checkAdmin, requestUpdateLogger, orgaSettingsController.saveMailController);
export default api;