iobroker.time-tracker 0.7.7 → 0.7.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/web/api.ts"],
4
- "sourcesContent": ["/**\n * REST API of the adapter.\n *\n * The endpoints are registered on the server agnostic router, so the same table can later be mounted on an own\n * HTTP server (`native.port`) or handed to the web extension of a `web` instance.\n *\n * Conventions:\n * - the paths follow the API specification (section 4): `/auth/...`, `/punch...`, `/entries...`, `/absences...`,\n * `/aggregates/{day,month,year}`; the server mounts all of them below the prefix `/api`;\n * - the session token travels in the `x-session-token` header, the CSRF token in `x-csrf-token`;\n * - `?userId=` selects another employee and requires the matching `\u2026_other` permission (otherwise the caller\n * only sees their own data);\n * - every write answers with the stored record and, where it matters, with the refreshed aggregates;\n * - reads recalculate the requested period, so a report is always up to date.\n */\n\nimport { createHash, randomBytes } from \"node:crypto\";\nimport * as fs from \"node:fs\";\n\nimport type { Db } from \"../db/database\";\nimport type { AbsencesRepository, AbsenceApproval, AbsenceRecord } from \"../db/repositories/absences\";\nimport { isApproved } from \"../db/repositories/absences\";\nimport { readTimeEntryAudit } from \"../db/repositories/audit\";\nimport type {\n\tAutomationKind,\n\tAutomationRepeat,\n\tAutomationRuleRecord,\n\tAutomationsRepository,\n} from \"../db/repositories/automations\";\nimport type { DayNotesRepository } from \"../db/repositories/dayNotes\";\nimport type { EntriesRepository, EntryDirection, EntryRecord } from \"../db/repositories/entries\";\nimport type { HolidaysRepository } from \"../db/repositories/holidays\";\nimport type { PayoutsRepository } from \"../db/repositories/payouts\";\nimport {\n\tbuildTagToken,\n\tnewTagUid,\n\tparseTagToken,\n\tsameSignature,\n\tsignTag,\n\ttype RfidRepository,\n\ttype RfidTagRecord,\n} from \"../db/repositories/rfid\";\nimport type { PauseRuleRecord, RulesRepository } from \"../db/repositories/rules\";\nimport type { SettingsRepository, SettingValue } from \"../db/repositories/settings\";\nimport type { TerminalRecord, TerminalsRepository } from \"../db/repositories/terminals\";\nimport type { TriggerAction, TriggerMode, TriggerRuleRecord, TriggersRepository } from \"../db/repositories/triggers\";\nimport type { UserRecord, UsersRepository, WorkProfileRecord } from \"../db/repositories/users\";\nimport type { AggregationService } from \"../services/aggregation\";\nimport type { AuthService } from \"../services/auth\";\nimport { checkPasswordPolicy, hashPassword, verifyPassword } from \"../services/auth\";\nimport type { SyncService } from \"../services/sync\";\nimport type { BackupService } from \"../services/backup\";\nimport {\n\tabsenceEvents,\n\tcalendarDocument,\n\tCOMPANY_CALENDAR_NAME,\n\tICAL_CONTENT_TYPE,\n\ttype CalendarEmployee,\n} from \"../services/calendar\";\nimport { NotFoundError, ValidationError, type FieldIssue } from \"../errors\";\nimport { SETTING_DEFAULTS } from \"../db/seed\";\nimport { roundToStep } from \"../domain/punch\";\nimport { MAX_AVATAR_BYTES, MAX_BRANDING_BYTES, parseAvatarDataUrl, readAvatar } from \"../domain/avatar\";\nimport { reportLabels } from \"../reports/labels\";\nimport { buildMonthReport } from \"../reports/xls\";\nimport { buildMonthStatement } from \"../reports/pdf\";\nimport { reportFileName, type ReportInput } from \"../reports/types\";\nimport { dateRange, isValidTimeZone, localDate, utcToWallTime } from \"../util/time\";\nimport { clearCookie, parseCookies, SESSION_COOKIE, serializeCookie } from \"./cookies\";\nimport { createEventBus, type ApiEvent, type EventBus } from \"./events\";\nimport { createPinGuard } from \"./pin-guard\";\nimport { problem, toProblem } from \"./problem\";\nimport {\n\tcreateRouter,\n\tjson,\n\tnoContent,\n\tbinary,\n\ttype HttpMethod,\n\ttype RouteContext,\n\ttype RouteResponse,\n\ttype Router,\n} from \"./router\";\n\n/** Data sources of the API. */\nexport interface ApiDeps {\n\t/** Open database handle */\n\tdb: Db;\n\t/** Authentication service */\n\tauth: AuthService;\n\t/** User storage */\n\tusers: UsersRepository;\n\t/** Punch storage */\n\tentries: EntriesRepository;\n\t/** Absence storage */\n\tabsences: AbsencesRepository;\n\t/** Notes the employees leave for the administration */\n\tdayNotes: DayNotesRepository;\n\t/** Holiday storage */\n\tholidays: HolidaysRepository;\n\t/** Surcharge and break rules */\n\trules: RulesRepository;\n\t/** Paid out overtime */\n\tpayouts: PayoutsRepository;\n\t/** Kiosk terminals */\n\tterminals: TerminalsRepository;\n\t/** RFID/NFC tags */\n\trfid: RfidRepository;\n\t/** Trigger rules: ioBroker states that punch or set the presence */\n\ttriggers: TriggersRepository;\n\t/** Automation rules: what the adapter does on its own (clock out, reminders) */\n\tautomations: AutomationsRepository;\n\t/**\n\t * Called after the trigger rules were saved, so the adapter watches the new states right away\n\t * (`undefined` in tests that do not care).\n\t */\n\tonTriggerRulesChanged?: () => void;\n\t/** Secret used to sign tag links (`hmacSecret`) */\n\thmacSecret?: string;\n\t/** True when the kiosk terminal is switched on (instance setting) */\n\tkioskEnabled?: boolean;\n\t/** Lifetime of a terminal session in minutes; the heartbeat of the device extends it (default 15) */\n\tterminalSessionMinutes?: number;\n\t/**\n\t * Trusts `x-forwarded-*` of a reverse proxy: the forwarded address is used for the rate limits and the\n\t * audit trail, and `x-forwarded-proto: https` makes the session cookie `Secure`.\n\t */\n\ttrustProxy?: boolean;\n\t/** Aggregation service (reports and refreshes) */\n\taggregation: AggregationService;\n\t/** Offline synchronisation */\n\tsync: SyncService;\n\t/** Instance settings */\n\tsettings: SettingsRepository;\n\t/** Backup service; without it the backup endpoints are not registered */\n\tbackup?: BackupService;\n\t/** Live event bus; a private one is created when it is not given */\n\tevents?: EventBus;\n\t/** Instant source, defaults to the system clock */\n\tnow?: () => number;\n\t/** Version reported by `GET /version` */\n\tversion?: string;\n\t/**\n\t * Allowed login attempts per minute and address. The browser tests sign in once per case, so their harness\n\t * raises the shipped limit of 20.\n\t */\n\tloginRateLimit?: number;\n}\n\n/** Media type of the Excel export. */\nconst XLSX_CONTENT_TYPE = \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\";\n\n/** Media type of the PDF export. */\nconst PDF_CONTENT_TYPE = \"application/pdf\";\n\n/**\n * Upper bound for an uploaded backup.\n *\n * A backup is the only request that may be bigger than the router-wide body limit, so the restore route raises\n * the limit for itself. The web server reads with the same bound, because the transport refuses a body before\n * any route sees it.\n */\nexport const MAX_BACKUP_UPLOAD_BYTES = 64 * 1024 * 1024;\n\n/** A registered route (used for the documentation). */\nexport interface ApiRoute {\n\t/** HTTP method */\n\tmethod: string;\n\t/** Path pattern */\n\tpath: string;\n\t/** Permission the route requires */\n\tpermission?: string;\n}\n\n/** The API. */\nexport interface Api {\n\t/** Router the endpoints are registered on */\n\trouter: Router;\n\t/** Route table */\n\troutes(): ApiRoute[];\n\t/** Bus every change is published on (also used by the WebSocket stream) */\n\tevents: EventBus;\n}\n\n/**\n * Reads a required string field.\n *\n * @param body - parsed request body\n * @param field - field name\n * @returns the trimmed value\n */\nfunction requireString(body: Record<string, unknown>, field: string): string {\n\tconst value = body[field];\n\tif (typeof value !== \"string\" || !value.trim()) {\n\t\tthrow new ValidationError(`${field} is required`);\n\t}\n\treturn value.trim();\n}\n\n/**\n * Reads an optional string field.\n *\n * @param body - parsed request body\n * @param field - field name\n * @returns the trimmed value or `null`\n */\nfunction optionalString(body: Record<string, unknown>, field: string): string | null {\n\tconst value = body[field];\n\tif (value === undefined || value === null) {\n\t\treturn null;\n\t}\n\tif (typeof value !== \"string\") {\n\t\tthrow new ValidationError(`${field} must be a string`);\n\t}\n\treturn value.trim() || null;\n}\n\n/**\n * Reads an optional number field.\n *\n * @param body - parsed request body\n * @param field - field name\n * @returns the value or `null`\n */\nfunction optionalNumber(body: Record<string, unknown>, field: string): number | null {\n\tconst value = body[field];\n\tif (value === undefined || value === null) {\n\t\treturn null;\n\t}\n\tif (typeof value !== \"number\" || !Number.isFinite(value)) {\n\t\tthrow new ValidationError(`${field} must be a number`);\n\t}\n\treturn value;\n}\n\n/**\n * Settings an administrator must not see through the API, even when they are compatible with the stored form.\n * They are matched case-insensitively and may occur anywhere in the key.\n */\nconst SECRET_SETTING_KEYS = [\"secret\", \"password\", \"token\", \"hash\", \"apikey\"];\n\n/** Settings whose value may be a nested structure (arrays and objects). */\nconst JSON_SETTINGS = [\"pause_staffel\"];\n\n/** Upper bound of a statistics range: every day of every employee is recalculated. */\nconst MAX_STATISTICS_DAYS = 366;\n\n/** Default lifetime of a terminal session; the heartbeat of the device extends it. */\nconst TERMINAL_SESSION_MINUTES_DEFAULT = 15;\n\n/** A terminal as it is handed out over the API (without any secret). */\nexport interface PublicTerminalPayload {\n\t/** Primary key */\n\tid: number;\n\t/** Display name */\n\tname: string;\n\t/** Optional location */\n\tlocation: string | null;\n\t/** True when a badge has to be combined with the personal PIN */\n\tpinRequired: boolean;\n\t/** False for revoked devices */\n\tisActive: boolean;\n\t/** Instant the device token expires, `null` = never */\n\texpiresAt: number | null;\n\t/** Instant of the last heartbeat */\n\tlastSeenAt: number | null;\n\t/** Instant of creation */\n\tcreatedAt: number;\n\t/** Employees shown on this terminal; an empty list means \u201Call employees\u201D */\n\tuserIds: number[];\n}\n\n/**\n * Removes the internals of a terminal record.\n *\n * @param terminal - stored terminal\n * @returns the public part\n */\nfunction publicTerminal(terminal: TerminalRecord): PublicTerminalPayload {\n\treturn {\n\t\tid: terminal.id,\n\t\tname: terminal.name,\n\t\tlocation: terminal.location,\n\t\tpinRequired: terminal.pinRequired,\n\t\tisActive: terminal.isActive,\n\t\texpiresAt: terminal.expiresAt,\n\t\tlastSeenAt: terminal.lastSeenAt,\n\t\tcreatedAt: terminal.createdAt,\n\t\tuserIds: terminal.userIds,\n\t};\n}\n\n/**\n * Settings that may be changed through the API: the instance defaults from the seed plus the structured ones.\n *\n * The list is derived from the defaults on purpose \u2014 a setting the adapter does not know would silently be\n * ignored by the services, so it is refused instead.\n *\n * @returns editable setting keys\n */\nfunction editableSettings(): string[] {\n\treturn [...Object.keys(SETTING_DEFAULTS), ...JSON_SETTINGS];\n}\n\n/** Settings that a read leaves out: they are large pictures and are delivered by their own routes. */\nconst HIDDEN_SETTING_KEYS = [\"brand_logo\", \"brand_background\"];\n\n/**\n * Removes settings that look like secrets from a read.\n *\n * @param all - all stored settings\n * @returns the settings that may be shown\n */\nfunction readableSettings(all: Record<string, string>): Record<string, string> {\n\tconst result: Record<string, string> = {};\n\tfor (const [key, value] of Object.entries(all)) {\n\t\tconst lower = key.toLowerCase();\n\t\tif (SECRET_SETTING_KEYS.some(marker => lower.includes(marker))) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (HIDDEN_SETTING_KEYS.includes(key)) {\n\t\t\tcontinue;\n\t\t}\n\t\tresult[key] = value;\n\t}\n\treturn result;\n}\n\n/**\n * Checks whether a value may be stored as a setting.\n *\n * @param value - value from the request body\n * @param allowStructured - true for settings that may hold arrays or objects\n * @returns true when the value is supported\n */\nfunction isSettingValue(value: unknown, allowStructured: boolean): boolean {\n\tif (value === null || typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\") {\n\t\treturn typeof value !== \"number\" || Number.isFinite(value);\n\t}\n\tif (!allowStructured) {\n\t\treturn false;\n\t}\n\tif (Array.isArray(value)) {\n\t\treturn value.every(entry => isSettingValue(entry, true));\n\t}\n\treturn (\n\t\ttypeof value === \"object\" &&\n\t\tObject.values(value as Record<string, unknown>).every(entry => isSettingValue(entry, true))\n\t);\n}\n\n/**\n * Reads a required boolean field.\n *\n * @param body - parsed request body\n * @param field - field name\n * @returns the value\n */\nfunction requireBoolean(body: Record<string, unknown>, field: string): boolean {\n\tconst value = optionalBoolean(body, field);\n\tif (value === null) {\n\t\tthrow new ValidationError(`${field} is required`);\n\t}\n\treturn value;\n}\n\n/**\n * Reads a year and an optional month from a body or the query.\n *\n * The range matches the validation of the payout repository, so a wrong year is refused by the API before it\n * reaches the storage layer.\n *\n * @param year - raw year\n * @param month - raw month, `null`/empty for the whole year\n * @returns validated year and month\n */\nfunction readPeriod(year: unknown, month: unknown): { year: number; month: number | null } {\n\tconst parsedYear = Number(year);\n\tif (!Number.isInteger(parsedYear) || parsedYear < 2000 || parsedYear > 2100) {\n\t\tthrow new ValidationError(`year must be a four digit year between 2000 and 2100 (got ${JSON.stringify(year)})`);\n\t}\n\tif (month === null || month === undefined || month === \"\") {\n\t\treturn { year: parsedYear, month: null };\n\t}\n\tconst parsedMonth = Number(month);\n\tif (!Number.isInteger(parsedMonth) || parsedMonth < 1 || parsedMonth > 12) {\n\t\tthrow new ValidationError(`month must be between 1 and 12 (got ${JSON.stringify(month)})`);\n\t}\n\treturn { year: parsedYear, month: parsedMonth };\n}\n\n/**\n * Reads an optional whole-number query parameter.\n *\n * @param context - route context\n * @param name - parameter name\n * @param fallback - value used when the parameter is missing\n * @returns the parsed number\n */\nfunction optionalNumberQuery(context: RouteContext, name: string, fallback: number): number {\n\tconst raw = context.query(name);\n\tif (raw === null) {\n\t\treturn fallback;\n\t}\n\tconst value = Number(raw);\n\tif (!Number.isInteger(value) || value < 0) {\n\t\tthrow new ValidationError(`${name} must be a non-negative whole number (got ${raw})`);\n\t}\n\treturn value;\n}\n\n/**\n * Reads a list of role keys.\n *\n * @param body - parsed request body\n * @returns the role keys or `null` when the field is absent\n */\nfunction readRoleKeys(body: Record<string, unknown>): string[] | null {\n\tconst value = body.roleKeys;\n\tif (value === undefined || value === null) {\n\t\treturn null;\n\t}\n\tif (!Array.isArray(value) || value.some(entry => typeof entry !== \"string\")) {\n\t\tthrow new ValidationError(\"roleKeys must be an array of strings\");\n\t}\n\treturn (value as string[]).map(entry => entry.trim()).filter(entry => entry.length > 0);\n}\n\n/**\n * Reads an optional list of user ids (the employees of a terminal).\n *\n * @param body - request body\n * @returns the ids without duplicates, `null` when the field is missing\n */\nfunction readUserIds(body: Record<string, unknown>): number[] | null {\n\tconst value = body.userIds;\n\tif (value === undefined || value === null) {\n\t\treturn null;\n\t}\n\tif (!Array.isArray(value) || value.some(entry => !Number.isInteger(entry))) {\n\t\tthrow new ValidationError(\"userIds must be an array of whole numbers\");\n\t}\n\treturn [...new Set(value as number[])];\n}\n\n/** Fields of a work profile a client may change. */\nconst PROFILE_FIELDS: (keyof Omit<WorkProfileRecord, \"userId\">)[] = [\n\t\"percent\",\n\t\"weeklyHours\",\n\t\"workdays\",\n\t\"startDate\",\n\t\"endDate\",\n\t\"overtimeCarryover\",\n\t\"vorholzeitPerYear\",\n\t\"vacationCarryover\",\n\t\"vacationPerYear\",\n\t\"overtimeModel\",\n\t\"holidayFlags\",\n\t\"pausePaidMinutes\",\n\t\"showWorkedTime\",\n];\n\n/**\n * Reads a work profile patch and checks its values.\n *\n * The repository merges the given fields into the stored profile, so every value has to be validated here \u2014\n * a broken profile would silently spoil the target time of every later calculation.\n *\n * @param body - parsed request body\n * @returns the patch\n */\nfunction readProfilePatch(body: Record<string, unknown>): Partial<Omit<WorkProfileRecord, \"userId\">> {\n\tconst patch: Partial<Omit<WorkProfileRecord, \"userId\">> = {};\n\t// every invalid field is collected instead of failing on the first one, so the client sees all problems at\n\t// once (RFC 9457 `errors[]`)\n\tconst issues: FieldIssue[] = [];\n\n\tfor (const [key, value] of Object.entries(body)) {\n\t\tif (key === \"reason\") {\n\t\t\tcontinue;\n\t\t}\n\t\tif (!(PROFILE_FIELDS as string[]).includes(key)) {\n\t\t\tissues.push({ path: key, message: \"unknown field of a work profile\" });\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (key === \"percent\") {\n\t\t\tconst percent = Number(value);\n\t\t\tif (!Number.isInteger(percent) || percent < 0 || percent > 100) {\n\t\t\t\tissues.push({ path: key, message: \"must be a whole number between 0 and 100\" });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tpatch.percent = percent;\n\t\t} else if (key === \"weeklyHours\") {\n\t\t\tconst hours = Number(value);\n\t\t\tif (!Number.isFinite(hours) || hours < 0 || hours > 168) {\n\t\t\t\tissues.push({ path: key, message: \"must be between 0 and 168\" });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tpatch.weeklyHours = hours;\n\t\t} else if (key === \"workdays\") {\n\t\t\tif (typeof value !== \"string\") {\n\t\t\t\tissues.push({ path: key, message: \"must be a string of weekdays\" });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst workdays = value.trim();\n\t\t\tif (!/^\\d(;\\d)*$/.test(workdays) || workdays.split(\";\").some(day => Number(day) > 6)) {\n\t\t\t\tissues.push({ path: key, message: 'must be weekdays separated by \";\" (0 = Sunday \u2026 6 = Saturday)' });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tpatch.workdays = workdays;\n\t\t} else if (key === \"overtimeModel\") {\n\t\t\tif (value !== \"cumulative\" && value !== \"yearly\" && value !== \"monthly\") {\n\t\t\t\tissues.push({ path: key, message: \"must be cumulative, yearly or monthly\" });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tpatch.overtimeModel = value;\n\t\t} else if (key === \"startDate\" || key === \"endDate\") {\n\t\t\tif (value !== null && (!Number.isInteger(Number(value)) || Number(value) < 0)) {\n\t\t\t\tissues.push({ path: key, message: \"must be an instant in seconds or null\" });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tpatch[key] = value === null ? null : Number(value);\n\t\t} else if (key === \"holidayFlags\") {\n\t\t\tif (value !== null && typeof value !== \"string\") {\n\t\t\t\tissues.push({ path: key, message: \"must be a JSON string or null\" });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tpatch.holidayFlags = value;\n\t\t} else if (key === \"pausePaidMinutes\") {\n\t\t\t// minutes of the break per day that are paid; a whole number, at most a full day\n\t\t\tconst minutes = Number(value);\n\t\t\tif (!Number.isInteger(minutes) || minutes < 0 || minutes > 1440) {\n\t\t\t\tissues.push({ path: key, message: \"must be a whole number of minutes between 0 and 1440\" });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tpatch.pausePaidMinutes = minutes;\n\t\t} else if (key === \"showWorkedTime\") {\n\t\t\t// whether the presence card shows the worked time of the day for this employee\n\t\t\tif (typeof value !== \"boolean\") {\n\t\t\t\tissues.push({ path: key, message: \"must be true or false\" });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tpatch.showWorkedTime = value;\n\t\t} else {\n\t\t\tconst number = Number(value);\n\t\t\tif (!Number.isInteger(number)) {\n\t\t\t\tissues.push({ path: key, message: \"must be a whole number of minutes\" });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tpatch[key as \"overtimeCarryover\" | \"vorholzeitPerYear\" | \"vacationCarryover\" | \"vacationPerYear\"] = number;\n\t\t}\n\t}\n\n\tif (issues.length > 0) {\n\t\tthrow new ValidationError(`${issues.length} field(s) of the work profile are not valid`, issues);\n\t}\n\treturn patch;\n}\n\n/** A user as it is handed out over the API (without the password hashes). */\nexport interface PublicUserPayload {\n\t/** Primary key */\n\tid: number;\n\t/** Login name */\n\tlogin: string;\n\t/** Shown name */\n\tdisplayName: string;\n\t/** E-mail address */\n\temail: string | null;\n\t/** RFID card id */\n\trfidCard: string | null;\n\t/** False for deactivated accounts */\n\tisActive: boolean;\n\t/** True while the user has to set a new password */\n\tmustChangePw: boolean;\n\t/** Preferred language */\n\tlocale: string;\n\t/** Same value as `locale`; the web app reads the language of the session from this field */\n\tlanguage: string;\n\t/** IANA time zone */\n\ttimezone: string;\n\t/** Instant of creation, UTC epoch seconds */\n\tcreatedAt: number;\n\t/** Instant of the last change, UTC epoch seconds */\n\tupdatedAt: number;\n\t/** Instant of the last login, `null` if never */\n\tlastLoginAt: number | null;\n\t/** Address of the picture of the user, `null` when none is stored (the web app shows a placeholder) */\n\tavatarUrl: string | null;\n}\n\n/**\n * Removes everything a client must not see from a user record.\n *\n * The list is explicit on purpose: a column added later stays invisible until it is added here.\n *\n * @param user - stored user\n * @returns the public part\n */\nfunction publicUser(user: UserRecord): PublicUserPayload {\n\treturn {\n\t\tid: user.id,\n\t\tlogin: user.login,\n\t\tdisplayName: user.displayName,\n\t\temail: user.email,\n\t\trfidCard: user.rfidCard,\n\t\tisActive: user.isActive,\n\t\tmustChangePw: user.mustChangePw,\n\t\tlocale: user.locale,\n\t\tlanguage: user.locale,\n\t\ttimezone: user.timezone,\n\t\t// the picture is served by its own route, so it never travels inside the JSON answer; the version in the\n\t\t// query makes the browser fetch it again after a change\n\t\tavatarUrl: user.avatar ? `/api/users/${user.id}/avatar?v=${user.updatedAt}` : null,\n\t\tcreatedAt: user.createdAt,\n\t\tupdatedAt: user.updatedAt,\n\t\tlastLoginAt: user.lastLoginAt,\n\t};\n}\n\n/**\n * Reads an optional boolean field.\n *\n * @param body - parsed request body\n * @param field - field name\n * @returns the value or `null`\n */\nfunction optionalBoolean(body: Record<string, unknown>, field: string): boolean | null {\n\tconst value = body[field];\n\tif (value === undefined || value === null) {\n\t\treturn null;\n\t}\n\tif (typeof value !== \"boolean\") {\n\t\tthrow new ValidationError(`${field} must be a boolean`);\n\t}\n\treturn value;\n}\n\n/**\n * Reads a numeric path parameter.\n *\n * @param context - route context\n * @param name - parameter name\n * @returns the parsed number\n */\nfunction numberParam(context: RouteContext, name: string): number {\n\tconst raw = context.params[name];\n\tconst value = Number(raw);\n\tif (!Number.isInteger(value)) {\n\t\tthrow new ValidationError(`${name} must be a whole number (got ${raw})`);\n\t}\n\treturn value;\n}\n\n/**\n * Reads the day direction of a punch.\n *\n * @param body - parsed request body\n * @returns the direction\n */\nfunction optionalDirection(body: Record<string, unknown>): EntryDirection {\n\tconst value = optionalString(body, \"direction\");\n\tif (value === null) {\n\t\treturn \"auto\";\n\t}\n\tif (value !== \"in\" && value !== \"out\" && value !== \"auto\") {\n\t\tthrow new ValidationError(`direction must be in, out or auto (got ${value})`);\n\t}\n\treturn value;\n}\n\n/**\n * Resolves the employee a request refers to and checks the matching permission.\n *\n * @param context - route context with the authenticated caller\n * @param requested - user id from the request, `null` for \u201Cthe caller\u201D\n * @param ownPermission - permission needed for the own account\n * @param otherPermission - permission needed for another employee\n * @returns the target user id\n */\nfunction resolveScope(\n\tcontext: RouteContext,\n\trequested: number | null,\n\townPermission: string,\n\totherPermission: string,\n): number {\n\tif (!context.auth) {\n\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t}\n\tconst target = requested ?? context.auth.user.id;\n\tif (target === context.auth.user.id) {\n\t\treturn target;\n\t}\n\tif (!context.auth.permissions.includes(otherPermission)) {\n\t\tthrow problem(403, \"permission_denied\", `request rejected (permission_denied: ${otherPermission})`);\n\t}\n\treturn target;\n}\n\n/**\n * Creates the API.\n *\n * @param deps - data sources\n * @returns API with its router\n */\n/**\n * Builds the CSV of the raw punches of one employee.\n *\n * The header is stable and English, because the file is meant for a payroll tool rather than for reading: the\n * separator is a semicolon and the file starts with a UTF-8 byte order mark \u2014 that is what a spreadsheet in a\n * European locale expects.\n *\n * @param entries - punches of the employee in chronological order\n * @param timeZone - time zone of the employee, used for the time column\n * @returns the CSV document\n */\nfunction buildEntriesCsv(entries: EntryRecord[], timeZone: string): Buffer {\n\tconst lines = [\"date;time;direction;source;note\"];\n\tfor (const entry of entries) {\n\t\tconst seconds = utcToWallTime(entry.tsUtc, timeZone) % 86_400;\n\t\tconst hour = String(Math.floor(seconds / 3600)).padStart(2, \"0\");\n\t\tconst minute = String(Math.floor((seconds % 3600) / 60)).padStart(2, \"0\");\n\t\t// a note may carry the separator and quotes: quoting keeps the columns intact\n\t\tconst note = `\"${(entry.note ?? \"\").replace(/\"/g, '\"\"')}\"`;\n\t\tlines.push([entry.localDate, `${hour}:${minute}`, entry.direction ?? \"\", entry.source, note].join(\";\"));\n\t}\n\treturn Buffer.from(`\\uFEFF${lines.join(\"\\r\\n\")}\\r\\n`, \"utf8\");\n}\n\n/**\n * Builds the API of the adapter: every route of the HTTP surface and the helpers they share.\n *\n * The dependencies are injected, so a test can drive the API with a fixed clock and an in-memory database.\n *\n * @param deps - repositories, services and the clock of the instance\n * @returns router, route table and the event bus every change is published on\n */\nexport function createApi(deps: ApiDeps): Api {\n\tconst {\n\t\tauth,\n\t\tusers,\n\t\tentries,\n\t\tabsences,\n\t\tholidays,\n\t\trules,\n\t\tpayouts,\n\t\tterminals,\n\t\trfid,\n\t\ttriggers,\n\t\tautomations,\n\t\taggregation,\n\t\tsync,\n\t\tsettings,\n\t} = deps;\n\tconst now = deps.now ?? (() => Math.floor(Date.now() / 1000));\n\tconst events = deps.events ?? createEventBus();\n\tconst registered: ApiRoute[] = [];\n\tconst router = createRouter({ auth, trustProxy: deps.trustProxy === true, now });\n\t// wrong PINs at the terminal are counted per account (specification 4.10); the counters are short lived\n\tconst pinGuard = createPinGuard();\n\n\t/**\n\t * Publishes a live event about a change.\n\t *\n\t * @param event - kind of the change, affected employee and a short summary\n\t */\n\tconst emit = (event: Omit<ApiEvent, \"atUtc\">): void => {\n\t\tevents.publish({ ...event, atUtc: now() });\n\t};\n\n\t/**\n\t * Refuses a change that would leave the installation without an active administrator.\n\t *\n\t * The installation creates exactly one administrator; if the last active one is deactivated or loses the role,\n\t * nobody can administer any more \u2014 only the `admin` role carries `user.edit`, `user.manage_roles`, `settings.edit`\n\t * and the rest. The way back would be an instance restart with a free `adminLogin`, so the change is refused\n\t * instead. Only active accounts count: a deactivated administrator helps nobody.\n\t *\n\t * @param userId - account that is changed\n\t * @param next - state the account would have afterwards\n\t * @param next.isActive - activity after the change\n\t * @param next.roleKeys - roles after the change\n\t */\n\tfunction requireActiveAdministrator(userId: number, next: { isActive: boolean; roleKeys: string[] }): void {\n\t\tif (next.isActive && next.roleKeys.includes(\"admin\")) {\n\t\t\treturn;\n\t\t}\n\t\tconst remaining = users.list().some(user => user.id !== userId && users.roles(user.id).includes(\"admin\"));\n\t\tif (!remaining) {\n\t\t\tthrow problem(\n\t\t\t\t409,\n\t\t\t\t\"last_administrator\",\n\t\t\t\t\"the last active administrator cannot be deactivated or lose the admin role\",\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Registers a route and remembers it for the documentation.\n\t *\n\t * @param method - HTTP method(s)\n\t * @param path - path pattern\n\t * @param routeSettings - permission and visibility of the route\n\t * @param routeSettings.permission - permission required\n\t * @param routeSettings.public - true when no session is needed\n\t * @param routeSettings.csrf - false when the route must never ask for a CSRF token (it runs without a session,\n\t * for example the login); otherwise the router decides from the credential: a browser session cookie needs the\n\t * token, an integration client with a bearer token does not\n\t * @param routeSettings.rateLimit - rate limit of the route class\n\t * @param routeSettings.rateLimit.name - name of the counted class\n\t * @param routeSettings.rateLimit.limit - allowed requests inside the window\n\t * @param routeSettings.rateLimit.windowSeconds - length of the window\n\t * @param routeSettings.maxBodyBytes - body limit of this route in bytes (a backup upload); the router-wide\n\t * limit applies when it is missing\n\t * @param handler - handler of the route\n\t */\n\tconst route = (\n\t\tmethod: HttpMethod | HttpMethod[],\n\t\tpath: string,\n\t\trouteSettings: {\n\t\t\t/** Permission the caller needs; without one the route only requires a session */\n\t\t\tpermission?: string;\n\t\t\t/** True for a route that runs without a session (health, login, branding) */\n\t\t\tpublic?: boolean;\n\t\t\t/** `false` switches the CSRF check off (public routes); otherwise the router decides */\n\t\t\tcsrf?: boolean;\n\t\t\t/** Requests allowed inside a window, e.g. `{ name: \"export\", limit: 20, windowSeconds: 60 }` */\n\t\t\trateLimit?: {\n\t\t\t\t/** Name the limit is counted under */\n\t\t\t\tname: string;\n\t\t\t\t/** Allowed requests inside the window */\n\t\t\t\tlimit: number;\n\t\t\t\t/** Length of the window in seconds */\n\t\t\t\twindowSeconds: number;\n\t\t\t};\n\t\t\t/** Raises the body limit of this one route (a backup upload) */\n\t\t\tmaxBodyBytes?: number;\n\t\t},\n\t\thandler: (context: RouteContext) => ReturnType<Parameters<Router[\"add\"]>[0][\"handler\"]>,\n\t): void => {\n\t\tconst methods = Array.isArray(method) ? method : [method];\n\t\tfor (const entry of methods) {\n\t\t\tregistered.push({\n\t\t\t\tmethod: entry,\n\t\t\t\tpath,\n\t\t\t\t...(routeSettings.permission ? { permission: routeSettings.permission } : {}),\n\t\t\t});\n\t\t}\n\t\trouter.add({\n\t\t\tmethod,\n\t\t\tpath,\n\t\t\tpermission: routeSettings.permission,\n\t\t\trequiresAuth: routeSettings.public !== true,\n\t\t\t// `undefined` hands the decision to the router: a session cookie needs the CSRF token, a bearer\n\t\t\t// client (integration, `x-session-token`) does not, because no foreign page can set that header.\n\t\t\t// `false` switches the check off completely \u2014 that is for the public routes, which run without a\n\t\t\t// session, so a CSRF token could not exist yet.\n\t\t\trequiresCsrf: routeSettings.csrf === false ? false : undefined,\n\t\t\trateLimit: routeSettings.rateLimit,\n\t\t\tmaxBodyBytes: routeSettings.maxBodyBytes,\n\t\t\thandler,\n\t\t});\n\t};\n\n\t/**\n\t * Writes the session cookie of a fresh login.\n\t *\n\t * The cookie is `httpOnly` (a script inside the page cannot read it), `SameSite=Lax` and carries `Secure`\n\t * as soon as the client reaches the adapter over HTTPS \u2014 directly or through a trusted proxy (specification\n\t * 4.x). The browser sends it with every request by itself, which is exactly why state changing requests\n\t * additionally need the CSRF token from `x-csrf-token`; a bearer client has no such ambient credential.\n\t *\n\t * @param context - route context of the request (for the `Secure` flag)\n\t * @param token - session token\n\t * @param expiresAt - instant the session ends, UTC epoch seconds\n\t * @returns value of the `set-cookie` header\n\t */\n\tconst sessionCookie = (context: RouteContext, token: string, expiresAt: number): string =>\n\t\tserializeCookie(SESSION_COOKIE, token, {\n\t\t\tmaxAgeSeconds: Math.max(0, expiresAt - now()),\n\t\t\tsecure: context.secure,\n\t\t});\n\n\t/**\n\t * Removes the session cookie, so the browser session really ends.\n\t *\n\t * @param context - route context of the request (the flags have to match the ones it was set with)\n\t * @returns value of the `set-cookie` header\n\t */\n\tconst removeSessionCookie = (context: RouteContext): string =>\n\t\tclearCookie(SESSION_COOKIE, { secure: context.secure });\n\n\t// authentication\n\n\troute(\n\t\t\"POST\",\n\t\t\"/auth/login\",\n\t\t{\n\t\t\tpublic: true,\n\t\t\tcsrf: false,\n\t\t\trateLimit: { name: \"login\", limit: deps.loginRateLimit ?? 20, windowSeconds: 60 },\n\t\t},\n\t\tcontext => {\n\t\t\tconst body = context.jsonBody();\n\t\t\tconst result = auth.login({\n\t\t\t\tlogin: requireString(body, \"login\"),\n\t\t\t\tpassword: requireString(body, \"password\"),\n\t\t\t\tuserAgent: context.header(\"user-agent\"),\n\t\t\t\tip: context.request.remoteAddress ?? null,\n\t\t\t\tnow: now(),\n\t\t\t});\n\n\t\t\tif (!result.ok) {\n\t\t\t\tthrow problem(\n\t\t\t\t\tresult.error === \"locked_out\" ? 423 : 401,\n\t\t\t\t\tresult.error,\n\t\t\t\t\tresult.error === \"locked_out\"\n\t\t\t\t\t\t? \"too many failed attempts, try again later\"\n\t\t\t\t\t\t: \"login or password is not correct\",\n\t\t\t\t);\n\t\t\t}\n\t\t\t// the token is returned for integration clients (bearer) and set as an httpOnly cookie for the\n\t\t\t// browser; the CSRF token stays readable for the page on purpose\n\t\t\treturn json(\n\t\t\t\t200,\n\t\t\t\t{\n\t\t\t\t\ttoken: result.token,\n\t\t\t\t\tcsrfToken: result.csrfToken,\n\t\t\t\t\texpiresAt: result.expiresAt,\n\t\t\t\t\tuser: result.user,\n\t\t\t\t},\n\t\t\t\t{ \"set-cookie\": sessionCookie(context, result.token, result.expiresAt) },\n\t\t\t);\n\t\t},\n\t);\n\n\troute(\"POST\", \"/auth/logout\", { csrf: true }, context => {\n\t\tauth.logout({\n\t\t\ttoken: context.sessionToken,\n\t\t\tactorId: context.auth?.user.id ?? null,\n\t\t\tip: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\t// the browser session ends with the cookie, not only with the session row\n\t\treturn noContent(204, { \"set-cookie\": removeSessionCookie(context) });\n\t});\n\n\troute(\"GET\", \"/auth/me\", {}, context =>\n\t\tjson(200, {\n\t\t\tuser: context.auth?.user,\n\t\t\tpermissions: context.auth?.permissions ?? [],\n\t\t\texpiresAt: context.auth?.expiresAt,\n\t\t\t// a client that only holds the cookie has no other way to learn the CSRF token of its session\n\t\t\tcsrfToken: context.auth ? auth.csrfToken(context.sessionToken) : undefined,\n\t\t}),\n\t);\n\n\troute(\"POST\", \"/auth/password\", { csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst password = requireString(context.jsonBody(), \"password\");\n\n\t\tauth.setPassword({ userId: context.auth.user.id, password, actorId: context.auth.user.id, now: now() });\n\t\t// a password change ends all running sessions, including this one\n\t\tauth.revokeSessions({ userId: context.auth.user.id, actorId: context.auth.user.id, now: now() });\n\t\treturn noContent(204, { \"set-cookie\": removeSessionCookie(context) });\n\t});\n\n\t// A session is renewed on every authenticated request (sliding renewal), so this route reports the current\n\t// expiry together with a fresh CSRF token; integration clients use it instead of repeating their login.\n\troute(\"POST\", \"/auth/refresh\", { csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst token = context.sessionToken;\n\t\treturn json(200, {\n\t\t\texpiresAt: context.auth.expiresAt,\n\t\t\tcsrfToken: auth.csrfToken(token),\n\t\t\tuser: context.auth.user,\n\t\t\tpermissions: context.auth.permissions,\n\t\t});\n\t});\n\n\t// punching\n\n\t/**\n\t * Resolves the employee a read request refers to from `?userId=`.\n\t *\n\t * @param context - route context with the authenticated caller\n\t * @param ownPermission - permission needed for the own account\n\t * @param otherPermission - permission needed for another employee\n\t * @returns the target user id\n\t */\n\tconst scopeUser = (\n\t\tcontext: RouteContext,\n\t\townPermission = \"report.view_own\",\n\t\totherPermission = \"report.view_other\",\n\t): number => {\n\t\tconst raw = context.query(\"userId\");\n\t\treturn resolveScope(context, raw ? Number(raw) : null, ownPermission, otherPermission);\n\t};\n\n\t/**\n\t * Resolves the employee a day note belongs to from `?userId=`.\n\t *\n\t * A note for somebody else is an administrative step and needs `time.edit_other`; without that right only the\n\t * own day can be commented. The target has to exist, like everywhere else.\n\t *\n\t * @param context - route context with the authenticated caller\n\t * @param actorId - id of the caller\n\t * @returns the target user id\n\t */\n\tconst dayNoteTarget = (context: RouteContext, actorId: number): number => {\n\t\tconst raw = context.query(\"userId\");\n\t\tconst target = raw === null || raw.trim() === \"\" ? actorId : Number(raw);\n\t\tif (!Number.isInteger(target)) {\n\t\t\tthrow new ValidationError(\"userId must be a whole number\");\n\t\t}\n\t\tif (target !== actorId && !(context.auth?.permissions.includes(\"time.edit_other\") ?? false)) {\n\t\t\tthrow problem(403, \"permission_denied\", \"request rejected (permission_denied: time.edit_other)\");\n\t\t}\n\t\tif (!users.findById(target)) {\n\t\t\tthrow new NotFoundError(`user ${target} not found`);\n\t\t}\n\t\treturn target;\n\t};\n\n\t/**\n\t * Reads a required whole-number query parameter.\n\t *\n\t * @param context - route context\n\t * @param name - parameter name\n\t * @returns the parsed number\n\t */\n\tconst numberQuery = (context: RouteContext, name: string): number => {\n\t\tconst raw = context.query(name);\n\t\tconst value = Number(raw);\n\t\t// a missing parameter is `null`, which would silently become `0`\n\t\tif (raw === null || raw.trim() === \"\" || !Number.isInteger(value)) {\n\t\t\tthrow new ValidationError(`${name} is required and must be a whole number`);\n\t\t}\n\t\treturn value;\n\t};\n\n\t/**\n\t * Stores a punch of the caller and refreshes the day aggregate.\n\t *\n\t * @param context - route context with the authenticated caller\n\t * @param options - options of the punch\n\t * @param options.quick - true rounds the instant to the configured quicktime step\n\t * @returns response with the stored entry and the refreshed day\n\t */\n\tconst punch = (\n\t\tcontext: RouteContext,\n\t\toptions: {\n\t\t\t/** True for a quick punch: the instant is rounded to the configured step */\n\t\t\tquick: boolean;\n\t\t},\n\t): RouteResponse => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst body = context.jsonBody();\n\t\tconst timestamp = now();\n\t\tconst user = users.findById(context.auth.user.id);\n\t\tif (!user) {\n\t\t\tthrow new NotFoundError(`user ${context.auth.user.id} not found`);\n\t\t}\n\n\t\tconst requestedTs = optionalNumber(body, \"tsUtc\") ?? timestamp;\n\t\tconst quickRoundMinutes = options.quick ? settings.getNumber(\"quick_round_minutes\", 0) : 0;\n\t\tconst tsUtc = quickRoundMinutes > 0 ? roundToStep(requestedTs, quickRoundMinutes, user.timezone) : requestedTs;\n\n\t\tconst stored = entries.insert({\n\t\t\tuserId: user.id,\n\t\t\ttsUtc,\n\t\t\tclientTsUtc: optionalNumber(body, \"clientTsUtc\"),\n\t\t\ttimeZone: user.timezone,\n\t\t\tsource: \"web\",\n\t\t\tdirection: optionalDirection(body),\n\t\t\tidempotencyKey: optionalString(body, \"idempotencyKey\"),\n\t\t\tnote: optionalString(body, \"note\"),\n\t\t\tactorId: user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: timestamp,\n\t\t});\n\n\t\tconst day = aggregation.recalculateDay(user.id, stored.entry.localDate, { now: timestamp });\n\t\temit({\n\t\t\ttype: \"punch\",\n\t\t\tuserId: user.id,\n\t\t\tdata: { entryId: stored.entry.id, created: stored.created, localDate: stored.entry.localDate },\n\t\t});\n\t\treturn json(\n\t\t\t201,\n\t\t\t{\n\t\t\t\tentry: stored.entry,\n\t\t\t\tcreated: stored.created,\n\t\t\t\tday,\n\t\t\t\t...(quickRoundMinutes > 0\n\t\t\t\t\t? { rounded: { from: requestedTs, to: tsUtc, roundMinutes: quickRoundMinutes } }\n\t\t\t\t\t: {}),\n\t\t\t},\n\t\t\t{ location: `/entries/${stored.entry.id}` },\n\t\t);\n\t};\n\n\troute(\n\t\t\"POST\",\n\t\t\"/punch\",\n\t\t{ permission: \"time.punch\", csrf: true, rateLimit: { name: \"punch\", limit: 60, windowSeconds: 60 } },\n\t\tcontext => punch(context, { quick: false }),\n\t);\n\n\troute(\n\t\t\"POST\",\n\t\t\"/punch/quick\",\n\t\t{ permission: \"time.punch\", csrf: true, rateLimit: { name: \"punch\", limit: 60, windowSeconds: 60 } },\n\t\tcontext => punch(context, { quick: true }),\n\t);\n\n\troute(\"GET\", \"/punch/status\", { permission: \"report.view_own\" }, context => {\n\t\tconst userId = scopeUser(context);\n\t\tconst user = users.findById(userId);\n\t\tif (!user) {\n\t\t\tthrow new NotFoundError(`user ${userId} not found`);\n\t\t}\n\n\t\tconst date = localDate(now(), user.timezone);\n\t\tconst day = aggregation.recalculateDay(userId, date, { now: now() });\n\t\tconst todayEntries = entries.listByRange(userId, date, date);\n\t\treturn json(200, {\n\t\t\tdate,\n\t\t\tday,\n\t\t\thasOpenEntry: day.hasOpenEntry,\n\t\t\tlastEntry: todayEntries.length > 0 ? todayEntries[todayEntries.length - 1] : null,\n\t\t\t// what a punch would do right now, so a client does not have to derive it\n\t\t\tnextDirection: day.hasOpenEntry ? \"out\" : \"in\",\n\t\t});\n\t});\n\n\t// offline synchronisation\n\n\troute(\n\t\t\"POST\",\n\t\t\"/entries/sync\",\n\t\t{ permission: \"time.punch\", csrf: true, rateLimit: { name: \"sync\", limit: 30, windowSeconds: 60 } },\n\t\tcontext => {\n\t\t\tif (!context.auth) {\n\t\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t\t}\n\t\t\tconst body = context.jsonBody();\n\t\t\tconst user = users.findById(context.auth.user.id);\n\t\t\tif (!user) {\n\t\t\t\tthrow new NotFoundError(`user ${context.auth.user.id} not found`);\n\t\t\t}\n\t\t\tif (!Array.isArray(body.punches)) {\n\t\t\t\tthrow new ValidationError(\"punches must be an array\");\n\t\t\t}\n\n\t\t\tconst result = sync.sync({\n\t\t\t\tuserId: user.id,\n\t\t\t\ttimeZone: user.timezone,\n\t\t\t\t// the offline queue is the only way an employee still writes a punch of his own, so the window of the\n\t\t\t\t// instance applies to it as well (`0` switches the bound off)\n\t\t\t\tmaxPastSeconds: Math.max(0, settings.getNumber(\"edit_window_days\", 0)) * 86_400,\n\t\t\t\tpunches: (body.punches as unknown[]).map(raw => {\n\t\t\t\t\tconst punch = (raw ?? {}) as Record<string, unknown>;\n\t\t\t\t\tconst tsUtc = optionalNumber(punch, \"tsUtc\");\n\t\t\t\t\tif (tsUtc === null) {\n\t\t\t\t\t\tthrow new ValidationError(\"tsUtc is required for every queued punch\");\n\t\t\t\t\t}\n\t\t\t\t\treturn {\n\t\t\t\t\t\tidempotencyKey: typeof punch.idempotencyKey === \"string\" ? punch.idempotencyKey : \"\",\n\t\t\t\t\t\ttsUtc,\n\t\t\t\t\t\tclientTsUtc: optionalNumber(punch, \"clientTsUtc\"),\n\t\t\t\t\t\tdirection: optionalDirection(punch),\n\t\t\t\t\t\tnote: optionalString(punch, \"note\"),\n\t\t\t\t\t};\n\t\t\t\t}),\n\t\t\t\tactorId: user.id,\n\t\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\t\tnow: now(),\n\t\t\t});\n\t\t\t// the batch belongs to one employee: their day changed, so their clients refresh it\n\t\t\temit({\n\t\t\t\ttype: \"punch\",\n\t\t\t\tuserId: user.id,\n\t\t\t\tdata: {\n\t\t\t\t\tsynced: result.accepted.filter(entry => entry.created).length,\n\t\t\t\t\tduplicates: result.accepted.filter(entry => !entry.created).length,\n\t\t\t\t\tconflicts: result.conflicts.length,\n\t\t\t\t\trejected: result.rejected.length,\n\t\t\t\t\tdays: result.recalculated.length,\n\t\t\t\t},\n\t\t\t});\n\t\t\treturn json(200, result);\n\t\t},\n\t);\n\n\troute(\"GET\", \"/entries/conflicts\", { permission: \"time.resolve_conflict\" }, context =>\n\t\t// without `?userId=` the own queue; the administration reads the queue of an employee with it, because a\n\t\t// conflict of an employee waits for a decision of the office\n\t\tjson(200, {\n\t\t\tconflicts: sync.conflicts(scopeUser(context, \"time.resolve_conflict\", \"time.resolve_conflict\")),\n\t\t}),\n\t);\n\n\troute(\"POST\", \"/entries/:id/resolve\", { permission: \"time.resolve_conflict\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst body = context.jsonBody();\n\t\tconst action = requireString(body, \"action\");\n\t\tif (action !== \"accept\" && action !== \"dismiss\") {\n\t\t\tthrow new ValidationError(`action must be accept or dismiss (got ${action})`);\n\t\t}\n\n\t\tconst result = sync.resolve({\n\t\t\tentryId: numberParam(context, \"id\"),\n\t\t\taction,\n\t\t\ttsUtc: optionalNumber(body, \"tsUtc\") ?? undefined,\n\t\t\treason: optionalString(body, \"reason\"),\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\tif (result.entry) {\n\t\t\temit({\n\t\t\t\ttype: \"entry.update\",\n\t\t\t\tuserId: result.entry.userId,\n\t\t\t\tdata: { entryId: result.entry.id, localDate: result.entry.localDate, resolved: action },\n\t\t\t});\n\t\t}\n\t\treturn json(200, result);\n\t});\n\n\t// aggregates and reports\n\n\troute(\"GET\", \"/aggregates/day\", { permission: \"report.view_own\" }, context => {\n\t\tconst userId = scopeUser(context);\n\t\tconst from = context.query(\"from\");\n\t\tconst to = context.query(\"to\") ?? from;\n\n\t\t// a range lists the single days (used by the calendar of the web app)\n\t\tif (from && to) {\n\t\t\tconst range = aggregation.recalculateRange(userId, from, to, { now: now() });\n\t\t\treturn json(200, { from, to, ...range, days: aggregation.days(userId, from, to) });\n\t\t}\n\n\t\tconst user = users.findById(userId);\n\t\tif (!user) {\n\t\t\tthrow new NotFoundError(`user ${userId} not found`);\n\t\t}\n\t\tconst date = context.query(\"date\") ?? localDate(now(), user.timezone);\n\t\tif (!/^\\d{4}-\\d{2}-\\d{2}$/.test(date)) {\n\t\t\tthrow new ValidationError(`date must be YYYY-MM-DD (got ${date})`);\n\t\t}\n\t\treturn json(200, { day: aggregation.recalculateDay(userId, date, { now: now() }) });\n\t});\n\n\troute(\"GET\", \"/aggregates/month\", { permission: \"report.view_own\" }, context => {\n\t\tconst userId = scopeUser(context);\n\t\tconst year = numberQuery(context, \"year\");\n\n\t\tconst requestedMonth = context.query(\"month\");\n\t\tif (!requestedMonth) {\n\t\t\t// the whole year: one recalculation pass, then the twelve monthly rows\n\t\t\tconst totals = aggregation.recalculateYear(userId, year, { now: now() });\n\t\t\tconst months = Array.from({ length: 12 }, (_, index) => aggregation.month(userId, year, index + 1));\n\t\t\treturn json(200, { year: totals, months });\n\t\t}\n\n\t\tconst month = Number(requestedMonth);\n\t\tif (!Number.isInteger(month) || month < 1 || month > 12) {\n\t\t\tthrow new ValidationError(`month must be between 1 and 12 (got ${requestedMonth})`);\n\t\t}\n\t\tconst monthly = aggregation.recalculateMonth(userId, year, month, { now: now() });\n\t\tconst yearly = aggregation.recalculateYear(userId, year, { now: now() });\n\t\treturn json(200, { month: monthly, year: yearly });\n\t});\n\n\troute(\"GET\", \"/aggregates/year\", { permission: \"report.view_own\" }, context => {\n\t\tconst userId = scopeUser(context);\n\t\tconst year = numberQuery(context, \"year\");\n\t\treturn json(200, { year: aggregation.recalculateYear(userId, year, { now: now() }) });\n\t});\n\n\troute(\"GET\", \"/entries\", { permission: \"report.view_own\" }, context => {\n\t\tconst userId = scopeUser(context);\n\t\tconst from = context.query(\"from\");\n\t\tconst to = context.query(\"to\") ?? from;\n\t\tif (!from || !to) {\n\t\t\tthrow new ValidationError(\"from and to are required\");\n\t\t}\n\t\treturn json(200, { entries: entries.listByRange(userId, from, to) });\n\t});\n\n\t// A punch written by hand is an administrative correction: an employee does not edit his own times, he leaves a\n\t// day note for the administration (`time.edit_own` is that right) and the administration books the day.\n\troute(\"POST\", \"/entries\", { permission: \"time.edit_other\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst body = context.jsonBody();\n\t\tconst target = Number(context.query(\"userId\") ?? context.auth.user.id);\n\t\tif (!Number.isInteger(target)) {\n\t\t\tthrow new ValidationError(\"userId must be a whole number\");\n\t\t}\n\n\t\tconst user = users.findById(target);\n\t\tif (!user) {\n\t\t\tthrow new NotFoundError(`user ${target} not found`);\n\t\t}\n\t\tconst tsUtc = optionalNumber(body, \"tsUtc\");\n\t\tif (tsUtc === null) {\n\t\t\tthrow new ValidationError(\"tsUtc is required\");\n\t\t}\n\n\t\tconst timestamp = now();\n\t\tconst stored = entries.insert({\n\t\t\tuserId: target,\n\t\t\ttsUtc,\n\t\t\tclientTsUtc: optionalNumber(body, \"clientTsUtc\"),\n\t\t\ttimeZone: user.timezone,\n\t\t\t// a hand-written punch is always a correction; the normal punch comes through `POST /punch` (`web`)\n\t\t\tsource: \"admin\",\n\t\t\tdirection: optionalDirection(body),\n\t\t\tidempotencyKey: optionalString(body, \"idempotencyKey\"),\n\t\t\tnote: optionalString(body, \"note\"),\n\t\t\treason: optionalString(body, \"reason\"),\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: timestamp,\n\t\t});\n\n\t\tconst day = aggregation.recalculateDay(target, stored.entry.localDate, { now: timestamp });\n\t\temit({\n\t\t\ttype: \"punch\",\n\t\t\tuserId: target,\n\t\t\tdata: {\n\t\t\t\tentryId: stored.entry.id,\n\t\t\t\tcreated: stored.created,\n\t\t\t\tlocalDate: stored.entry.localDate,\n\t\t\t\tsource: \"admin\",\n\t\t\t},\n\t\t});\n\t\treturn json(\n\t\t\t201,\n\t\t\t{ entry: stored.entry, created: stored.created, day },\n\t\t\t{ location: `/entries/${stored.entry.id}` },\n\t\t);\n\t});\n\n\t// absences\n\n\t/**\n\t * The fields of an employee the calendar needs.\n\t *\n\t * @param user - the employee\n\t * @returns the employee as the calendar service expects it\n\t */\n\tconst calendarEmployee = (user: UserRecord): CalendarEmployee => ({\n\t\tid: user.id,\n\t\tdisplayName: user.displayName,\n\t\tlogin: user.login,\n\t});\n\n\t/**\n\t * Adds the code and the name of the type to an absence.\n\t *\n\t * The stored record only knows the id of its type, while a client shows the code (specification 4: `Absence`).\n\t *\n\t * @param absence - stored absence\n\t * @returns the absence as the API hands it out\n\t */\n\tconst publicAbsence = (\n\t\tabsence: AbsenceRecord,\n\t): AbsenceRecord & {\n\t\t/** Code of the type, e.g. `vacation` */\n\t\ttypeCode: string | null;\n\t\t/** Name of the type as the client shows it */\n\t\ttypeName: string | null;\n\t} => {\n\t\tconst type = absences.findType(absence.typeId);\n\t\treturn { ...absence, typeCode: type?.code ?? null, typeName: type?.name ?? null };\n\t};\n\n\troute(\"GET\", \"/absences\", { permission: \"report.view_own\" }, context => {\n\t\t// `scope=all` is the overview of the administration: every employee in one answer, so the tab needs a single\n\t\t// request. The open requests and \u201Cwho is away\u201D both read from here. A path segment would clash with\n\t\t// `/absences/:id`, which the router matches first \u2014 hence the query parameter.\n\t\tif (context.query(\"scope\") === \"all\") {\n\t\t\tif (!context.auth?.permissions.includes(\"absence.approve\")) {\n\t\t\t\tthrow problem(403, \"permission_denied\", \"request rejected (permission_denied: absence.approve)\");\n\t\t\t}\n\t\t\tconst from = context.query(\"from\");\n\t\t\tconst to = context.query(\"to\");\n\t\t\tif (!from || !to) {\n\t\t\t\tthrow new ValidationError(\"from and to are required with scope=all\");\n\t\t\t}\n\t\t\treturn json(200, { absences: absences.allInRange(from, to).map(publicAbsence) });\n\t\t}\n\t\tconst requested = context.query(\"userId\") ? Number(context.query(\"userId\")) : null;\n\t\tconst userId = resolveScope(context, requested, \"report.view_own\", \"report.view_other\");\n\t\tconst year = context.query(\"year\") ? Number(context.query(\"year\")) : undefined;\n\t\treturn json(200, {\n\t\t\tabsences: absences.listByUser(userId, year === undefined ? {} : { year }).map(publicAbsence),\n\t\t});\n\t});\n\n\troute(\"POST\", \"/absences\", { permission: \"absence.request\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst body = context.jsonBody();\n\t\tconst userId = resolveScope(context, optionalNumber(body, \"userId\"), \"absence.request\", \"absence.edit_other\");\n\n\t\tconst status = optionalString(body, \"status\");\n\t\tif (status !== null && status !== \"taken\" && status !== \"planned\") {\n\t\t\tthrow new ValidationError(`status must be taken or planned (got ${status})`);\n\t\t}\n\t\t// The administration books dates straight away, an employee only requests them: who may change an absence\n\t\t// of somebody else decides about it, everybody else waits for that decision.\n\t\tconst mayDecide = context.auth.permissions.includes(\"absence.edit_other\");\n\t\tconst approval: AbsenceApproval = mayDecide ? \"approved\" : \"requested\";\n\n\t\t// An employee may only pick a type the administration released for everybody: a sickness note, an accident or\n\t\t// military service are things the company books itself, nobody requests them.\n\t\tif (!mayDecide) {\n\t\t\tconst wanted = absences.findType(\n\t\t\t\toptionalNumber(body, \"typeId\") ?? optionalString(body, \"typeCode\") ?? \"\",\n\t\t\t\tuserId,\n\t\t\t);\n\t\t\tif (!wanted) {\n\t\t\t\tthrow new ValidationError(\"unknown absence type\");\n\t\t\t}\n\t\t\tif (!wanted.isActive) {\n\t\t\t\tthrow problem(\n\t\t\t\t\t403,\n\t\t\t\t\t\"permission_denied\",\n\t\t\t\t\t\"request rejected (permission_denied: absence type is not public)\",\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tconst created = absences.create({\n\t\t\tuserId,\n\t\t\ttypeCode: optionalString(body, \"typeCode\") ?? undefined,\n\t\t\ttypeId: optionalNumber(body, \"typeId\") ?? undefined,\n\t\t\tdateFrom: requireString(body, \"dateFrom\"),\n\t\t\tdateTo: optionalString(body, \"dateTo\") ?? undefined,\n\t\t\tdayPortion: optionalNumber(body, \"dayPortion\") ?? undefined,\n\t\t\thours: optionalNumber(body, \"hours\"),\n\t\t\tstatus: status ?? undefined,\n\t\t\tapproval,\n\t\t\tnote: optionalString(body, \"note\"),\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\temit({ type: \"absence.change\", userId, data: { absenceId: created.id, action: \"created\" } });\n\t\treturn json(201, { absence: publicAbsence(created) }, { location: `/absences/${created.id}` });\n\t});\n\n\troute(\"POST\", \"/absences/:id/status\", { permission: \"absence.approve\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst status = requireString(context.jsonBody(), \"status\");\n\t\tif (status !== \"taken\" && status !== \"planned\") {\n\t\t\tthrow new ValidationError(`status must be taken or planned (got ${status})`);\n\t\t}\n\n\t\tconst updated = absences.setStatus({\n\t\t\tid: numberParam(context, \"id\"),\n\t\t\tstatus,\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\temit({ type: \"absence.change\", userId: updated.userId, data: { absenceId: updated.id, action: \"status\" } });\n\t\treturn json(200, { absence: publicAbsence(updated) });\n\t});\n\n\t// The other decision: the administration approves or rejects what an employee asked for. The reason travels\n\t// with the decision, so the employee sees why a request came back.\n\troute(\"POST\", \"/absences/:id/approval\", { permission: \"absence.approve\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst body = context.jsonBody();\n\t\tconst approval = requireString(body, \"approval\");\n\t\tif (approval !== \"requested\" && approval !== \"approved\" && approval !== \"rejected\") {\n\t\t\tthrow new ValidationError(`approval must be requested, approved or rejected (got ${approval})`);\n\t\t}\n\n\t\tconst updated = absences.setApproval({\n\t\t\tid: numberParam(context, \"id\"),\n\t\t\tapproval,\n\t\t\tnote: optionalString(body, \"note\"),\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\temit({ type: \"absence.change\", userId: updated.userId, data: { absenceId: updated.id, action: \"approval\" } });\n\t\treturn json(200, { absence: publicAbsence(updated) });\n\t});\n\n\t/**\n\t * Loads an absence and checks whether the caller may change it.\n\t *\n\t * @param context - route context\n\t * @returns the absence\n\t */\n\tconst changeableAbsence = (context: RouteContext): AbsenceRecord => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst absence = absences.findById(numberParam(context, \"id\"));\n\t\tif (!absence) {\n\t\t\tthrow new NotFoundError(`absence ${context.params.id} not found`);\n\t\t}\n\t\tif (absence.userId !== context.auth.user.id && !context.auth.permissions.includes(\"absence.edit_other\")) {\n\t\t\tthrow problem(403, \"permission_denied\", \"request rejected (permission_denied: absence.edit_other)\");\n\t\t}\n\t\treturn absence;\n\t};\n\n\troute(\"PATCH\", \"/absences/:id\", { permission: \"absence.request\", csrf: true }, context => {\n\t\tconst absence = changeableAbsence(context);\n\t\tconst body = context.jsonBody();\n\t\tconst status = optionalString(body, \"status\");\n\n\t\tif (status !== null && status !== \"taken\" && status !== \"planned\") {\n\t\t\tthrow new ValidationError(`status must be taken or planned (got ${status})`);\n\t\t}\n\t\t// changing the state of an absence is an approval, even for the own one\n\t\tif (status !== null && !context.auth?.permissions.includes(\"absence.approve\")) {\n\t\t\tthrow problem(403, \"permission_denied\", \"request rejected (permission_denied: absence.approve)\");\n\t\t}\n\n\t\tconst patch = {\n\t\t\ttypeId: optionalNumber(body, \"typeId\") ?? undefined,\n\t\t\ttypeCode: optionalString(body, \"typeCode\") ?? undefined,\n\t\t\tdateFrom: optionalString(body, \"dateFrom\") ?? undefined,\n\t\t\tdateTo: optionalString(body, \"dateTo\") ?? undefined,\n\t\t\tdayPortion: optionalNumber(body, \"dayPortion\") ?? undefined,\n\t\t\t...(Object.prototype.hasOwnProperty.call(body, \"hours\") ? { hours: optionalNumber(body, \"hours\") } : {}),\n\t\t\t...(Object.prototype.hasOwnProperty.call(body, \"note\") ? { note: optionalString(body, \"note\") } : {}),\n\t\t};\n\t\tconst hasFields = Object.values(patch).some(value => value !== undefined);\n\n\t\t// The same guard as in POST /absences: an employee may not move an absence to a type that is not public.\n\t\tif (\n\t\t\thasFields &&\n\t\t\t!context.auth?.permissions.includes(\"absence.edit_other\") &&\n\t\t\t(patch.typeId !== undefined || patch.typeCode !== undefined)\n\t\t) {\n\t\t\tconst wanted = absences.findType(patch.typeId ?? patch.typeCode ?? \"\", absence.userId);\n\t\t\tif (!wanted) {\n\t\t\t\tthrow new ValidationError(\"unknown absence type\");\n\t\t\t}\n\t\t\tif (!wanted.isActive) {\n\t\t\t\tthrow problem(\n\t\t\t\t\t403,\n\t\t\t\t\t\"permission_denied\",\n\t\t\t\t\t\"request rejected (permission_denied: absence type is not public)\",\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (status !== null) {\n\t\t\tabsences.setStatus({\n\t\t\t\tid: absence.id,\n\t\t\t\tstatus,\n\t\t\t\tactorId: context.auth?.user.id ?? 0,\n\t\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\t\tnow: now(),\n\t\t\t});\n\t\t}\n\n\t\tconst changed = hasFields\n\t\t\t? absences.update({\n\t\t\t\t\tid: absence.id,\n\t\t\t\t\tpatch,\n\t\t\t\t\treason: optionalString(body, \"reason\"),\n\t\t\t\t\tactorId: context.auth?.user.id ?? 0,\n\t\t\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\t\t\tnow: now(),\n\t\t\t\t})\n\t\t\t: (absences.findById(absence.id) ?? absence);\n\n\t\t// An employee who changes a request asks again: the decision of the administration belonged to the old\n\t\t// dates. Who may change absences of somebody else decides as well, so nothing goes back for them.\n\t\tconst updated =\n\t\t\thasFields && !context.auth?.permissions.includes(\"absence.edit_other\") && changed.approval !== \"requested\"\n\t\t\t\t? absences.setApproval({\n\t\t\t\t\t\tid: changed.id,\n\t\t\t\t\t\tapproval: \"requested\",\n\t\t\t\t\t\tnote: null,\n\t\t\t\t\t\tactorId: context.auth?.user.id ?? 0,\n\t\t\t\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\t\t\t\tnow: now(),\n\t\t\t\t\t})\n\t\t\t\t: changed;\n\n\t\temit({ type: \"absence.change\", userId: updated.userId, data: { absenceId: updated.id, action: \"updated\" } });\n\t\treturn json(200, { absence: publicAbsence(updated) });\n\t});\n\n\troute(\"DELETE\", \"/absences/:id\", { permission: \"absence.request\", csrf: true }, context => {\n\t\tconst absence = changeableAbsence(context);\n\t\tconst removed = absences.remove({\n\t\t\tid: absence.id,\n\t\t\tactorId: context.auth?.user.id ?? 0,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\tif (!removed) {\n\t\t\tthrow new NotFoundError(`absence ${absence.id} not found`);\n\t\t}\n\t\temit({ type: \"absence.change\", userId: absence.userId, data: { absenceId: absence.id, action: \"deleted\" } });\n\t\treturn noContent();\n\t});\n\n\t// master data: absence types, holidays and instance settings\n\n\t// The administration sees the types that are switched off as well \u2014 that is what \u201Cactive\u201D is for: an inactive type\n\t// stays out of the picker of the employees, while the administration can still book it for somebody.\n\t// The calendar feed: a public route with the secret in the URL, so a phone or a calendar app can subscribe without\n\t// The calendar feed: a public route with the secret in the URL, so a phone or a calendar app can subscribe without\n\t// a session. Two tokens are accepted: the personal one of an employee (nobody else sees anything through such a\n\t// link) and the token of the instance, which carries the absences of the whole company \u2014 that is the feed ioBroker\n\t// and a company calendar subscribe to.\n\troute(\n\t\t\"GET\",\n\t\t\"/calendar.ics\",\n\t\t{ public: true, csrf: false, rateLimit: { name: \"calendar\", limit: 30, windowSeconds: 60 } },\n\t\tcontext => {\n\t\t\tconst token = (context.query(\"token\") ?? \"\").trim();\n\t\t\tconst instanceToken = settings.get(\"calendar_token\");\n\t\t\tconst company = instanceToken !== null && instanceToken !== \"\" && token === instanceToken;\n\n\t\t\t// without the instance token the link belongs to one employee\n\t\t\tconst user = company || token === \"\" ? null : users.findByCalendarToken(token);\n\t\t\tif (!company && (!user || !user.isActive)) {\n\t\t\t\tthrow problem(401, \"invalid_credentials\", \"the calendar token is not valid\");\n\t\t\t}\n\n\t\t\t// a window around today keeps a feed small: a year back and to the end of next year\n\t\t\tconst stamp = now();\n\t\t\tconst year = new Date(stamp * 1000).getUTCFullYear();\n\t\t\tconst from = `${year - 1}-01-01`;\n\t\t\tconst to = `${year + 1}-12-31`;\n\n\t\t\t// the company feed skips deactivated accounts (`users.list()` returns the active ones)\n\t\t\tconst owners = user ? [user] : users.list();\n\t\t\tconst employees = new Map(owners.map(employee => [employee.id, calendarEmployee(employee)]));\n\t\t\tconst list = user ? absences.withTypesInRange(user.id, from, to) : absences.allInRange(from, to);\n\t\t\tconst name = user ? `Abwesenheiten ${user.displayName}` : COMPANY_CALENDAR_NAME;\n\n\t\t\treturn binary(\n\t\t\t\t200,\n\t\t\t\tBuffer.from(calendarDocument(name, absenceEvents(list, employees, !user), stamp), \"utf8\"),\n\t\t\t\tICAL_CONTENT_TYPE,\n\t\t\t\t{\n\t\t\t\t\t\"content-disposition\": 'inline; filename=\"time-tracker.ics\"',\n\t\t\t\t\t\"cache-control\": \"no-store\",\n\t\t\t\t},\n\t\t\t);\n\t\t},\n\t);\n\n\t// The subscription link of the own calendar: the token is created on the first call and can be rotated, which\n\t// invalidates the old link. The administration may ask for the token of somebody else.\n\troute(\"POST\", \"/calendar/token\", { permission: \"report.view_own\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst body = context.jsonBody();\n\t\tconst userId = resolveScope(context, optionalNumber(body, \"userId\"), \"report.view_own\", \"absence.edit_other\");\n\t\tconst current = users.findById(userId);\n\t\tif (!current) {\n\t\t\tthrow new NotFoundError(`user ${userId} not found`);\n\t\t}\n\n\t\tconst rotate = optionalBoolean(body, \"rotate\") ?? false;\n\t\tconst token =\n\t\t\trotate || current.calendarToken === null ? randomBytes(32).toString(\"base64url\") : current.calendarToken;\n\t\tconst updated = users.setCalendarToken({\n\t\t\tuserId,\n\t\t\ttoken,\n\t\t\tactorId: context.auth.user.id,\n\t\t\tnow: now(),\n\t\t});\n\t\treturn json(200, { token: updated.calendarToken });\n\t});\n\n\troute(\"GET\", \"/absence-types\", {}, context => {\n\t\tconst includeInactive =\n\t\t\tcontext.query(\"includeInactive\") === \"true\" &&\n\t\t\t(context.auth?.permissions.includes(\"absence.manage_types\") ?? false);\n\t\treturn json(200, {\n\t\t\ttypes: absences.types({ userId: context.auth?.user.id ?? null, includeInactive }),\n\t\t});\n\t});\n\n\troute(\"POST\", \"/absence-types\", { permission: \"absence.manage_types\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst body = context.jsonBody();\n\t\tconst result = absences.upsertType({\n\t\t\tuserId: optionalNumber(body, \"userId\"),\n\t\t\tcode: requireString(body, \"code\"),\n\t\t\tname: requireString(body, \"name\"),\n\t\t\tpaid: optionalBoolean(body, \"paid\") ?? undefined,\n\t\t\tfactor: optionalNumber(body, \"factor\") ?? undefined,\n\t\t\treduceVacation: optionalBoolean(body, \"reduceVacation\") ?? undefined,\n\t\t\tisActive: optionalBoolean(body, \"isActive\") ?? undefined,\n\t\t\tcolor: optionalString(body, \"color\"),\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\treturn json(result.created ? 201 : 200, { type: result.type, created: result.created });\n\t});\n\n\troute(\"DELETE\", \"/absence-types/:id\", { permission: \"absence.manage_types\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst removed = absences.removeType({\n\t\t\tid: numberParam(context, \"id\"),\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\tif (!removed) {\n\t\t\tthrow new NotFoundError(`absence type ${context.params.id} not found`);\n\t\t}\n\t\treturn noContent();\n\t});\n\n\troute(\"GET\", \"/holidays\", { permission: \"report.view_own\" }, context => {\n\t\tconst year = numberQuery(context, \"year\");\n\t\tconst region = context.query(\"region\") ?? undefined;\n\t\treturn json(200, { year, region: region ?? null, holidays: holidays.listByYear(year, region) });\n\t});\n\n\troute(\"POST\", \"/holidays\", { permission: \"holiday.manage\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst body = context.jsonBody();\n\t\tconst created = holidays.add({\n\t\t\tdate: requireString(body, \"date\"),\n\t\t\tname: requireString(body, \"name\"),\n\t\t\tregion: optionalString(body, \"region\") ?? undefined,\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\treturn json(201, { holiday: created }, { location: `/holidays/${created.id}` });\n\t});\n\n\troute(\"DELETE\", \"/holidays/:id\", { permission: \"holiday.manage\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst removed = holidays.remove({\n\t\t\tid: numberParam(context, \"id\"),\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\tif (!removed) {\n\t\t\tthrow new NotFoundError(`holiday ${context.params.id} not found`);\n\t\t}\n\t\treturn noContent();\n\t});\n\n\troute(\"GET\", \"/settings\", { permission: \"settings.view\" }, () =>\n\t\tjson(200, { settings: readableSettings(settings.all()) }),\n\t);\n\n\troute(\"PUT\", \"/settings\", { permission: \"settings.edit\", csrf: true }, context => {\n\t\tconst body = context.jsonBody();\n\t\tconst changes: Record<string, SettingValue> = {};\n\t\tfor (const [key, value] of Object.entries(body)) {\n\t\t\tif (!editableSettings().includes(key)) {\n\t\t\t\tthrow new ValidationError(`setting \"${key}\" cannot be changed through the API`);\n\t\t\t}\n\t\t\t// the branding pictures and the accent colour have rules of their own: a picture (data URL) and a hex\n\t\t\t// colour \u2014 and they are the only settings that a public route hands out\n\t\t\tif (key === \"brand_logo\" || key === \"brand_background\") {\n\t\t\t\tconst raw = typeof value === \"string\" ? value.trim() : \"\";\n\t\t\t\tif (raw !== \"\" && !parseAvatarDataUrl(raw, MAX_BRANDING_BYTES)) {\n\t\t\t\t\tthrow new ValidationError(\n\t\t\t\t\t\t`setting \"${key}\" must be a data URL of a png, jpeg, webp or gif of up to ${MAX_BRANDING_BYTES} bytes`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tchanges[key] = raw;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (key === \"brand_color\") {\n\t\t\t\tconst raw = typeof value === \"string\" ? value.trim() : \"\";\n\t\t\t\tif (raw !== \"\" && !/^#[0-9a-f]{3}([0-9a-f]{3})?$/i.test(raw)) {\n\t\t\t\t\tthrow new ValidationError('setting \"brand_color\" must be a hex colour like \"#1a2b3c\"');\n\t\t\t\t}\n\t\t\t\tchanges[key] = raw;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// the pause mode decides how the pause of a day is determined; the calculation tolerates an unknown\n\t\t\t// value, but the API does not hand one out in the first place\n\t\t\tif (key === \"pause_mode\") {\n\t\t\t\tif (value !== \"auto\" && value !== \"punched\" && value !== \"staffel\") {\n\t\t\t\t\tthrow new ValidationError('setting \"pause_mode\" must be \"auto\", \"punched\" or \"staffel\"');\n\t\t\t\t}\n\t\t\t\tchanges[key] = value;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (!isSettingValue(value, JSON_SETTINGS.includes(key))) {\n\t\t\t\tthrow new ValidationError(`setting \"${key}\" has an unsupported value`);\n\t\t\t}\n\t\t\tchanges[key] = value as SettingValue;\n\t\t}\n\t\tif (Object.keys(changes).length === 0) {\n\t\t\tthrow new ValidationError(\"no settings given\");\n\t\t}\n\n\t\tfor (const [key, value] of Object.entries(changes)) {\n\t\t\tsettings.set(key, value, context.auth?.user.id ?? null, now());\n\t\t}\n\t\treturn json(200, { settings: changes });\n\t});\n\n\t/**\n\t * Replaces the graduated break rules of one owner.\n\t *\n\t * The payload is the whole table: rules that are missing are removed, the rest is saved. Values are checked by\n\t * the repository, so an impossible rule becomes a client error.\n\t *\n\t * @param context - route context of the request\n\t * @param userId - owner of the rules, `null` for the company default\n\t * @returns the saved rules\n\t */\n\tfunction replacePauseRules(context: RouteContext, userId: number | null): PauseRuleRecord[] {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst body = context.jsonBody();\n\t\tif (!Array.isArray(body.pauseRules)) {\n\t\t\tthrow new ValidationError(\"pauseRules must be an array\");\n\t\t}\n\t\tconst actor = {\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t};\n\t\tconst wanted = (body.pauseRules as unknown[]).map(raw => {\n\t\t\tconst rule = (raw ?? {}) as Record<string, unknown>;\n\t\t\treturn {\n\t\t\t\tid: optionalNumber(rule, \"id\") ?? undefined,\n\t\t\t\tfromMin: Number(rule.fromMin),\n\t\t\t\ttoMin: optionalNumber(rule, \"toMin\"),\n\t\t\t\tpauseMin: Number(rule.pauseMin),\n\t\t\t\tisActive: optionalBoolean(rule, \"isActive\") ?? undefined,\n\t\t\t};\n\t\t});\n\n\t\tconst keep = new Set(wanted.map(rule => rule.id).filter((value): value is number => value !== undefined));\n\t\tfor (const existing of rules.listPauseRules({ userId, includeInactive: true })) {\n\t\t\tif (!keep.has(existing.id)) {\n\t\t\t\trules.removePauseRule({ id: existing.id, ...actor });\n\t\t\t}\n\t\t}\n\t\treturn wanted.map(rule =>\n\t\t\trules.savePauseRule({\n\t\t\t\t...(rule.id !== undefined ? { id: rule.id } : {}),\n\t\t\t\tuserId,\n\t\t\t\tfromMin: rule.fromMin,\n\t\t\t\ttoMin: rule.toMin,\n\t\t\t\tpauseMin: rule.pauseMin,\n\t\t\t\t...(rule.isActive !== undefined ? { isActive: rule.isActive } : {}),\n\t\t\t\t...actor,\n\t\t\t}),\n\t\t);\n\t}\n\n\t// graduated break rules of the instance (the company default): they decide the pause of a day on which nobody\n\t// punched a break, so they are edited as a whole table \u2014 like the shift rules of an employee.\n\n\troute(\"GET\", \"/pause-rules\", { permission: \"settings.view\" }, () =>\n\t\tjson(200, { pauseRules: rules.listPauseRules({ userId: null, includeInactive: true }) }),\n\t);\n\n\troute(\"PUT\", \"/pause-rules\", { permission: \"settings.edit\", csrf: true }, context =>\n\t\tjson(200, { pauseRules: replacePauseRules(context, null) }),\n\t);\n\n\t// The rules of one employee: they replace the company rule with the same `fromMin`, so a single person can\n\t// deviate from the house rule without changing it for everybody.\n\n\troute(\"GET\", \"/users/:id/pause-rules\", { permission: \"user.view\" }, context => {\n\t\tconst id = numberParam(context, \"id\");\n\t\tif (!users.findById(id)) {\n\t\t\tthrow new NotFoundError(`user ${id} not found`);\n\t\t}\n\t\treturn json(200, { pauseRules: rules.listPauseRules({ userId: id, includeInactive: true }) });\n\t});\n\n\troute(\"PUT\", \"/users/:id/pause-rules\", { permission: \"user.edit\", csrf: true }, context => {\n\t\tconst id = numberParam(context, \"id\");\n\t\tif (!users.findById(id)) {\n\t\t\tthrow new NotFoundError(`user ${id} not found`);\n\t\t}\n\t\treturn json(200, { pauseRules: replacePauseRules(context, id) });\n\t});\n\n\t/**\n\t * Replaces the trigger rules.\n\t *\n\t * Like the break rules the payload is the whole table: rules that are missing are removed, the rest is saved.\n\t * The values are checked by the repository, so an impossible rule becomes a client error. Afterwards the\n\t * adapter is told to watch the states of the new list.\n\t *\n\t * @param context - route context of the request\n\t * @returns the saved rules\n\t */\n\tfunction replaceTriggerRules(context: RouteContext): TriggerRuleRecord[] {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst body = context.jsonBody();\n\t\tif (!Array.isArray(body.triggerRules)) {\n\t\t\tthrow new ValidationError(\"triggerRules must be an array\");\n\t\t}\n\t\tconst actor = {\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t};\n\t\tconst wanted = (body.triggerRules as unknown[]).map(raw => {\n\t\t\tconst rule = (raw ?? {}) as Record<string, unknown>;\n\t\t\tconst mode = optionalString(rule, \"mode\") ?? \"condition\";\n\t\t\tconst target = optionalNumber(rule, \"userId\");\n\t\t\tif (mode === \"condition\" && target !== null && !users.findById(target)) {\n\t\t\t\tthrow new ValidationError(`userId must reference an existing employee (got ${target})`);\n\t\t\t}\n\t\t\tif (mode === \"user\" && target !== null) {\n\t\t\t\tthrow new ValidationError(\"mode user reads the employee from the state, userId has to stay empty\");\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tid: optionalNumber(rule, \"id\") ?? undefined,\n\t\t\t\tlabel: optionalString(rule, \"label\"),\n\t\t\t\tsourceState: optionalString(rule, \"sourceState\") ?? \"\",\n\t\t\t\tmode,\n\t\t\t\tcondition: optionalString(rule, \"condition\"),\n\t\t\t\tuserId: target,\n\t\t\t\taction: optionalString(rule, \"action\") ?? \"punch\",\n\t\t\t\tisActive: optionalBoolean(rule, \"isActive\") ?? true,\n\t\t\t\tcooldownSec: optionalNumber(rule, \"cooldownSec\") ?? 0,\n\t\t\t};\n\t\t});\n\n\t\tconst keep = new Set(wanted.map(rule => rule.id).filter((value): value is number => value !== undefined));\n\t\tfor (const existing of triggers.list({ includeInactive: true })) {\n\t\t\tif (!keep.has(existing.id)) {\n\t\t\t\ttriggers.remove({ id: existing.id, ...actor });\n\t\t\t}\n\t\t}\n\n\t\tconst saved = wanted.map(rule =>\n\t\t\ttriggers.save({\n\t\t\t\t...(rule.id !== undefined ? { id: rule.id } : {}),\n\t\t\t\tlabel: rule.label,\n\t\t\t\tsourceState: rule.sourceState,\n\t\t\t\tmode: rule.mode as TriggerMode,\n\t\t\t\tcondition: rule.condition,\n\t\t\t\tuserId: rule.userId,\n\t\t\t\taction: rule.action as TriggerAction,\n\t\t\t\tisActive: rule.isActive,\n\t\t\t\tcooldownSec: rule.cooldownSec,\n\t\t\t\t...actor,\n\t\t\t}),\n\t\t);\n\n\t\t// the adapter subscribes to the states the rules name: tell it about the new list right away\n\t\tdeps.onTriggerRulesChanged?.();\n\t\treturn saved;\n\t}\n\n\t// Trigger rules: a state of another adapter (fingerprint reader, button, door contact) punches or sets the\n\t// presence. The rules are edited as a whole table, like the break rules of the company.\n\troute(\"GET\", \"/trigger-rules\", { permission: \"settings.view\" }, () =>\n\t\tjson(200, { triggerRules: triggers.list({ includeInactive: true }) }),\n\t);\n\n\troute(\"PUT\", \"/trigger-rules\", { permission: \"settings.edit\", csrf: true }, context =>\n\t\tjson(200, { triggerRules: replaceTriggerRules(context) }),\n\t);\n\n\t/**\n\t * Replaces the automation rules.\n\t *\n\t * Like the other rule tables the payload is the whole table: rules that are missing are removed, the rest is\n\t * saved. The adapter reads the table every minute, so nothing has to be told about the change.\n\t *\n\t * @param context - route context of the request\n\t * @returns the saved rules\n\t */\n\tfunction replaceAutomationRules(context: RouteContext): AutomationRuleRecord[] {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst body = context.jsonBody();\n\t\tif (!Array.isArray(body.automationRules)) {\n\t\t\tthrow new ValidationError(\"automationRules must be an array\");\n\t\t}\n\t\tconst actor = {\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t};\n\t\tconst wanted = (body.automationRules as unknown[]).map(raw => {\n\t\t\tconst rule = (raw ?? {}) as Record<string, unknown>;\n\t\t\tconst target = optionalNumber(rule, \"userId\");\n\t\t\tif (target !== null && !users.findById(target)) {\n\t\t\t\tthrow new ValidationError(`userId must reference an existing employee (got ${target})`);\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tid: optionalNumber(rule, \"id\") ?? undefined,\n\t\t\t\tlabel: optionalString(rule, \"label\"),\n\t\t\t\tkind: optionalString(rule, \"kind\") ?? \"clockOut\",\n\t\t\t\tuserId: target,\n\t\t\t\tatMinute: optionalNumber(rule, \"atMinute\"),\n\t\t\t\tafterMinutes: optionalNumber(rule, \"afterMinutes\"),\n\t\t\t\t// the repository validates the selection and the repeat: a wrong entry comes back as a 400\n\t\t\t\tweekdays: Array.isArray(rule.weekdays) ? rule.weekdays.map(Number) : undefined,\n\t\t\t\trepeat: optionalString(rule, \"repeat\") as AutomationRepeat | undefined,\n\t\t\t\tisActive: optionalBoolean(rule, \"isActive\") ?? true,\n\t\t\t\tactiveFrom: optionalString(rule, \"activeFrom\"),\n\t\t\t\tactiveUntil: optionalString(rule, \"activeUntil\"),\n\t\t\t};\n\t\t});\n\n\t\tconst keep = new Set(wanted.map(rule => rule.id).filter((value): value is number => value !== undefined));\n\t\tfor (const existing of automations.list({ includeInactive: true })) {\n\t\t\tif (!keep.has(existing.id)) {\n\t\t\t\tautomations.remove({ id: existing.id, ...actor });\n\t\t\t}\n\t\t}\n\n\t\treturn wanted.map(rule =>\n\t\t\tautomations.save({\n\t\t\t\t...(rule.id !== undefined ? { id: rule.id } : {}),\n\t\t\t\tlabel: rule.label,\n\t\t\t\tkind: rule.kind as AutomationKind,\n\t\t\t\tuserId: rule.userId,\n\t\t\t\tatMinute: rule.atMinute,\n\t\t\t\tafterMinutes: rule.afterMinutes,\n\t\t\t\t...(rule.weekdays === undefined ? {} : { weekdays: rule.weekdays }),\n\t\t\t\t...(rule.repeat === undefined ? {} : { repeat: rule.repeat }),\n\t\t\t\tisActive: rule.isActive,\n\t\t\t\tactiveFrom: rule.activeFrom,\n\t\t\t\tactiveUntil: rule.activeUntil,\n\t\t\t\t...actor,\n\t\t\t}),\n\t\t);\n\t}\n\n\t// Automation rules: what the adapter does on its own \u2014 clock out at a time, report a missing punch, remind\n\t// about a break. The runs of the last days come with it, so the administration can see what happened.\n\troute(\"GET\", \"/automation-rules\", { permission: \"settings.view\" }, () =>\n\t\tjson(200, { automationRules: automations.list({ includeInactive: true }) }),\n\t);\n\n\troute(\"GET\", \"/automation-rules/runs\", { permission: \"settings.view\" }, () =>\n\t\tjson(200, { runs: automations.runs({ limit: 5 }) }),\n\t);\n\n\troute(\"PUT\", \"/automation-rules\", { permission: \"settings.edit\", csrf: true }, context =>\n\t\tjson(200, { automationRules: replaceAutomationRules(context) }),\n\t);\n\n\t// branding: logo, background and accent colour of the installation.\n\t//\n\t// The values are public on purpose \u2014 the sign in screen, the kiosk terminal and the presence screen show them\n\t// before anybody is signed in. Written they are through the settings route above, delivered here as a small\n\t// JSON plus two image routes, so a browser can cache the pictures.\n\n\t/**\n\t * Short fingerprint of a stored branding picture.\n\t *\n\t * It goes into the image URLs, so a browser picks up a new logo right away while the old picture stays cached\n\t * until then.\n\t *\n\t * @param value - stored data URL\n\t * @returns twelve hex characters\n\t */\n\tconst brandingVersion = (value: string): string => createHash(\"sha256\").update(value).digest(\"hex\").slice(0, 12);\n\n\t/**\n\t * Reads the branding.\n\t *\n\t * @returns colour and the addresses of the two pictures, `null` when nothing is configured\n\t */\n\tconst brandingState = (): {\n\t\t/** Accent colour of the instance, `null` when none is configured */\n\t\tcolor: string | null;\n\t\t/** Address of the logo, `null` when none is configured */\n\t\tlogoUrl: string | null;\n\t\t/** Address of the background picture, `null` when none is configured */\n\t\tbackgroundUrl: string | null;\n\t} => {\n\t\tconst color = (settings.get(\"brand_color\") ?? \"\").trim();\n\t\tconst logo = (settings.get(\"brand_logo\") ?? \"\").trim();\n\t\tconst background = (settings.get(\"brand_background\") ?? \"\").trim();\n\t\treturn {\n\t\t\tcolor: color === \"\" ? null : color,\n\t\t\tlogoUrl: logo === \"\" ? null : `/api/branding/logo?v=${brandingVersion(logo)}`,\n\t\t\tbackgroundUrl: background === \"\" ? null : `/api/branding/background?v=${brandingVersion(background)}`,\n\t\t};\n\t};\n\n\troute(\"GET\", \"/branding\", { public: true }, () => json(200, brandingState()));\n\n\troute(\"GET\", \"/branding/logo\", { public: true }, () => {\n\t\tconst image = readAvatar(settings.get(\"brand_logo\"));\n\t\tif (!image) {\n\t\t\tthrow new NotFoundError(\"no logo is configured\");\n\t\t}\n\t\treturn binary(200, Buffer.from(image.base64, \"base64\"), image.contentType, {\n\t\t\t\"cache-control\": \"public, max-age=86400\",\n\t\t});\n\t});\n\n\troute(\"GET\", \"/branding/background\", { public: true }, () => {\n\t\tconst image = readAvatar(settings.get(\"brand_background\"));\n\t\tif (!image) {\n\t\t\tthrow new NotFoundError(\"no background is configured\");\n\t\t}\n\t\treturn binary(200, Buffer.from(image.base64, \"base64\"), image.contentType, {\n\t\t\t\"cache-control\": \"public, max-age=86400\",\n\t\t});\n\t});\n\n\troute(\"GET\", \"/version\", { public: true }, () =>\n\t\tjson(200, { name: \"iobroker.time-tracker\", version: deps.version ?? \"0.0.0\" }),\n\t);\n\n\t// corrections\n\n\troute(\"PATCH\", \"/entries/:id\", { permission: \"time.edit_own\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst entryId = numberParam(context, \"id\");\n\t\tconst existing = entries.findById(entryId);\n\t\tif (!existing) {\n\t\t\tthrow new NotFoundError(`entry ${entryId} not found`);\n\t\t}\n\t\tif (existing.userId !== context.auth.user.id && !context.auth.permissions.includes(\"time.edit_other\")) {\n\t\t\tthrow problem(403, \"permission_denied\", \"request rejected (permission_denied: time.edit_other)\");\n\t\t}\n\n\t\tconst body = context.jsonBody();\n\t\tconst revision = optionalNumber(body, \"revision\");\n\t\tif (revision === null) {\n\t\t\tthrow new ValidationError(\"revision is required\");\n\t\t}\n\t\t// an employee only comments his own punch: the times of a day belong to the administration\n\t\tconst wantedTs = optionalNumber(body, \"tsUtc\");\n\t\tif (wantedTs !== null && !context.auth.permissions.includes(\"time.edit_other\")) {\n\t\t\tthrow problem(403, \"permission_denied\", \"request rejected (permission_denied: time.edit_other)\");\n\t\t}\n\t\tconst target = users.findById(existing.userId);\n\t\tif (!target) {\n\t\t\tthrow new NotFoundError(`user ${existing.userId} not found`);\n\t\t}\n\n\t\tconst updated = entries.update({\n\t\t\tid: entryId,\n\t\t\texpectedRevision: revision,\n\t\t\tpatch: {\n\t\t\t\ttsUtc: optionalNumber(body, \"tsUtc\") ?? undefined,\n\t\t\t\tnote: optionalString(body, \"note\"),\n\t\t\t},\n\t\t\treason: optionalString(body, \"reason\"),\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\ttimeZone: target.timezone,\n\t\t\tnow: now(),\n\t\t});\n\n\t\tconst day = aggregation.recalculateDay(updated.userId, updated.localDate, { now: now() });\n\t\temit({\n\t\t\ttype: \"entry.update\",\n\t\t\tuserId: updated.userId,\n\t\t\tdata: { entryId: updated.id, localDate: updated.localDate, revision: updated.revision },\n\t\t});\n\t\treturn json(200, { entry: updated, day });\n\t});\n\n\troute(\"DELETE\", \"/entries/:id\", { permission: \"time.delete\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst entryId = numberParam(context, \"id\");\n\t\tconst existing = entries.findById(entryId);\n\t\tif (!existing) {\n\t\t\tthrow new NotFoundError(`entry ${entryId} not found`);\n\t\t}\n\n\t\tentries.remove({\n\t\t\tid: entryId,\n\t\t\tactorId: context.auth.user.id,\n\t\t\treason: optionalString(context.optionalJsonBody(), \"reason\"),\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\taggregation.recalculateDay(existing.userId, existing.localDate, { now: now() });\n\t\temit({\n\t\t\ttype: \"entry.delete\",\n\t\t\tuserId: existing.userId,\n\t\t\tdata: { entryId: existing.id, localDate: existing.localDate },\n\t\t});\n\t\treturn noContent();\n\t});\n\n\t// day notes: what an employee wants the administration to know about one day\n\n\troute(\"GET\", \"/day-notes\", { permission: \"report.view_own\" }, context => {\n\t\tconst userId = scopeUser(context);\n\t\tconst from = context.query(\"from\");\n\t\tconst to = context.query(\"to\") ?? from;\n\t\tif (!from || !to) {\n\t\t\tthrow new ValidationError(\"from and to are required\");\n\t\t}\n\t\treturn json(200, { notes: deps.dayNotes.listByRange(userId, from, to) });\n\t});\n\n\troute(\"PUT\", \"/day-notes\", { permission: \"time.edit_own\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst body = context.jsonBody();\n\t\tconst userId = dayNoteTarget(context, context.auth.user.id);\n\t\tconst localDate = context.query(\"date\");\n\t\tif (!localDate) {\n\t\t\tthrow new ValidationError(\"date is required\");\n\t\t}\n\t\tconst note = deps.dayNotes.save({\n\t\t\tuserId,\n\t\t\tlocalDate,\n\t\t\t// an empty text is how a note is taken back\n\t\t\tnote: optionalString(body, \"note\") ?? \"\",\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\temit({ type: \"dayNote.change\", userId, data: { localDate } });\n\t\treturn json(200, { note });\n\t});\n\n\troute(\"POST\", \"/day-notes/handled\", { permission: \"time.edit_other\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst userId = dayNoteTarget(context, context.auth.user.id);\n\t\tconst localDate = context.query(\"date\");\n\t\tif (!localDate) {\n\t\t\tthrow new ValidationError(\"date is required\");\n\t\t}\n\t\tconst handled = optionalBoolean(context.jsonBody(), \"handled\") ?? true;\n\t\tconst note = deps.dayNotes.setHandled({\n\t\t\tuserId,\n\t\t\tlocalDate,\n\t\t\thandled,\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\temit({ type: \"dayNote.change\", userId, data: { localDate } });\n\t\treturn json(200, { note });\n\t});\n\n\troute(\"GET\", \"/entries/:id/audit\", { permission: \"audit.view\" }, context => {\n\t\tconst entryId = numberParam(context, \"id\");\n\t\tif (!entries.findById(entryId)) {\n\t\t\tthrow new NotFoundError(`entry ${entryId} not found`);\n\t\t}\n\t\t// the trail names the people who changed the punch, so it needs the audit permission\n\t\treturn json(200, {\n\t\t\taudit: readTimeEntryAudit(deps.db, entryId).map(row => ({\n\t\t\t\t...row,\n\t\t\t\tactorName: users.findById(row.actorId)?.displayName ?? null,\n\t\t\t})),\n\t\t});\n\t});\n\n\t// users, roles and work profiles\n\n\troute(\"GET\", \"/users\", { permission: \"user.view\" }, context => {\n\t\tconst includeInactive = context.query(\"includeInactive\") === \"true\";\n\t\tconst search = (context.query(\"q\") ?? \"\").trim().toLowerCase();\n\t\tconst limit = optionalNumberQuery(context, \"limit\", Number.MAX_SAFE_INTEGER);\n\t\tconst offset = optionalNumberQuery(context, \"offset\", 0);\n\n\t\tconst all = users.list({ includeInactive });\n\t\tconst filtered = search\n\t\t\t? all.filter(\n\t\t\t\t\tuser =>\n\t\t\t\t\t\tuser.login.toLowerCase().includes(search) || user.displayName.toLowerCase().includes(search),\n\t\t\t\t)\n\t\t\t: all;\n\n\t\treturn json(200, {\n\t\t\ttotal: filtered.length,\n\t\t\toffset,\n\t\t\tusers: filtered.slice(offset, offset + limit).map(user => ({\n\t\t\t\t...publicUser(user),\n\t\t\t\troles: users.roles(user.id),\n\t\t\t})),\n\t\t});\n\t});\n\n\troute(\"GET\", \"/users/:id\", { permission: \"user.view\" }, context => {\n\t\tconst id = numberParam(context, \"id\");\n\t\tconst user = users.findById(id);\n\t\tif (!user) {\n\t\t\tthrow new NotFoundError(`user ${id} not found`);\n\t\t}\n\t\treturn json(200, {\n\t\t\tuser: { ...publicUser(user), roles: users.roles(id), permissions: users.permissions(id) },\n\t\t\tprofile: users.getWorkProfile(id),\n\t\t});\n\t});\n\n\troute(\"POST\", \"/users\", { permission: \"user.create\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst body = context.jsonBody();\n\t\tconst password = requireString(body, \"password\");\n\t\tconst policyIssue = checkPasswordPolicy(password);\n\t\tif (policyIssue) {\n\t\t\tthrow new ValidationError(policyIssue);\n\t\t}\n\n\t\tconst created = users.create({\n\t\t\tlogin: requireString(body, \"login\"),\n\t\t\tdisplayName: requireString(body, \"displayName\"),\n\t\t\tpasswordHash: hashPassword(password),\n\t\t\temail: optionalString(body, \"email\"),\n\t\t\tlocale: optionalString(body, \"locale\") ?? undefined,\n\t\t\ttimezone: optionalString(body, \"timezone\") ?? undefined,\n\t\t\tmustChangePw: optionalBoolean(body, \"mustChangePw\") ?? undefined,\n\t\t\troleKeys: readRoleKeys(body) ?? undefined,\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\n\t\treturn json(\n\t\t\t201,\n\t\t\t{ user: { ...publicUser(created), roles: users.roles(created.id) } },\n\t\t\t{ location: `/users/${created.id}` },\n\t\t);\n\t});\n\n\troute(\"GET\", \"/roles\", { permission: \"user.view\" }, () => json(200, { roles: users.roleCatalog() }));\n\n\troute(\"GET\", \"/users/:id/avatar\", { public: true }, context => {\n\t\t// The picture is shown by the kiosk and the presence screen as well, and those authenticate with the device\n\t\t// token of a terminal instead of a user session \u2014 so either credential is accepted here, and nothing else.\n\t\tconst id = numberParam(context, \"id\");\n\t\tconst terminalSession = context.query(\"terminalSession\") ?? \"\";\n\t\tconst browserToken =\n\t\t\tcontext.header(\"x-session-token\") ?? parseCookies(context.header(\"cookie\"))[SESSION_COOKIE] ?? \"\";\n\t\tconst browserOk =\n\t\t\tbrowserToken !== \"\" &&\n\t\t\tdeps.auth.authenticate({ token: browserToken, permission: undefined, now: now() }).ok;\n\t\tconst terminalOk = terminalSession !== \"\" && terminals.findBySession(terminalSession, now()) !== null;\n\t\tif (!browserOk && !terminalOk) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\n\t\tconst user = users.findById(id);\n\t\tconst image = user ? readAvatar(user.avatar) : null;\n\t\tif (!image) {\n\t\t\tthrow new NotFoundError(`user ${id} has no picture`);\n\t\t}\n\t\treturn binary(200, Buffer.from(image.base64, \"base64\"), image.contentType, {\n\t\t\t\"cache-control\": \"private, max-age=300\",\n\t\t});\n\t});\n\n\troute(\"PATCH\", \"/users/:id\", { permission: \"user.edit\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst id = numberParam(context, \"id\");\n\t\tconst target = users.findById(id);\n\t\tif (!target) {\n\t\t\tthrow new NotFoundError(`user ${id} not found`);\n\t\t}\n\n\t\tconst body = context.jsonBody();\n\t\tconst roleKeys = readRoleKeys(body);\n\t\t// assigning roles is a right of its own\n\t\tif (roleKeys !== null && !context.auth.permissions.includes(\"user.manage_roles\")) {\n\t\t\tthrow problem(403, \"permission_denied\", \"request rejected (permission_denied: user.manage_roles)\");\n\t\t}\n\n\t\tconst password = optionalString(body, \"password\");\n\t\tif (password !== null) {\n\t\t\tconst policyIssue = checkPasswordPolicy(password);\n\t\t\tif (policyIssue) {\n\t\t\t\tthrow new ValidationError(policyIssue);\n\t\t\t}\n\t\t}\n\n\t\tconst patch: {\n\t\t\t/** New display name */\n\t\t\tdisplayName?: string;\n\t\t\t/** New e-mail address, `null` clears it */\n\t\t\temail?: string | null;\n\t\t\t/** New language of the account */\n\t\t\tlocale?: string;\n\t\t\t/** New time zone of the account */\n\t\t\ttimezone?: string;\n\t\t\t/** New badge number, `null` clears it */\n\t\t\trfidCard?: string | null;\n\t\t\t/** Activates or deactivates the account */\n\t\t\tisActive?: boolean;\n\t\t\t/** True when the next login has to change the password */\n\t\t\tmustChangePw?: boolean;\n\t\t\t/** Already hashed password (the caller hashes it) */\n\t\t\tpasswordHash?: string;\n\t\t} = {};\n\t\tif (Object.prototype.hasOwnProperty.call(body, \"displayName\")) {\n\t\t\tpatch.displayName = requireString(body, \"displayName\");\n\t\t}\n\t\tif (Object.prototype.hasOwnProperty.call(body, \"email\")) {\n\t\t\tpatch.email = optionalString(body, \"email\");\n\t\t}\n\t\tif (Object.prototype.hasOwnProperty.call(body, \"locale\")) {\n\t\t\tpatch.locale = requireString(body, \"locale\");\n\t\t}\n\t\tif (Object.prototype.hasOwnProperty.call(body, \"timezone\")) {\n\t\t\tconst timezone = requireString(body, \"timezone\");\n\t\t\tif (!isValidTimeZone(timezone)) {\n\t\t\t\tthrow new ValidationError(`timezone is not a valid IANA name (got ${timezone})`);\n\t\t\t}\n\t\t\tpatch.timezone = timezone;\n\t\t}\n\t\tif (Object.prototype.hasOwnProperty.call(body, \"rfidCard\")) {\n\t\t\tpatch.rfidCard = optionalString(body, \"rfidCard\");\n\t\t}\n\t\tif (Object.prototype.hasOwnProperty.call(body, \"mustChangePw\")) {\n\t\t\tpatch.mustChangePw = requireBoolean(body, \"mustChangePw\");\n\t\t}\n\t\tconst isActive = optionalBoolean(body, \"isActive\");\n\t\tif (isActive !== null) {\n\t\t\tif (isActive === false && id === context.auth.user.id) {\n\t\t\t\tthrow new ValidationError(\"an account cannot deactivate itself\");\n\t\t\t}\n\t\t\tpatch.isActive = isActive;\n\t\t}\n\t\tif (password !== null) {\n\t\t\tpatch.passwordHash = hashPassword(password);\n\t\t}\n\n\t\t// The picture is not part of the audited patch \u2014 `setAvatar` stores it on its own (and keeps the image out\n\t\t// of the audit trail). Sending `avatar: null` removes it, leaving the field out keeps it.\n\t\tif (Object.prototype.hasOwnProperty.call(body, \"avatar\")) {\n\t\t\tconst avatar = optionalString(body, \"avatar\");\n\t\t\tif (avatar !== null && !parseAvatarDataUrl(avatar)) {\n\t\t\t\tthrow new ValidationError(\n\t\t\t\t\t`avatar must be a data URL of a png, jpeg, webp or gif of up to ${MAX_AVATAR_BYTES} bytes`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tusers.setAvatar({\n\t\t\t\tuserId: id,\n\t\t\t\tavatar,\n\t\t\t\tactorId: context.auth.user.id,\n\t\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\t\tnow: now(),\n\t\t\t});\n\t\t}\n\n\t\t// one rule for both fields: deactivating or demoting the last active administrator is refused, so the\n\t\t// installation always keeps somebody who may administer it\n\t\tif (patch.isActive === false || roleKeys !== null) {\n\t\t\trequireActiveAdministrator(id, {\n\t\t\t\tisActive: patch.isActive ?? target.isActive,\n\t\t\t\troleKeys: roleKeys ?? users.roles(id),\n\t\t\t});\n\t\t}\n\n\t\tconst updated = users.update({\n\t\t\tid,\n\t\t\tpatch,\n\t\t\treason: optionalString(body, \"reason\"),\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\tconst roles = roleKeys\n\t\t\t? users.setRoles({\n\t\t\t\t\tuserId: id,\n\t\t\t\t\troleKeys,\n\t\t\t\t\tactorId: context.auth.user.id,\n\t\t\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\t\t\tnow: now(),\n\t\t\t\t})\n\t\t\t: users.roles(id);\n\n\t\treturn json(200, { user: { ...publicUser(updated), roles } });\n\t});\n\n\troute(\"DELETE\", \"/users/:id\", { permission: \"user.deactivate\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst id = numberParam(context, \"id\");\n\t\tconst target = users.findById(id);\n\t\tif (!target) {\n\t\t\tthrow new NotFoundError(`user ${id} not found`);\n\t\t}\n\t\tif (!target.isActive) {\n\t\t\t// a second call has nothing left to do and says so\n\t\t\tthrow new NotFoundError(`user ${id} is already deactivated`);\n\t\t}\n\t\tif (id === context.auth.user.id) {\n\t\t\tthrow new ValidationError(\"an account cannot deactivate itself\");\n\t\t}\n\t\t// `user.deactivate` is carried by the admin role alone, so the caller is an active administrator: an account\n\t\t// deactivated here is never the last one (and the own one is refused above)\n\n\t\t// accounts are never deleted, they are deactivated and keep their history\n\t\tusers.update({\n\t\t\tid,\n\t\t\tpatch: { isActive: false },\n\t\t\treason: optionalString(context.optionalJsonBody(), \"reason\"),\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\treturn noContent();\n\t});\n\n\troute(\"GET\", \"/users/:id/profile\", { permission: \"user.view\" }, context => {\n\t\tconst id = numberParam(context, \"id\");\n\t\tif (!users.findById(id)) {\n\t\t\tthrow new NotFoundError(`user ${id} not found`);\n\t\t}\n\t\treturn json(200, { profile: users.getWorkProfile(id) });\n\t});\n\n\troute(\"PUT\", \"/users/:id/profile\", { permission: \"user.edit\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst id = numberParam(context, \"id\");\n\t\tif (!users.findById(id)) {\n\t\t\tthrow new NotFoundError(`user ${id} not found`);\n\t\t}\n\t\tconst body = context.jsonBody();\n\n\t\tconst profile = users.saveWorkProfile({\n\t\t\tuserId: id,\n\t\t\tprofile: readProfilePatch(body),\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\treturn json(200, { profile });\n\t});\n\n\troute(\"GET\", \"/users/:id/shift-rules\", { permission: \"user.view\" }, context => {\n\t\tconst id = numberParam(context, \"id\");\n\t\tif (!users.findById(id)) {\n\t\t\tthrow new NotFoundError(`user ${id} not found`);\n\t\t}\n\t\treturn json(200, { shiftRules: rules.shiftRules(id, { includeInactive: true }) });\n\t});\n\n\troute(\"PUT\", \"/users/:id/shift-rules\", { permission: \"user.edit\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst id = numberParam(context, \"id\");\n\t\tif (!users.findById(id)) {\n\t\t\tthrow new NotFoundError(`user ${id} not found`);\n\t\t}\n\n\t\tconst body = context.jsonBody();\n\t\tif (!Array.isArray(body.shiftRules)) {\n\t\t\tthrow new ValidationError(\"shiftRules must be an array\");\n\t\t}\n\t\tconst actor = {\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t};\n\t\tconst wanted = (body.shiftRules as unknown[]).map(raw => {\n\t\t\tconst rule = (raw ?? {}) as Record<string, unknown>;\n\t\t\treturn {\n\t\t\t\tid: optionalNumber(rule, \"id\") ?? undefined,\n\t\t\t\tsurcharge: Number(rule.surcharge),\n\t\t\t\tdayOfWeek: optionalNumber(rule, \"dayOfWeek\"),\n\t\t\t\tfromMin: optionalNumber(rule, \"fromMin\"),\n\t\t\t\ttoMin: optionalNumber(rule, \"toMin\"),\n\t\t\t\tisActive: optionalBoolean(rule, \"isActive\") ?? undefined,\n\t\t\t};\n\t\t});\n\n\t\t// the payload replaces the rules of the employee: missing rules are removed, the rest is saved\n\t\tconst keep = new Set(wanted.map(rule => rule.id).filter((value): value is number => value !== undefined));\n\t\tfor (const existing of rules.shiftRules(id, { includeInactive: true })) {\n\t\t\tif (!keep.has(existing.id)) {\n\t\t\t\trules.removeShiftRule({ id: existing.id, ...actor });\n\t\t\t}\n\t\t}\n\t\tconst saved = wanted.map(rule =>\n\t\t\trules.saveShiftRule({\n\t\t\t\t...(rule.id !== undefined ? { id: rule.id } : {}),\n\t\t\t\tuserId: id,\n\t\t\t\tsurcharge: rule.surcharge,\n\t\t\t\t...(rule.dayOfWeek !== null ? { dayOfWeek: rule.dayOfWeek } : {}),\n\t\t\t\t...(rule.fromMin !== null ? { fromMin: rule.fromMin } : {}),\n\t\t\t\t...(rule.toMin !== null ? { toMin: rule.toMin } : {}),\n\t\t\t\t...(rule.isActive !== undefined ? { isActive: rule.isActive } : {}),\n\t\t\t\t...actor,\n\t\t\t}),\n\t\t);\n\t\treturn json(200, { shiftRules: saved });\n\t});\n\n\t// payouts (paid out overtime)\n\n\troute(\"GET\", \"/payouts\", { permission: \"payout.view\" }, context => {\n\t\tconst requested = context.query(\"userId\");\n\t\tconst userId = requested ? Number(requested) : (context.auth?.user.id ?? 0);\n\t\tif (!Number.isInteger(userId)) {\n\t\t\tthrow new ValidationError(\"userId must be a whole number\");\n\t\t}\n\t\tconst period = readPeriod(context.query(\"year\"), context.query(\"month\"));\n\t\tconst list = payouts.list(\n\t\t\tuserId,\n\t\t\tperiod.month === null ? { year: period.year } : { year: period.year, month: period.month },\n\t\t);\n\t\treturn json(200, {\n\t\t\tuserId,\n\t\t\t...period,\n\t\t\ttotalMinutes: payouts.sumMinutes(userId, period.year, period.month ?? undefined),\n\t\t\tpayouts: list,\n\t\t});\n\t});\n\n\troute(\"POST\", \"/payouts\", { permission: \"payout.create\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst body = context.jsonBody();\n\t\tconst target = Number(body.userId ?? context.auth.user.id);\n\t\tif (!Number.isInteger(target) || !users.findById(target)) {\n\t\t\tthrow new ValidationError(\"userId must reference an existing employee\");\n\t\t}\n\t\tconst period = readPeriod(body.year, body.month);\n\t\tconst minutes = optionalNumber(body, \"minutes\");\n\t\tif (minutes === null || !Number.isInteger(minutes) || minutes === 0) {\n\t\t\tthrow new ValidationError(\"minutes must be a non-zero whole number\");\n\t\t}\n\n\t\tconst created = payouts.create({\n\t\t\tuserId: target,\n\t\t\t...period,\n\t\t\tminutes,\n\t\t\tamount: optionalNumber(body, \"amount\"),\n\t\t\tnote: optionalString(body, \"note\"),\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\treturn json(201, { payout: created }, { location: `/payouts/${created.id}` });\n\t});\n\n\troute(\"PATCH\", \"/payouts/:id\", { permission: \"payout.create\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst id = numberParam(context, \"id\");\n\t\tif (!payouts.findById(id)) {\n\t\t\tthrow new NotFoundError(`payout ${id} not found`);\n\t\t}\n\t\tconst body = context.jsonBody();\n\t\tconst minutes = optionalNumber(body, \"minutes\");\n\t\tif (minutes !== null && (!Number.isInteger(minutes) || minutes === 0)) {\n\t\t\tthrow new ValidationError(\"minutes must be a non-zero whole number\");\n\t\t}\n\n\t\tconst updated = payouts.update({\n\t\t\tid,\n\t\t\tpatch: {\n\t\t\t\t...(minutes !== null ? { minutes } : {}),\n\t\t\t\t...(Object.prototype.hasOwnProperty.call(body, \"amount\")\n\t\t\t\t\t? { amount: optionalNumber(body, \"amount\") }\n\t\t\t\t\t: {}),\n\t\t\t\t...(Object.prototype.hasOwnProperty.call(body, \"note\") ? { note: optionalString(body, \"note\") } : {}),\n\t\t\t},\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\treturn json(200, { payout: updated });\n\t});\n\n\troute(\"DELETE\", \"/payouts/:id\", { permission: \"payout.create\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst removed = payouts.remove({\n\t\t\tid: numberParam(context, \"id\"),\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\tif (!removed) {\n\t\t\tthrow new NotFoundError(`payout ${context.params.id} not found`);\n\t\t}\n\t\treturn noContent();\n\t});\n\n\t// bulk import of punches (administration and migration)\n\n\troute(\n\t\t\"POST\",\n\t\t\"/entries/bulk\",\n\t\t{ permission: \"time.import\", csrf: true, rateLimit: { name: \"import\", limit: 10, windowSeconds: 60 } },\n\t\tcontext => {\n\t\t\tif (!context.auth) {\n\t\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t\t}\n\t\t\tconst body = context.jsonBody();\n\t\t\tif (!Array.isArray(body.entries)) {\n\t\t\t\tthrow new ValidationError(\"entries must be an array\");\n\t\t\t}\n\n\t\t\tconst actor = context.auth;\n\t\t\tconst timestamp = now();\n\t\t\tconst touched = new Map<string, number>();\n\t\t\tconst results: {\n\t\t\t\t/** Position of the punch in the request */\n\t\t\t\tindex: number;\n\t\t\t\t/** Id of the stored punch, missing when it was rejected */\n\t\t\t\tentryId?: number;\n\t\t\t\t/** Reason of the rejection, missing when the punch was stored */\n\t\t\t\terror?: string;\n\t\t\t}[] = [];\n\n\t\t\t(body.entries as unknown[]).forEach((raw, index) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst item = (raw ?? {}) as Record<string, unknown>;\n\t\t\t\t\tconst target = Number(item.userId ?? actor.user.id);\n\t\t\t\t\tif (!Number.isInteger(target)) {\n\t\t\t\t\t\tthrow new ValidationError(\"userId must be a whole number\");\n\t\t\t\t\t}\n\t\t\t\t\t// writing punches for somebody else needs the matching permission\n\t\t\t\t\tif (target !== actor.user.id && !actor.permissions.includes(\"time.edit_other\")) {\n\t\t\t\t\t\tthrow problem(\n\t\t\t\t\t\t\t403,\n\t\t\t\t\t\t\t\"permission_denied\",\n\t\t\t\t\t\t\t\"request rejected (permission_denied: time.edit_other)\",\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tconst user = users.findById(target);\n\t\t\t\t\tif (!user) {\n\t\t\t\t\t\tthrow new NotFoundError(`user ${target} not found`);\n\t\t\t\t\t}\n\t\t\t\t\tconst tsUtc = optionalNumber(item, \"tsUtc\");\n\t\t\t\t\tif (tsUtc === null) {\n\t\t\t\t\t\tthrow new ValidationError(\"tsUtc is required\");\n\t\t\t\t\t}\n\n\t\t\t\t\tconst stored = entries.insert({\n\t\t\t\t\t\tuserId: target,\n\t\t\t\t\t\ttsUtc,\n\t\t\t\t\t\tclientTsUtc: optionalNumber(item, \"clientTsUtc\"),\n\t\t\t\t\t\ttimeZone: user.timezone,\n\t\t\t\t\t\tsource: \"import\",\n\t\t\t\t\t\tdirection: optionalDirection(item),\n\t\t\t\t\t\tidempotencyKey: optionalString(item, \"idempotencyKey\"),\n\t\t\t\t\t\tnote: optionalString(item, \"note\"),\n\t\t\t\t\t\tactorId: actor.user.id,\n\t\t\t\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\t\t\t\tnow: timestamp,\n\t\t\t\t\t});\n\n\t\t\t\t\ttouched.set(`${target}|${stored.entry.localDate}`, target);\n\t\t\t\t\tresults.push({ index, entryId: stored.entry.id });\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// a faulty row does not discard the rest of the import\n\t\t\t\t\tconst details = toProblem(error).problem;\n\t\t\t\t\tresults.push({ index, error: details.code });\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// the aggregates of the imported days are refreshed once, not per row\n\t\t\tfor (const key of touched.keys()) {\n\t\t\t\tconst [userId, localDate] = key.split(\"|\");\n\t\t\t\taggregation.recalculateDay(Number(userId), localDate, { now: timestamp });\n\t\t\t}\n\n\t\t\tconst imported = results.filter(result => result.entryId !== undefined).length;\n\t\t\t// one event per touched employee: the import may mix several of them\n\t\t\tfor (const [key, userId] of touched) {\n\t\t\t\temit({\n\t\t\t\t\ttype: \"punch\",\n\t\t\t\t\tuserId,\n\t\t\t\t\tdata: { imported: true, localDate: key.split(\"|\")[1], source: \"import\" },\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn json(200, {\n\t\t\t\timported,\n\t\t\t\tfailed: results.length - imported,\n\t\t\t\trecalculatedDays: touched.size,\n\t\t\t\tresults,\n\t\t\t});\n\t\t},\n\t);\n\n\t// statistics\n\n\troute(\"GET\", \"/reports/statistics\", { permission: \"report.statistics\" }, context => {\n\t\tconst from = context.query(\"from\");\n\t\tconst to = context.query(\"to\") ?? from;\n\t\tif (!from || !to) {\n\t\t\tthrow new ValidationError(\"from and to are required\");\n\t\t}\n\t\tif (from > to) {\n\t\t\tthrow new ValidationError(`from (${from}) must not be after to (${to})`);\n\t\t}\n\t\t// every day of every employee is recalculated, so the range stays bounded\n\t\tconst days = dateRange(from, to).length;\n\t\tif (days > MAX_STATISTICS_DAYS) {\n\t\t\tthrow new ValidationError(`the range may cover at most ${MAX_STATISTICS_DAYS} days (got ${days})`);\n\t\t}\n\n\t\tconst requested = context.query(\"userId\");\n\t\tif (requested) {\n\t\t\t// looking at somebody else is a permission of its own\n\t\t\tif (!context.auth?.permissions.includes(\"report.view_other\")) {\n\t\t\t\tthrow problem(403, \"permission_denied\", \"request rejected (permission_denied: report.view_other)\");\n\t\t\t}\n\t\t}\n\t\tconst employees = requested ? [Number(requested)] : users.list({ includeInactive: false }).map(user => user.id);\n\t\tif (employees.some(id => !Number.isInteger(id) || !users.findById(id))) {\n\t\t\tthrow new ValidationError(\"userId must reference an existing employee\");\n\t\t}\n\n\t\tconst timestamp = now();\n\t\tconst rows = employees.map(userId => {\n\t\t\tconst user = users.findById(userId);\n\t\t\tconst range = aggregation.recalculateRange(userId, from, to, { now: timestamp });\n\t\t\treturn {\n\t\t\t\tuserId,\n\t\t\t\tdisplayName: user?.displayName ?? \"\",\n\t\t\t\t...range,\n\t\t\t};\n\t\t});\n\n\t\treturn json(200, {\n\t\t\tfrom,\n\t\t\tto,\n\t\t\tusers: rows,\n\t\t\ttotals: rows.reduce(\n\t\t\t\t(sum, row) => ({\n\t\t\t\t\tworkedMin: sum.workedMin + row.workedMin,\n\t\t\t\t\ttargetMin: sum.targetMin + row.targetMin,\n\t\t\t\t\tbalanceMin: sum.balanceMin + row.balanceMin,\n\t\t\t\t\topenDays: sum.openDays + row.openDays,\n\t\t\t\t}),\n\t\t\t\t{ workedMin: 0, targetMin: 0, balanceMin: 0, openDays: 0 },\n\t\t\t),\n\t\t});\n\t});\n\n\t// kiosk terminal (device token -> short lived session, badge/PIN punching)\n\n\t/**\n\t * Refuses terminal requests when the instance switch is off.\n\t */\n\tconst requireKiosk = (): void => {\n\t\tif (deps.kioskEnabled !== true) {\n\t\t\tthrow problem(403, \"kiosk_disabled\", \"the kiosk terminal is switched off in the adapter settings\");\n\t\t}\n\t};\n\n\t// how long a session of a kiosk device lives; the heartbeat of the device extends it. A test may shorten it,\n\t// and an instance may raise it for a tablet that is never touched.\n\tconst TERMINAL_SESSION_MINUTES = deps.terminalSessionMinutes ?? TERMINAL_SESSION_MINUTES_DEFAULT;\n\n\t/**\n\t * Resolves the terminal of a request.\n\t *\n\t * @param context - route context (body or query carries the session)\n\t * @returns the terminal\n\t */\n\tconst requireTerminal = (context: RouteContext): TerminalRecord => {\n\t\trequireKiosk();\n\t\t// the session travels in the body of a POST and in the query of a GET\n\t\tconst body = context.optionalJsonBody<Record<string, unknown>>();\n\t\tconst fromBody = typeof body.terminalSession === \"string\" ? body.terminalSession : null;\n\t\tconst session = fromBody ?? context.query(\"terminalSession\");\n\t\tif (!session) {\n\t\t\tthrow problem(401, \"no_session\", \"terminal session is missing\");\n\t\t}\n\t\tconst terminal = terminals.findBySession(session, now());\n\t\tif (!terminal) {\n\t\t\tthrow problem(401, \"no_session\", \"terminal session is not valid any more\");\n\t\t}\n\t\treturn terminal;\n\t};\n\n\troute(\"GET\", \"/terminal/status\", { public: true }, () =>\n\t\tjson(200, {\n\t\t\tenabled: deps.kioskEnabled === true,\n\t\t\tserverTime: now(),\n\t\t\ttimezone: settings.get(\"timezone\") ?? \"Europe/Berlin\",\n\t\t\tversion: deps.version ?? \"0.0.0\",\n\t\t}),\n\t);\n\n\troute(\n\t\t\"POST\",\n\t\t\"/terminal/session\",\n\t\t{ public: true, csrf: false, rateLimit: { name: \"terminal-session\", limit: 30, windowSeconds: 60 } },\n\t\tcontext => {\n\t\t\trequireKiosk();\n\t\t\tconst body = context.jsonBody();\n\t\t\tconst deviceToken = requireString(body, \"deviceToken\");\n\t\t\tconst terminal = terminals.findByToken(deviceToken, now());\n\t\t\tif (!terminal) {\n\t\t\t\tthrow problem(401, \"invalid_credentials\", \"the device token is not valid any more\");\n\t\t\t}\n\n\t\t\tconst started = terminals.startSession({\n\t\t\t\tid: terminal.id,\n\t\t\t\tttlMinutes: TERMINAL_SESSION_MINUTES,\n\t\t\t\tnow: now(),\n\t\t\t});\n\t\t\treturn json(200, {\n\t\t\t\tterminalSession: started.terminalSession,\n\t\t\t\texpiresAt: started.expiresAt,\n\t\t\t\tterminal: {\n\t\t\t\t\tname: terminal.name,\n\t\t\t\t\tlocation: terminal.location,\n\t\t\t\t\tpinRequired: terminal.pinRequired,\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\t);\n\n\troute(\"POST\", \"/terminal/heartbeat\", { public: true, csrf: false }, context => {\n\t\tconst terminal = requireTerminal(context);\n\t\tconst extended = terminals.touch({\n\t\t\tid: terminal.id,\n\t\t\tttlMinutes: TERMINAL_SESSION_MINUTES,\n\t\t\tnow: now(),\n\t\t});\n\t\treturn json(200, {\n\t\t\tstatus: \"ok\",\n\t\t\tserverTime: now(),\n\t\t\ttimezone: settings.get(\"timezone\") ?? \"Europe/Berlin\",\n\t\t\tversion: deps.version ?? \"0.0.0\",\n\t\t\texpiresAt: extended.expiresAt,\n\t\t});\n\t});\n\n\troute(\n\t\t\"GET\",\n\t\t\"/terminal/users\",\n\t\t{ public: true, rateLimit: { name: \"terminal-users\", limit: 60, windowSeconds: 60 } },\n\t\tcontext => {\n\t\t\tconst terminal = requireTerminal(context);\n\t\t\t// A terminal stands at one place: it shows the employees that were assigned to it. No assignment means\n\t\t\t// every employee \u2014 that is how terminals worked before the assignment existed.\n\t\t\t//\n\t\t\t// Only what a badge/PIN selection needs \u2014 no mail address, no profile, no working times. Whether the\n\t\t\t// employee is at the workplace right now is the one exception: the presence screen highlights it.\n\t\t\tconst timestamp = now();\n\t\t\tconst terminalSession = context.query(\"terminalSession\") ?? \"\";\n\t\t\tconst candidates = users.list({ includeInactive: false });\n\t\t\tconst visible =\n\t\t\t\tterminal.userIds.length === 0\n\t\t\t\t\t? candidates\n\t\t\t\t\t: candidates.filter(user => terminal.userIds.includes(user.id));\n\t\t\treturn json(200, {\n\t\t\t\tusers: visible.map(user => {\n\t\t\t\t\t// The aggregate counts finished punches only. While a punch is open, the time that passed since\n\t\t\t\t\t// it started is added here, so the board shows the time that really passed. (A break that the\n\t\t\t\t\t// pause rules deduct later is not part of this - the next punch brings the exact value.)\n\t\t\t\t\tconst date = localDate(timestamp, user.timezone);\n\t\t\t\t\tconst day = aggregation.recalculateDay(user.id, date, { now: timestamp });\n\t\t\t\t\tconst open = day?.hasOpenEntry ? entries.listByDate(user.id, date).at(-1) : undefined;\n\t\t\t\t\tconst runningMin = open ? Math.max(0, Math.floor((timestamp - open.tsUtc) / 60)) : 0;\n\t\t\t\t\t// The worked time of the day travels only for employees whose work profile allows it: the\n\t\t\t\t\t// presence screen is visible before the PIN is entered, so the decision stays with the\n\t\t\t\t\t// administration - and it is made here, not in the browser.\n\t\t\t\t\tconst showWorkedTime = users.getWorkProfile(user.id)?.showWorkedTime === true;\n\t\t\t\t\treturn {\n\t\t\t\t\t\tid: user.id,\n\t\t\t\t\t\tdisplayName: user.displayName,\n\t\t\t\t\t\tpresent: day?.hasOpenEntry === true,\n\t\t\t\t\t\tworkedMin: showWorkedTime ? (day?.workedMin ?? 0) + runningMin : undefined,\n\t\t\t\t\t\t// the picture is fetched by the browser from its own route; the session of this device travels\n\t\t\t\t\t\t// in the query, so a plain `<img>` can load it\n\t\t\t\t\t\tavatarUrl: user.avatar\n\t\t\t\t\t\t\t? `/api/users/${user.id}/avatar?terminalSession=${encodeURIComponent(terminalSession)}&v=${user.updatedAt}`\n\t\t\t\t\t\t\t: null,\n\t\t\t\t\t};\n\t\t\t\t}),\n\t\t\t});\n\t\t},\n\t);\n\n\troute(\n\t\t\"POST\",\n\t\t\"/terminal/punch\",\n\t\t{ public: true, csrf: false, rateLimit: { name: \"terminal-punch\", limit: 60, windowSeconds: 60 } },\n\t\tcontext => {\n\t\t\tconst terminal = requireTerminal(context);\n\t\t\tconst body = context.jsonBody();\n\t\t\tconst badge = optionalString(body, \"badge\");\n\t\t\tconst pin = optionalString(body, \"pin\");\n\t\t\tconst targetId = optionalNumber(body, \"userId\");\n\n\t\t\tif (badge === null && targetId === null) {\n\t\t\t\tthrow new ValidationError(\"a badge or a userId is required\");\n\t\t\t}\n\t\t\t// a device that demands the PIN authenticates with it; a device without that duty may punch for a\n\t\t\t// selected employee right away (the administrator decided that this place is trusted)\n\t\t\tif (badge === null && terminal.pinRequired && pin === null) {\n\t\t\t\tthrow new ValidationError(\"a userId together with the PIN is required\");\n\t\t\t}\n\n\t\t\tconst user = badge !== null ? users.findByRfidCard(badge) : users.findById(targetId ?? 0);\n\t\t\tif (!user || !user.isActive) {\n\t\t\t\t// the same answer for an unknown badge and an unknown user: no enumeration of accounts.\n\t\t\t\t// There is no account to count here \u2014 the rate limit of the route covers this case.\n\t\t\t\tthrow problem(401, \"invalid_credentials\", \"badge or PIN is not known\");\n\t\t\t}\n\n\t\t\t// a shared device plus a four digit PIN: too many wrong tries block the account for a while (4.10)\n\t\t\tconst blocked = pinGuard.state({ userId: user.id, now: now() });\n\t\t\tif (blocked.locked) {\n\t\t\t\tthrow problem(423, \"locked_out\", `too many wrong PINs, try again in ${blocked.retryAfterSeconds} s`);\n\t\t\t}\n\n\t\t\tconst pinMatches = pin !== null && user.pinHash !== null && verifyPassword(pin, user.pinHash);\n\t\t\t// a wrong PIN is refused on every device; a missing one only where the PIN is demanded\n\t\t\tif (terminal.pinRequired ? !pinMatches : pin !== null && !pinMatches) {\n\t\t\t\tpinGuard.fail({ userId: user.id, now: now() });\n\t\t\t\tthrow problem(401, \"invalid_credentials\", \"badge or PIN is not known\");\n\t\t\t}\n\t\t\t// the correct PIN clears the counter of that account\n\t\t\tpinGuard.reset(user.id);\n\n\t\t\tconst timestamp = now();\n\t\t\tconst stored = entries.insert({\n\t\t\t\tuserId: user.id,\n\t\t\t\ttsUtc: optionalNumber(body, \"tsUtc\") ?? timestamp,\n\t\t\t\ttimeZone: user.timezone,\n\t\t\t\tsource: \"terminal\",\n\t\t\t\tdirection: optionalDirection(body),\n\t\t\t\tnote: optionalString(body, \"note\"),\n\t\t\t\tactorId: user.id,\n\t\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\t\tnow: timestamp,\n\t\t\t});\n\t\t\tconst day = aggregation.recalculateDay(user.id, stored.entry.localDate, { now: timestamp });\n\t\t\tterminals.touch({ id: terminal.id, ttlMinutes: TERMINAL_SESSION_MINUTES, now: timestamp });\n\t\t\temit({\n\t\t\t\ttype: \"terminal.punch\",\n\t\t\t\tuserId: user.id,\n\t\t\t\tdata: {\n\t\t\t\t\tentryId: stored.entry.id,\n\t\t\t\t\tlocalDate: stored.entry.localDate,\n\t\t\t\t\tterminalId: terminal.id,\n\t\t\t\t\tterminalName: terminal.name,\n\t\t\t\t},\n\t\t\t});\n\n\t\t\t// the answer stays minimal: the terminal shows a name, a time and the state of the day\n\t\t\treturn json(201, {\n\t\t\t\tuser: { id: user.id, displayName: user.displayName },\n\t\t\t\tentry: {\n\t\t\t\t\tid: stored.entry.id,\n\t\t\t\t\ttsUtc: stored.entry.tsUtc,\n\t\t\t\t\tdirection: stored.entry.direction,\n\t\t\t\t\tlocalDate: stored.entry.localDate,\n\t\t\t\t},\n\t\t\t\tday: {\n\t\t\t\t\t// the worked time of the day only when the work profile of the employee allows it: the\n\t\t\t\t\t// presence card is public, so the same rule as in `/terminal/users` applies here\n\t\t\t\t\tworkedMin: users.getWorkProfile(user.id)?.showWorkedTime === true ? day.workedMin : undefined,\n\t\t\t\t\ttargetMin: day.targetMin,\n\t\t\t\t\tbalanceMin: day.balanceMin,\n\t\t\t\t\thasOpenEntry: day.hasOpenEntry,\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\t);\n\n\t// terminals and PINs (administration)\n\n\troute(\"GET\", \"/terminals\", { permission: \"terminal.manage\" }, () =>\n\t\tjson(200, { terminals: terminals.list({ includeInactive: true }).map(publicTerminal) }),\n\t);\n\n\troute(\"POST\", \"/terminals\", { permission: \"terminal.manage\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst body = context.jsonBody();\n\t\tconst created = terminals.create({\n\t\t\tname: requireString(body, \"name\"),\n\t\t\tlocation: optionalString(body, \"location\"),\n\t\t\tpinRequired: optionalBoolean(body, \"pinRequired\") ?? undefined,\n\t\t\tuserIds: readUserIds(body) ?? undefined,\n\t\t\tttlDays: optionalNumber(body, \"ttlDays\"),\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\n\t\t// the device token is returned exactly once \u2014 the server only keeps its hash\n\t\treturn json(\n\t\t\t201,\n\t\t\t{ terminal: publicTerminal(created.terminal), deviceToken: created.deviceToken },\n\t\t\t{ location: `/terminals/${created.terminal.id}` },\n\t\t);\n\t});\n\n\troute(\"PUT\", \"/terminals/:id/users\", { permission: \"terminal.manage\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst id = numberParam(context, \"id\");\n\t\tconst body = context.jsonBody();\n\t\t// an empty list means \u201Call employees\u201D, which is also how a terminal starts its life\n\t\tconst userIds = readUserIds(body) ?? [];\n\t\tfor (const userId of userIds) {\n\t\t\tif (!users.findById(userId)) {\n\t\t\t\tthrow new ValidationError(`user ${userId} does not exist`);\n\t\t\t}\n\t\t}\n\n\t\tconst terminal = terminals.setUsers({\n\t\t\tid,\n\t\t\tuserIds,\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\treturn json(200, { terminal: publicTerminal(terminal) });\n\t});\n\n\troute(\"DELETE\", \"/terminals/:id\", { permission: \"terminal.manage\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst revoked = terminals.revoke({\n\t\t\tid: numberParam(context, \"id\"),\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\tif (!revoked) {\n\t\t\tthrow new NotFoundError(`terminal ${context.params.id} not found or already revoked`);\n\t\t}\n\t\treturn noContent();\n\t});\n\n\troute(\"POST\", \"/users/:id/pin\", { permission: \"user.edit\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst id = numberParam(context, \"id\");\n\t\tif (!users.findById(id)) {\n\t\t\tthrow new NotFoundError(`user ${id} not found`);\n\t\t}\n\t\tconst body = context.jsonBody();\n\t\tconst pin = optionalString(body, \"pin\");\n\n\t\tif (pin !== null && !/^\\d{4,8}$/.test(pin)) {\n\t\t\tthrow new ValidationError(\"pin must be 4 to 8 digits\");\n\t\t}\n\t\tusers.setPin({\n\t\t\tuserId: id,\n\t\t\t// a PIN is a secret of the same weight as a password, so it is hashed the same way\n\t\t\tpinHash: pin === null ? null : hashPassword(pin),\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\treturn noContent();\n\t});\n\n\t// RFID/NFC tags (signed deep links)\n\n\t/**\n\t * Refuses tag management when the instance has no HMAC secret.\n\t *\n\t * @returns the secret\n\t */\n\tconst requireHmacSecret = (): string => {\n\t\tconst secret = deps.hmacSecret ?? \"\";\n\t\tif (!secret) {\n\t\t\tthrow problem(403, \"not_configured\", \"no HMAC secret is configured for signed tag links\");\n\t\t}\n\t\treturn secret;\n\t};\n\n\t/**\n\t * Builds the link a phone scans for a badge token.\n\t *\n\t * @param context - request context (host and scheme)\n\t * @param token - signed token of the badge\n\t * @returns absolute URL the badge carries\n\t */\n\tconst tagLink = (context: RouteContext, token: string): string => {\n\t\tconst host = context.header(\"host\") ?? \"localhost\";\n\t\t// the link is what a phone scans: it opens the web app, which sends the token to /rfid/scan. The scheme comes\n\t\t// from the request (and from a trusted reverse proxy), so the link matches the address the administration\n\t\t// itself is reached with \u2014 a fixed `https://` pointed nowhere on a plain HTTP instance.\n\t\tconst scheme = host.includes(\"://\") ? \"\" : `${context.secure ? \"https\" : \"http\"}://`;\n\t\treturn `${scheme}${host}/?tag=${token}`;\n\t};\n\n\t/**\n\t * Signs a new link for a badge and stores it.\n\t *\n\t * The signature binds `uid`, owner and expiry, so this is what replaces the link: everything handed out before\n\t * stops working immediately, and a revoked badge comes back into use with it.\n\t *\n\t * @param context - request context\n\t * @param tag - badge to give a new link (its `userId` may be the new owner)\n\t * @param ttlDays - validity of the new link in days\n\t * @param label - new label, `undefined` keeps the stored one\n\t * @returns the changed badge and its new token\n\t */\n\tconst reissueLink = (\n\t\tcontext: RouteContext,\n\t\ttag: RfidTagRecord,\n\t\tttlDays: number,\n\t\tlabel?: string | null,\n\t): { tag: RfidTagRecord; token: string } => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tif (tag.userId === null) {\n\t\t\tthrow new ValidationError(\"the badge has no employee any more \u2014 give it one first\");\n\t\t}\n\t\tconst timestamp = now();\n\t\tconst expiresAt = timestamp + ttlDays * 86400;\n\t\tconst signature = signTag(requireHmacSecret(), tag.uid, tag.userId, expiresAt);\n\t\tconst updated = rfid.update({\n\t\t\tid: tag.id,\n\t\t\tuserId: tag.userId,\n\t\t\texpiresAt,\n\t\t\tsignature,\n\t\t\tactivate: true,\n\t\t\t...(label === undefined ? {} : { label }),\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: timestamp,\n\t\t});\n\t\tif (!updated) {\n\t\t\tthrow new NotFoundError(`tag ${tag.id} not found`);\n\t\t}\n\t\treturn { tag: updated, token: buildTagToken(updated.uid, tag.userId, expiresAt, signature) };\n\t};\n\n\troute(\"GET\", \"/rfid/tags\", { permission: \"rfid.manage\" }, () =>\n\t\tjson(200, { tags: rfid.list({ includeInactive: true }) }),\n\t);\n\n\troute(\"POST\", \"/rfid/tags\", { permission: \"rfid.manage\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst secret = requireHmacSecret();\n\t\tconst body = context.jsonBody();\n\t\tconst target = Number(body.userId ?? context.auth.user.id);\n\t\tif (!Number.isInteger(target) || !users.findById(target)) {\n\t\t\tthrow new ValidationError(\"userId must reference an existing employee\");\n\t\t}\n\t\tconst ttlDays = optionalNumber(body, \"ttlDays\") ?? 365;\n\t\tif (!Number.isInteger(ttlDays) || ttlDays <= 0) {\n\t\t\tthrow new ValidationError(`ttlDays must be a positive whole number (got ${ttlDays})`);\n\t\t}\n\n\t\tconst timestamp = now();\n\t\tconst expiresAt = timestamp + ttlDays * 86400;\n\t\tconst uid = optionalString(body, \"uid\") ?? newTagUid();\n\t\tconst signature = signTag(secret, uid, target, expiresAt);\n\t\tconst tag = rfid.create({\n\t\t\tuid,\n\t\t\tuserId: target,\n\t\t\tsignature,\n\t\t\tlabel: optionalString(body, \"label\"),\n\t\t\texpiresAt,\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: timestamp,\n\t\t});\n\n\t\tconst token = buildTagToken(uid, target, expiresAt, signature);\n\t\treturn json(201, { tag, token, url: tagLink(context, token) }, { location: `/rfid/tags/${tag.id}` });\n\t});\n\n\troute(\"DELETE\", \"/rfid/tags/:id\", { permission: \"rfid.manage\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst revoked = rfid.revoke({\n\t\t\tid: numberParam(context, \"id\"),\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\tif (!revoked) {\n\t\t\tthrow new NotFoundError(`tag ${context.params.id} not found or already revoked`);\n\t\t}\n\t\treturn noContent();\n\t});\n\n\t// A badge that was revoked can be removed from the list for good. Revoking is what stops the link from working,\n\t// so this only removes the row \u2014 the audit trail keeps the trace either way.\n\troute(\"DELETE\", \"/rfid/tags/:id/permanent\", { permission: \"rfid.manage\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst removed = rfid.remove({\n\t\t\tid: numberParam(context, \"id\"),\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\tif (!removed) {\n\t\t\tthrow new NotFoundError(`tag ${context.params.id} not found`);\n\t\t}\n\t\treturn noContent();\n\t});\n\n\t// Changes a badge: label, owner and validity. The signature binds uid, owner and expiry, so a new owner or a new\n\t// validity re-issues the link \u2014 the answer carries the new token then, and the link given out before is dead.\n\troute(\"PATCH\", \"/rfid/tags/:id\", { permission: \"rfid.manage\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst id = numberParam(context, \"id\");\n\t\tconst body = context.jsonBody();\n\t\tconst current = rfid.findById(id);\n\t\tif (!current) {\n\t\t\tthrow new NotFoundError(`tag ${context.params.id} not found`);\n\t\t}\n\t\tconst userId = body.userId === undefined ? current.userId : Number(body.userId);\n\t\tif (userId === null || !Number.isInteger(userId) || !users.findById(userId)) {\n\t\t\tthrow new ValidationError(\"userId must reference an existing employee\");\n\t\t}\n\t\tconst ttlDays = optionalNumber(body, \"ttlDays\") ?? undefined;\n\t\tif (ttlDays !== undefined && (!Number.isInteger(ttlDays) || ttlDays <= 0)) {\n\t\t\tthrow new ValidationError(`ttlDays must be a positive whole number (got ${ttlDays})`);\n\t\t}\n\t\tconst label = body.label === undefined ? undefined : optionalString(body, \"label\");\n\n\t\tif (userId !== current.userId || ttlDays !== undefined) {\n\t\t\t// the link has to be signed again: it carries the owner in its payload\n\t\t\tconst signed = reissueLink(context, { ...current, userId }, ttlDays ?? 365, label);\n\t\t\treturn json(200, { tag: signed.tag, token: signed.token, url: tagLink(context, signed.token) });\n\t\t}\n\n\t\t// a label on its own leaves the link alone \u2014 the signature does not know about it\n\t\tconst changed = rfid.update({\n\t\t\tid,\n\t\t\tuserId,\n\t\t\t...(label === undefined ? {} : { label }),\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\tif (!changed) {\n\t\t\tthrow new NotFoundError(`tag ${context.params.id} not found`);\n\t\t}\n\t\treturn json(200, { tag: changed });\n\t});\n\n\t// A new link for an existing badge: for one that was lost, expired, or revoked and should work again. The link\n\t// handed out before stops working with it.\n\troute(\"POST\", \"/rfid/tags/:id/link\", { permission: \"rfid.manage\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst tag = rfid.findById(numberParam(context, \"id\"));\n\t\tif (!tag) {\n\t\t\tthrow new NotFoundError(`tag ${context.params.id} not found`);\n\t\t}\n\t\tconst ttlDays = optionalNumber(context.jsonBody(), \"ttlDays\") ?? 365;\n\t\tif (!Number.isInteger(ttlDays) || ttlDays <= 0) {\n\t\t\tthrow new ValidationError(`ttlDays must be a positive whole number (got ${ttlDays})`);\n\t\t}\n\t\tconst signed = reissueLink(context, tag, ttlDays);\n\t\treturn json(200, { tag: signed.tag, token: signed.token, url: tagLink(context, signed.token) });\n\t});\n\n\troute(\n\t\t\"POST\",\n\t\t\"/rfid/scan\",\n\t\t{ public: true, csrf: false, rateLimit: { name: \"rfid-scan\", limit: 30, windowSeconds: 60 } },\n\t\tcontext => {\n\t\t\tconst secret = requireHmacSecret();\n\t\t\tconst body = context.jsonBody();\n\t\t\tconst parsed = parseTagToken(requireString(body, \"token\"));\n\t\t\tif (!parsed) {\n\t\t\t\tthrow new ValidationError(\"token is not valid\");\n\t\t\t}\n\n\t\t\t// the signature is recomputed from the payload of the token and compared with the signature it carries,\n\t\t\t// so a forged uid/user/exp or a patched signature fails here\n\t\t\tconst expected = signTag(secret, parsed.uid, parsed.userId, parsed.expiresAt);\n\t\t\tif (!sameSignature(expected, parsed.signature)) {\n\t\t\t\tthrow problem(401, \"invalid_credentials\", \"the tag is not valid any more\");\n\t\t\t}\n\n\t\t\tconst tag = rfid.verify({\n\t\t\t\tuid: parsed.uid,\n\t\t\t\tuserId: parsed.userId,\n\t\t\t\tsignature: parsed.signature,\n\t\t\t\tnow: now(),\n\t\t\t});\n\t\t\tif (!tag) {\n\t\t\t\t// one answer for an unknown tag and a revoked one: no probing\n\t\t\t\tthrow problem(401, \"invalid_credentials\", \"the tag is not valid any more\");\n\t\t\t}\n\n\t\t\tconst user = users.findById(tag.userId ?? 0);\n\t\t\tif (!user || !user.isActive) {\n\t\t\t\tthrow problem(401, \"user_inactive\", \"the owner of the tag is not active\");\n\t\t\t}\n\n\t\t\tconst timestamp = now();\n\t\t\tconst stored = entries.insert({\n\t\t\t\tuserId: user.id,\n\t\t\t\ttsUtc: optionalNumber(body, \"tsUtc\") ?? timestamp,\n\t\t\t\ttimeZone: user.timezone,\n\t\t\t\tsource: \"nfc\",\n\t\t\t\tdirection: optionalDirection(body),\n\t\t\t\tnote: optionalString(body, \"note\"),\n\t\t\t\tactorId: user.id,\n\t\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\t\tnow: timestamp,\n\t\t\t});\n\t\t\trfid.touch({ id: tag.id, now: timestamp });\n\t\t\tconst day = aggregation.recalculateDay(user.id, stored.entry.localDate, { now: timestamp });\n\t\t\temit({\n\t\t\t\ttype: \"rfid.scan\",\n\t\t\t\tuserId: user.id,\n\t\t\t\tdata: {\n\t\t\t\t\tentryId: stored.entry.id,\n\t\t\t\t\tlocalDate: stored.entry.localDate,\n\t\t\t\t\ttagId: tag.id,\n\t\t\t\t\tuid: tag.uid,\n\t\t\t\t},\n\t\t\t});\n\n\t\t\treturn json(201, {\n\t\t\t\tuser: { id: user.id, displayName: user.displayName },\n\t\t\t\tentry: {\n\t\t\t\t\tid: stored.entry.id,\n\t\t\t\t\ttsUtc: stored.entry.tsUtc,\n\t\t\t\t\tlocalDate: stored.entry.localDate,\n\t\t\t\t},\n\t\t\t\tday: {\n\t\t\t\t\tworkedMin: day.workedMin,\n\t\t\t\t\ttargetMin: day.targetMin,\n\t\t\t\t\tbalanceMin: day.balanceMin,\n\t\t\t\t\thasOpenEntry: day.hasOpenEntry,\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\t);\n\n\t// reports\n\n\t/**\n\t * Collects everything a monthly statement needs.\n\t *\n\t * The Excel and the PDF export show the same statement, so they share this step: the caller is resolved\n\t * from `?userId=` (permission checked), the month is recalculated and read, and the labels follow the\n\t * language of the employee.\n\t *\n\t * @param context - route context with the authenticated caller\n\t * @returns the report input plus the year and month\n\t */\n\tconst monthlyReport = (\n\t\tcontext: RouteContext,\n\t): {\n\t\t/** Input of the PDF and Excel builder */\n\t\tinput: ReportInput & {\n\t\t\t/** Line of the generator that is printed into the document */\n\t\t\tgenerator: string;\n\t\t};\n\t\t/** Year of the statement */\n\t\tyear: number;\n\t\t/** Month of the statement (1-12) */\n\t\tmonth: number;\n\t\t/** Login of the employee, used in the file name */\n\t\tlogin: string;\n\t} => {\n\t\tconst userId = scopeUser(context);\n\t\tconst user = users.findById(userId);\n\t\tif (!user) {\n\t\t\tthrow new NotFoundError(`user ${userId} not found`);\n\t\t}\n\n\t\tconst year = numberQuery(context, \"year\");\n\t\tconst month = numberQuery(context, \"month\");\n\t\tif (month < 1 || month > 12) {\n\t\t\tthrow new ValidationError(`month must be between 1 and 12 (got ${month})`);\n\t\t}\n\n\t\tconst prefix = `${year}-${String(month).padStart(2, \"0\")}`;\n\t\tconst first = `${prefix}-01`;\n\t\tconst lastDay = new Date(Date.UTC(year, month, 0)).getUTCDate();\n\t\tconst last = `${prefix}-${String(lastDay).padStart(2, \"0\")}`;\n\t\tconst timestamp = now();\n\n\t\t// a statement has to show the current state of the month, so the range is recalculated first\n\t\taggregation.recalculateRange(userId, first, last, { now: timestamp });\n\t\tconst { labels, language } = reportLabels(user.locale);\n\n\t\treturn {\n\t\t\tyear,\n\t\t\tmonth,\n\t\t\tlogin: user.login,\n\t\t\tinput: {\n\t\t\t\tuser: { displayName: user.displayName, login: user.login, timezone: user.timezone },\n\t\t\t\tlabels,\n\t\t\t\tlanguage,\n\t\t\t\tlocale: user.locale || \"en\",\n\t\t\t\tyear,\n\t\t\t\tmonth,\n\t\t\t\tdays: aggregation.days(userId, first, last),\n\t\t\t\t// the document only lists effective absences \u2014 a request waits for its decision\n\t\t\t\tabsences: absences\n\t\t\t\t\t.withTypesInRange(userId, first, last)\n\t\t\t\t\t.filter(isApproved)\n\t\t\t\t\t.map(absence => ({\n\t\t\t\t\t\ttypeCode: absence.typeCode,\n\t\t\t\t\t\ttypeName: absence.typeName,\n\t\t\t\t\t\tdateFrom: absence.dateFrom,\n\t\t\t\t\t\tdateTo: absence.dateTo,\n\t\t\t\t\t\tdayPortion: absence.dayPortion,\n\t\t\t\t\t\thours: absence.hours,\n\t\t\t\t\t})),\n\t\t\t\tgeneratedAt: timestamp,\n\t\t\t\tgenerator: `time-tracker ${deps.version ?? \"\"}`.trim(),\n\t\t\t},\n\t\t};\n\t};\n\n\t// the raw punches of a month as CSV: for payroll and for tools that want the single punch instead of a total\n\troute(\n\t\t\"GET\",\n\t\t\"/reports/csv\",\n\t\t{ permission: \"report.view_own\", rateLimit: { name: \"export\", limit: 20, windowSeconds: 60 } },\n\t\tcontext => {\n\t\t\tconst { year, month, login } = monthlyReport(context);\n\t\t\tconst author = users.findById(scopeUser(context));\n\t\t\tif (!author) {\n\t\t\t\tthrow new NotFoundError(\"user not found\");\n\t\t\t}\n\t\t\tconst prefix = `${year}-${String(month).padStart(2, \"0\")}`;\n\t\t\tconst lastDay = new Date(Date.UTC(year, month, 0)).getUTCDate();\n\t\t\tconst csv = buildEntriesCsv(\n\t\t\t\tentries.listByRange(author.id, `${prefix}-01`, `${prefix}-${String(lastDay).padStart(2, \"0\")}`),\n\t\t\t\tauthor.timezone,\n\t\t\t);\n\n\t\t\treturn binary(200, csv, \"text/csv; charset=utf-8\", {\n\t\t\t\t\"content-disposition\": `attachment; filename=\"${reportFileName(login, year, month, \"csv\")}\"`,\n\t\t\t\t\"cache-control\": \"no-store\",\n\t\t\t});\n\t\t},\n\t);\n\n\troute(\n\t\t\"GET\",\n\t\t\"/reports/xls\",\n\t\t{ permission: \"report.view_own\", rateLimit: { name: \"export\", limit: 20, windowSeconds: 60 } },\n\t\tasync context => {\n\t\t\tconst { input, year, month, login } = monthlyReport(context);\n\t\t\tconst workbook = await buildMonthReport(input);\n\n\t\t\t// the file name is generated from the login, so it never contains a header delimiter\n\t\t\treturn binary(200, workbook, XLSX_CONTENT_TYPE, {\n\t\t\t\t\"content-disposition\": `attachment; filename=\"${reportFileName(login, year, month, \"xlsx\")}\"`,\n\t\t\t\t\"cache-control\": \"no-store\",\n\t\t\t});\n\t\t},\n\t);\n\n\troute(\n\t\t\"GET\",\n\t\t\"/reports/pdf\",\n\t\t{ permission: \"report.view_own\", rateLimit: { name: \"export\", limit: 20, windowSeconds: 60 } },\n\t\tasync context => {\n\t\t\tconst { input, year, month, login } = monthlyReport(context);\n\t\t\tlet statement: Buffer;\n\t\t\ttry {\n\t\t\t\tstatement = await buildMonthStatement({\n\t\t\t\t\t...input,\n\t\t\t\t\t// a Unicode font is only needed for scripts the built-in fonts cannot show (see the renderer)\n\t\t\t\t\tfontPath: settings.get(\"report_font_path\") ?? \"\",\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\t// a language the built-in fonts cannot draw is a configuration problem, not a bad request:\n\t\t\t\t// the UI can name the setting that solves it\n\t\t\t\tif (error instanceof ValidationError && error.message.includes(\"Unicode font\")) {\n\t\t\t\t\tthrow problem(\n\t\t\t\t\t\t422,\n\t\t\t\t\t\t\"report_font_missing\",\n\t\t\t\t\t\t\"this language needs a Unicode font: set report_font_path to a .ttf/.otf file that covers the script\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\n\t\t\treturn binary(200, statement, PDF_CONTENT_TYPE, {\n\t\t\t\t\"content-disposition\": `attachment; filename=\"${reportFileName(login, year, month, \"pdf\")}\"`,\n\t\t\t\t\"cache-control\": \"no-store\",\n\t\t\t});\n\t\t},\n\t);\n\n\t// backups (administration); the read side lists what exists, the write side takes a new copy\n\n\tif (deps.backup) {\n\t\tconst backup = deps.backup;\n\n\t\troute(\"GET\", \"/backup\", { permission: \"backup.run\" }, () =>\n\t\t\tjson(200, {\n\t\t\t\tretentionDays: settings.getNumber(\"backup_retention_days\", 30),\n\t\t\t\tbackups: backup.list(),\n\t\t\t\t// a restore that waits for the next start, so the screen can say so\n\t\t\t\tpending: backup.pending(),\n\t\t\t}),\n\t\t);\n\n\t\troute(\"GET\", \"/backup/:name\", { permission: \"backup.run\" }, context => {\n\t\t\t// only files the service knows are served: the requested name never reaches the file system\n\t\t\tconst wanted = context.params.name;\n\t\t\tconst file = backup.list().find(entry => entry.name === wanted);\n\t\t\tif (!file) {\n\t\t\t\tthrow new NotFoundError(`backup ${wanted} not found`);\n\t\t\t}\n\t\t\treturn binary(200, fs.readFileSync(file.file), \"application/vnd.sqlite3\", {\n\t\t\t\t\"content-disposition\": `attachment; filename=\"${file.name}\"`,\n\t\t\t\t\"cache-control\": \"no-store\",\n\t\t\t});\n\t\t});\n\n\t\troute(\n\t\t\t\"POST\",\n\t\t\t\"/backup\",\n\t\t\t{ permission: \"backup.run\", csrf: true, rateLimit: { name: \"backup\", limit: 10, windowSeconds: 60 } },\n\t\t\tcontext => {\n\t\t\t\tconst created = backup.create({\n\t\t\t\t\tactorId: context.auth?.user.id ?? null,\n\t\t\t\t\treason: \"api\",\n\t\t\t\t});\n\t\t\t\temit({ type: \"backup.create\", userId: null, data: { backup: created.backup.name } });\n\t\t\t\treturn json(201, { backup: created.backup, removed: created.removed });\n\t\t\t},\n\t\t);\n\n\t\t// A single backup is removed by hand; the retention keeps doing its own job in the background. Only files\n\t\t// of the list are deleted, so the requested name never reaches the file system.\n\t\troute(\n\t\t\t\"DELETE\",\n\t\t\t\"/backup/:name\",\n\t\t\t{ permission: \"backup.run\", csrf: true, rateLimit: { name: \"backup\", limit: 10, windowSeconds: 60 } },\n\t\t\tcontext => {\n\t\t\t\ttry {\n\t\t\t\t\tbackup.remove(context.params.name, { actorId: context.auth?.user.id ?? null, reason: \"api\" });\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (error instanceof ValidationError) {\n\t\t\t\t\t\t// a name that is not in the list is a missing resource, not a bad request\n\t\t\t\t\t\tthrow new NotFoundError(error.message);\n\t\t\t\t\t}\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t\treturn noContent(204);\n\t\t\t},\n\t\t);\n\n\t\t// One of the listed backups is queued for the next start: the swap itself happens while the adapter starts,\n\t\t// because a restore needs a closed database (see `restore` in the backup service). Only files of the list are\n\t\t// accepted, so the requested name never reaches the file system.\n\t\t//\n\t\t// The same route takes an uploaded file \u2014 the way back for a machine that lost its data directory. The browser\n\t\t// sends the chosen file as the raw body (`application/octet-stream`), everything else stays JSON with the name\n\t\t// of a listed backup. An upload is the only request that may exceed the router-wide body limit, so the route\n\t\t// raises it for itself (`MAX_BACKUP_UPLOAD_BYTES`; the server reads with the same bound).\n\t\troute(\n\t\t\t\"POST\",\n\t\t\t\"/backup/restore\",\n\t\t\t{\n\t\t\t\tpermission: \"backup.run\",\n\t\t\t\tcsrf: true,\n\t\t\t\trateLimit: { name: \"backup\", limit: 10, windowSeconds: 60 },\n\t\t\t\tmaxBodyBytes: MAX_BACKUP_UPLOAD_BYTES,\n\t\t\t},\n\t\t\tcontext => {\n\t\t\t\tconst upload = (context.header(\"content-type\") ?? \"\")\n\t\t\t\t\t.toLowerCase()\n\t\t\t\t\t.startsWith(\"application/octet-stream\");\n\t\t\t\ttry {\n\t\t\t\t\tif (upload) {\n\t\t\t\t\t\t// the file itself travels as the body; name and reason are query parameters\n\t\t\t\t\t\tconst pending = backup.queueRestore(context.rawBody(), {\n\t\t\t\t\t\t\tname: context.query(\"name\") ?? undefined,\n\t\t\t\t\t\t\tactorId: context.auth?.user.id ?? null,\n\t\t\t\t\t\t\treason: context.query(\"reason\") ?? undefined,\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn json(201, { pending });\n\t\t\t\t\t}\n\n\t\t\t\t\tconst body = context.jsonBody();\n\t\t\t\t\tconst name = typeof body.name === \"string\" ? body.name.trim() : \"\";\n\t\t\t\t\tif (name === \"\") {\n\t\t\t\t\t\tthrow problem(400, \"backup_invalid\", \"the request needs `name` with one of the listed backups\");\n\t\t\t\t\t}\n\t\t\t\t\tconst pending = backup.queueExistingBackup(name, {\n\t\t\t\t\t\tactorId: context.auth?.user.id ?? null,\n\t\t\t\t\t\treason: typeof body.reason === \"string\" ? body.reason : undefined,\n\t\t\t\t\t});\n\t\t\t\t\treturn json(201, { pending });\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (error instanceof ValidationError) {\n\t\t\t\t\t\t// the file is not a backup of this adapter: the message says what is wrong with it\n\t\t\t\t\t\tthrow problem(400, \"backup_invalid\", error.message);\n\t\t\t\t\t}\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t},\n\t\t);\n\t}\n\n\t// system\n\n\troute(\"GET\", \"/health\", { public: true }, () =>\n\t\tjson(200, {\n\t\t\tstatus: \"ok\",\n\t\t\tholidayCountry: settings.get(\"holiday_country\") ?? \"DE\",\n\t\t\ttime: now(),\n\t\t}),\n\t);\n\n\troute(\"GET\", \"/routes\", { public: true }, () => json(200, { routes: registered }));\n\n\treturn { router, routes: () => registered.slice(), events };\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * REST API of the adapter.\n *\n * The endpoints are registered on the server agnostic router, so the same table can later be mounted on an own\n * HTTP server (`native.port`) or handed to the web extension of a `web` instance.\n *\n * Conventions:\n * - the paths follow the API specification (section 4): `/auth/...`, `/punch...`, `/entries...`, `/absences...`,\n * `/aggregates/{day,month,year}`; the server mounts all of them below the prefix `/api`;\n * - the session token travels in the `x-session-token` header, the CSRF token in `x-csrf-token`;\n * - `?userId=` selects another employee and requires the matching `\u2026_other` permission (otherwise the caller\n * only sees their own data);\n * - every write answers with the stored record and, where it matters, with the refreshed aggregates;\n * - reads recalculate the requested period, so a report is always up to date.\n */\n\nimport { createHash, randomBytes } from \"node:crypto\";\nimport * as fs from \"node:fs\";\n\nimport type { Db } from \"../db/database\";\nimport type { AbsencesRepository, AbsenceApproval, AbsenceRecord } from \"../db/repositories/absences\";\nimport { isApproved } from \"../db/repositories/absences\";\nimport { readTimeEntryAudit } from \"../db/repositories/audit\";\nimport type {\n\tAutomationKind,\n\tAutomationRepeat,\n\tAutomationRuleRecord,\n\tAutomationsRepository,\n} from \"../db/repositories/automations\";\nimport type { DayNotesRepository } from \"../db/repositories/dayNotes\";\nimport type { EntriesRepository, EntryDirection, EntryRecord } from \"../db/repositories/entries\";\nimport type { HolidaysRepository } from \"../db/repositories/holidays\";\nimport type { PayoutsRepository } from \"../db/repositories/payouts\";\nimport {\n\tbuildTagToken,\n\tnewTagUid,\n\tparseTagToken,\n\tsameSignature,\n\tsignTag,\n\ttype RfidRepository,\n\ttype RfidTagRecord,\n} from \"../db/repositories/rfid\";\nimport type { PauseRuleRecord, RulesRepository } from \"../db/repositories/rules\";\nimport type { SettingsRepository, SettingValue } from \"../db/repositories/settings\";\nimport type { TerminalRecord, TerminalsRepository } from \"../db/repositories/terminals\";\nimport type { TriggerAction, TriggerMode, TriggerRuleRecord, TriggersRepository } from \"../db/repositories/triggers\";\nimport type { UserRecord, UsersRepository, WorkProfileRecord } from \"../db/repositories/users\";\nimport type { AggregationService } from \"../services/aggregation\";\nimport type { AuthService } from \"../services/auth\";\nimport { checkPasswordPolicy, hashPassword, verifyPassword } from \"../services/auth\";\nimport type { SyncService } from \"../services/sync\";\nimport type { BackupService } from \"../services/backup\";\nimport {\n\tabsenceEvents,\n\tcalendarDocument,\n\tCOMPANY_CALENDAR_NAME,\n\tICAL_CONTENT_TYPE,\n\ttype CalendarEmployee,\n} from \"../services/calendar\";\nimport { NotFoundError, ValidationError, type FieldIssue } from \"../errors\";\nimport { SETTING_DEFAULTS } from \"../db/seed\";\nimport { roundToStep } from \"../domain/punch\";\nimport { MAX_AVATAR_BYTES, MAX_BRANDING_BYTES, parseAvatarDataUrl, readAvatar } from \"../domain/avatar\";\nimport { reportLabels } from \"../reports/labels\";\nimport { buildMonthReport } from \"../reports/xls\";\nimport { buildMonthStatement } from \"../reports/pdf\";\nimport { reportFileName, type ReportInput } from \"../reports/types\";\nimport { dateRange, isValidTimeZone, localDate, utcToWallTime } from \"../util/time\";\nimport { clearCookie, parseCookies, SESSION_COOKIE, serializeCookie } from \"./cookies\";\nimport { createEventBus, type ApiEvent, type EventBus } from \"./events\";\nimport { createPinGuard } from \"./pin-guard\";\nimport { problem, toProblem } from \"./problem\";\nimport {\n\tcreateRouter,\n\tjson,\n\tnoContent,\n\tbinary,\n\ttype HttpMethod,\n\ttype RouteContext,\n\ttype RouteResponse,\n\ttype Router,\n} from \"./router\";\n\n/** Data sources of the API. */\nexport interface ApiDeps {\n\t/** Open database handle */\n\tdb: Db;\n\t/** Authentication service */\n\tauth: AuthService;\n\t/** User storage */\n\tusers: UsersRepository;\n\t/** Punch storage */\n\tentries: EntriesRepository;\n\t/** Absence storage */\n\tabsences: AbsencesRepository;\n\t/** Notes the employees leave for the administration */\n\tdayNotes: DayNotesRepository;\n\t/** Holiday storage */\n\tholidays: HolidaysRepository;\n\t/** Surcharge and break rules */\n\trules: RulesRepository;\n\t/** Paid out overtime */\n\tpayouts: PayoutsRepository;\n\t/** Kiosk terminals */\n\tterminals: TerminalsRepository;\n\t/** RFID/NFC tags */\n\trfid: RfidRepository;\n\t/** Trigger rules: ioBroker states that punch or set the presence */\n\ttriggers: TriggersRepository;\n\t/** Automation rules: what the adapter does on its own (clock out, reminders) */\n\tautomations: AutomationsRepository;\n\t/**\n\t * Called after the trigger rules were saved, so the adapter watches the new states right away\n\t * (`undefined` in tests that do not care).\n\t */\n\tonTriggerRulesChanged?: () => void;\n\t/** Secret used to sign tag links (`hmacSecret`) */\n\thmacSecret?: string;\n\t/** True when the kiosk terminal is switched on (instance setting) */\n\tkioskEnabled?: boolean;\n\t/** Lifetime of a terminal session in minutes; the heartbeat of the device extends it (default 15) */\n\tterminalSessionMinutes?: number;\n\t/**\n\t * Trusts `x-forwarded-*` of a reverse proxy: the forwarded address is used for the rate limits and the\n\t * audit trail, and `x-forwarded-proto: https` makes the session cookie `Secure`.\n\t */\n\ttrustProxy?: boolean;\n\t/** Aggregation service (reports and refreshes) */\n\taggregation: AggregationService;\n\t/** Offline synchronisation */\n\tsync: SyncService;\n\t/** Instance settings */\n\tsettings: SettingsRepository;\n\t/** Backup service; without it the backup endpoints are not registered */\n\tbackup?: BackupService;\n\t/** Live event bus; a private one is created when it is not given */\n\tevents?: EventBus;\n\t/** Instant source, defaults to the system clock */\n\tnow?: () => number;\n\t/** Version reported by `GET /version` */\n\tversion?: string;\n\t/**\n\t * Allowed login attempts per minute and address. The browser tests sign in once per case, so their harness\n\t * raises the shipped limit of 20.\n\t */\n\tloginRateLimit?: number;\n}\n\n/** Media type of the Excel export. */\nconst XLSX_CONTENT_TYPE = \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\";\n\n/** Media type of the PDF export. */\nconst PDF_CONTENT_TYPE = \"application/pdf\";\n\n/**\n * Upper bound for an uploaded backup.\n *\n * A backup is the only request that may be bigger than the router-wide body limit, so the restore route raises\n * the limit for itself. The web server reads with the same bound, because the transport refuses a body before\n * any route sees it.\n */\nexport const MAX_BACKUP_UPLOAD_BYTES = 64 * 1024 * 1024;\n\n/** A registered route (used for the documentation). */\nexport interface ApiRoute {\n\t/** HTTP method */\n\tmethod: string;\n\t/** Path pattern */\n\tpath: string;\n\t/** Permission the route requires */\n\tpermission?: string;\n}\n\n/** The API. */\nexport interface Api {\n\t/** Router the endpoints are registered on */\n\trouter: Router;\n\t/** Route table */\n\troutes(): ApiRoute[];\n\t/** Bus every change is published on (also used by the WebSocket stream) */\n\tevents: EventBus;\n}\n\n/**\n * Reads a required string field.\n *\n * @param body - parsed request body\n * @param field - field name\n * @returns the trimmed value\n */\nfunction requireString(body: Record<string, unknown>, field: string): string {\n\tconst value = body[field];\n\tif (typeof value !== \"string\" || !value.trim()) {\n\t\tthrow new ValidationError(`${field} is required`);\n\t}\n\treturn value.trim();\n}\n\n/**\n * Reads an optional string field.\n *\n * @param body - parsed request body\n * @param field - field name\n * @returns the trimmed value or `null`\n */\nfunction optionalString(body: Record<string, unknown>, field: string): string | null {\n\tconst value = body[field];\n\tif (value === undefined || value === null) {\n\t\treturn null;\n\t}\n\tif (typeof value !== \"string\") {\n\t\tthrow new ValidationError(`${field} must be a string`);\n\t}\n\treturn value.trim() || null;\n}\n\n/**\n * Reads an optional number field.\n *\n * @param body - parsed request body\n * @param field - field name\n * @returns the value or `null`\n */\nfunction optionalNumber(body: Record<string, unknown>, field: string): number | null {\n\tconst value = body[field];\n\tif (value === undefined || value === null) {\n\t\treturn null;\n\t}\n\tif (typeof value !== \"number\" || !Number.isFinite(value)) {\n\t\tthrow new ValidationError(`${field} must be a number`);\n\t}\n\treturn value;\n}\n\n/**\n * Settings an administrator must not see through the API, even when they are compatible with the stored form.\n * They are matched case-insensitively and may occur anywhere in the key.\n */\nconst SECRET_SETTING_KEYS = [\"secret\", \"password\", \"token\", \"hash\", \"apikey\"];\n\n/** Settings whose value may be a nested structure (arrays and objects). */\nconst JSON_SETTINGS = [\"pause_staffel\"];\n\n/** Upper bound of a statistics range: every day of every employee is recalculated. */\nconst MAX_STATISTICS_DAYS = 366;\n\n/** Default lifetime of a terminal session; the heartbeat of the device extends it. */\nconst TERMINAL_SESSION_MINUTES_DEFAULT = 15;\n\n/** A terminal as it is handed out over the API (without any secret). */\nexport interface PublicTerminalPayload {\n\t/** Primary key */\n\tid: number;\n\t/** Display name */\n\tname: string;\n\t/** Optional location */\n\tlocation: string | null;\n\t/** True when a badge has to be combined with the personal PIN */\n\tpinRequired: boolean;\n\t/** False for revoked devices */\n\tisActive: boolean;\n\t/** Instant the device token expires, `null` = never */\n\texpiresAt: number | null;\n\t/** Instant of the last heartbeat */\n\tlastSeenAt: number | null;\n\t/** Instant of creation */\n\tcreatedAt: number;\n\t/** Employees shown on this terminal; an empty list means \u201Call employees\u201D */\n\tuserIds: number[];\n}\n\n/**\n * Removes the internals of a terminal record.\n *\n * @param terminal - stored terminal\n * @returns the public part\n */\nfunction publicTerminal(terminal: TerminalRecord): PublicTerminalPayload {\n\treturn {\n\t\tid: terminal.id,\n\t\tname: terminal.name,\n\t\tlocation: terminal.location,\n\t\tpinRequired: terminal.pinRequired,\n\t\tisActive: terminal.isActive,\n\t\texpiresAt: terminal.expiresAt,\n\t\tlastSeenAt: terminal.lastSeenAt,\n\t\tcreatedAt: terminal.createdAt,\n\t\tuserIds: terminal.userIds,\n\t};\n}\n\n/**\n * Settings that may be changed through the API: the instance defaults from the seed plus the structured ones.\n *\n * The list is derived from the defaults on purpose \u2014 a setting the adapter does not know would silently be\n * ignored by the services, so it is refused instead.\n *\n * @returns editable setting keys\n */\nfunction editableSettings(): string[] {\n\treturn [...Object.keys(SETTING_DEFAULTS), ...JSON_SETTINGS];\n}\n\n/** Settings that a read leaves out: they are large pictures and are delivered by their own routes. */\nconst HIDDEN_SETTING_KEYS = [\"brand_logo\", \"brand_background\"];\n\n/**\n * Removes settings that look like secrets from a read.\n *\n * @param all - all stored settings\n * @returns the settings that may be shown\n */\nfunction readableSettings(all: Record<string, string>): Record<string, string> {\n\tconst result: Record<string, string> = {};\n\tfor (const [key, value] of Object.entries(all)) {\n\t\tconst lower = key.toLowerCase();\n\t\tif (SECRET_SETTING_KEYS.some(marker => lower.includes(marker))) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (HIDDEN_SETTING_KEYS.includes(key)) {\n\t\t\tcontinue;\n\t\t}\n\t\tresult[key] = value;\n\t}\n\treturn result;\n}\n\n/**\n * Checks whether a value may be stored as a setting.\n *\n * @param value - value from the request body\n * @param allowStructured - true for settings that may hold arrays or objects\n * @returns true when the value is supported\n */\nfunction isSettingValue(value: unknown, allowStructured: boolean): boolean {\n\tif (value === null || typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\") {\n\t\treturn typeof value !== \"number\" || Number.isFinite(value);\n\t}\n\tif (!allowStructured) {\n\t\treturn false;\n\t}\n\tif (Array.isArray(value)) {\n\t\treturn value.every(entry => isSettingValue(entry, true));\n\t}\n\treturn (\n\t\ttypeof value === \"object\" &&\n\t\tObject.values(value as Record<string, unknown>).every(entry => isSettingValue(entry, true))\n\t);\n}\n\n/**\n * Reads a required boolean field.\n *\n * @param body - parsed request body\n * @param field - field name\n * @returns the value\n */\nfunction requireBoolean(body: Record<string, unknown>, field: string): boolean {\n\tconst value = optionalBoolean(body, field);\n\tif (value === null) {\n\t\tthrow new ValidationError(`${field} is required`);\n\t}\n\treturn value;\n}\n\n/**\n * Reads a year and an optional month from a body or the query.\n *\n * The range matches the validation of the payout repository, so a wrong year is refused by the API before it\n * reaches the storage layer.\n *\n * @param year - raw year\n * @param month - raw month, `null`/empty for the whole year\n * @returns validated year and month\n */\nfunction readPeriod(year: unknown, month: unknown): { year: number; month: number | null } {\n\tconst parsedYear = Number(year);\n\tif (!Number.isInteger(parsedYear) || parsedYear < 2000 || parsedYear > 2100) {\n\t\tthrow new ValidationError(`year must be a four digit year between 2000 and 2100 (got ${JSON.stringify(year)})`);\n\t}\n\tif (month === null || month === undefined || month === \"\") {\n\t\treturn { year: parsedYear, month: null };\n\t}\n\tconst parsedMonth = Number(month);\n\tif (!Number.isInteger(parsedMonth) || parsedMonth < 1 || parsedMonth > 12) {\n\t\tthrow new ValidationError(`month must be between 1 and 12 (got ${JSON.stringify(month)})`);\n\t}\n\treturn { year: parsedYear, month: parsedMonth };\n}\n\n/**\n * Reads an optional whole-number query parameter.\n *\n * @param context - route context\n * @param name - parameter name\n * @param fallback - value used when the parameter is missing\n * @returns the parsed number\n */\nfunction optionalNumberQuery(context: RouteContext, name: string, fallback: number): number {\n\tconst raw = context.query(name);\n\tif (raw === null) {\n\t\treturn fallback;\n\t}\n\tconst value = Number(raw);\n\tif (!Number.isInteger(value) || value < 0) {\n\t\tthrow new ValidationError(`${name} must be a non-negative whole number (got ${raw})`);\n\t}\n\treturn value;\n}\n\n/**\n * Reads a list of role keys.\n *\n * @param body - parsed request body\n * @returns the role keys or `null` when the field is absent\n */\nfunction readRoleKeys(body: Record<string, unknown>): string[] | null {\n\tconst value = body.roleKeys;\n\tif (value === undefined || value === null) {\n\t\treturn null;\n\t}\n\tif (!Array.isArray(value) || value.some(entry => typeof entry !== \"string\")) {\n\t\tthrow new ValidationError(\"roleKeys must be an array of strings\");\n\t}\n\treturn (value as string[]).map(entry => entry.trim()).filter(entry => entry.length > 0);\n}\n\n/**\n * Reads an optional list of user ids (the employees of a terminal).\n *\n * @param body - request body\n * @returns the ids without duplicates, `null` when the field is missing\n */\nfunction readUserIds(body: Record<string, unknown>): number[] | null {\n\tconst value = body.userIds;\n\tif (value === undefined || value === null) {\n\t\treturn null;\n\t}\n\tif (!Array.isArray(value) || value.some(entry => !Number.isInteger(entry))) {\n\t\tthrow new ValidationError(\"userIds must be an array of whole numbers\");\n\t}\n\treturn [...new Set(value as number[])];\n}\n\n/** Fields of a work profile a client may change. */\nconst PROFILE_FIELDS: (keyof Omit<WorkProfileRecord, \"userId\">)[] = [\n\t\"percent\",\n\t\"weeklyHours\",\n\t\"workdays\",\n\t\"startDate\",\n\t\"endDate\",\n\t\"overtimeCarryover\",\n\t\"vorholzeitPerYear\",\n\t\"vacationCarryover\",\n\t\"vacationPerYear\",\n\t\"overtimeModel\",\n\t\"holidayFlags\",\n\t\"pausePaidMinutes\",\n\t\"showWorkedTime\",\n];\n\n/**\n * Reads a work profile patch and checks its values.\n *\n * The repository merges the given fields into the stored profile, so every value has to be validated here \u2014\n * a broken profile would silently spoil the target time of every later calculation.\n *\n * @param body - parsed request body\n * @returns the patch\n */\nfunction readProfilePatch(body: Record<string, unknown>): Partial<Omit<WorkProfileRecord, \"userId\">> {\n\tconst patch: Partial<Omit<WorkProfileRecord, \"userId\">> = {};\n\t// every invalid field is collected instead of failing on the first one, so the client sees all problems at\n\t// once (RFC 9457 `errors[]`)\n\tconst issues: FieldIssue[] = [];\n\n\tfor (const [key, value] of Object.entries(body)) {\n\t\tif (key === \"reason\") {\n\t\t\tcontinue;\n\t\t}\n\t\tif (!(PROFILE_FIELDS as string[]).includes(key)) {\n\t\t\tissues.push({ path: key, message: \"unknown field of a work profile\" });\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (key === \"percent\") {\n\t\t\tconst percent = Number(value);\n\t\t\tif (!Number.isInteger(percent) || percent < 0 || percent > 100) {\n\t\t\t\tissues.push({ path: key, message: \"must be a whole number between 0 and 100\" });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tpatch.percent = percent;\n\t\t} else if (key === \"weeklyHours\") {\n\t\t\tconst hours = Number(value);\n\t\t\tif (!Number.isFinite(hours) || hours < 0 || hours > 168) {\n\t\t\t\tissues.push({ path: key, message: \"must be between 0 and 168\" });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tpatch.weeklyHours = hours;\n\t\t} else if (key === \"workdays\") {\n\t\t\tif (typeof value !== \"string\") {\n\t\t\t\tissues.push({ path: key, message: \"must be a string of weekdays\" });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst workdays = value.trim();\n\t\t\tif (!/^\\d(;\\d)*$/.test(workdays) || workdays.split(\";\").some(day => Number(day) > 6)) {\n\t\t\t\tissues.push({ path: key, message: 'must be weekdays separated by \";\" (0 = Sunday \u2026 6 = Saturday)' });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tpatch.workdays = workdays;\n\t\t} else if (key === \"overtimeModel\") {\n\t\t\tif (value !== \"cumulative\" && value !== \"yearly\" && value !== \"monthly\") {\n\t\t\t\tissues.push({ path: key, message: \"must be cumulative, yearly or monthly\" });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tpatch.overtimeModel = value;\n\t\t} else if (key === \"startDate\" || key === \"endDate\") {\n\t\t\tif (value !== null && (!Number.isInteger(Number(value)) || Number(value) < 0)) {\n\t\t\t\tissues.push({ path: key, message: \"must be an instant in seconds or null\" });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tpatch[key] = value === null ? null : Number(value);\n\t\t} else if (key === \"holidayFlags\") {\n\t\t\tif (value !== null && typeof value !== \"string\") {\n\t\t\t\tissues.push({ path: key, message: \"must be a JSON string or null\" });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tpatch.holidayFlags = value;\n\t\t} else if (key === \"pausePaidMinutes\") {\n\t\t\t// minutes of the break per day that are paid; a whole number, at most a full day\n\t\t\tconst minutes = Number(value);\n\t\t\tif (!Number.isInteger(minutes) || minutes < 0 || minutes > 1440) {\n\t\t\t\tissues.push({ path: key, message: \"must be a whole number of minutes between 0 and 1440\" });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tpatch.pausePaidMinutes = minutes;\n\t\t} else if (key === \"showWorkedTime\") {\n\t\t\t// whether the presence card shows the worked time of the day for this employee\n\t\t\tif (typeof value !== \"boolean\") {\n\t\t\t\tissues.push({ path: key, message: \"must be true or false\" });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tpatch.showWorkedTime = value;\n\t\t} else {\n\t\t\tconst number = Number(value);\n\t\t\tif (!Number.isInteger(number)) {\n\t\t\t\tissues.push({ path: key, message: \"must be a whole number of minutes\" });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tpatch[key as \"overtimeCarryover\" | \"vorholzeitPerYear\" | \"vacationCarryover\" | \"vacationPerYear\"] = number;\n\t\t}\n\t}\n\n\tif (issues.length > 0) {\n\t\tthrow new ValidationError(`${issues.length} field(s) of the work profile are not valid`, issues);\n\t}\n\treturn patch;\n}\n\n/** A user as it is handed out over the API (without the password hashes). */\nexport interface PublicUserPayload {\n\t/** Primary key */\n\tid: number;\n\t/** Login name */\n\tlogin: string;\n\t/** Shown name */\n\tdisplayName: string;\n\t/** E-mail address */\n\temail: string | null;\n\t/** RFID card id */\n\trfidCard: string | null;\n\t/** False for deactivated accounts */\n\tisActive: boolean;\n\t/** True while the user has to set a new password */\n\tmustChangePw: boolean;\n\t/** Preferred language */\n\tlocale: string;\n\t/** Same value as `locale`; the web app reads the language of the session from this field */\n\tlanguage: string;\n\t/** IANA time zone */\n\ttimezone: string;\n\t/** Instant of creation, UTC epoch seconds */\n\tcreatedAt: number;\n\t/** Instant of the last change, UTC epoch seconds */\n\tupdatedAt: number;\n\t/** Instant of the last login, `null` if never */\n\tlastLoginAt: number | null;\n\t/** Address of the picture of the user, `null` when none is stored (the web app shows a placeholder) */\n\tavatarUrl: string | null;\n}\n\n/**\n * Removes everything a client must not see from a user record.\n *\n * The list is explicit on purpose: a column added later stays invisible until it is added here.\n *\n * @param user - stored user\n * @returns the public part\n */\nfunction publicUser(user: UserRecord): PublicUserPayload {\n\treturn {\n\t\tid: user.id,\n\t\tlogin: user.login,\n\t\tdisplayName: user.displayName,\n\t\temail: user.email,\n\t\trfidCard: user.rfidCard,\n\t\tisActive: user.isActive,\n\t\tmustChangePw: user.mustChangePw,\n\t\tlocale: user.locale,\n\t\tlanguage: user.locale,\n\t\ttimezone: user.timezone,\n\t\t// the picture is served by its own route, so it never travels inside the JSON answer; the version in the\n\t\t// query makes the browser fetch it again after a change\n\t\tavatarUrl: user.avatar ? `/api/users/${user.id}/avatar?v=${user.updatedAt}` : null,\n\t\tcreatedAt: user.createdAt,\n\t\tupdatedAt: user.updatedAt,\n\t\tlastLoginAt: user.lastLoginAt,\n\t};\n}\n\n/**\n * Reads an optional boolean field.\n *\n * @param body - parsed request body\n * @param field - field name\n * @returns the value or `null`\n */\nfunction optionalBoolean(body: Record<string, unknown>, field: string): boolean | null {\n\tconst value = body[field];\n\tif (value === undefined || value === null) {\n\t\treturn null;\n\t}\n\tif (typeof value !== \"boolean\") {\n\t\tthrow new ValidationError(`${field} must be a boolean`);\n\t}\n\treturn value;\n}\n\n/**\n * Reads a numeric path parameter.\n *\n * @param context - route context\n * @param name - parameter name\n * @returns the parsed number\n */\nfunction numberParam(context: RouteContext, name: string): number {\n\tconst raw = context.params[name];\n\tconst value = Number(raw);\n\tif (!Number.isInteger(value)) {\n\t\tthrow new ValidationError(`${name} must be a whole number (got ${raw})`);\n\t}\n\treturn value;\n}\n\n/**\n * Reads the day direction of a punch.\n *\n * @param body - parsed request body\n * @returns the direction\n */\nfunction optionalDirection(body: Record<string, unknown>): EntryDirection {\n\tconst value = optionalString(body, \"direction\");\n\tif (value === null) {\n\t\treturn \"auto\";\n\t}\n\tif (value !== \"in\" && value !== \"out\" && value !== \"auto\") {\n\t\tthrow new ValidationError(`direction must be in, out or auto (got ${value})`);\n\t}\n\treturn value;\n}\n\n/**\n * Resolves the employee a request refers to and checks the matching permission.\n *\n * @param context - route context with the authenticated caller\n * @param requested - user id from the request, `null` for \u201Cthe caller\u201D\n * @param ownPermission - permission needed for the own account\n * @param otherPermission - permission needed for another employee\n * @returns the target user id\n */\nfunction resolveScope(\n\tcontext: RouteContext,\n\trequested: number | null,\n\townPermission: string,\n\totherPermission: string,\n): number {\n\tif (!context.auth) {\n\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t}\n\tconst target = requested ?? context.auth.user.id;\n\tif (target === context.auth.user.id) {\n\t\treturn target;\n\t}\n\tif (!context.auth.permissions.includes(otherPermission)) {\n\t\tthrow problem(403, \"permission_denied\", `request rejected (permission_denied: ${otherPermission})`);\n\t}\n\treturn target;\n}\n\n/**\n * Creates the API.\n *\n * @param deps - data sources\n * @returns API with its router\n */\n/**\n * Builds the CSV of the raw punches of one employee.\n *\n * The header is stable and English, because the file is meant for a payroll tool rather than for reading: the\n * separator is a semicolon and the file starts with a UTF-8 byte order mark \u2014 that is what a spreadsheet in a\n * European locale expects.\n *\n * @param entries - punches of the employee in chronological order\n * @param timeZone - time zone of the employee, used for the time column\n * @returns the CSV document\n */\nfunction buildEntriesCsv(entries: EntryRecord[], timeZone: string): Buffer {\n\tconst lines = [\"date;time;direction;source;note\"];\n\tfor (const entry of entries) {\n\t\tconst seconds = utcToWallTime(entry.tsUtc, timeZone) % 86_400;\n\t\tconst hour = String(Math.floor(seconds / 3600)).padStart(2, \"0\");\n\t\tconst minute = String(Math.floor((seconds % 3600) / 60)).padStart(2, \"0\");\n\t\t// a note may carry the separator and quotes: quoting keeps the columns intact\n\t\tconst note = `\"${(entry.note ?? \"\").replace(/\"/g, '\"\"')}\"`;\n\t\tlines.push([entry.localDate, `${hour}:${minute}`, entry.direction ?? \"\", entry.source, note].join(\";\"));\n\t}\n\treturn Buffer.from(`\\uFEFF${lines.join(\"\\r\\n\")}\\r\\n`, \"utf8\");\n}\n\n/**\n * Builds the API of the adapter: every route of the HTTP surface and the helpers they share.\n *\n * The dependencies are injected, so a test can drive the API with a fixed clock and an in-memory database.\n *\n * @param deps - repositories, services and the clock of the instance\n * @returns router, route table and the event bus every change is published on\n */\nexport function createApi(deps: ApiDeps): Api {\n\tconst {\n\t\tauth,\n\t\tusers,\n\t\tentries,\n\t\tabsences,\n\t\tholidays,\n\t\trules,\n\t\tpayouts,\n\t\tterminals,\n\t\trfid,\n\t\ttriggers,\n\t\tautomations,\n\t\taggregation,\n\t\tsync,\n\t\tsettings,\n\t} = deps;\n\tconst now = deps.now ?? (() => Math.floor(Date.now() / 1000));\n\tconst events = deps.events ?? createEventBus();\n\tconst registered: ApiRoute[] = [];\n\tconst router = createRouter({ auth, trustProxy: deps.trustProxy === true, now });\n\t// wrong PINs at the terminal are counted per account (specification 4.10); the counters are short lived\n\tconst pinGuard = createPinGuard();\n\n\t/**\n\t * Publishes a live event about a change.\n\t *\n\t * @param event - kind of the change, affected employee and a short summary\n\t */\n\tconst emit = (event: Omit<ApiEvent, \"atUtc\">): void => {\n\t\tevents.publish({ ...event, atUtc: now() });\n\t};\n\n\t/**\n\t * Refuses a change that would leave the installation without an active administrator.\n\t *\n\t * The installation creates exactly one administrator; if the last active one is deactivated or loses the role,\n\t * nobody can administer any more \u2014 only the `admin` role carries `user.edit`, `user.manage_roles`, `settings.edit`\n\t * and the rest. The way back would be an instance restart with a free `adminLogin`, so the change is refused\n\t * instead. Only active accounts count: a deactivated administrator helps nobody.\n\t *\n\t * @param userId - account that is changed\n\t * @param next - state the account would have afterwards\n\t * @param next.isActive - activity after the change\n\t * @param next.roleKeys - roles after the change\n\t */\n\tfunction requireActiveAdministrator(userId: number, next: { isActive: boolean; roleKeys: string[] }): void {\n\t\tif (next.isActive && next.roleKeys.includes(\"admin\")) {\n\t\t\treturn;\n\t\t}\n\t\tconst remaining = users.list().some(user => user.id !== userId && users.roles(user.id).includes(\"admin\"));\n\t\tif (!remaining) {\n\t\t\tthrow problem(\n\t\t\t\t409,\n\t\t\t\t\"last_administrator\",\n\t\t\t\t\"the last active administrator cannot be deactivated or lose the admin role\",\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Registers a route and remembers it for the documentation.\n\t *\n\t * @param method - HTTP method(s)\n\t * @param path - path pattern\n\t * @param routeSettings - permission and visibility of the route\n\t * @param routeSettings.permission - permission required\n\t * @param routeSettings.public - true when no session is needed\n\t * @param routeSettings.csrf - false when the route must never ask for a CSRF token (it runs without a session,\n\t * for example the login); otherwise the router decides from the credential: a browser session cookie needs the\n\t * token, an integration client with a bearer token does not\n\t * @param routeSettings.rateLimit - rate limit of the route class\n\t * @param routeSettings.rateLimit.name - name of the counted class\n\t * @param routeSettings.rateLimit.limit - allowed requests inside the window\n\t * @param routeSettings.rateLimit.windowSeconds - length of the window\n\t * @param routeSettings.maxBodyBytes - body limit of this route in bytes (a backup upload); the router-wide\n\t * limit applies when it is missing\n\t * @param handler - handler of the route\n\t */\n\tconst route = (\n\t\tmethod: HttpMethod | HttpMethod[],\n\t\tpath: string,\n\t\trouteSettings: {\n\t\t\t/** Permission the caller needs; without one the route only requires a session */\n\t\t\tpermission?: string;\n\t\t\t/** True for a route that runs without a session (health, login, branding) */\n\t\t\tpublic?: boolean;\n\t\t\t/** `false` switches the CSRF check off (public routes); otherwise the router decides */\n\t\t\tcsrf?: boolean;\n\t\t\t/** Requests allowed inside a window, e.g. `{ name: \"export\", limit: 20, windowSeconds: 60 }` */\n\t\t\trateLimit?: {\n\t\t\t\t/** Name the limit is counted under */\n\t\t\t\tname: string;\n\t\t\t\t/** Allowed requests inside the window */\n\t\t\t\tlimit: number;\n\t\t\t\t/** Length of the window in seconds */\n\t\t\t\twindowSeconds: number;\n\t\t\t};\n\t\t\t/** Raises the body limit of this one route (a backup upload) */\n\t\t\tmaxBodyBytes?: number;\n\t\t},\n\t\thandler: (context: RouteContext) => ReturnType<Parameters<Router[\"add\"]>[0][\"handler\"]>,\n\t): void => {\n\t\tconst methods = Array.isArray(method) ? method : [method];\n\t\tfor (const entry of methods) {\n\t\t\tregistered.push({\n\t\t\t\tmethod: entry,\n\t\t\t\tpath,\n\t\t\t\t...(routeSettings.permission ? { permission: routeSettings.permission } : {}),\n\t\t\t});\n\t\t}\n\t\trouter.add({\n\t\t\tmethod,\n\t\t\tpath,\n\t\t\tpermission: routeSettings.permission,\n\t\t\trequiresAuth: routeSettings.public !== true,\n\t\t\t// `undefined` hands the decision to the router: a session cookie needs the CSRF token, a bearer\n\t\t\t// client (integration, `x-session-token`) does not, because no foreign page can set that header.\n\t\t\t// `false` switches the check off completely \u2014 that is for the public routes, which run without a\n\t\t\t// session, so a CSRF token could not exist yet.\n\t\t\trequiresCsrf: routeSettings.csrf === false ? false : undefined,\n\t\t\trateLimit: routeSettings.rateLimit,\n\t\t\tmaxBodyBytes: routeSettings.maxBodyBytes,\n\t\t\thandler,\n\t\t});\n\t};\n\n\t/**\n\t * Writes the session cookie of a fresh login.\n\t *\n\t * The cookie is `httpOnly` (a script inside the page cannot read it), `SameSite=Lax` and carries `Secure`\n\t * as soon as the client reaches the adapter over HTTPS \u2014 directly or through a trusted proxy (specification\n\t * 4.x). The browser sends it with every request by itself, which is exactly why state changing requests\n\t * additionally need the CSRF token from `x-csrf-token`; a bearer client has no such ambient credential.\n\t *\n\t * @param context - route context of the request (for the `Secure` flag)\n\t * @param token - session token\n\t * @param expiresAt - instant the session ends, UTC epoch seconds\n\t * @returns value of the `set-cookie` header\n\t */\n\tconst sessionCookie = (context: RouteContext, token: string, expiresAt: number): string =>\n\t\tserializeCookie(SESSION_COOKIE, token, {\n\t\t\tmaxAgeSeconds: Math.max(0, expiresAt - now()),\n\t\t\tsecure: context.secure,\n\t\t});\n\n\t/**\n\t * Removes the session cookie, so the browser session really ends.\n\t *\n\t * @param context - route context of the request (the flags have to match the ones it was set with)\n\t * @returns value of the `set-cookie` header\n\t */\n\tconst removeSessionCookie = (context: RouteContext): string =>\n\t\tclearCookie(SESSION_COOKIE, { secure: context.secure });\n\n\t// authentication\n\n\troute(\n\t\t\"POST\",\n\t\t\"/auth/login\",\n\t\t{\n\t\t\tpublic: true,\n\t\t\tcsrf: false,\n\t\t\trateLimit: { name: \"login\", limit: deps.loginRateLimit ?? 20, windowSeconds: 60 },\n\t\t},\n\t\tcontext => {\n\t\t\tconst body = context.jsonBody();\n\t\t\tconst result = auth.login({\n\t\t\t\tlogin: requireString(body, \"login\"),\n\t\t\t\tpassword: requireString(body, \"password\"),\n\t\t\t\tuserAgent: context.header(\"user-agent\"),\n\t\t\t\tip: context.request.remoteAddress ?? null,\n\t\t\t\tnow: now(),\n\t\t\t});\n\n\t\t\tif (!result.ok) {\n\t\t\t\tthrow problem(\n\t\t\t\t\tresult.error === \"locked_out\" ? 423 : 401,\n\t\t\t\t\tresult.error,\n\t\t\t\t\tresult.error === \"locked_out\"\n\t\t\t\t\t\t? \"too many failed attempts, try again later\"\n\t\t\t\t\t\t: \"login or password is not correct\",\n\t\t\t\t);\n\t\t\t}\n\t\t\t// the token is returned for integration clients (bearer) and set as an httpOnly cookie for the\n\t\t\t// browser; the CSRF token stays readable for the page on purpose\n\t\t\treturn json(\n\t\t\t\t200,\n\t\t\t\t{\n\t\t\t\t\ttoken: result.token,\n\t\t\t\t\tcsrfToken: result.csrfToken,\n\t\t\t\t\texpiresAt: result.expiresAt,\n\t\t\t\t\tuser: result.user,\n\t\t\t\t},\n\t\t\t\t{ \"set-cookie\": sessionCookie(context, result.token, result.expiresAt) },\n\t\t\t);\n\t\t},\n\t);\n\n\troute(\"POST\", \"/auth/logout\", { csrf: true }, context => {\n\t\tauth.logout({\n\t\t\ttoken: context.sessionToken,\n\t\t\tactorId: context.auth?.user.id ?? null,\n\t\t\tip: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\t// the browser session ends with the cookie, not only with the session row\n\t\treturn noContent(204, { \"set-cookie\": removeSessionCookie(context) });\n\t});\n\n\troute(\"GET\", \"/auth/me\", {}, context =>\n\t\tjson(200, {\n\t\t\tuser: context.auth?.user,\n\t\t\tpermissions: context.auth?.permissions ?? [],\n\t\t\texpiresAt: context.auth?.expiresAt,\n\t\t\t// a client that only holds the cookie has no other way to learn the CSRF token of its session\n\t\t\tcsrfToken: context.auth ? auth.csrfToken(context.sessionToken) : undefined,\n\t\t}),\n\t);\n\n\troute(\"POST\", \"/auth/password\", { csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst password = requireString(context.jsonBody(), \"password\");\n\n\t\tauth.setPassword({ userId: context.auth.user.id, password, actorId: context.auth.user.id, now: now() });\n\t\t// a password change ends all running sessions, including this one\n\t\tauth.revokeSessions({ userId: context.auth.user.id, actorId: context.auth.user.id, now: now() });\n\t\treturn noContent(204, { \"set-cookie\": removeSessionCookie(context) });\n\t});\n\n\t// A session is renewed on every authenticated request (sliding renewal), so this route reports the current\n\t// expiry together with a fresh CSRF token; integration clients use it instead of repeating their login.\n\troute(\"POST\", \"/auth/refresh\", { csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst token = context.sessionToken;\n\t\treturn json(200, {\n\t\t\texpiresAt: context.auth.expiresAt,\n\t\t\tcsrfToken: auth.csrfToken(token),\n\t\t\tuser: context.auth.user,\n\t\t\tpermissions: context.auth.permissions,\n\t\t});\n\t});\n\n\t// punching\n\n\t/**\n\t * Resolves the employee a read request refers to from `?userId=`.\n\t *\n\t * @param context - route context with the authenticated caller\n\t * @param ownPermission - permission needed for the own account\n\t * @param otherPermission - permission needed for another employee\n\t * @returns the target user id\n\t */\n\tconst scopeUser = (\n\t\tcontext: RouteContext,\n\t\townPermission = \"report.view_own\",\n\t\totherPermission = \"report.view_other\",\n\t): number => {\n\t\tconst raw = context.query(\"userId\");\n\t\treturn resolveScope(context, raw ? Number(raw) : null, ownPermission, otherPermission);\n\t};\n\n\t/**\n\t * Resolves the employee a day note belongs to from `?userId=`.\n\t *\n\t * A note for somebody else is an administrative step and needs `time.edit_other`; without that right only the\n\t * own day can be commented. The target has to exist, like everywhere else.\n\t *\n\t * @param context - route context with the authenticated caller\n\t * @param actorId - id of the caller\n\t * @returns the target user id\n\t */\n\tconst dayNoteTarget = (context: RouteContext, actorId: number): number => {\n\t\tconst raw = context.query(\"userId\");\n\t\tconst target = raw === null || raw.trim() === \"\" ? actorId : Number(raw);\n\t\tif (!Number.isInteger(target)) {\n\t\t\tthrow new ValidationError(\"userId must be a whole number\");\n\t\t}\n\t\tif (target !== actorId && !(context.auth?.permissions.includes(\"time.edit_other\") ?? false)) {\n\t\t\tthrow problem(403, \"permission_denied\", \"request rejected (permission_denied: time.edit_other)\");\n\t\t}\n\t\tif (!users.findById(target)) {\n\t\t\tthrow new NotFoundError(`user ${target} not found`);\n\t\t}\n\t\treturn target;\n\t};\n\n\t/**\n\t * Reads a required whole-number query parameter.\n\t *\n\t * @param context - route context\n\t * @param name - parameter name\n\t * @returns the parsed number\n\t */\n\tconst numberQuery = (context: RouteContext, name: string): number => {\n\t\tconst raw = context.query(name);\n\t\tconst value = Number(raw);\n\t\t// a missing parameter is `null`, which would silently become `0`\n\t\tif (raw === null || raw.trim() === \"\" || !Number.isInteger(value)) {\n\t\t\tthrow new ValidationError(`${name} is required and must be a whole number`);\n\t\t}\n\t\treturn value;\n\t};\n\n\t/**\n\t * Stores a punch of the caller and refreshes the day aggregate.\n\t *\n\t * @param context - route context with the authenticated caller\n\t * @param options - options of the punch\n\t * @param options.quick - true rounds the instant to the configured quicktime step\n\t * @returns response with the stored entry and the refreshed day\n\t */\n\tconst punch = (\n\t\tcontext: RouteContext,\n\t\toptions: {\n\t\t\t/** True for a quick punch: the instant is rounded to the configured step */\n\t\t\tquick: boolean;\n\t\t},\n\t): RouteResponse => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst body = context.jsonBody();\n\t\tconst timestamp = now();\n\t\tconst user = users.findById(context.auth.user.id);\n\t\tif (!user) {\n\t\t\tthrow new NotFoundError(`user ${context.auth.user.id} not found`);\n\t\t}\n\n\t\tconst requestedTs = optionalNumber(body, \"tsUtc\") ?? timestamp;\n\t\tconst quickRoundMinutes = options.quick ? settings.getNumber(\"quick_round_minutes\", 0) : 0;\n\t\tconst tsUtc = quickRoundMinutes > 0 ? roundToStep(requestedTs, quickRoundMinutes, user.timezone) : requestedTs;\n\n\t\tconst stored = entries.insert({\n\t\t\tuserId: user.id,\n\t\t\ttsUtc,\n\t\t\tclientTsUtc: optionalNumber(body, \"clientTsUtc\"),\n\t\t\ttimeZone: user.timezone,\n\t\t\tsource: \"web\",\n\t\t\tdirection: optionalDirection(body),\n\t\t\tidempotencyKey: optionalString(body, \"idempotencyKey\"),\n\t\t\tnote: optionalString(body, \"note\"),\n\t\t\tactorId: user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: timestamp,\n\t\t});\n\n\t\tconst day = aggregation.recalculateDay(user.id, stored.entry.localDate, { now: timestamp });\n\t\temit({\n\t\t\ttype: \"punch\",\n\t\t\tuserId: user.id,\n\t\t\tdata: { entryId: stored.entry.id, created: stored.created, localDate: stored.entry.localDate },\n\t\t});\n\t\treturn json(\n\t\t\t201,\n\t\t\t{\n\t\t\t\tentry: stored.entry,\n\t\t\t\tcreated: stored.created,\n\t\t\t\tday,\n\t\t\t\t...(quickRoundMinutes > 0\n\t\t\t\t\t? { rounded: { from: requestedTs, to: tsUtc, roundMinutes: quickRoundMinutes } }\n\t\t\t\t\t: {}),\n\t\t\t},\n\t\t\t{ location: `/entries/${stored.entry.id}` },\n\t\t);\n\t};\n\n\troute(\n\t\t\"POST\",\n\t\t\"/punch\",\n\t\t{ permission: \"time.punch\", csrf: true, rateLimit: { name: \"punch\", limit: 60, windowSeconds: 60 } },\n\t\tcontext => punch(context, { quick: false }),\n\t);\n\n\troute(\n\t\t\"POST\",\n\t\t\"/punch/quick\",\n\t\t{ permission: \"time.punch\", csrf: true, rateLimit: { name: \"punch\", limit: 60, windowSeconds: 60 } },\n\t\tcontext => punch(context, { quick: true }),\n\t);\n\n\troute(\"GET\", \"/punch/status\", { permission: \"report.view_own\" }, context => {\n\t\tconst userId = scopeUser(context);\n\t\tconst user = users.findById(userId);\n\t\tif (!user) {\n\t\t\tthrow new NotFoundError(`user ${userId} not found`);\n\t\t}\n\n\t\tconst date = localDate(now(), user.timezone);\n\t\tconst day = aggregation.recalculateDay(userId, date, { now: now() });\n\t\tconst todayEntries = entries.listByRange(userId, date, date);\n\t\treturn json(200, {\n\t\t\tdate,\n\t\t\tday,\n\t\t\thasOpenEntry: day.hasOpenEntry,\n\t\t\tlastEntry: todayEntries.length > 0 ? todayEntries[todayEntries.length - 1] : null,\n\t\t\t// what a punch would do right now, so a client does not have to derive it\n\t\t\tnextDirection: day.hasOpenEntry ? \"out\" : \"in\",\n\t\t});\n\t});\n\n\t// offline synchronisation\n\n\troute(\n\t\t\"POST\",\n\t\t\"/entries/sync\",\n\t\t{ permission: \"time.punch\", csrf: true, rateLimit: { name: \"sync\", limit: 30, windowSeconds: 60 } },\n\t\tcontext => {\n\t\t\tif (!context.auth) {\n\t\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t\t}\n\t\t\tconst body = context.jsonBody();\n\t\t\tconst user = users.findById(context.auth.user.id);\n\t\t\tif (!user) {\n\t\t\t\tthrow new NotFoundError(`user ${context.auth.user.id} not found`);\n\t\t\t}\n\t\t\tif (!Array.isArray(body.punches)) {\n\t\t\t\tthrow new ValidationError(\"punches must be an array\");\n\t\t\t}\n\n\t\t\tconst result = sync.sync({\n\t\t\t\tuserId: user.id,\n\t\t\t\ttimeZone: user.timezone,\n\t\t\t\t// the offline queue is the only way an employee still writes a punch of his own, so the window of the\n\t\t\t\t// instance applies to it as well (`0` switches the bound off)\n\t\t\t\tmaxPastSeconds: Math.max(0, settings.getNumber(\"edit_window_days\", 0)) * 86_400,\n\t\t\t\tpunches: (body.punches as unknown[]).map(raw => {\n\t\t\t\t\tconst punch = (raw ?? {}) as Record<string, unknown>;\n\t\t\t\t\tconst tsUtc = optionalNumber(punch, \"tsUtc\");\n\t\t\t\t\tif (tsUtc === null) {\n\t\t\t\t\t\tthrow new ValidationError(\"tsUtc is required for every queued punch\");\n\t\t\t\t\t}\n\t\t\t\t\treturn {\n\t\t\t\t\t\tidempotencyKey: typeof punch.idempotencyKey === \"string\" ? punch.idempotencyKey : \"\",\n\t\t\t\t\t\ttsUtc,\n\t\t\t\t\t\tclientTsUtc: optionalNumber(punch, \"clientTsUtc\"),\n\t\t\t\t\t\tdirection: optionalDirection(punch),\n\t\t\t\t\t\tnote: optionalString(punch, \"note\"),\n\t\t\t\t\t};\n\t\t\t\t}),\n\t\t\t\tactorId: user.id,\n\t\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\t\tnow: now(),\n\t\t\t});\n\t\t\t// the batch belongs to one employee: their day changed, so their clients refresh it\n\t\t\temit({\n\t\t\t\ttype: \"punch\",\n\t\t\t\tuserId: user.id,\n\t\t\t\tdata: {\n\t\t\t\t\tsynced: result.accepted.filter(entry => entry.created).length,\n\t\t\t\t\tduplicates: result.accepted.filter(entry => !entry.created).length,\n\t\t\t\t\tconflicts: result.conflicts.length,\n\t\t\t\t\trejected: result.rejected.length,\n\t\t\t\t\tdays: result.recalculated.length,\n\t\t\t\t},\n\t\t\t});\n\t\t\treturn json(200, result);\n\t\t},\n\t);\n\n\troute(\"GET\", \"/entries/conflicts\", { permission: \"time.resolve_conflict\" }, context =>\n\t\t// without `?userId=` the own queue; the administration reads the queue of an employee with it, because a\n\t\t// conflict of an employee waits for a decision of the office\n\t\tjson(200, {\n\t\t\tconflicts: sync.conflicts(scopeUser(context, \"time.resolve_conflict\", \"time.resolve_conflict\")),\n\t\t}),\n\t);\n\n\troute(\"POST\", \"/entries/:id/resolve\", { permission: \"time.resolve_conflict\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst body = context.jsonBody();\n\t\tconst action = requireString(body, \"action\");\n\t\tif (action !== \"accept\" && action !== \"dismiss\") {\n\t\t\tthrow new ValidationError(`action must be accept or dismiss (got ${action})`);\n\t\t}\n\n\t\tconst result = sync.resolve({\n\t\t\tentryId: numberParam(context, \"id\"),\n\t\t\taction,\n\t\t\ttsUtc: optionalNumber(body, \"tsUtc\") ?? undefined,\n\t\t\treason: optionalString(body, \"reason\"),\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\tif (result.entry) {\n\t\t\temit({\n\t\t\t\ttype: \"entry.update\",\n\t\t\t\tuserId: result.entry.userId,\n\t\t\t\tdata: { entryId: result.entry.id, localDate: result.entry.localDate, resolved: action },\n\t\t\t});\n\t\t}\n\t\treturn json(200, result);\n\t});\n\n\t// aggregates and reports\n\n\troute(\"GET\", \"/aggregates/day\", { permission: \"report.view_own\" }, context => {\n\t\tconst userId = scopeUser(context);\n\t\tconst from = context.query(\"from\");\n\t\tconst to = context.query(\"to\") ?? from;\n\n\t\t// a range lists the single days (used by the calendar of the web app)\n\t\tif (from && to) {\n\t\t\tconst range = aggregation.recalculateRange(userId, from, to, { now: now() });\n\t\t\treturn json(200, { from, to, ...range, days: aggregation.days(userId, from, to) });\n\t\t}\n\n\t\tconst user = users.findById(userId);\n\t\tif (!user) {\n\t\t\tthrow new NotFoundError(`user ${userId} not found`);\n\t\t}\n\t\tconst date = context.query(\"date\") ?? localDate(now(), user.timezone);\n\t\tif (!/^\\d{4}-\\d{2}-\\d{2}$/.test(date)) {\n\t\t\tthrow new ValidationError(`date must be YYYY-MM-DD (got ${date})`);\n\t\t}\n\t\treturn json(200, { day: aggregation.recalculateDay(userId, date, { now: now() }) });\n\t});\n\n\troute(\"GET\", \"/aggregates/month\", { permission: \"report.view_own\" }, context => {\n\t\tconst userId = scopeUser(context);\n\t\tconst year = numberQuery(context, \"year\");\n\n\t\tconst requestedMonth = context.query(\"month\");\n\t\tif (!requestedMonth) {\n\t\t\t// the whole year: one recalculation pass, then the twelve monthly rows\n\t\t\tconst totals = aggregation.recalculateYear(userId, year, { now: now() });\n\t\t\tconst months = Array.from({ length: 12 }, (_, index) => aggregation.month(userId, year, index + 1));\n\t\t\treturn json(200, { year: totals, months });\n\t\t}\n\n\t\tconst month = Number(requestedMonth);\n\t\tif (!Number.isInteger(month) || month < 1 || month > 12) {\n\t\t\tthrow new ValidationError(`month must be between 1 and 12 (got ${requestedMonth})`);\n\t\t}\n\t\tconst monthly = aggregation.recalculateMonth(userId, year, month, { now: now() });\n\t\tconst yearly = aggregation.recalculateYear(userId, year, { now: now() });\n\t\treturn json(200, { month: monthly, year: yearly });\n\t});\n\n\troute(\"GET\", \"/aggregates/year\", { permission: \"report.view_own\" }, context => {\n\t\tconst userId = scopeUser(context);\n\t\tconst year = numberQuery(context, \"year\");\n\t\treturn json(200, { year: aggregation.recalculateYear(userId, year, { now: now() }) });\n\t});\n\n\troute(\"GET\", \"/entries\", { permission: \"report.view_own\" }, context => {\n\t\tconst userId = scopeUser(context);\n\t\tconst from = context.query(\"from\");\n\t\tconst to = context.query(\"to\") ?? from;\n\t\tif (!from || !to) {\n\t\t\tthrow new ValidationError(\"from and to are required\");\n\t\t}\n\t\treturn json(200, { entries: entries.listByRange(userId, from, to) });\n\t});\n\n\t// A punch written by hand is an administrative correction: an employee does not edit his own times, he leaves a\n\t// day note for the administration (`time.edit_own` is that right) and the administration books the day.\n\troute(\"POST\", \"/entries\", { permission: \"time.edit_other\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst body = context.jsonBody();\n\t\tconst target = Number(context.query(\"userId\") ?? context.auth.user.id);\n\t\tif (!Number.isInteger(target)) {\n\t\t\tthrow new ValidationError(\"userId must be a whole number\");\n\t\t}\n\n\t\tconst user = users.findById(target);\n\t\tif (!user) {\n\t\t\tthrow new NotFoundError(`user ${target} not found`);\n\t\t}\n\t\tconst tsUtc = optionalNumber(body, \"tsUtc\");\n\t\tif (tsUtc === null) {\n\t\t\tthrow new ValidationError(\"tsUtc is required\");\n\t\t}\n\n\t\tconst timestamp = now();\n\t\tconst stored = entries.insert({\n\t\t\tuserId: target,\n\t\t\ttsUtc,\n\t\t\tclientTsUtc: optionalNumber(body, \"clientTsUtc\"),\n\t\t\ttimeZone: user.timezone,\n\t\t\t// a hand-written punch is always a correction; the normal punch comes through `POST /punch` (`web`)\n\t\t\tsource: \"admin\",\n\t\t\tdirection: optionalDirection(body),\n\t\t\tidempotencyKey: optionalString(body, \"idempotencyKey\"),\n\t\t\tnote: optionalString(body, \"note\"),\n\t\t\treason: optionalString(body, \"reason\"),\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: timestamp,\n\t\t});\n\n\t\tconst day = aggregation.recalculateDay(target, stored.entry.localDate, { now: timestamp });\n\t\temit({\n\t\t\ttype: \"punch\",\n\t\t\tuserId: target,\n\t\t\tdata: {\n\t\t\t\tentryId: stored.entry.id,\n\t\t\t\tcreated: stored.created,\n\t\t\t\tlocalDate: stored.entry.localDate,\n\t\t\t\tsource: \"admin\",\n\t\t\t},\n\t\t});\n\t\treturn json(\n\t\t\t201,\n\t\t\t{ entry: stored.entry, created: stored.created, day },\n\t\t\t{ location: `/entries/${stored.entry.id}` },\n\t\t);\n\t});\n\n\t// absences\n\n\t/**\n\t * The fields of an employee the calendar needs.\n\t *\n\t * @param user - the employee\n\t * @returns the employee as the calendar service expects it\n\t */\n\tconst calendarEmployee = (user: UserRecord): CalendarEmployee => ({\n\t\tid: user.id,\n\t\tdisplayName: user.displayName,\n\t\tlogin: user.login,\n\t});\n\n\t/**\n\t * Adds the code and the name of the type to an absence.\n\t *\n\t * The stored record only knows the id of its type, while a client shows the code (specification 4: `Absence`).\n\t *\n\t * @param absence - stored absence\n\t * @returns the absence as the API hands it out\n\t */\n\tconst publicAbsence = (\n\t\tabsence: AbsenceRecord,\n\t): AbsenceRecord & {\n\t\t/** Code of the type, e.g. `vacation` */\n\t\ttypeCode: string | null;\n\t\t/** Name of the type as the client shows it */\n\t\ttypeName: string | null;\n\t} => {\n\t\tconst type = absences.findType(absence.typeId);\n\t\treturn { ...absence, typeCode: type?.code ?? null, typeName: type?.name ?? null };\n\t};\n\n\troute(\"GET\", \"/absences\", { permission: \"report.view_own\" }, context => {\n\t\t// `scope=all` is the overview of the administration: every employee in one answer, so the tab needs a single\n\t\t// request. The open requests and \u201Cwho is away\u201D both read from here. A path segment would clash with\n\t\t// `/absences/:id`, which the router matches first \u2014 hence the query parameter.\n\t\tif (context.query(\"scope\") === \"all\") {\n\t\t\tif (!context.auth?.permissions.includes(\"absence.approve\")) {\n\t\t\t\tthrow problem(403, \"permission_denied\", \"request rejected (permission_denied: absence.approve)\");\n\t\t\t}\n\t\t\tconst from = context.query(\"from\");\n\t\t\tconst to = context.query(\"to\");\n\t\t\tif (!from || !to) {\n\t\t\t\tthrow new ValidationError(\"from and to are required with scope=all\");\n\t\t\t}\n\t\t\treturn json(200, { absences: absences.allInRange(from, to).map(publicAbsence) });\n\t\t}\n\t\tconst requested = context.query(\"userId\") ? Number(context.query(\"userId\")) : null;\n\t\tconst userId = resolveScope(context, requested, \"report.view_own\", \"report.view_other\");\n\t\tconst year = context.query(\"year\") ? Number(context.query(\"year\")) : undefined;\n\t\treturn json(200, {\n\t\t\tabsences: absences.listByUser(userId, year === undefined ? {} : { year }).map(publicAbsence),\n\t\t});\n\t});\n\n\troute(\"POST\", \"/absences\", { permission: \"absence.request\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst body = context.jsonBody();\n\t\tconst userId = resolveScope(context, optionalNumber(body, \"userId\"), \"absence.request\", \"absence.edit_other\");\n\n\t\tconst status = optionalString(body, \"status\");\n\t\tif (status !== null && status !== \"taken\" && status !== \"planned\") {\n\t\t\tthrow new ValidationError(`status must be taken or planned (got ${status})`);\n\t\t}\n\t\t// The administration books dates straight away, an employee only requests them: who may change an absence\n\t\t// of somebody else decides about it, everybody else waits for that decision.\n\t\tconst mayDecide = context.auth.permissions.includes(\"absence.edit_other\");\n\t\tconst approval: AbsenceApproval = mayDecide ? \"approved\" : \"requested\";\n\n\t\t// An employee may only pick a type the administration released for everybody: a sickness note, an accident or\n\t\t// military service are things the company books itself, nobody requests them.\n\t\tif (!mayDecide) {\n\t\t\tconst wanted = absences.findType(\n\t\t\t\toptionalNumber(body, \"typeId\") ?? optionalString(body, \"typeCode\") ?? \"\",\n\t\t\t\tuserId,\n\t\t\t);\n\t\t\tif (!wanted) {\n\t\t\t\tthrow new ValidationError(\"unknown absence type\");\n\t\t\t}\n\t\t\tif (!wanted.isActive) {\n\t\t\t\tthrow problem(\n\t\t\t\t\t403,\n\t\t\t\t\t\"permission_denied\",\n\t\t\t\t\t\"request rejected (permission_denied: absence type is not public)\",\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tconst created = absences.create({\n\t\t\tuserId,\n\t\t\ttypeCode: optionalString(body, \"typeCode\") ?? undefined,\n\t\t\ttypeId: optionalNumber(body, \"typeId\") ?? undefined,\n\t\t\tdateFrom: requireString(body, \"dateFrom\"),\n\t\t\tdateTo: optionalString(body, \"dateTo\") ?? undefined,\n\t\t\tdayPortion: optionalNumber(body, \"dayPortion\") ?? undefined,\n\t\t\thours: optionalNumber(body, \"hours\"),\n\t\t\tstatus: status ?? undefined,\n\t\t\tapproval,\n\t\t\tnote: optionalString(body, \"note\"),\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\temit({ type: \"absence.change\", userId, data: { absenceId: created.id, action: \"created\" } });\n\t\treturn json(201, { absence: publicAbsence(created) }, { location: `/absences/${created.id}` });\n\t});\n\n\troute(\"POST\", \"/absences/:id/status\", { permission: \"absence.approve\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst status = requireString(context.jsonBody(), \"status\");\n\t\tif (status !== \"taken\" && status !== \"planned\") {\n\t\t\tthrow new ValidationError(`status must be taken or planned (got ${status})`);\n\t\t}\n\n\t\tconst updated = absences.setStatus({\n\t\t\tid: numberParam(context, \"id\"),\n\t\t\tstatus,\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\temit({ type: \"absence.change\", userId: updated.userId, data: { absenceId: updated.id, action: \"status\" } });\n\t\treturn json(200, { absence: publicAbsence(updated) });\n\t});\n\n\t// The other decision: the administration approves or rejects what an employee asked for. The reason travels\n\t// with the decision, so the employee sees why a request came back.\n\troute(\"POST\", \"/absences/:id/approval\", { permission: \"absence.approve\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst body = context.jsonBody();\n\t\tconst approval = requireString(body, \"approval\");\n\t\tif (approval !== \"requested\" && approval !== \"approved\" && approval !== \"rejected\") {\n\t\t\tthrow new ValidationError(`approval must be requested, approved or rejected (got ${approval})`);\n\t\t}\n\n\t\tconst updated = absences.setApproval({\n\t\t\tid: numberParam(context, \"id\"),\n\t\t\tapproval,\n\t\t\tnote: optionalString(body, \"note\"),\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\temit({ type: \"absence.change\", userId: updated.userId, data: { absenceId: updated.id, action: \"approval\" } });\n\t\treturn json(200, { absence: publicAbsence(updated) });\n\t});\n\n\t/**\n\t * Loads an absence and checks whether the caller may change it.\n\t *\n\t * @param context - route context\n\t * @returns the absence\n\t */\n\tconst changeableAbsence = (context: RouteContext): AbsenceRecord => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst absence = absences.findById(numberParam(context, \"id\"));\n\t\tif (!absence) {\n\t\t\tthrow new NotFoundError(`absence ${context.params.id} not found`);\n\t\t}\n\t\tif (absence.userId !== context.auth.user.id && !context.auth.permissions.includes(\"absence.edit_other\")) {\n\t\t\tthrow problem(403, \"permission_denied\", \"request rejected (permission_denied: absence.edit_other)\");\n\t\t}\n\t\treturn absence;\n\t};\n\n\troute(\"PATCH\", \"/absences/:id\", { permission: \"absence.request\", csrf: true }, context => {\n\t\tconst absence = changeableAbsence(context);\n\t\tconst body = context.jsonBody();\n\t\tconst status = optionalString(body, \"status\");\n\n\t\tif (status !== null && status !== \"taken\" && status !== \"planned\") {\n\t\t\tthrow new ValidationError(`status must be taken or planned (got ${status})`);\n\t\t}\n\t\t// changing the state of an absence is an approval, even for the own one\n\t\tif (status !== null && !context.auth?.permissions.includes(\"absence.approve\")) {\n\t\t\tthrow problem(403, \"permission_denied\", \"request rejected (permission_denied: absence.approve)\");\n\t\t}\n\n\t\tconst patch = {\n\t\t\ttypeId: optionalNumber(body, \"typeId\") ?? undefined,\n\t\t\ttypeCode: optionalString(body, \"typeCode\") ?? undefined,\n\t\t\tdateFrom: optionalString(body, \"dateFrom\") ?? undefined,\n\t\t\tdateTo: optionalString(body, \"dateTo\") ?? undefined,\n\t\t\tdayPortion: optionalNumber(body, \"dayPortion\") ?? undefined,\n\t\t\t...(Object.prototype.hasOwnProperty.call(body, \"hours\") ? { hours: optionalNumber(body, \"hours\") } : {}),\n\t\t\t...(Object.prototype.hasOwnProperty.call(body, \"note\") ? { note: optionalString(body, \"note\") } : {}),\n\t\t};\n\t\tconst hasFields = Object.values(patch).some(value => value !== undefined);\n\n\t\t// The same guard as in POST /absences: an employee may not move an absence to a type that is not public.\n\t\tif (\n\t\t\thasFields &&\n\t\t\t!context.auth?.permissions.includes(\"absence.edit_other\") &&\n\t\t\t(patch.typeId !== undefined || patch.typeCode !== undefined)\n\t\t) {\n\t\t\tconst wanted = absences.findType(patch.typeId ?? patch.typeCode ?? \"\", absence.userId);\n\t\t\tif (!wanted) {\n\t\t\t\tthrow new ValidationError(\"unknown absence type\");\n\t\t\t}\n\t\t\tif (!wanted.isActive) {\n\t\t\t\tthrow problem(\n\t\t\t\t\t403,\n\t\t\t\t\t\"permission_denied\",\n\t\t\t\t\t\"request rejected (permission_denied: absence type is not public)\",\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (status !== null) {\n\t\t\tabsences.setStatus({\n\t\t\t\tid: absence.id,\n\t\t\t\tstatus,\n\t\t\t\tactorId: context.auth?.user.id ?? 0,\n\t\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\t\tnow: now(),\n\t\t\t});\n\t\t}\n\n\t\tconst changed = hasFields\n\t\t\t? absences.update({\n\t\t\t\t\tid: absence.id,\n\t\t\t\t\tpatch,\n\t\t\t\t\treason: optionalString(body, \"reason\"),\n\t\t\t\t\tactorId: context.auth?.user.id ?? 0,\n\t\t\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\t\t\tnow: now(),\n\t\t\t\t})\n\t\t\t: (absences.findById(absence.id) ?? absence);\n\n\t\t// An employee who changes a request asks again: the decision of the administration belonged to the old\n\t\t// dates. Who may change absences of somebody else decides as well, so nothing goes back for them.\n\t\tconst updated =\n\t\t\thasFields && !context.auth?.permissions.includes(\"absence.edit_other\") && changed.approval !== \"requested\"\n\t\t\t\t? absences.setApproval({\n\t\t\t\t\t\tid: changed.id,\n\t\t\t\t\t\tapproval: \"requested\",\n\t\t\t\t\t\tnote: null,\n\t\t\t\t\t\tactorId: context.auth?.user.id ?? 0,\n\t\t\t\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\t\t\t\tnow: now(),\n\t\t\t\t\t})\n\t\t\t\t: changed;\n\n\t\temit({ type: \"absence.change\", userId: updated.userId, data: { absenceId: updated.id, action: \"updated\" } });\n\t\treturn json(200, { absence: publicAbsence(updated) });\n\t});\n\n\troute(\"DELETE\", \"/absences/:id\", { permission: \"absence.request\", csrf: true }, context => {\n\t\tconst absence = changeableAbsence(context);\n\t\tconst removed = absences.remove({\n\t\t\tid: absence.id,\n\t\t\tactorId: context.auth?.user.id ?? 0,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\tif (!removed) {\n\t\t\tthrow new NotFoundError(`absence ${absence.id} not found`);\n\t\t}\n\t\temit({ type: \"absence.change\", userId: absence.userId, data: { absenceId: absence.id, action: \"deleted\" } });\n\t\treturn noContent();\n\t});\n\n\t// A cancellation of an approved absence. The employee asks for it; the days keep counting until the\n\t// administration answers \u2014 deleting the absence is the \u201Cyes\u201D, declining the request leaves it as it is. An\n\t// employee never cancels around that decision, because deleting an absence of somebody else needs\n\t// `absence.edit_other` (`changeableAbsence`).\n\troute(\"POST\", \"/absences/:id/cancellation\", { permission: \"absence.request\", csrf: true }, context => {\n\t\tconst absence = changeableAbsence(context);\n\t\tconst body = context.optionalJsonBody();\n\t\tconst updated = absences.requestCancel({\n\t\t\tid: absence.id,\n\t\t\tnote: optionalString(body, \"note\"),\n\t\t\tactorId: context.auth?.user.id ?? 0,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\temit({\n\t\t\ttype: \"absence.change\",\n\t\t\tuserId: updated.userId,\n\t\t\tdata: { absenceId: updated.id, action: \"cancellation\" },\n\t\t});\n\t\treturn json(200, { absence: publicAbsence(updated) });\n\t});\n\n\t// The request goes back: the employee withdraws it, the administration declines it (`\u2026/decline`). Both end in\n\t// the same state, only the log tells them apart.\n\troute(\"DELETE\", \"/absences/:id/cancellation\", { permission: \"absence.request\", csrf: true }, context => {\n\t\tconst absence = changeableAbsence(context);\n\t\tconst updated = absences.clearCancel({\n\t\t\tid: absence.id,\n\t\t\tactorId: context.auth?.user.id ?? 0,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\temit({\n\t\t\ttype: \"absence.change\",\n\t\t\tuserId: updated.userId,\n\t\t\tdata: { absenceId: updated.id, action: \"cancellationWithdrawn\" },\n\t\t});\n\t\treturn json(200, { absence: publicAbsence(updated) });\n\t});\n\n\troute(\"POST\", \"/absences/:id/cancellation/decline\", { permission: \"absence.approve\", csrf: true }, context => {\n\t\tconst id = numberParam(context, \"id\");\n\t\tconst absence = absences.findById(id);\n\t\tif (!absence) {\n\t\t\tthrow new NotFoundError(`absence ${id} not found`);\n\t\t}\n\t\tconst body = context.optionalJsonBody();\n\t\tconst updated = absences.clearCancel({\n\t\t\tid,\n\t\t\tdeclined: true,\n\t\t\tnote: optionalString(body, \"note\"),\n\t\t\tactorId: context.auth?.user.id ?? 0,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\temit({\n\t\t\ttype: \"absence.change\",\n\t\t\tuserId: updated.userId,\n\t\t\tdata: { absenceId: updated.id, action: \"cancellationDeclined\" },\n\t\t});\n\t\treturn json(200, { absence: publicAbsence(updated) });\n\t});\n\n\t// master data: absence types, holidays and instance settings\n\n\t// The administration sees the types that are switched off as well \u2014 that is what \u201Cactive\u201D is for: an inactive type\n\t// stays out of the picker of the employees, while the administration can still book it for somebody.\n\t// The calendar feed: a public route with the secret in the URL, so a phone or a calendar app can subscribe without\n\t// a session. Two tokens are accepted: the personal one of an employee (nobody else sees anything through such a\n\t// link) and the token of the instance, which carries the absences of the whole company \u2014 that is the feed ioBroker\n\t// and a company calendar subscribe to.\n\troute(\n\t\t\"GET\",\n\t\t\"/calendar.ics\",\n\t\t{ public: true, csrf: false, rateLimit: { name: \"calendar\", limit: 30, windowSeconds: 60 } },\n\t\tcontext => {\n\t\t\tconst token = (context.query(\"token\") ?? \"\").trim();\n\t\t\tconst instanceToken = settings.get(\"calendar_token\");\n\t\t\tconst company = instanceToken !== null && instanceToken !== \"\" && token === instanceToken;\n\n\t\t\t// without the instance token the link belongs to one employee\n\t\t\tconst user = company || token === \"\" ? null : users.findByCalendarToken(token);\n\t\t\tif (!company && (!user || !user.isActive)) {\n\t\t\t\tthrow problem(401, \"invalid_credentials\", \"the calendar token is not valid\");\n\t\t\t}\n\n\t\t\t// a window around today keeps a feed small: a year back and to the end of next year\n\t\t\tconst stamp = now();\n\t\t\tconst year = new Date(stamp * 1000).getUTCFullYear();\n\t\t\tconst from = `${year - 1}-01-01`;\n\t\t\tconst to = `${year + 1}-12-31`;\n\n\t\t\t// the company feed skips deactivated accounts (`users.list()` returns the active ones)\n\t\t\tconst owners = user ? [user] : users.list();\n\t\t\tconst employees = new Map(owners.map(employee => [employee.id, calendarEmployee(employee)]));\n\t\t\tconst list = user ? absences.withTypesInRange(user.id, from, to) : absences.allInRange(from, to);\n\t\t\tconst name = user ? `Abwesenheiten ${user.displayName}` : COMPANY_CALENDAR_NAME;\n\n\t\t\treturn binary(\n\t\t\t\t200,\n\t\t\t\tBuffer.from(calendarDocument(name, absenceEvents(list, employees, !user), stamp), \"utf8\"),\n\t\t\t\tICAL_CONTENT_TYPE,\n\t\t\t\t{\n\t\t\t\t\t\"content-disposition\": 'inline; filename=\"time-tracker.ics\"',\n\t\t\t\t\t\"cache-control\": \"no-store\",\n\t\t\t\t},\n\t\t\t);\n\t\t},\n\t);\n\n\t// The subscription link of the own calendar: the token is created on the first call and can be rotated, which\n\t// invalidates the old link. The administration may ask for the token of somebody else.\n\troute(\"POST\", \"/calendar/token\", { permission: \"report.view_own\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst body = context.jsonBody();\n\t\tconst userId = resolveScope(context, optionalNumber(body, \"userId\"), \"report.view_own\", \"absence.edit_other\");\n\t\tconst current = users.findById(userId);\n\t\tif (!current) {\n\t\t\tthrow new NotFoundError(`user ${userId} not found`);\n\t\t}\n\n\t\tconst rotate = optionalBoolean(body, \"rotate\") ?? false;\n\t\tconst token =\n\t\t\trotate || current.calendarToken === null ? randomBytes(32).toString(\"base64url\") : current.calendarToken;\n\t\tconst updated = users.setCalendarToken({\n\t\t\tuserId,\n\t\t\ttoken,\n\t\t\tactorId: context.auth.user.id,\n\t\t\tnow: now(),\n\t\t});\n\t\treturn json(200, { token: updated.calendarToken });\n\t});\n\n\troute(\"GET\", \"/absence-types\", {}, context => {\n\t\tconst includeInactive =\n\t\t\tcontext.query(\"includeInactive\") === \"true\" &&\n\t\t\t(context.auth?.permissions.includes(\"absence.manage_types\") ?? false);\n\t\treturn json(200, {\n\t\t\ttypes: absences.types({ userId: context.auth?.user.id ?? null, includeInactive }),\n\t\t});\n\t});\n\n\troute(\"POST\", \"/absence-types\", { permission: \"absence.manage_types\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst body = context.jsonBody();\n\t\tconst result = absences.upsertType({\n\t\t\tuserId: optionalNumber(body, \"userId\"),\n\t\t\tcode: requireString(body, \"code\"),\n\t\t\tname: requireString(body, \"name\"),\n\t\t\tpaid: optionalBoolean(body, \"paid\") ?? undefined,\n\t\t\tfactor: optionalNumber(body, \"factor\") ?? undefined,\n\t\t\treduceVacation: optionalBoolean(body, \"reduceVacation\") ?? undefined,\n\t\t\tisActive: optionalBoolean(body, \"isActive\") ?? undefined,\n\t\t\tcolor: optionalString(body, \"color\"),\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\treturn json(result.created ? 201 : 200, { type: result.type, created: result.created });\n\t});\n\n\troute(\"DELETE\", \"/absence-types/:id\", { permission: \"absence.manage_types\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst removed = absences.removeType({\n\t\t\tid: numberParam(context, \"id\"),\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\tif (!removed) {\n\t\t\tthrow new NotFoundError(`absence type ${context.params.id} not found`);\n\t\t}\n\t\treturn noContent();\n\t});\n\n\troute(\"GET\", \"/holidays\", { permission: \"report.view_own\" }, context => {\n\t\tconst year = numberQuery(context, \"year\");\n\t\tconst region = context.query(\"region\") ?? undefined;\n\t\treturn json(200, { year, region: region ?? null, holidays: holidays.listByYear(year, region) });\n\t});\n\n\troute(\"POST\", \"/holidays\", { permission: \"holiday.manage\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst body = context.jsonBody();\n\t\tconst created = holidays.add({\n\t\t\tdate: requireString(body, \"date\"),\n\t\t\tname: requireString(body, \"name\"),\n\t\t\t// without a region the day belongs to the country of the instance: the calculation looks only there, so a\n\t\t\t// day filed under a foreign region would never count\n\t\t\tregion: optionalString(body, \"region\") ?? settings.get(\"holiday_country\") ?? undefined,\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\treturn json(201, { holiday: created }, { location: `/holidays/${created.id}` });\n\t});\n\n\troute(\"DELETE\", \"/holidays/:id\", { permission: \"holiday.manage\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst removed = holidays.remove({\n\t\t\tid: numberParam(context, \"id\"),\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\tif (!removed) {\n\t\t\tthrow new NotFoundError(`holiday ${context.params.id} not found`);\n\t\t}\n\t\treturn noContent();\n\t});\n\n\troute(\"GET\", \"/settings\", { permission: \"settings.view\" }, () =>\n\t\tjson(200, { settings: readableSettings(settings.all()) }),\n\t);\n\n\troute(\"PUT\", \"/settings\", { permission: \"settings.edit\", csrf: true }, context => {\n\t\tconst body = context.jsonBody();\n\t\tconst changes: Record<string, SettingValue> = {};\n\t\tfor (const [key, value] of Object.entries(body)) {\n\t\t\tif (!editableSettings().includes(key)) {\n\t\t\t\tthrow new ValidationError(`setting \"${key}\" cannot be changed through the API`);\n\t\t\t}\n\t\t\t// the branding pictures and the accent colour have rules of their own: a picture (data URL) and a hex\n\t\t\t// colour \u2014 and they are the only settings that a public route hands out\n\t\t\tif (key === \"brand_logo\" || key === \"brand_background\") {\n\t\t\t\tconst raw = typeof value === \"string\" ? value.trim() : \"\";\n\t\t\t\tif (raw !== \"\" && !parseAvatarDataUrl(raw, MAX_BRANDING_BYTES)) {\n\t\t\t\t\tthrow new ValidationError(\n\t\t\t\t\t\t`setting \"${key}\" must be a data URL of a png, jpeg, webp or gif of up to ${MAX_BRANDING_BYTES} bytes`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tchanges[key] = raw;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (key === \"brand_color\") {\n\t\t\t\tconst raw = typeof value === \"string\" ? value.trim() : \"\";\n\t\t\t\tif (raw !== \"\" && !/^#[0-9a-f]{3}([0-9a-f]{3})?$/i.test(raw)) {\n\t\t\t\t\tthrow new ValidationError('setting \"brand_color\" must be a hex colour like \"#1a2b3c\"');\n\t\t\t\t}\n\t\t\t\tchanges[key] = raw;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// the pause mode decides how the pause of a day is determined; the calculation tolerates an unknown\n\t\t\t// value, but the API does not hand one out in the first place\n\t\t\tif (key === \"pause_mode\") {\n\t\t\t\tif (value !== \"auto\" && value !== \"punched\" && value !== \"staffel\") {\n\t\t\t\t\tthrow new ValidationError('setting \"pause_mode\" must be \"auto\", \"punched\" or \"staffel\"');\n\t\t\t\t}\n\t\t\t\tchanges[key] = value;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (!isSettingValue(value, JSON_SETTINGS.includes(key))) {\n\t\t\t\tthrow new ValidationError(`setting \"${key}\" has an unsupported value`);\n\t\t\t}\n\t\t\tchanges[key] = value as SettingValue;\n\t\t}\n\t\tif (Object.keys(changes).length === 0) {\n\t\t\tthrow new ValidationError(\"no settings given\");\n\t\t}\n\n\t\tfor (const [key, value] of Object.entries(changes)) {\n\t\t\tsettings.set(key, value, context.auth?.user.id ?? null, now());\n\t\t}\n\t\treturn json(200, { settings: changes });\n\t});\n\n\t/**\n\t * Replaces the graduated break rules of one owner.\n\t *\n\t * The payload is the whole table: rules that are missing are removed, the rest is saved. Values are checked by\n\t * the repository, so an impossible rule becomes a client error.\n\t *\n\t * @param context - route context of the request\n\t * @param userId - owner of the rules, `null` for the company default\n\t * @returns the saved rules\n\t */\n\tfunction replacePauseRules(context: RouteContext, userId: number | null): PauseRuleRecord[] {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst body = context.jsonBody();\n\t\tif (!Array.isArray(body.pauseRules)) {\n\t\t\tthrow new ValidationError(\"pauseRules must be an array\");\n\t\t}\n\t\tconst actor = {\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t};\n\t\tconst wanted = (body.pauseRules as unknown[]).map(raw => {\n\t\t\tconst rule = (raw ?? {}) as Record<string, unknown>;\n\t\t\treturn {\n\t\t\t\tid: optionalNumber(rule, \"id\") ?? undefined,\n\t\t\t\tfromMin: Number(rule.fromMin),\n\t\t\t\ttoMin: optionalNumber(rule, \"toMin\"),\n\t\t\t\tpauseMin: Number(rule.pauseMin),\n\t\t\t\tisActive: optionalBoolean(rule, \"isActive\") ?? undefined,\n\t\t\t};\n\t\t});\n\n\t\tconst keep = new Set(wanted.map(rule => rule.id).filter((value): value is number => value !== undefined));\n\t\tfor (const existing of rules.listPauseRules({ userId, includeInactive: true })) {\n\t\t\tif (!keep.has(existing.id)) {\n\t\t\t\trules.removePauseRule({ id: existing.id, ...actor });\n\t\t\t}\n\t\t}\n\t\treturn wanted.map(rule =>\n\t\t\trules.savePauseRule({\n\t\t\t\t...(rule.id !== undefined ? { id: rule.id } : {}),\n\t\t\t\tuserId,\n\t\t\t\tfromMin: rule.fromMin,\n\t\t\t\ttoMin: rule.toMin,\n\t\t\t\tpauseMin: rule.pauseMin,\n\t\t\t\t...(rule.isActive !== undefined ? { isActive: rule.isActive } : {}),\n\t\t\t\t...actor,\n\t\t\t}),\n\t\t);\n\t}\n\n\t// graduated break rules of the instance (the company default): they decide the pause of a day on which nobody\n\t// punched a break, so they are edited as a whole table \u2014 like the shift rules of an employee.\n\n\troute(\"GET\", \"/pause-rules\", { permission: \"settings.view\" }, () =>\n\t\tjson(200, { pauseRules: rules.listPauseRules({ userId: null, includeInactive: true }) }),\n\t);\n\n\troute(\"PUT\", \"/pause-rules\", { permission: \"settings.edit\", csrf: true }, context =>\n\t\tjson(200, { pauseRules: replacePauseRules(context, null) }),\n\t);\n\n\t// The rules of one employee: they replace the company rule with the same `fromMin`, so a single person can\n\t// deviate from the house rule without changing it for everybody.\n\n\troute(\"GET\", \"/users/:id/pause-rules\", { permission: \"user.view\" }, context => {\n\t\tconst id = numberParam(context, \"id\");\n\t\tif (!users.findById(id)) {\n\t\t\tthrow new NotFoundError(`user ${id} not found`);\n\t\t}\n\t\treturn json(200, { pauseRules: rules.listPauseRules({ userId: id, includeInactive: true }) });\n\t});\n\n\troute(\"PUT\", \"/users/:id/pause-rules\", { permission: \"user.edit\", csrf: true }, context => {\n\t\tconst id = numberParam(context, \"id\");\n\t\tif (!users.findById(id)) {\n\t\t\tthrow new NotFoundError(`user ${id} not found`);\n\t\t}\n\t\treturn json(200, { pauseRules: replacePauseRules(context, id) });\n\t});\n\n\t/**\n\t * Replaces the trigger rules.\n\t *\n\t * Like the break rules the payload is the whole table: rules that are missing are removed, the rest is saved.\n\t * The values are checked by the repository, so an impossible rule becomes a client error. Afterwards the\n\t * adapter is told to watch the states of the new list.\n\t *\n\t * @param context - route context of the request\n\t * @returns the saved rules\n\t */\n\tfunction replaceTriggerRules(context: RouteContext): TriggerRuleRecord[] {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst body = context.jsonBody();\n\t\tif (!Array.isArray(body.triggerRules)) {\n\t\t\tthrow new ValidationError(\"triggerRules must be an array\");\n\t\t}\n\t\tconst actor = {\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t};\n\t\tconst wanted = (body.triggerRules as unknown[]).map(raw => {\n\t\t\tconst rule = (raw ?? {}) as Record<string, unknown>;\n\t\t\tconst mode = optionalString(rule, \"mode\") ?? \"condition\";\n\t\t\tconst target = optionalNumber(rule, \"userId\");\n\t\t\tif (mode === \"condition\" && target !== null && !users.findById(target)) {\n\t\t\t\tthrow new ValidationError(`userId must reference an existing employee (got ${target})`);\n\t\t\t}\n\t\t\tif (mode === \"user\" && target !== null) {\n\t\t\t\tthrow new ValidationError(\"mode user reads the employee from the state, userId has to stay empty\");\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tid: optionalNumber(rule, \"id\") ?? undefined,\n\t\t\t\tlabel: optionalString(rule, \"label\"),\n\t\t\t\tsourceState: optionalString(rule, \"sourceState\") ?? \"\",\n\t\t\t\tmode,\n\t\t\t\tcondition: optionalString(rule, \"condition\"),\n\t\t\t\tuserId: target,\n\t\t\t\taction: optionalString(rule, \"action\") ?? \"punch\",\n\t\t\t\tisActive: optionalBoolean(rule, \"isActive\") ?? true,\n\t\t\t\tcooldownSec: optionalNumber(rule, \"cooldownSec\") ?? 0,\n\t\t\t};\n\t\t});\n\n\t\tconst keep = new Set(wanted.map(rule => rule.id).filter((value): value is number => value !== undefined));\n\t\tfor (const existing of triggers.list({ includeInactive: true })) {\n\t\t\tif (!keep.has(existing.id)) {\n\t\t\t\ttriggers.remove({ id: existing.id, ...actor });\n\t\t\t}\n\t\t}\n\n\t\tconst saved = wanted.map(rule =>\n\t\t\ttriggers.save({\n\t\t\t\t...(rule.id !== undefined ? { id: rule.id } : {}),\n\t\t\t\tlabel: rule.label,\n\t\t\t\tsourceState: rule.sourceState,\n\t\t\t\tmode: rule.mode as TriggerMode,\n\t\t\t\tcondition: rule.condition,\n\t\t\t\tuserId: rule.userId,\n\t\t\t\taction: rule.action as TriggerAction,\n\t\t\t\tisActive: rule.isActive,\n\t\t\t\tcooldownSec: rule.cooldownSec,\n\t\t\t\t...actor,\n\t\t\t}),\n\t\t);\n\n\t\t// the adapter subscribes to the states the rules name: tell it about the new list right away\n\t\tdeps.onTriggerRulesChanged?.();\n\t\treturn saved;\n\t}\n\n\t// Trigger rules: a state of another adapter (fingerprint reader, button, door contact) punches or sets the\n\t// presence. The rules are edited as a whole table, like the break rules of the company.\n\troute(\"GET\", \"/trigger-rules\", { permission: \"settings.view\" }, () =>\n\t\tjson(200, { triggerRules: triggers.list({ includeInactive: true }) }),\n\t);\n\n\troute(\"PUT\", \"/trigger-rules\", { permission: \"settings.edit\", csrf: true }, context =>\n\t\tjson(200, { triggerRules: replaceTriggerRules(context) }),\n\t);\n\n\t/**\n\t * Replaces the automation rules.\n\t *\n\t * Like the other rule tables the payload is the whole table: rules that are missing are removed, the rest is\n\t * saved. The adapter reads the table every minute, so nothing has to be told about the change.\n\t *\n\t * @param context - route context of the request\n\t * @returns the saved rules\n\t */\n\tfunction replaceAutomationRules(context: RouteContext): AutomationRuleRecord[] {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst body = context.jsonBody();\n\t\tif (!Array.isArray(body.automationRules)) {\n\t\t\tthrow new ValidationError(\"automationRules must be an array\");\n\t\t}\n\t\tconst actor = {\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t};\n\t\tconst wanted = (body.automationRules as unknown[]).map(raw => {\n\t\t\tconst rule = (raw ?? {}) as Record<string, unknown>;\n\t\t\tconst target = optionalNumber(rule, \"userId\");\n\t\t\tif (target !== null && !users.findById(target)) {\n\t\t\t\tthrow new ValidationError(`userId must reference an existing employee (got ${target})`);\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tid: optionalNumber(rule, \"id\") ?? undefined,\n\t\t\t\tlabel: optionalString(rule, \"label\"),\n\t\t\t\tkind: optionalString(rule, \"kind\") ?? \"clockOut\",\n\t\t\t\tuserId: target,\n\t\t\t\tatMinute: optionalNumber(rule, \"atMinute\"),\n\t\t\t\tafterMinutes: optionalNumber(rule, \"afterMinutes\"),\n\t\t\t\t// the repository validates the selection and the repeat: a wrong entry comes back as a 400\n\t\t\t\tweekdays: Array.isArray(rule.weekdays) ? rule.weekdays.map(Number) : undefined,\n\t\t\t\trepeat: optionalString(rule, \"repeat\") as AutomationRepeat | undefined,\n\t\t\t\tisActive: optionalBoolean(rule, \"isActive\") ?? true,\n\t\t\t\tactiveFrom: optionalString(rule, \"activeFrom\"),\n\t\t\t\tactiveUntil: optionalString(rule, \"activeUntil\"),\n\t\t\t};\n\t\t});\n\n\t\tconst keep = new Set(wanted.map(rule => rule.id).filter((value): value is number => value !== undefined));\n\t\tfor (const existing of automations.list({ includeInactive: true })) {\n\t\t\tif (!keep.has(existing.id)) {\n\t\t\t\tautomations.remove({ id: existing.id, ...actor });\n\t\t\t}\n\t\t}\n\n\t\treturn wanted.map(rule =>\n\t\t\tautomations.save({\n\t\t\t\t...(rule.id !== undefined ? { id: rule.id } : {}),\n\t\t\t\tlabel: rule.label,\n\t\t\t\tkind: rule.kind as AutomationKind,\n\t\t\t\tuserId: rule.userId,\n\t\t\t\tatMinute: rule.atMinute,\n\t\t\t\tafterMinutes: rule.afterMinutes,\n\t\t\t\t...(rule.weekdays === undefined ? {} : { weekdays: rule.weekdays }),\n\t\t\t\t...(rule.repeat === undefined ? {} : { repeat: rule.repeat }),\n\t\t\t\tisActive: rule.isActive,\n\t\t\t\tactiveFrom: rule.activeFrom,\n\t\t\t\tactiveUntil: rule.activeUntil,\n\t\t\t\t...actor,\n\t\t\t}),\n\t\t);\n\t}\n\n\t// Automation rules: what the adapter does on its own \u2014 clock out at a time, report a missing punch, remind\n\t// about a break. Every rule comes with its newest run, so the administration sees at one glance whether it\n\t// still works; the complete log stays in the repository.\n\troute(\"GET\", \"/automation-rules\", { permission: \"settings.view\" }, () =>\n\t\tjson(200, { automationRules: automations.list({ includeInactive: true }) }),\n\t);\n\n\troute(\"GET\", \"/automation-rules/runs\", { permission: \"settings.view\" }, () =>\n\t\tjson(200, { runs: automations.latestRuns() }),\n\t);\n\n\troute(\"PUT\", \"/automation-rules\", { permission: \"settings.edit\", csrf: true }, context =>\n\t\tjson(200, { automationRules: replaceAutomationRules(context) }),\n\t);\n\n\t// branding: logo, background and accent colour of the installation.\n\t//\n\t// The values are public on purpose \u2014 the sign in screen, the kiosk terminal and the presence screen show them\n\t// before anybody is signed in. Written they are through the settings route above, delivered here as a small\n\t// JSON plus two image routes, so a browser can cache the pictures.\n\n\t/**\n\t * Short fingerprint of a stored branding picture.\n\t *\n\t * It goes into the image URLs, so a browser picks up a new logo right away while the old picture stays cached\n\t * until then.\n\t *\n\t * @param value - stored data URL\n\t * @returns twelve hex characters\n\t */\n\tconst brandingVersion = (value: string): string => createHash(\"sha256\").update(value).digest(\"hex\").slice(0, 12);\n\n\t/**\n\t * Reads the branding.\n\t *\n\t * @returns colour and the addresses of the two pictures, `null` when nothing is configured\n\t */\n\tconst brandingState = (): {\n\t\t/** Accent colour of the instance, `null` when none is configured */\n\t\tcolor: string | null;\n\t\t/** Address of the logo, `null` when none is configured */\n\t\tlogoUrl: string | null;\n\t\t/** Address of the background picture, `null` when none is configured */\n\t\tbackgroundUrl: string | null;\n\t} => {\n\t\tconst color = (settings.get(\"brand_color\") ?? \"\").trim();\n\t\tconst logo = (settings.get(\"brand_logo\") ?? \"\").trim();\n\t\tconst background = (settings.get(\"brand_background\") ?? \"\").trim();\n\t\treturn {\n\t\t\tcolor: color === \"\" ? null : color,\n\t\t\tlogoUrl: logo === \"\" ? null : `/api/branding/logo?v=${brandingVersion(logo)}`,\n\t\t\tbackgroundUrl: background === \"\" ? null : `/api/branding/background?v=${brandingVersion(background)}`,\n\t\t};\n\t};\n\n\troute(\"GET\", \"/branding\", { public: true }, () => json(200, brandingState()));\n\n\troute(\"GET\", \"/branding/logo\", { public: true }, () => {\n\t\tconst image = readAvatar(settings.get(\"brand_logo\"));\n\t\tif (!image) {\n\t\t\tthrow new NotFoundError(\"no logo is configured\");\n\t\t}\n\t\treturn binary(200, Buffer.from(image.base64, \"base64\"), image.contentType, {\n\t\t\t\"cache-control\": \"public, max-age=86400\",\n\t\t});\n\t});\n\n\troute(\"GET\", \"/branding/background\", { public: true }, () => {\n\t\tconst image = readAvatar(settings.get(\"brand_background\"));\n\t\tif (!image) {\n\t\t\tthrow new NotFoundError(\"no background is configured\");\n\t\t}\n\t\treturn binary(200, Buffer.from(image.base64, \"base64\"), image.contentType, {\n\t\t\t\"cache-control\": \"public, max-age=86400\",\n\t\t});\n\t});\n\n\troute(\"GET\", \"/version\", { public: true }, () =>\n\t\tjson(200, { name: \"iobroker.time-tracker\", version: deps.version ?? \"0.0.0\" }),\n\t);\n\n\t// corrections\n\n\troute(\"PATCH\", \"/entries/:id\", { permission: \"time.edit_own\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst entryId = numberParam(context, \"id\");\n\t\tconst existing = entries.findById(entryId);\n\t\tif (!existing) {\n\t\t\tthrow new NotFoundError(`entry ${entryId} not found`);\n\t\t}\n\t\tif (existing.userId !== context.auth.user.id && !context.auth.permissions.includes(\"time.edit_other\")) {\n\t\t\tthrow problem(403, \"permission_denied\", \"request rejected (permission_denied: time.edit_other)\");\n\t\t}\n\n\t\tconst body = context.jsonBody();\n\t\tconst revision = optionalNumber(body, \"revision\");\n\t\tif (revision === null) {\n\t\t\tthrow new ValidationError(\"revision is required\");\n\t\t}\n\t\t// an employee only comments his own punch: the times of a day belong to the administration\n\t\tconst wantedTs = optionalNumber(body, \"tsUtc\");\n\t\tif (wantedTs !== null && !context.auth.permissions.includes(\"time.edit_other\")) {\n\t\t\tthrow problem(403, \"permission_denied\", \"request rejected (permission_denied: time.edit_other)\");\n\t\t}\n\t\tconst target = users.findById(existing.userId);\n\t\tif (!target) {\n\t\t\tthrow new NotFoundError(`user ${existing.userId} not found`);\n\t\t}\n\n\t\tconst updated = entries.update({\n\t\t\tid: entryId,\n\t\t\texpectedRevision: revision,\n\t\t\tpatch: {\n\t\t\t\ttsUtc: optionalNumber(body, \"tsUtc\") ?? undefined,\n\t\t\t\tnote: optionalString(body, \"note\"),\n\t\t\t},\n\t\t\treason: optionalString(body, \"reason\"),\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\ttimeZone: target.timezone,\n\t\t\tnow: now(),\n\t\t});\n\n\t\tconst day = aggregation.recalculateDay(updated.userId, updated.localDate, { now: now() });\n\t\temit({\n\t\t\ttype: \"entry.update\",\n\t\t\tuserId: updated.userId,\n\t\t\tdata: { entryId: updated.id, localDate: updated.localDate, revision: updated.revision },\n\t\t});\n\t\treturn json(200, { entry: updated, day });\n\t});\n\n\troute(\"DELETE\", \"/entries/:id\", { permission: \"time.delete\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst entryId = numberParam(context, \"id\");\n\t\tconst existing = entries.findById(entryId);\n\t\tif (!existing) {\n\t\t\tthrow new NotFoundError(`entry ${entryId} not found`);\n\t\t}\n\n\t\tentries.remove({\n\t\t\tid: entryId,\n\t\t\tactorId: context.auth.user.id,\n\t\t\treason: optionalString(context.optionalJsonBody(), \"reason\"),\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\taggregation.recalculateDay(existing.userId, existing.localDate, { now: now() });\n\t\temit({\n\t\t\ttype: \"entry.delete\",\n\t\t\tuserId: existing.userId,\n\t\t\tdata: { entryId: existing.id, localDate: existing.localDate },\n\t\t});\n\t\treturn noContent();\n\t});\n\n\t// day notes: what an employee wants the administration to know about one day\n\n\troute(\"GET\", \"/day-notes\", { permission: \"report.view_own\" }, context => {\n\t\tconst userId = scopeUser(context);\n\t\tconst from = context.query(\"from\");\n\t\tconst to = context.query(\"to\") ?? from;\n\t\tif (!from || !to) {\n\t\t\tthrow new ValidationError(\"from and to are required\");\n\t\t}\n\t\treturn json(200, { notes: deps.dayNotes.listByRange(userId, from, to) });\n\t});\n\n\troute(\"PUT\", \"/day-notes\", { permission: \"time.edit_own\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst body = context.jsonBody();\n\t\tconst userId = dayNoteTarget(context, context.auth.user.id);\n\t\tconst localDate = context.query(\"date\");\n\t\tif (!localDate) {\n\t\t\tthrow new ValidationError(\"date is required\");\n\t\t}\n\t\tconst note = deps.dayNotes.save({\n\t\t\tuserId,\n\t\t\tlocalDate,\n\t\t\t// an empty text is how a note is taken back\n\t\t\tnote: optionalString(body, \"note\") ?? \"\",\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\temit({ type: \"dayNote.change\", userId, data: { localDate } });\n\t\treturn json(200, { note });\n\t});\n\n\troute(\"POST\", \"/day-notes/handled\", { permission: \"time.edit_other\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst userId = dayNoteTarget(context, context.auth.user.id);\n\t\tconst localDate = context.query(\"date\");\n\t\tif (!localDate) {\n\t\t\tthrow new ValidationError(\"date is required\");\n\t\t}\n\t\tconst handled = optionalBoolean(context.jsonBody(), \"handled\") ?? true;\n\t\tconst note = deps.dayNotes.setHandled({\n\t\t\tuserId,\n\t\t\tlocalDate,\n\t\t\thandled,\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\temit({ type: \"dayNote.change\", userId, data: { localDate } });\n\t\treturn json(200, { note });\n\t});\n\n\troute(\"GET\", \"/entries/:id/audit\", { permission: \"audit.view\" }, context => {\n\t\tconst entryId = numberParam(context, \"id\");\n\t\tif (!entries.findById(entryId)) {\n\t\t\tthrow new NotFoundError(`entry ${entryId} not found`);\n\t\t}\n\t\t// the trail names the people who changed the punch, so it needs the audit permission\n\t\treturn json(200, {\n\t\t\taudit: readTimeEntryAudit(deps.db, entryId).map(row => ({\n\t\t\t\t...row,\n\t\t\t\tactorName: users.findById(row.actorId)?.displayName ?? null,\n\t\t\t})),\n\t\t});\n\t});\n\n\t// users, roles and work profiles\n\n\troute(\"GET\", \"/users\", { permission: \"user.view\" }, context => {\n\t\tconst includeInactive = context.query(\"includeInactive\") === \"true\";\n\t\tconst search = (context.query(\"q\") ?? \"\").trim().toLowerCase();\n\t\tconst limit = optionalNumberQuery(context, \"limit\", Number.MAX_SAFE_INTEGER);\n\t\tconst offset = optionalNumberQuery(context, \"offset\", 0);\n\n\t\tconst all = users.list({ includeInactive });\n\t\tconst filtered = search\n\t\t\t? all.filter(\n\t\t\t\t\tuser =>\n\t\t\t\t\t\tuser.login.toLowerCase().includes(search) || user.displayName.toLowerCase().includes(search),\n\t\t\t\t)\n\t\t\t: all;\n\n\t\treturn json(200, {\n\t\t\ttotal: filtered.length,\n\t\t\toffset,\n\t\t\tusers: filtered.slice(offset, offset + limit).map(user => ({\n\t\t\t\t...publicUser(user),\n\t\t\t\troles: users.roles(user.id),\n\t\t\t})),\n\t\t});\n\t});\n\n\troute(\"GET\", \"/users/:id\", { permission: \"user.view\" }, context => {\n\t\tconst id = numberParam(context, \"id\");\n\t\tconst user = users.findById(id);\n\t\tif (!user) {\n\t\t\tthrow new NotFoundError(`user ${id} not found`);\n\t\t}\n\t\treturn json(200, {\n\t\t\tuser: { ...publicUser(user), roles: users.roles(id), permissions: users.permissions(id) },\n\t\t\tprofile: users.getWorkProfile(id),\n\t\t});\n\t});\n\n\troute(\"POST\", \"/users\", { permission: \"user.create\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst body = context.jsonBody();\n\t\tconst password = requireString(body, \"password\");\n\t\tconst policyIssue = checkPasswordPolicy(password);\n\t\tif (policyIssue) {\n\t\t\tthrow new ValidationError(policyIssue);\n\t\t}\n\n\t\tconst created = users.create({\n\t\t\tlogin: requireString(body, \"login\"),\n\t\t\tdisplayName: requireString(body, \"displayName\"),\n\t\t\tpasswordHash: hashPassword(password),\n\t\t\temail: optionalString(body, \"email\"),\n\t\t\tlocale: optionalString(body, \"locale\") ?? undefined,\n\t\t\ttimezone: optionalString(body, \"timezone\") ?? undefined,\n\t\t\tmustChangePw: optionalBoolean(body, \"mustChangePw\") ?? undefined,\n\t\t\troleKeys: readRoleKeys(body) ?? undefined,\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\n\t\treturn json(\n\t\t\t201,\n\t\t\t{ user: { ...publicUser(created), roles: users.roles(created.id) } },\n\t\t\t{ location: `/users/${created.id}` },\n\t\t);\n\t});\n\n\troute(\"GET\", \"/roles\", { permission: \"user.view\" }, () => json(200, { roles: users.roleCatalog() }));\n\n\troute(\"GET\", \"/users/:id/avatar\", { public: true }, context => {\n\t\t// The picture is shown by the kiosk and the presence screen as well, and those authenticate with the device\n\t\t// token of a terminal instead of a user session \u2014 so either credential is accepted here, and nothing else.\n\t\tconst id = numberParam(context, \"id\");\n\t\tconst terminalSession = context.query(\"terminalSession\") ?? \"\";\n\t\tconst browserToken =\n\t\t\tcontext.header(\"x-session-token\") ?? parseCookies(context.header(\"cookie\"))[SESSION_COOKIE] ?? \"\";\n\t\tconst browserOk =\n\t\t\tbrowserToken !== \"\" &&\n\t\t\tdeps.auth.authenticate({ token: browserToken, permission: undefined, now: now() }).ok;\n\t\tconst terminalOk = terminalSession !== \"\" && terminals.findBySession(terminalSession, now()) !== null;\n\t\tif (!browserOk && !terminalOk) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\n\t\tconst user = users.findById(id);\n\t\tconst image = user ? readAvatar(user.avatar) : null;\n\t\tif (!image) {\n\t\t\tthrow new NotFoundError(`user ${id} has no picture`);\n\t\t}\n\t\treturn binary(200, Buffer.from(image.base64, \"base64\"), image.contentType, {\n\t\t\t\"cache-control\": \"private, max-age=300\",\n\t\t});\n\t});\n\n\troute(\"PATCH\", \"/users/:id\", { permission: \"user.edit\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst id = numberParam(context, \"id\");\n\t\tconst target = users.findById(id);\n\t\tif (!target) {\n\t\t\tthrow new NotFoundError(`user ${id} not found`);\n\t\t}\n\n\t\tconst body = context.jsonBody();\n\t\tconst roleKeys = readRoleKeys(body);\n\t\t// assigning roles is a right of its own\n\t\tif (roleKeys !== null && !context.auth.permissions.includes(\"user.manage_roles\")) {\n\t\t\tthrow problem(403, \"permission_denied\", \"request rejected (permission_denied: user.manage_roles)\");\n\t\t}\n\n\t\tconst password = optionalString(body, \"password\");\n\t\tif (password !== null) {\n\t\t\tconst policyIssue = checkPasswordPolicy(password);\n\t\t\tif (policyIssue) {\n\t\t\t\tthrow new ValidationError(policyIssue);\n\t\t\t}\n\t\t}\n\n\t\tconst patch: {\n\t\t\t/** New display name */\n\t\t\tdisplayName?: string;\n\t\t\t/** New e-mail address, `null` clears it */\n\t\t\temail?: string | null;\n\t\t\t/** New language of the account */\n\t\t\tlocale?: string;\n\t\t\t/** New time zone of the account */\n\t\t\ttimezone?: string;\n\t\t\t/** New badge number, `null` clears it */\n\t\t\trfidCard?: string | null;\n\t\t\t/** Activates or deactivates the account */\n\t\t\tisActive?: boolean;\n\t\t\t/** True when the next login has to change the password */\n\t\t\tmustChangePw?: boolean;\n\t\t\t/** Already hashed password (the caller hashes it) */\n\t\t\tpasswordHash?: string;\n\t\t} = {};\n\t\tif (Object.prototype.hasOwnProperty.call(body, \"displayName\")) {\n\t\t\tpatch.displayName = requireString(body, \"displayName\");\n\t\t}\n\t\tif (Object.prototype.hasOwnProperty.call(body, \"email\")) {\n\t\t\tpatch.email = optionalString(body, \"email\");\n\t\t}\n\t\tif (Object.prototype.hasOwnProperty.call(body, \"locale\")) {\n\t\t\tpatch.locale = requireString(body, \"locale\");\n\t\t}\n\t\tif (Object.prototype.hasOwnProperty.call(body, \"timezone\")) {\n\t\t\tconst timezone = requireString(body, \"timezone\");\n\t\t\tif (!isValidTimeZone(timezone)) {\n\t\t\t\tthrow new ValidationError(`timezone is not a valid IANA name (got ${timezone})`);\n\t\t\t}\n\t\t\tpatch.timezone = timezone;\n\t\t}\n\t\tif (Object.prototype.hasOwnProperty.call(body, \"rfidCard\")) {\n\t\t\tpatch.rfidCard = optionalString(body, \"rfidCard\");\n\t\t}\n\t\tif (Object.prototype.hasOwnProperty.call(body, \"mustChangePw\")) {\n\t\t\tpatch.mustChangePw = requireBoolean(body, \"mustChangePw\");\n\t\t}\n\t\tconst isActive = optionalBoolean(body, \"isActive\");\n\t\tif (isActive !== null) {\n\t\t\tif (isActive === false && id === context.auth.user.id) {\n\t\t\t\tthrow new ValidationError(\"an account cannot deactivate itself\");\n\t\t\t}\n\t\t\tpatch.isActive = isActive;\n\t\t}\n\t\tif (password !== null) {\n\t\t\tpatch.passwordHash = hashPassword(password);\n\t\t}\n\n\t\t// The picture is not part of the audited patch \u2014 `setAvatar` stores it on its own (and keeps the image out\n\t\t// of the audit trail). Sending `avatar: null` removes it, leaving the field out keeps it.\n\t\tif (Object.prototype.hasOwnProperty.call(body, \"avatar\")) {\n\t\t\tconst avatar = optionalString(body, \"avatar\");\n\t\t\tif (avatar !== null && !parseAvatarDataUrl(avatar)) {\n\t\t\t\tthrow new ValidationError(\n\t\t\t\t\t`avatar must be a data URL of a png, jpeg, webp or gif of up to ${MAX_AVATAR_BYTES} bytes`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tusers.setAvatar({\n\t\t\t\tuserId: id,\n\t\t\t\tavatar,\n\t\t\t\tactorId: context.auth.user.id,\n\t\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\t\tnow: now(),\n\t\t\t});\n\t\t}\n\n\t\t// one rule for both fields: deactivating or demoting the last active administrator is refused, so the\n\t\t// installation always keeps somebody who may administer it\n\t\tif (patch.isActive === false || roleKeys !== null) {\n\t\t\trequireActiveAdministrator(id, {\n\t\t\t\tisActive: patch.isActive ?? target.isActive,\n\t\t\t\troleKeys: roleKeys ?? users.roles(id),\n\t\t\t});\n\t\t}\n\n\t\tconst updated = users.update({\n\t\t\tid,\n\t\t\tpatch,\n\t\t\treason: optionalString(body, \"reason\"),\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\tconst roles = roleKeys\n\t\t\t? users.setRoles({\n\t\t\t\t\tuserId: id,\n\t\t\t\t\troleKeys,\n\t\t\t\t\tactorId: context.auth.user.id,\n\t\t\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\t\t\tnow: now(),\n\t\t\t\t})\n\t\t\t: users.roles(id);\n\n\t\treturn json(200, { user: { ...publicUser(updated), roles } });\n\t});\n\n\troute(\"DELETE\", \"/users/:id\", { permission: \"user.deactivate\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst id = numberParam(context, \"id\");\n\t\tconst target = users.findById(id);\n\t\tif (!target) {\n\t\t\tthrow new NotFoundError(`user ${id} not found`);\n\t\t}\n\t\tif (!target.isActive) {\n\t\t\t// a second call has nothing left to do and says so\n\t\t\tthrow new NotFoundError(`user ${id} is already deactivated`);\n\t\t}\n\t\tif (id === context.auth.user.id) {\n\t\t\tthrow new ValidationError(\"an account cannot deactivate itself\");\n\t\t}\n\t\t// `user.deactivate` is carried by the admin role alone, so the caller is an active administrator: an account\n\t\t// deactivated here is never the last one (and the own one is refused above)\n\n\t\t// accounts are never deleted, they are deactivated and keep their history\n\t\tusers.update({\n\t\t\tid,\n\t\t\tpatch: { isActive: false },\n\t\t\treason: optionalString(context.optionalJsonBody(), \"reason\"),\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\treturn noContent();\n\t});\n\n\troute(\"GET\", \"/users/:id/profile\", { permission: \"user.view\" }, context => {\n\t\tconst id = numberParam(context, \"id\");\n\t\tif (!users.findById(id)) {\n\t\t\tthrow new NotFoundError(`user ${id} not found`);\n\t\t}\n\t\treturn json(200, { profile: users.getWorkProfile(id) });\n\t});\n\n\troute(\"PUT\", \"/users/:id/profile\", { permission: \"user.edit\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst id = numberParam(context, \"id\");\n\t\tif (!users.findById(id)) {\n\t\t\tthrow new NotFoundError(`user ${id} not found`);\n\t\t}\n\t\tconst body = context.jsonBody();\n\n\t\tconst profile = users.saveWorkProfile({\n\t\t\tuserId: id,\n\t\t\tprofile: readProfilePatch(body),\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\treturn json(200, { profile });\n\t});\n\n\troute(\"GET\", \"/users/:id/shift-rules\", { permission: \"user.view\" }, context => {\n\t\tconst id = numberParam(context, \"id\");\n\t\tif (!users.findById(id)) {\n\t\t\tthrow new NotFoundError(`user ${id} not found`);\n\t\t}\n\t\treturn json(200, { shiftRules: rules.shiftRules(id, { includeInactive: true }) });\n\t});\n\n\troute(\"PUT\", \"/users/:id/shift-rules\", { permission: \"user.edit\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst id = numberParam(context, \"id\");\n\t\tif (!users.findById(id)) {\n\t\t\tthrow new NotFoundError(`user ${id} not found`);\n\t\t}\n\n\t\tconst body = context.jsonBody();\n\t\tif (!Array.isArray(body.shiftRules)) {\n\t\t\tthrow new ValidationError(\"shiftRules must be an array\");\n\t\t}\n\t\tconst actor = {\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t};\n\t\tconst wanted = (body.shiftRules as unknown[]).map(raw => {\n\t\t\tconst rule = (raw ?? {}) as Record<string, unknown>;\n\t\t\treturn {\n\t\t\t\tid: optionalNumber(rule, \"id\") ?? undefined,\n\t\t\t\tsurcharge: Number(rule.surcharge),\n\t\t\t\tdayOfWeek: optionalNumber(rule, \"dayOfWeek\"),\n\t\t\t\tfromMin: optionalNumber(rule, \"fromMin\"),\n\t\t\t\ttoMin: optionalNumber(rule, \"toMin\"),\n\t\t\t\tisActive: optionalBoolean(rule, \"isActive\") ?? undefined,\n\t\t\t};\n\t\t});\n\n\t\t// the payload replaces the rules of the employee: missing rules are removed, the rest is saved\n\t\tconst keep = new Set(wanted.map(rule => rule.id).filter((value): value is number => value !== undefined));\n\t\tfor (const existing of rules.shiftRules(id, { includeInactive: true })) {\n\t\t\tif (!keep.has(existing.id)) {\n\t\t\t\trules.removeShiftRule({ id: existing.id, ...actor });\n\t\t\t}\n\t\t}\n\t\tconst saved = wanted.map(rule =>\n\t\t\trules.saveShiftRule({\n\t\t\t\t...(rule.id !== undefined ? { id: rule.id } : {}),\n\t\t\t\tuserId: id,\n\t\t\t\tsurcharge: rule.surcharge,\n\t\t\t\t...(rule.dayOfWeek !== null ? { dayOfWeek: rule.dayOfWeek } : {}),\n\t\t\t\t...(rule.fromMin !== null ? { fromMin: rule.fromMin } : {}),\n\t\t\t\t...(rule.toMin !== null ? { toMin: rule.toMin } : {}),\n\t\t\t\t...(rule.isActive !== undefined ? { isActive: rule.isActive } : {}),\n\t\t\t\t...actor,\n\t\t\t}),\n\t\t);\n\t\treturn json(200, { shiftRules: saved });\n\t});\n\n\t// payouts (paid out overtime)\n\n\troute(\"GET\", \"/payouts\", { permission: \"payout.view\" }, context => {\n\t\tconst requested = context.query(\"userId\");\n\t\tconst userId = requested ? Number(requested) : (context.auth?.user.id ?? 0);\n\t\tif (!Number.isInteger(userId)) {\n\t\t\tthrow new ValidationError(\"userId must be a whole number\");\n\t\t}\n\t\tconst period = readPeriod(context.query(\"year\"), context.query(\"month\"));\n\t\tconst list = payouts.list(\n\t\t\tuserId,\n\t\t\tperiod.month === null ? { year: period.year } : { year: period.year, month: period.month },\n\t\t);\n\t\treturn json(200, {\n\t\t\tuserId,\n\t\t\t...period,\n\t\t\ttotalMinutes: payouts.sumMinutes(userId, period.year, period.month ?? undefined),\n\t\t\tpayouts: list,\n\t\t});\n\t});\n\n\troute(\"POST\", \"/payouts\", { permission: \"payout.create\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst body = context.jsonBody();\n\t\tconst target = Number(body.userId ?? context.auth.user.id);\n\t\tif (!Number.isInteger(target) || !users.findById(target)) {\n\t\t\tthrow new ValidationError(\"userId must reference an existing employee\");\n\t\t}\n\t\tconst period = readPeriod(body.year, body.month);\n\t\tconst minutes = optionalNumber(body, \"minutes\");\n\t\tif (minutes === null || !Number.isInteger(minutes) || minutes === 0) {\n\t\t\tthrow new ValidationError(\"minutes must be a non-zero whole number\");\n\t\t}\n\n\t\tconst created = payouts.create({\n\t\t\tuserId: target,\n\t\t\t...period,\n\t\t\tminutes,\n\t\t\tamount: optionalNumber(body, \"amount\"),\n\t\t\tnote: optionalString(body, \"note\"),\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\treturn json(201, { payout: created }, { location: `/payouts/${created.id}` });\n\t});\n\n\troute(\"PATCH\", \"/payouts/:id\", { permission: \"payout.create\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst id = numberParam(context, \"id\");\n\t\tif (!payouts.findById(id)) {\n\t\t\tthrow new NotFoundError(`payout ${id} not found`);\n\t\t}\n\t\tconst body = context.jsonBody();\n\t\tconst minutes = optionalNumber(body, \"minutes\");\n\t\tif (minutes !== null && (!Number.isInteger(minutes) || minutes === 0)) {\n\t\t\tthrow new ValidationError(\"minutes must be a non-zero whole number\");\n\t\t}\n\n\t\tconst updated = payouts.update({\n\t\t\tid,\n\t\t\tpatch: {\n\t\t\t\t...(minutes !== null ? { minutes } : {}),\n\t\t\t\t...(Object.prototype.hasOwnProperty.call(body, \"amount\")\n\t\t\t\t\t? { amount: optionalNumber(body, \"amount\") }\n\t\t\t\t\t: {}),\n\t\t\t\t...(Object.prototype.hasOwnProperty.call(body, \"note\") ? { note: optionalString(body, \"note\") } : {}),\n\t\t\t},\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\treturn json(200, { payout: updated });\n\t});\n\n\troute(\"DELETE\", \"/payouts/:id\", { permission: \"payout.create\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst removed = payouts.remove({\n\t\t\tid: numberParam(context, \"id\"),\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\tif (!removed) {\n\t\t\tthrow new NotFoundError(`payout ${context.params.id} not found`);\n\t\t}\n\t\treturn noContent();\n\t});\n\n\t// bulk import of punches (administration and migration)\n\n\troute(\n\t\t\"POST\",\n\t\t\"/entries/bulk\",\n\t\t{ permission: \"time.import\", csrf: true, rateLimit: { name: \"import\", limit: 10, windowSeconds: 60 } },\n\t\tcontext => {\n\t\t\tif (!context.auth) {\n\t\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t\t}\n\t\t\tconst body = context.jsonBody();\n\t\t\tif (!Array.isArray(body.entries)) {\n\t\t\t\tthrow new ValidationError(\"entries must be an array\");\n\t\t\t}\n\n\t\t\tconst actor = context.auth;\n\t\t\tconst timestamp = now();\n\t\t\tconst touched = new Map<string, number>();\n\t\t\tconst results: {\n\t\t\t\t/** Position of the punch in the request */\n\t\t\t\tindex: number;\n\t\t\t\t/** Id of the stored punch, missing when it was rejected */\n\t\t\t\tentryId?: number;\n\t\t\t\t/** Reason of the rejection, missing when the punch was stored */\n\t\t\t\terror?: string;\n\t\t\t}[] = [];\n\n\t\t\t(body.entries as unknown[]).forEach((raw, index) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst item = (raw ?? {}) as Record<string, unknown>;\n\t\t\t\t\tconst target = Number(item.userId ?? actor.user.id);\n\t\t\t\t\tif (!Number.isInteger(target)) {\n\t\t\t\t\t\tthrow new ValidationError(\"userId must be a whole number\");\n\t\t\t\t\t}\n\t\t\t\t\t// writing punches for somebody else needs the matching permission\n\t\t\t\t\tif (target !== actor.user.id && !actor.permissions.includes(\"time.edit_other\")) {\n\t\t\t\t\t\tthrow problem(\n\t\t\t\t\t\t\t403,\n\t\t\t\t\t\t\t\"permission_denied\",\n\t\t\t\t\t\t\t\"request rejected (permission_denied: time.edit_other)\",\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tconst user = users.findById(target);\n\t\t\t\t\tif (!user) {\n\t\t\t\t\t\tthrow new NotFoundError(`user ${target} not found`);\n\t\t\t\t\t}\n\t\t\t\t\tconst tsUtc = optionalNumber(item, \"tsUtc\");\n\t\t\t\t\tif (tsUtc === null) {\n\t\t\t\t\t\tthrow new ValidationError(\"tsUtc is required\");\n\t\t\t\t\t}\n\n\t\t\t\t\tconst stored = entries.insert({\n\t\t\t\t\t\tuserId: target,\n\t\t\t\t\t\ttsUtc,\n\t\t\t\t\t\tclientTsUtc: optionalNumber(item, \"clientTsUtc\"),\n\t\t\t\t\t\ttimeZone: user.timezone,\n\t\t\t\t\t\tsource: \"import\",\n\t\t\t\t\t\tdirection: optionalDirection(item),\n\t\t\t\t\t\tidempotencyKey: optionalString(item, \"idempotencyKey\"),\n\t\t\t\t\t\tnote: optionalString(item, \"note\"),\n\t\t\t\t\t\tactorId: actor.user.id,\n\t\t\t\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\t\t\t\tnow: timestamp,\n\t\t\t\t\t});\n\n\t\t\t\t\ttouched.set(`${target}|${stored.entry.localDate}`, target);\n\t\t\t\t\tresults.push({ index, entryId: stored.entry.id });\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// a faulty row does not discard the rest of the import\n\t\t\t\t\tconst details = toProblem(error).problem;\n\t\t\t\t\tresults.push({ index, error: details.code });\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// the aggregates of the imported days are refreshed once, not per row\n\t\t\tfor (const key of touched.keys()) {\n\t\t\t\tconst [userId, localDate] = key.split(\"|\");\n\t\t\t\taggregation.recalculateDay(Number(userId), localDate, { now: timestamp });\n\t\t\t}\n\n\t\t\tconst imported = results.filter(result => result.entryId !== undefined).length;\n\t\t\t// one event per touched employee: the import may mix several of them\n\t\t\tfor (const [key, userId] of touched) {\n\t\t\t\temit({\n\t\t\t\t\ttype: \"punch\",\n\t\t\t\t\tuserId,\n\t\t\t\t\tdata: { imported: true, localDate: key.split(\"|\")[1], source: \"import\" },\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn json(200, {\n\t\t\t\timported,\n\t\t\t\tfailed: results.length - imported,\n\t\t\t\trecalculatedDays: touched.size,\n\t\t\t\tresults,\n\t\t\t});\n\t\t},\n\t);\n\n\t// statistics\n\n\troute(\"GET\", \"/reports/statistics\", { permission: \"report.statistics\" }, context => {\n\t\tconst from = context.query(\"from\");\n\t\tconst to = context.query(\"to\") ?? from;\n\t\tif (!from || !to) {\n\t\t\tthrow new ValidationError(\"from and to are required\");\n\t\t}\n\t\tif (from > to) {\n\t\t\tthrow new ValidationError(`from (${from}) must not be after to (${to})`);\n\t\t}\n\t\t// every day of every employee is recalculated, so the range stays bounded\n\t\tconst days = dateRange(from, to).length;\n\t\tif (days > MAX_STATISTICS_DAYS) {\n\t\t\tthrow new ValidationError(`the range may cover at most ${MAX_STATISTICS_DAYS} days (got ${days})`);\n\t\t}\n\n\t\tconst requested = context.query(\"userId\");\n\t\tif (requested) {\n\t\t\t// looking at somebody else is a permission of its own\n\t\t\tif (!context.auth?.permissions.includes(\"report.view_other\")) {\n\t\t\t\tthrow problem(403, \"permission_denied\", \"request rejected (permission_denied: report.view_other)\");\n\t\t\t}\n\t\t}\n\t\tconst employees = requested ? [Number(requested)] : users.list({ includeInactive: false }).map(user => user.id);\n\t\tif (employees.some(id => !Number.isInteger(id) || !users.findById(id))) {\n\t\t\tthrow new ValidationError(\"userId must reference an existing employee\");\n\t\t}\n\n\t\tconst timestamp = now();\n\t\tconst rows = employees.map(userId => {\n\t\t\tconst user = users.findById(userId);\n\t\t\tconst range = aggregation.recalculateRange(userId, from, to, { now: timestamp });\n\t\t\treturn {\n\t\t\t\tuserId,\n\t\t\t\tdisplayName: user?.displayName ?? \"\",\n\t\t\t\t...range,\n\t\t\t};\n\t\t});\n\n\t\treturn json(200, {\n\t\t\tfrom,\n\t\t\tto,\n\t\t\tusers: rows,\n\t\t\ttotals: rows.reduce(\n\t\t\t\t(sum, row) => ({\n\t\t\t\t\tworkedMin: sum.workedMin + row.workedMin,\n\t\t\t\t\ttargetMin: sum.targetMin + row.targetMin,\n\t\t\t\t\tbalanceMin: sum.balanceMin + row.balanceMin,\n\t\t\t\t\topenDays: sum.openDays + row.openDays,\n\t\t\t\t}),\n\t\t\t\t{ workedMin: 0, targetMin: 0, balanceMin: 0, openDays: 0 },\n\t\t\t),\n\t\t});\n\t});\n\n\t// kiosk terminal (device token -> short lived session, badge/PIN punching)\n\n\t/**\n\t * Refuses terminal requests when the instance switch is off.\n\t */\n\tconst requireKiosk = (): void => {\n\t\tif (deps.kioskEnabled !== true) {\n\t\t\tthrow problem(403, \"kiosk_disabled\", \"the kiosk terminal is switched off in the adapter settings\");\n\t\t}\n\t};\n\n\t// how long a session of a kiosk device lives; the heartbeat of the device extends it. A test may shorten it,\n\t// and an instance may raise it for a tablet that is never touched.\n\tconst TERMINAL_SESSION_MINUTES = deps.terminalSessionMinutes ?? TERMINAL_SESSION_MINUTES_DEFAULT;\n\n\t/**\n\t * Resolves the terminal of a request.\n\t *\n\t * @param context - route context (body or query carries the session)\n\t * @returns the terminal\n\t */\n\tconst requireTerminal = (context: RouteContext): TerminalRecord => {\n\t\trequireKiosk();\n\t\t// the session travels in the body of a POST and in the query of a GET\n\t\tconst body = context.optionalJsonBody<Record<string, unknown>>();\n\t\tconst fromBody = typeof body.terminalSession === \"string\" ? body.terminalSession : null;\n\t\tconst session = fromBody ?? context.query(\"terminalSession\");\n\t\tif (!session) {\n\t\t\tthrow problem(401, \"no_session\", \"terminal session is missing\");\n\t\t}\n\t\tconst terminal = terminals.findBySession(session, now());\n\t\tif (!terminal) {\n\t\t\tthrow problem(401, \"no_session\", \"terminal session is not valid any more\");\n\t\t}\n\t\treturn terminal;\n\t};\n\n\troute(\"GET\", \"/terminal/status\", { public: true }, () =>\n\t\tjson(200, {\n\t\t\tenabled: deps.kioskEnabled === true,\n\t\t\tserverTime: now(),\n\t\t\ttimezone: settings.get(\"timezone\") ?? \"Europe/Berlin\",\n\t\t\tversion: deps.version ?? \"0.0.0\",\n\t\t}),\n\t);\n\n\troute(\n\t\t\"POST\",\n\t\t\"/terminal/session\",\n\t\t{ public: true, csrf: false, rateLimit: { name: \"terminal-session\", limit: 30, windowSeconds: 60 } },\n\t\tcontext => {\n\t\t\trequireKiosk();\n\t\t\tconst body = context.jsonBody();\n\t\t\tconst deviceToken = requireString(body, \"deviceToken\");\n\t\t\tconst terminal = terminals.findByToken(deviceToken, now());\n\t\t\tif (!terminal) {\n\t\t\t\tthrow problem(401, \"invalid_credentials\", \"the device token is not valid any more\");\n\t\t\t}\n\n\t\t\tconst started = terminals.startSession({\n\t\t\t\tid: terminal.id,\n\t\t\t\tttlMinutes: TERMINAL_SESSION_MINUTES,\n\t\t\t\tnow: now(),\n\t\t\t});\n\t\t\treturn json(200, {\n\t\t\t\tterminalSession: started.terminalSession,\n\t\t\t\texpiresAt: started.expiresAt,\n\t\t\t\tterminal: {\n\t\t\t\t\tname: terminal.name,\n\t\t\t\t\tlocation: terminal.location,\n\t\t\t\t\tpinRequired: terminal.pinRequired,\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\t);\n\n\troute(\"POST\", \"/terminal/heartbeat\", { public: true, csrf: false }, context => {\n\t\tconst terminal = requireTerminal(context);\n\t\tconst extended = terminals.touch({\n\t\t\tid: terminal.id,\n\t\t\tttlMinutes: TERMINAL_SESSION_MINUTES,\n\t\t\tnow: now(),\n\t\t});\n\t\treturn json(200, {\n\t\t\tstatus: \"ok\",\n\t\t\tserverTime: now(),\n\t\t\ttimezone: settings.get(\"timezone\") ?? \"Europe/Berlin\",\n\t\t\tversion: deps.version ?? \"0.0.0\",\n\t\t\texpiresAt: extended.expiresAt,\n\t\t});\n\t});\n\n\troute(\n\t\t\"GET\",\n\t\t\"/terminal/users\",\n\t\t{ public: true, rateLimit: { name: \"terminal-users\", limit: 60, windowSeconds: 60 } },\n\t\tcontext => {\n\t\t\tconst terminal = requireTerminal(context);\n\t\t\t// A terminal stands at one place: it shows the employees that were assigned to it. No assignment means\n\t\t\t// every employee \u2014 that is how terminals worked before the assignment existed.\n\t\t\t//\n\t\t\t// Only what a badge/PIN selection needs \u2014 no mail address, no profile, no working times. Whether the\n\t\t\t// employee is at the workplace right now is the one exception: the presence screen highlights it.\n\t\t\tconst timestamp = now();\n\t\t\tconst terminalSession = context.query(\"terminalSession\") ?? \"\";\n\t\t\tconst candidates = users.list({ includeInactive: false });\n\t\t\tconst visible =\n\t\t\t\tterminal.userIds.length === 0\n\t\t\t\t\t? candidates\n\t\t\t\t\t: candidates.filter(user => terminal.userIds.includes(user.id));\n\t\t\treturn json(200, {\n\t\t\t\tusers: visible.map(user => {\n\t\t\t\t\t// The aggregate counts finished punches only. While a punch is open, the time that passed since\n\t\t\t\t\t// it started is added here, so the board shows the time that really passed. (A break that the\n\t\t\t\t\t// pause rules deduct later is not part of this - the next punch brings the exact value.)\n\t\t\t\t\tconst date = localDate(timestamp, user.timezone);\n\t\t\t\t\tconst day = aggregation.recalculateDay(user.id, date, { now: timestamp });\n\t\t\t\t\tconst open = day?.hasOpenEntry ? entries.listByDate(user.id, date).at(-1) : undefined;\n\t\t\t\t\tconst runningMin = open ? Math.max(0, Math.floor((timestamp - open.tsUtc) / 60)) : 0;\n\t\t\t\t\t// The worked time of the day travels only for employees whose work profile allows it: the\n\t\t\t\t\t// presence screen is visible before the PIN is entered, so the decision stays with the\n\t\t\t\t\t// administration - and it is made here, not in the browser.\n\t\t\t\t\tconst showWorkedTime = users.getWorkProfile(user.id)?.showWorkedTime === true;\n\t\t\t\t\treturn {\n\t\t\t\t\t\tid: user.id,\n\t\t\t\t\t\tdisplayName: user.displayName,\n\t\t\t\t\t\tpresent: day?.hasOpenEntry === true,\n\t\t\t\t\t\tworkedMin: showWorkedTime ? (day?.workedMin ?? 0) + runningMin : undefined,\n\t\t\t\t\t\t// the picture is fetched by the browser from its own route; the session of this device travels\n\t\t\t\t\t\t// in the query, so a plain `<img>` can load it\n\t\t\t\t\t\tavatarUrl: user.avatar\n\t\t\t\t\t\t\t? `/api/users/${user.id}/avatar?terminalSession=${encodeURIComponent(terminalSession)}&v=${user.updatedAt}`\n\t\t\t\t\t\t\t: null,\n\t\t\t\t\t};\n\t\t\t\t}),\n\t\t\t});\n\t\t},\n\t);\n\n\troute(\n\t\t\"POST\",\n\t\t\"/terminal/punch\",\n\t\t{ public: true, csrf: false, rateLimit: { name: \"terminal-punch\", limit: 60, windowSeconds: 60 } },\n\t\tcontext => {\n\t\t\tconst terminal = requireTerminal(context);\n\t\t\tconst body = context.jsonBody();\n\t\t\tconst badge = optionalString(body, \"badge\");\n\t\t\tconst pin = optionalString(body, \"pin\");\n\t\t\tconst targetId = optionalNumber(body, \"userId\");\n\n\t\t\tif (badge === null && targetId === null) {\n\t\t\t\tthrow new ValidationError(\"a badge or a userId is required\");\n\t\t\t}\n\t\t\t// a device that demands the PIN authenticates with it; a device without that duty may punch for a\n\t\t\t// selected employee right away (the administrator decided that this place is trusted)\n\t\t\tif (badge === null && terminal.pinRequired && pin === null) {\n\t\t\t\tthrow new ValidationError(\"a userId together with the PIN is required\");\n\t\t\t}\n\n\t\t\tconst user = badge !== null ? users.findByRfidCard(badge) : users.findById(targetId ?? 0);\n\t\t\tif (!user || !user.isActive) {\n\t\t\t\t// the same answer for an unknown badge and an unknown user: no enumeration of accounts.\n\t\t\t\t// There is no account to count here \u2014 the rate limit of the route covers this case.\n\t\t\t\tthrow problem(401, \"invalid_credentials\", \"badge or PIN is not known\");\n\t\t\t}\n\n\t\t\t// a shared device plus a four digit PIN: too many wrong tries block the account for a while (4.10)\n\t\t\tconst blocked = pinGuard.state({ userId: user.id, now: now() });\n\t\t\tif (blocked.locked) {\n\t\t\t\tthrow problem(423, \"locked_out\", `too many wrong PINs, try again in ${blocked.retryAfterSeconds} s`);\n\t\t\t}\n\n\t\t\tconst pinMatches = pin !== null && user.pinHash !== null && verifyPassword(pin, user.pinHash);\n\t\t\t// a wrong PIN is refused on every device; a missing one only where the PIN is demanded\n\t\t\tif (terminal.pinRequired ? !pinMatches : pin !== null && !pinMatches) {\n\t\t\t\tpinGuard.fail({ userId: user.id, now: now() });\n\t\t\t\tthrow problem(401, \"invalid_credentials\", \"badge or PIN is not known\");\n\t\t\t}\n\t\t\t// the correct PIN clears the counter of that account\n\t\t\tpinGuard.reset(user.id);\n\n\t\t\tconst timestamp = now();\n\t\t\tconst stored = entries.insert({\n\t\t\t\tuserId: user.id,\n\t\t\t\ttsUtc: optionalNumber(body, \"tsUtc\") ?? timestamp,\n\t\t\t\ttimeZone: user.timezone,\n\t\t\t\tsource: \"terminal\",\n\t\t\t\tdirection: optionalDirection(body),\n\t\t\t\tnote: optionalString(body, \"note\"),\n\t\t\t\tactorId: user.id,\n\t\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\t\tnow: timestamp,\n\t\t\t});\n\t\t\tconst day = aggregation.recalculateDay(user.id, stored.entry.localDate, { now: timestamp });\n\t\t\tterminals.touch({ id: terminal.id, ttlMinutes: TERMINAL_SESSION_MINUTES, now: timestamp });\n\t\t\temit({\n\t\t\t\ttype: \"terminal.punch\",\n\t\t\t\tuserId: user.id,\n\t\t\t\tdata: {\n\t\t\t\t\tentryId: stored.entry.id,\n\t\t\t\t\tlocalDate: stored.entry.localDate,\n\t\t\t\t\tterminalId: terminal.id,\n\t\t\t\t\tterminalName: terminal.name,\n\t\t\t\t},\n\t\t\t});\n\n\t\t\t// the answer stays minimal: the terminal shows a name, a time and the state of the day\n\t\t\treturn json(201, {\n\t\t\t\tuser: { id: user.id, displayName: user.displayName },\n\t\t\t\tentry: {\n\t\t\t\t\tid: stored.entry.id,\n\t\t\t\t\ttsUtc: stored.entry.tsUtc,\n\t\t\t\t\tdirection: stored.entry.direction,\n\t\t\t\t\tlocalDate: stored.entry.localDate,\n\t\t\t\t},\n\t\t\t\tday: {\n\t\t\t\t\t// the worked time of the day only when the work profile of the employee allows it: the\n\t\t\t\t\t// presence card is public, so the same rule as in `/terminal/users` applies here\n\t\t\t\t\tworkedMin: users.getWorkProfile(user.id)?.showWorkedTime === true ? day.workedMin : undefined,\n\t\t\t\t\ttargetMin: day.targetMin,\n\t\t\t\t\tbalanceMin: day.balanceMin,\n\t\t\t\t\thasOpenEntry: day.hasOpenEntry,\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\t);\n\n\t// terminals and PINs (administration)\n\n\troute(\"GET\", \"/terminals\", { permission: \"terminal.manage\" }, () =>\n\t\tjson(200, { terminals: terminals.list({ includeInactive: true }).map(publicTerminal) }),\n\t);\n\n\troute(\"POST\", \"/terminals\", { permission: \"terminal.manage\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst body = context.jsonBody();\n\t\tconst created = terminals.create({\n\t\t\tname: requireString(body, \"name\"),\n\t\t\tlocation: optionalString(body, \"location\"),\n\t\t\tpinRequired: optionalBoolean(body, \"pinRequired\") ?? undefined,\n\t\t\tuserIds: readUserIds(body) ?? undefined,\n\t\t\tttlDays: optionalNumber(body, \"ttlDays\"),\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\n\t\t// the device token is returned exactly once \u2014 the server only keeps its hash\n\t\treturn json(\n\t\t\t201,\n\t\t\t{ terminal: publicTerminal(created.terminal), deviceToken: created.deviceToken },\n\t\t\t{ location: `/terminals/${created.terminal.id}` },\n\t\t);\n\t});\n\n\troute(\"PUT\", \"/terminals/:id/users\", { permission: \"terminal.manage\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst id = numberParam(context, \"id\");\n\t\tconst body = context.jsonBody();\n\t\t// an empty list means \u201Call employees\u201D, which is also how a terminal starts its life\n\t\tconst userIds = readUserIds(body) ?? [];\n\t\tfor (const userId of userIds) {\n\t\t\tif (!users.findById(userId)) {\n\t\t\t\tthrow new ValidationError(`user ${userId} does not exist`);\n\t\t\t}\n\t\t}\n\n\t\tconst terminal = terminals.setUsers({\n\t\t\tid,\n\t\t\tuserIds,\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\treturn json(200, { terminal: publicTerminal(terminal) });\n\t});\n\n\troute(\"DELETE\", \"/terminals/:id\", { permission: \"terminal.manage\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst revoked = terminals.revoke({\n\t\t\tid: numberParam(context, \"id\"),\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\tif (!revoked) {\n\t\t\tthrow new NotFoundError(`terminal ${context.params.id} not found or already revoked`);\n\t\t}\n\t\treturn noContent();\n\t});\n\n\troute(\"POST\", \"/users/:id/pin\", { permission: \"user.edit\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst id = numberParam(context, \"id\");\n\t\tif (!users.findById(id)) {\n\t\t\tthrow new NotFoundError(`user ${id} not found`);\n\t\t}\n\t\tconst body = context.jsonBody();\n\t\tconst pin = optionalString(body, \"pin\");\n\n\t\tif (pin !== null && !/^\\d{4,8}$/.test(pin)) {\n\t\t\tthrow new ValidationError(\"pin must be 4 to 8 digits\");\n\t\t}\n\t\tusers.setPin({\n\t\t\tuserId: id,\n\t\t\t// a PIN is a secret of the same weight as a password, so it is hashed the same way\n\t\t\tpinHash: pin === null ? null : hashPassword(pin),\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\treturn noContent();\n\t});\n\n\t// RFID/NFC tags (signed deep links)\n\n\t/**\n\t * Refuses tag management when the instance has no HMAC secret.\n\t *\n\t * @returns the secret\n\t */\n\tconst requireHmacSecret = (): string => {\n\t\tconst secret = deps.hmacSecret ?? \"\";\n\t\tif (!secret) {\n\t\t\tthrow problem(403, \"not_configured\", \"no HMAC secret is configured for signed tag links\");\n\t\t}\n\t\treturn secret;\n\t};\n\n\t/**\n\t * Builds the link a phone scans for a badge token.\n\t *\n\t * @param context - request context (host and scheme)\n\t * @param token - signed token of the badge\n\t * @returns absolute URL the badge carries\n\t */\n\tconst tagLink = (context: RouteContext, token: string): string => {\n\t\tconst host = context.header(\"host\") ?? \"localhost\";\n\t\t// the link is what a phone scans: it opens the web app, which sends the token to /rfid/scan. The scheme comes\n\t\t// from the request (and from a trusted reverse proxy), so the link matches the address the administration\n\t\t// itself is reached with \u2014 a fixed `https://` pointed nowhere on a plain HTTP instance.\n\t\tconst scheme = host.includes(\"://\") ? \"\" : `${context.secure ? \"https\" : \"http\"}://`;\n\t\treturn `${scheme}${host}/?tag=${token}`;\n\t};\n\n\t/**\n\t * Signs a new link for a badge and stores it.\n\t *\n\t * The signature binds `uid`, owner and expiry, so this is what replaces the link: everything handed out before\n\t * stops working immediately, and a revoked badge comes back into use with it.\n\t *\n\t * @param context - request context\n\t * @param tag - badge to give a new link (its `userId` may be the new owner)\n\t * @param ttlDays - validity of the new link in days\n\t * @param label - new label, `undefined` keeps the stored one\n\t * @returns the changed badge and its new token\n\t */\n\tconst reissueLink = (\n\t\tcontext: RouteContext,\n\t\ttag: RfidTagRecord,\n\t\tttlDays: number,\n\t\tlabel?: string | null,\n\t): { tag: RfidTagRecord; token: string } => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tif (tag.userId === null) {\n\t\t\tthrow new ValidationError(\"the badge has no employee any more \u2014 give it one first\");\n\t\t}\n\t\tconst timestamp = now();\n\t\tconst expiresAt = timestamp + ttlDays * 86400;\n\t\tconst signature = signTag(requireHmacSecret(), tag.uid, tag.userId, expiresAt);\n\t\tconst updated = rfid.update({\n\t\t\tid: tag.id,\n\t\t\tuserId: tag.userId,\n\t\t\texpiresAt,\n\t\t\tsignature,\n\t\t\tactivate: true,\n\t\t\t...(label === undefined ? {} : { label }),\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: timestamp,\n\t\t});\n\t\tif (!updated) {\n\t\t\tthrow new NotFoundError(`tag ${tag.id} not found`);\n\t\t}\n\t\treturn { tag: updated, token: buildTagToken(updated.uid, tag.userId, expiresAt, signature) };\n\t};\n\n\troute(\"GET\", \"/rfid/tags\", { permission: \"rfid.manage\" }, () =>\n\t\tjson(200, { tags: rfid.list({ includeInactive: true }) }),\n\t);\n\n\troute(\"POST\", \"/rfid/tags\", { permission: \"rfid.manage\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst secret = requireHmacSecret();\n\t\tconst body = context.jsonBody();\n\t\tconst target = Number(body.userId ?? context.auth.user.id);\n\t\tif (!Number.isInteger(target) || !users.findById(target)) {\n\t\t\tthrow new ValidationError(\"userId must reference an existing employee\");\n\t\t}\n\t\tconst ttlDays = optionalNumber(body, \"ttlDays\") ?? 365;\n\t\tif (!Number.isInteger(ttlDays) || ttlDays <= 0) {\n\t\t\tthrow new ValidationError(`ttlDays must be a positive whole number (got ${ttlDays})`);\n\t\t}\n\n\t\tconst timestamp = now();\n\t\tconst expiresAt = timestamp + ttlDays * 86400;\n\t\tconst uid = optionalString(body, \"uid\") ?? newTagUid();\n\t\tconst signature = signTag(secret, uid, target, expiresAt);\n\t\tconst tag = rfid.create({\n\t\t\tuid,\n\t\t\tuserId: target,\n\t\t\tsignature,\n\t\t\tlabel: optionalString(body, \"label\"),\n\t\t\texpiresAt,\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: timestamp,\n\t\t});\n\n\t\tconst token = buildTagToken(uid, target, expiresAt, signature);\n\t\treturn json(201, { tag, token, url: tagLink(context, token) }, { location: `/rfid/tags/${tag.id}` });\n\t});\n\n\troute(\"DELETE\", \"/rfid/tags/:id\", { permission: \"rfid.manage\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst revoked = rfid.revoke({\n\t\t\tid: numberParam(context, \"id\"),\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\tif (!revoked) {\n\t\t\tthrow new NotFoundError(`tag ${context.params.id} not found or already revoked`);\n\t\t}\n\t\treturn noContent();\n\t});\n\n\t// A badge that was revoked can be removed from the list for good. Revoking is what stops the link from working,\n\t// so this only removes the row \u2014 the audit trail keeps the trace either way.\n\troute(\"DELETE\", \"/rfid/tags/:id/permanent\", { permission: \"rfid.manage\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst removed = rfid.remove({\n\t\t\tid: numberParam(context, \"id\"),\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\tif (!removed) {\n\t\t\tthrow new NotFoundError(`tag ${context.params.id} not found`);\n\t\t}\n\t\treturn noContent();\n\t});\n\n\t// Changes a badge: label, owner and validity. The signature binds uid, owner and expiry, so a new owner or a new\n\t// validity re-issues the link \u2014 the answer carries the new token then, and the link given out before is dead.\n\troute(\"PATCH\", \"/rfid/tags/:id\", { permission: \"rfid.manage\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst id = numberParam(context, \"id\");\n\t\tconst body = context.jsonBody();\n\t\tconst current = rfid.findById(id);\n\t\tif (!current) {\n\t\t\tthrow new NotFoundError(`tag ${context.params.id} not found`);\n\t\t}\n\t\tconst userId = body.userId === undefined ? current.userId : Number(body.userId);\n\t\tif (userId === null || !Number.isInteger(userId) || !users.findById(userId)) {\n\t\t\tthrow new ValidationError(\"userId must reference an existing employee\");\n\t\t}\n\t\tconst ttlDays = optionalNumber(body, \"ttlDays\") ?? undefined;\n\t\tif (ttlDays !== undefined && (!Number.isInteger(ttlDays) || ttlDays <= 0)) {\n\t\t\tthrow new ValidationError(`ttlDays must be a positive whole number (got ${ttlDays})`);\n\t\t}\n\t\tconst label = body.label === undefined ? undefined : optionalString(body, \"label\");\n\n\t\tif (userId !== current.userId || ttlDays !== undefined) {\n\t\t\t// the link has to be signed again: it carries the owner in its payload\n\t\t\tconst signed = reissueLink(context, { ...current, userId }, ttlDays ?? 365, label);\n\t\t\treturn json(200, { tag: signed.tag, token: signed.token, url: tagLink(context, signed.token) });\n\t\t}\n\n\t\t// a label on its own leaves the link alone \u2014 the signature does not know about it\n\t\tconst changed = rfid.update({\n\t\t\tid,\n\t\t\tuserId,\n\t\t\t...(label === undefined ? {} : { label }),\n\t\t\tactorId: context.auth.user.id,\n\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\tnow: now(),\n\t\t});\n\t\tif (!changed) {\n\t\t\tthrow new NotFoundError(`tag ${context.params.id} not found`);\n\t\t}\n\t\treturn json(200, { tag: changed });\n\t});\n\n\t// A new link for an existing badge: for one that was lost, expired, or revoked and should work again. The link\n\t// handed out before stops working with it.\n\troute(\"POST\", \"/rfid/tags/:id/link\", { permission: \"rfid.manage\", csrf: true }, context => {\n\t\tif (!context.auth) {\n\t\t\tthrow problem(401, \"no_session\", \"request rejected (no_session)\");\n\t\t}\n\t\tconst tag = rfid.findById(numberParam(context, \"id\"));\n\t\tif (!tag) {\n\t\t\tthrow new NotFoundError(`tag ${context.params.id} not found`);\n\t\t}\n\t\tconst ttlDays = optionalNumber(context.jsonBody(), \"ttlDays\") ?? 365;\n\t\tif (!Number.isInteger(ttlDays) || ttlDays <= 0) {\n\t\t\tthrow new ValidationError(`ttlDays must be a positive whole number (got ${ttlDays})`);\n\t\t}\n\t\tconst signed = reissueLink(context, tag, ttlDays);\n\t\treturn json(200, { tag: signed.tag, token: signed.token, url: tagLink(context, signed.token) });\n\t});\n\n\troute(\n\t\t\"POST\",\n\t\t\"/rfid/scan\",\n\t\t{ public: true, csrf: false, rateLimit: { name: \"rfid-scan\", limit: 30, windowSeconds: 60 } },\n\t\tcontext => {\n\t\t\tconst secret = requireHmacSecret();\n\t\t\tconst body = context.jsonBody();\n\t\t\tconst parsed = parseTagToken(requireString(body, \"token\"));\n\t\t\tif (!parsed) {\n\t\t\t\tthrow new ValidationError(\"token is not valid\");\n\t\t\t}\n\n\t\t\t// the signature is recomputed from the payload of the token and compared with the signature it carries,\n\t\t\t// so a forged uid/user/exp or a patched signature fails here\n\t\t\tconst expected = signTag(secret, parsed.uid, parsed.userId, parsed.expiresAt);\n\t\t\tif (!sameSignature(expected, parsed.signature)) {\n\t\t\t\tthrow problem(401, \"invalid_credentials\", \"the tag is not valid any more\");\n\t\t\t}\n\n\t\t\tconst tag = rfid.verify({\n\t\t\t\tuid: parsed.uid,\n\t\t\t\tuserId: parsed.userId,\n\t\t\t\tsignature: parsed.signature,\n\t\t\t\tnow: now(),\n\t\t\t});\n\t\t\tif (!tag) {\n\t\t\t\t// one answer for an unknown tag and a revoked one: no probing\n\t\t\t\tthrow problem(401, \"invalid_credentials\", \"the tag is not valid any more\");\n\t\t\t}\n\n\t\t\tconst user = users.findById(tag.userId ?? 0);\n\t\t\tif (!user || !user.isActive) {\n\t\t\t\tthrow problem(401, \"user_inactive\", \"the owner of the tag is not active\");\n\t\t\t}\n\n\t\t\tconst timestamp = now();\n\t\t\tconst stored = entries.insert({\n\t\t\t\tuserId: user.id,\n\t\t\t\ttsUtc: optionalNumber(body, \"tsUtc\") ?? timestamp,\n\t\t\t\ttimeZone: user.timezone,\n\t\t\t\tsource: \"nfc\",\n\t\t\t\tdirection: optionalDirection(body),\n\t\t\t\tnote: optionalString(body, \"note\"),\n\t\t\t\tactorId: user.id,\n\t\t\t\tactorIp: context.request.remoteAddress ?? null,\n\t\t\t\tnow: timestamp,\n\t\t\t});\n\t\t\trfid.touch({ id: tag.id, now: timestamp });\n\t\t\tconst day = aggregation.recalculateDay(user.id, stored.entry.localDate, { now: timestamp });\n\t\t\temit({\n\t\t\t\ttype: \"rfid.scan\",\n\t\t\t\tuserId: user.id,\n\t\t\t\tdata: {\n\t\t\t\t\tentryId: stored.entry.id,\n\t\t\t\t\tlocalDate: stored.entry.localDate,\n\t\t\t\t\ttagId: tag.id,\n\t\t\t\t\tuid: tag.uid,\n\t\t\t\t},\n\t\t\t});\n\n\t\t\treturn json(201, {\n\t\t\t\tuser: { id: user.id, displayName: user.displayName },\n\t\t\t\tentry: {\n\t\t\t\t\tid: stored.entry.id,\n\t\t\t\t\ttsUtc: stored.entry.tsUtc,\n\t\t\t\t\tlocalDate: stored.entry.localDate,\n\t\t\t\t},\n\t\t\t\tday: {\n\t\t\t\t\tworkedMin: day.workedMin,\n\t\t\t\t\ttargetMin: day.targetMin,\n\t\t\t\t\tbalanceMin: day.balanceMin,\n\t\t\t\t\thasOpenEntry: day.hasOpenEntry,\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\t);\n\n\t// reports\n\n\t/**\n\t * Collects everything a monthly statement needs.\n\t *\n\t * The Excel and the PDF export show the same statement, so they share this step: the caller is resolved\n\t * from `?userId=` (permission checked), the month is recalculated and read, and the labels follow the\n\t * language of the employee.\n\t *\n\t * @param context - route context with the authenticated caller\n\t * @returns the report input plus the year and month\n\t */\n\tconst monthlyReport = (\n\t\tcontext: RouteContext,\n\t): {\n\t\t/** Input of the PDF and Excel builder */\n\t\tinput: ReportInput & {\n\t\t\t/** Line of the generator that is printed into the document */\n\t\t\tgenerator: string;\n\t\t};\n\t\t/** Year of the statement */\n\t\tyear: number;\n\t\t/** Month of the statement (1-12) */\n\t\tmonth: number;\n\t\t/** Login of the employee, used in the file name */\n\t\tlogin: string;\n\t} => {\n\t\tconst userId = scopeUser(context);\n\t\tconst user = users.findById(userId);\n\t\tif (!user) {\n\t\t\tthrow new NotFoundError(`user ${userId} not found`);\n\t\t}\n\n\t\tconst year = numberQuery(context, \"year\");\n\t\tconst month = numberQuery(context, \"month\");\n\t\tif (month < 1 || month > 12) {\n\t\t\tthrow new ValidationError(`month must be between 1 and 12 (got ${month})`);\n\t\t}\n\n\t\tconst prefix = `${year}-${String(month).padStart(2, \"0\")}`;\n\t\tconst first = `${prefix}-01`;\n\t\tconst lastDay = new Date(Date.UTC(year, month, 0)).getUTCDate();\n\t\tconst last = `${prefix}-${String(lastDay).padStart(2, \"0\")}`;\n\t\tconst timestamp = now();\n\n\t\t// a statement has to show the current state of the month, so the range is recalculated first\n\t\taggregation.recalculateRange(userId, first, last, { now: timestamp });\n\t\tconst { labels, language } = reportLabels(user.locale);\n\n\t\treturn {\n\t\t\tyear,\n\t\t\tmonth,\n\t\t\tlogin: user.login,\n\t\t\tinput: {\n\t\t\t\tuser: { displayName: user.displayName, login: user.login, timezone: user.timezone },\n\t\t\t\tlabels,\n\t\t\t\tlanguage,\n\t\t\t\tlocale: user.locale || \"en\",\n\t\t\t\tyear,\n\t\t\t\tmonth,\n\t\t\t\tdays: aggregation.days(userId, first, last),\n\t\t\t\t// the document only lists effective absences \u2014 a request waits for its decision\n\t\t\t\tabsences: absences\n\t\t\t\t\t.withTypesInRange(userId, first, last)\n\t\t\t\t\t.filter(isApproved)\n\t\t\t\t\t.map(absence => ({\n\t\t\t\t\t\ttypeCode: absence.typeCode,\n\t\t\t\t\t\ttypeName: absence.typeName,\n\t\t\t\t\t\tdateFrom: absence.dateFrom,\n\t\t\t\t\t\tdateTo: absence.dateTo,\n\t\t\t\t\t\tdayPortion: absence.dayPortion,\n\t\t\t\t\t\thours: absence.hours,\n\t\t\t\t\t})),\n\t\t\t\tgeneratedAt: timestamp,\n\t\t\t\tgenerator: `time-tracker ${deps.version ?? \"\"}`.trim(),\n\t\t\t},\n\t\t};\n\t};\n\n\t// the raw punches of a month as CSV: for payroll and for tools that want the single punch instead of a total\n\troute(\n\t\t\"GET\",\n\t\t\"/reports/csv\",\n\t\t{ permission: \"report.view_own\", rateLimit: { name: \"export\", limit: 20, windowSeconds: 60 } },\n\t\tcontext => {\n\t\t\tconst { year, month, login } = monthlyReport(context);\n\t\t\tconst author = users.findById(scopeUser(context));\n\t\t\tif (!author) {\n\t\t\t\tthrow new NotFoundError(\"user not found\");\n\t\t\t}\n\t\t\tconst prefix = `${year}-${String(month).padStart(2, \"0\")}`;\n\t\t\tconst lastDay = new Date(Date.UTC(year, month, 0)).getUTCDate();\n\t\t\tconst csv = buildEntriesCsv(\n\t\t\t\tentries.listByRange(author.id, `${prefix}-01`, `${prefix}-${String(lastDay).padStart(2, \"0\")}`),\n\t\t\t\tauthor.timezone,\n\t\t\t);\n\n\t\t\treturn binary(200, csv, \"text/csv; charset=utf-8\", {\n\t\t\t\t\"content-disposition\": `attachment; filename=\"${reportFileName(login, year, month, \"csv\")}\"`,\n\t\t\t\t\"cache-control\": \"no-store\",\n\t\t\t});\n\t\t},\n\t);\n\n\troute(\n\t\t\"GET\",\n\t\t\"/reports/xls\",\n\t\t{ permission: \"report.view_own\", rateLimit: { name: \"export\", limit: 20, windowSeconds: 60 } },\n\t\tasync context => {\n\t\t\tconst { input, year, month, login } = monthlyReport(context);\n\t\t\tconst workbook = await buildMonthReport(input);\n\n\t\t\t// the file name is generated from the login, so it never contains a header delimiter\n\t\t\treturn binary(200, workbook, XLSX_CONTENT_TYPE, {\n\t\t\t\t\"content-disposition\": `attachment; filename=\"${reportFileName(login, year, month, \"xlsx\")}\"`,\n\t\t\t\t\"cache-control\": \"no-store\",\n\t\t\t});\n\t\t},\n\t);\n\n\troute(\n\t\t\"GET\",\n\t\t\"/reports/pdf\",\n\t\t{ permission: \"report.view_own\", rateLimit: { name: \"export\", limit: 20, windowSeconds: 60 } },\n\t\tasync context => {\n\t\t\tconst { input, year, month, login } = monthlyReport(context);\n\t\t\tlet statement: Buffer;\n\t\t\ttry {\n\t\t\t\tstatement = await buildMonthStatement({\n\t\t\t\t\t...input,\n\t\t\t\t\t// a Unicode font is only needed for scripts the built-in fonts cannot show (see the renderer)\n\t\t\t\t\tfontPath: settings.get(\"report_font_path\") ?? \"\",\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\t// a language the built-in fonts cannot draw is a configuration problem, not a bad request:\n\t\t\t\t// the UI can name the setting that solves it\n\t\t\t\tif (error instanceof ValidationError && error.message.includes(\"Unicode font\")) {\n\t\t\t\t\tthrow problem(\n\t\t\t\t\t\t422,\n\t\t\t\t\t\t\"report_font_missing\",\n\t\t\t\t\t\t\"this language needs a Unicode font: set report_font_path to a .ttf/.otf file that covers the script\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\n\t\t\treturn binary(200, statement, PDF_CONTENT_TYPE, {\n\t\t\t\t\"content-disposition\": `attachment; filename=\"${reportFileName(login, year, month, \"pdf\")}\"`,\n\t\t\t\t\"cache-control\": \"no-store\",\n\t\t\t});\n\t\t},\n\t);\n\n\t// backups (administration); the read side lists what exists, the write side takes a new copy\n\n\tif (deps.backup) {\n\t\tconst backup = deps.backup;\n\n\t\troute(\"GET\", \"/backup\", { permission: \"backup.run\" }, () =>\n\t\t\tjson(200, {\n\t\t\t\tretentionDays: settings.getNumber(\"backup_retention_days\", 30),\n\t\t\t\tbackups: backup.list(),\n\t\t\t\t// a restore that waits for the next start, so the screen can say so\n\t\t\t\tpending: backup.pending(),\n\t\t\t}),\n\t\t);\n\n\t\troute(\"GET\", \"/backup/:name\", { permission: \"backup.run\" }, context => {\n\t\t\t// only files the service knows are served: the requested name never reaches the file system\n\t\t\tconst wanted = context.params.name;\n\t\t\tconst file = backup.list().find(entry => entry.name === wanted);\n\t\t\tif (!file) {\n\t\t\t\tthrow new NotFoundError(`backup ${wanted} not found`);\n\t\t\t}\n\t\t\treturn binary(200, fs.readFileSync(file.file), \"application/vnd.sqlite3\", {\n\t\t\t\t\"content-disposition\": `attachment; filename=\"${file.name}\"`,\n\t\t\t\t\"cache-control\": \"no-store\",\n\t\t\t});\n\t\t});\n\n\t\troute(\n\t\t\t\"POST\",\n\t\t\t\"/backup\",\n\t\t\t{ permission: \"backup.run\", csrf: true, rateLimit: { name: \"backup\", limit: 10, windowSeconds: 60 } },\n\t\t\tcontext => {\n\t\t\t\tconst created = backup.create({\n\t\t\t\t\tactorId: context.auth?.user.id ?? null,\n\t\t\t\t\treason: \"api\",\n\t\t\t\t});\n\t\t\t\temit({ type: \"backup.create\", userId: null, data: { backup: created.backup.name } });\n\t\t\t\treturn json(201, { backup: created.backup, removed: created.removed });\n\t\t\t},\n\t\t);\n\n\t\t// A single backup is removed by hand; the retention keeps doing its own job in the background. Only files\n\t\t// of the list are deleted, so the requested name never reaches the file system.\n\t\troute(\n\t\t\t\"DELETE\",\n\t\t\t\"/backup/:name\",\n\t\t\t{ permission: \"backup.run\", csrf: true, rateLimit: { name: \"backup\", limit: 10, windowSeconds: 60 } },\n\t\t\tcontext => {\n\t\t\t\ttry {\n\t\t\t\t\tbackup.remove(context.params.name, { actorId: context.auth?.user.id ?? null, reason: \"api\" });\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (error instanceof ValidationError) {\n\t\t\t\t\t\t// a name that is not in the list is a missing resource, not a bad request\n\t\t\t\t\t\tthrow new NotFoundError(error.message);\n\t\t\t\t\t}\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t\treturn noContent(204);\n\t\t\t},\n\t\t);\n\n\t\t// One of the listed backups is queued for the next start: the swap itself happens while the adapter starts,\n\t\t// because a restore needs a closed database (see `restore` in the backup service). Only files of the list are\n\t\t// accepted, so the requested name never reaches the file system.\n\t\t//\n\t\t// The same route takes an uploaded file \u2014 the way back for a machine that lost its data directory. The browser\n\t\t// sends the chosen file as the raw body (`application/octet-stream`), everything else stays JSON with the name\n\t\t// of a listed backup. An upload is the only request that may exceed the router-wide body limit, so the route\n\t\t// raises it for itself (`MAX_BACKUP_UPLOAD_BYTES`; the server reads with the same bound).\n\t\troute(\n\t\t\t\"POST\",\n\t\t\t\"/backup/restore\",\n\t\t\t{\n\t\t\t\tpermission: \"backup.run\",\n\t\t\t\tcsrf: true,\n\t\t\t\trateLimit: { name: \"backup\", limit: 10, windowSeconds: 60 },\n\t\t\t\tmaxBodyBytes: MAX_BACKUP_UPLOAD_BYTES,\n\t\t\t},\n\t\t\tcontext => {\n\t\t\t\tconst upload = (context.header(\"content-type\") ?? \"\")\n\t\t\t\t\t.toLowerCase()\n\t\t\t\t\t.startsWith(\"application/octet-stream\");\n\t\t\t\ttry {\n\t\t\t\t\tif (upload) {\n\t\t\t\t\t\t// the file itself travels as the body; name and reason are query parameters\n\t\t\t\t\t\tconst pending = backup.queueRestore(context.rawBody(), {\n\t\t\t\t\t\t\tname: context.query(\"name\") ?? undefined,\n\t\t\t\t\t\t\tactorId: context.auth?.user.id ?? null,\n\t\t\t\t\t\t\treason: context.query(\"reason\") ?? undefined,\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn json(201, { pending });\n\t\t\t\t\t}\n\n\t\t\t\t\tconst body = context.jsonBody();\n\t\t\t\t\tconst name = typeof body.name === \"string\" ? body.name.trim() : \"\";\n\t\t\t\t\tif (name === \"\") {\n\t\t\t\t\t\tthrow problem(400, \"backup_invalid\", \"the request needs `name` with one of the listed backups\");\n\t\t\t\t\t}\n\t\t\t\t\tconst pending = backup.queueExistingBackup(name, {\n\t\t\t\t\t\tactorId: context.auth?.user.id ?? null,\n\t\t\t\t\t\treason: typeof body.reason === \"string\" ? body.reason : undefined,\n\t\t\t\t\t});\n\t\t\t\t\treturn json(201, { pending });\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (error instanceof ValidationError) {\n\t\t\t\t\t\t// the file is not a backup of this adapter: the message says what is wrong with it\n\t\t\t\t\t\tthrow problem(400, \"backup_invalid\", error.message);\n\t\t\t\t\t}\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t},\n\t\t);\n\t}\n\n\t// system\n\n\troute(\"GET\", \"/health\", { public: true }, () =>\n\t\tjson(200, {\n\t\t\tstatus: \"ok\",\n\t\t\tholidayCountry: settings.get(\"holiday_country\") ?? \"DE\",\n\t\t\ttime: now(),\n\t\t}),\n\t);\n\n\troute(\"GET\", \"/routes\", { public: true }, () => json(200, { routes: registered }));\n\n\treturn { router, routes: () => registered.slice(), events };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["_a", "_b", "_c", "_d", "punch", "localDate", "pending"]
7
7
  }