diff --git a/docs/proposals/bulk-actions/REDESIGN.md b/docs/proposals/bulk-actions/REDESIGN.md new file mode 100644 index 000000000..a53279c38 --- /dev/null +++ b/docs/proposals/bulk-actions/REDESIGN.md @@ -0,0 +1,214 @@ +# Bulk Actions — Consolidated Reimplementation Design + +## 0. How the seven lenses were reconciled + +Seven lenses split into two camps: + +- **"Go client-side + 6 modals"** (frontend-data, modal-ux recommendation, endpoints, testing-migration): move preview into the browser, one modal per action, one submit endpoint per action. +- **"The FE is fine, the real bug is backend preview/execute duplication"** (complexity critique, correctness critique, modal-ux *critique*, divergence critique): keep one modal (or thin per-action wrappers), fix the duplication by extracting a shared *decision function* both preview and execute call. + +The maintainer's brief is the tie-breaker and it is explicit: **per-action modals, client-side preview from existing FE data where feasible, one submit endpoint per action.** So we adopt the first camp's *shape*. But every critique of that camp landed on the same three load-bearing facts, which I verified in the actual code, and they force a **tiered** rather than uniform client-side move: + +1. **FE `memberSince` is the *earliest* membership start** (`useMemberRows.ts:30-34`, `reduce` to min), but backend fee resolution uses the ***latest*** membership start (`maxByOrNull { it.startDate }`, `BulkContributionReminderHandlers.kt:45`). A naive JS port of `resolveFeeType` would use the wrong date and silently send wrong fee tiers. This kills "just port FeeResolution to TS" unless we also expose the latest-membership start. +2. **Resume basis period is `periods.findLatest()`** (`BulkResumeMembershipCommandHandlers.kt:74`), a global most-recent period that the FE does **not** load and that is **not** `selectedPeriod`. The frontend-data lens guessed `selectedPeriod`; that guess is wrong and would flip WILL_RESUME↔WILL_START_NEW. Confirmed by correctness + frontend-data critiques. +3. **Reminder/incasso execute has an `email.isBlank()` skip that preview lacks** (`BulkContributionReminderHandlers.kt:146`), plus `lastSentOn` comes from an audit table (`reminders.findLastReminderForUserAndPeriod`) the FE never loads. Pure silent-divergence + a real UX signal that only the server has. + +**Verdict adopted:** the maintainer's target is correct as a *direction*, but "compute preview from FE data" is only *sound* for the actions whose entire decision input already lives in the FE. It is *unsound* for the two email actions (fee tier + audit) unless we ship more data than we have today. So the consolidated design is a **tiered preview**, not a blanket client-side move, and it pairs that with the backend camp's non-negotiable fix (single decision function per action) so that even the server-computed actions stop diverging. + +--- + +## 1. Target architecture + +``` +┌─────────────────────────── FRONTEND ───────────────────────────┐ +│ MemberManager.vue │ +│ ├─ useMemberSelection (unchanged) │ +│ ├─ useMemberRows / usePaidToggle (unchanged, source of truth │ +│ │ for FE-derived state: paid set, memberships, period) │ +│ └─ dynamic │ +│ renders exactly ONE of 6 per-action dialogs │ +│ │ +│ 6 per-action dialogs (thin), each ~70-110 lines: │ +│ MarkPaidDialog / MarkUnpaidDialog → FE preview │ +│ EndMembershipDialog → FE preview │ +│ ResumeMembershipDialog → SERVER preview │ +│ ReminderDialog / IncassoDialog → SERVER preview │ +│ │ +│ Shared FE building blocks: │ +│ BulkDialogScaffold.vue (title/counts/table/sort/confirm) │ +│ useBulkPreview() (rows, counts, reinclude, submit) │ +│ bulkDisposition.ts (label/color/reason pure helpers) │ +│ feePreview.ts (TS port of FeeResolution, tier 2) │ +└─────────────────────────────────────────────────────────────────┘ + │ HTTP (one submit endpoint per action) +┌─────────────────────────── BACKEND ────────────────────────────┐ +│ Controllers unchanged as 2 files, but preview endpoints removed │ +│ for stateless actions; kept only for reminder/incasso/resume. │ +│ │ +│ Per action: ONE decide() pure-ish domain function. │ +│ decide() → List (disposition+reason+fee context) │ +│ preview handler = decide() → rows │ +│ execute handler = decide() → apply side effects │ +│ ⇒ preview and execute can no longer diverge: same code path. │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### Preview tiering (the core resolved decision) + +| Action | Preview source | Why | +|---|---|---| +| mark-paid | **FE** | Decision = `userId ∈ paidUserIds`? SKIPPED(ALREADY_PAID):INCLUDED. All data in FE. No server preview endpoint. | +| mark-unpaid | **FE** | Mirror: `userId ∈ paidUserIds`? INCLUDED:SKIPPED(NOT_PAID). No server preview endpoint. | +| end-membership | **FE** | Decision = any membership with `endDate==null && startDate < today`. Memberships fully loaded. No server preview endpoint. **Needs server `today`** (see §4). | +| resume-membership | **SERVER** | Depends on `periods.findLatest()` basis period the FE does not have, and per-user full membership history with end dates. Keep preview endpoint. | +| contribution-reminder | **SERVER** | Fee tier needs *latest*-membership start (FE has *earliest*); `alreadyPaid`; `lastSentOn` from audit. Keep preview endpoint. | +| incasso-notification | **SERVER** | Same as reminder + incasso-flag check. Keep preview endpoint. | + +This directly answers the maintainer's item (2): we compute preview from FE data **wherever feasible** — and "feasible" is precisely the three stateless actions. We explicitly do **not** port fee resolution or duplicate audit/period lookups into the browser, because the correctness and frontend-data critiques both showed that path is a divergence generator, not a divergence fix. + +--- + +## 2. Endpoints (maintainer item 4: one submit endpoint per action) + +Rename to action-named paths so the generated client methods are unambiguous. Keep the two controller files (splitting into 6 controller files was rejected by the complexity + testing critiques as cosmetic churn; the controllers are 3-line dispatchers). + +### ContributionBulkController.kt + +| Method | Path | Request → Response | Notes | +|---|---|---|---| +| POST | `/contributions/bulk/mark-paid` | `BulkMarkPaidRequest{userIds, contributionPeriodId}` → `BulkActionResult` | execute only; **no preview** | +| POST | `/contributions/bulk/mark-unpaid` | `BulkMarkUnpaidRequest{userIds, contributionPeriodId}` → `BulkActionResult` | execute only; **no preview** | +| POST | `/contributionReminders/bulk/preview` | `BulkContributionReminderRequest{userIds, contributionPeriodId, cutoffDate, paymentDueDate}` → `BulkPreviewResult` | preview kept | +| POST | `/contributionReminders/bulk/execute` | `…+{includedUserIds, feeTypeOverrides}` → `BulkActionResult` | | +| POST | `/incassoNotifications/bulk/preview` | `BulkIncassoNotificationRequest{userIds, contributionPeriodId, cutoffDate, expectedIncassoDate}` → `BulkPreviewResult` | preview kept | +| POST | `/incassoNotifications/bulk/execute` | `…+{includedUserIds, feeTypeOverrides}` → `BulkActionResult` | | + +### MembershipBulkController.kt + +| Method | Path | Request → Response | Notes | +|---|---|---|---| +| POST | `/memberships/bulk/end/preview` | `BulkEndMembershipRequest{userIds}` → `BulkPreviewResult{…, serverToday}` | **kept but demoted**: only used to hand the FE the server `today`; FE still computes rows. Alternatively fold `today` into a cheap `/system/today` — see decision D4. | +| POST | `/memberships/bulk/end/execute` | `BulkEndMembershipRequest{userIds}` → `BulkActionResult` | | +| POST | `/memberships/bulk/resume/preview` | `BulkResumeMembershipRequest{userIds}` → `BulkPreviewResult` | preview kept | +| POST | `/memberships/bulk/resume/execute` | `BulkResumeMembershipRequest{userIds}` → `BulkActionResult` | | + +**Preview no longer receives `includedUserIds`/`feeTypeOverrides`** (they were passed and ignored — divergence + complexity lenses both flagged this dead asymmetry, `BulkActionConfirmDialog` sent `props.userIds` and `{}`). Preview is now purely the immutable server truth; operator overrides live only in FE state and are sent only to `execute`. This is the "preview is immutable, execute is operator-driven" fix the complexity lens converged on. + +Request DTOs become **immutable `data class`es with `val`** and Jakarta constraints (`@NotEmpty userIds`, `@Positive contributionPeriodId`, `@NotNull` dates) per the repo's jakarta-validation convention. + +No versioned/parallel endpoints, no adapter layer, no feature flag — the testing-migration lens proposed a 3-phase dual-stack rollout; the critique of that lens (and the "no external consumers of these board-only endpoints" reality) rejected it as coverage debt. This is a single-PR internal refactor. + +--- + +## 3. Backend: kill preview↔execute duplication (the fix every lens agreed on) + +For **every** action that still has server logic, extract ONE decision function; preview and execute both call it. This is the single most important backend change and it is what actually satisfies the maintainer's "split logic / dual behaviors / divergence-prone" complaint — not the endpoint rename. + +Resume already does this well (`classifyUser` + `ResumeOutcome` sealed class). Generalize the pattern: + +```kotlin +// per action, in the domain/application layer +data class ReminderDecision( + val userId: Long, val name: String, val memberType: MemberType, + val memberSince: LocalDate?, val disposition: BulkRowDisposition, + val reason: BulkRowReason?, val recommendedFeeType: BulkFeeType?, + val amount: Double?, val lastSentOn: LocalDate?, +) + +fun decideReminder(userId, periodId, period, cutoffDate, services): ReminderDecision { + val activeMembership = memberships.findByUserId(userId).maxByOrNull { it.startDate } + val memberType = activeMembership?.memberType ?: MemberType.REGULAR + val recommendedFeeType = resolveFeeType(memberType, activeMembership?.startDate, cutoffDate) + val alreadyPaid = contributions.existsByUserIdAndPeriodId(userId, periodId) + val emailMissing = users.findById(userId).email.isBlank() // ← now visible in preview + return when { + recommendedFeeType == null -> …EXCLUDED, HONORARY + emailMissing -> …SKIPPED, NO_EMAIL // ← NEW reason, ends silent skip + alreadyPaid -> …WARNING, ALREADY_PAID + else -> …INCLUDED + } +} +``` + +- **Preview handler:** map `decide()` → `BulkPreviewRow`. +- **Execute handler:** call `decide()`; if `disposition==INCLUDED` OR (`disposition==WARNING` AND `userId ∈ includedUserIds`) → apply, using `feeTypeOverrides[userId] ?: recommendedFeeType` for the amount; else count skipped/excluded. + +Effects of this on the confirmed bugs: +- **email-blank silent skip** → gone: `NO_EMAIL` is a first-class `BulkRowReason`, shown in preview. (New enum value; add to `BulkRowReason`.) +- **midnight `LocalDate.now()` drift** (end + resume) → gone within a request: `decide()` takes a single `actionDate` computed once at the top of each handler and threaded in. (The correctness lens rightly downgraded cross-request midnight risk as low, but making `today` a parameter is free and also enables the FE-end-membership tiering in §4.) +- **fee-override amount divergence** → contained: preview shows the recommended amount; execute recomputes from override; FE shows the effective amount from a local lookup as the operator changes the selector (no new preview round-trip). This is the complexity lens's "immutable preview + local effective recompute", and it only needs a **tiny** TS fee helper keyed off the row's *recommended* type and the period fees the FE already has — not a full port. + +Add **fee-override validation** on execute (endpoints critique gap): reject `feeTypeOverrides` for users who are EXCLUDED/HONORARY; reject overrides for users not in `includedUserIds`; missing override → recommended type. Enforce with a guard in the execute handler (throwing a `ValidationException` mapped to 400), consistent with jakarta-style FE errors. + +**Fee-override wiring is already live** in the reminder/incasso execute handlers (`feeTypeOverrides[userId] ?: recommendedFeeType`, line 152) — the testing-migration lens's worry that it was dead code is **false for reminder/incasso** (verified). Keep it; just add the validation guard. + +--- + +## 4. `serverToday` for end-membership FE preview + +End-membership is stateless enough for FE preview, but "started today" is a same-day boundary and browser TZ ≠ server TZ (system tests already pin Europe/Amsterdam for exactly this flake — see commit `4deb7a13`). Resolution: the **end-preview endpoint returns `serverToday: LocalDate`** in an extended envelope, and the FE dialog uses that date (not `new Date()`) to compute `startDate < serverToday`. This is one extra field, negligible cost, and eliminates the TZ pitfall the frontend-data and correctness lenses both raised. + +Rejected alternative: a generic `/system/today`. Folding it into the already-present end-preview call is simpler and keeps the FE decision atomic (D4). + +Note this means end-membership technically keeps a preview *call*, but the **row computation is FE-side** — the endpoint returns only counts + `serverToday`, not per-row dispositions. This honors "compute from FE data" while staying correct. + +--- + +## 5. Frontend components + +### 5.1 Shared scaffold (extract FIRST, before splitting — modal-ux critique's ordering) + +- **`BulkDialogScaffold.vue`** — BaseModal wrapper: title/icon slot, counts summary bar, the sortable preview `` with a `#row-extra` slot (fee selector / reinclude checkbox columns), cancel/confirm buttons, `useSubmitFeedback` wiring. Renders whatever `rows` + column config it's handed; knows nothing about action type. +- **`useBulkPreview.ts`** — generic composable ``: holds `rows`, `counts` (derived), `reincludeOverrides` map, computed `includedUserIds` (`INCLUDED ∪ (WARNING ∧ reincluded)`), `submitting`, and a `submit(fn)` runner. Both FE-preview and server-preview dialogs use it; the only difference is how `rows` are populated (local compute vs API call). +- **`bulkDisposition.ts`** — pure: `dispositionLabel`, `dispositionColor`, `rowColorClass`, `reasonLabel(reason)` (incl. new `NO_EMAIL`), `formatMemberSince`. Lifted verbatim out of the current monolith (they're already pure, `BulkActionConfirmDialog.vue:172-232`). +- **`feePreview.ts`** — minimal: `effectiveAmount(recommendedFeeType | overrideType, period)` for live re-display as the operator changes a row's fee selector. **Not** a port of `resolveFeeType` — the recommended type always comes from the server preview row; this only maps type→€ from `period.{fullYearFee,halfYearFee,alumniFee}`. + +### 5.2 Six per-action dialogs (thin) + +Each imports the scaffold + `useBulkPreview`, declares only its own form state and columns, and defines `loadPreview()` + `onSubmit()`: + +- **MarkPaidDialog.vue / MarkUnpaidDialog.vue** — `loadPreview()` computes rows locally from `paidUserIds` + selection; no fee/date UI; `onSubmit` → `markPaid`/`markUnpaid`. Simplest (~70 lines). +- **EndMembershipDialog.vue** — calls end-preview once to get `serverToday` + counts, computes rows locally from `memberships` using `serverToday`; no fee UI; `onSubmit` → `endMembership`. +- **ResumeMembershipDialog.vue** — server preview (WILL_RESUME / WILL_START_NEW / ALREADY_ACTIVE / NO_CONTRIBUTION_PERIOD); read-only rows; `onSubmit` → `resumeMembership`. +- **ReminderDialog.vue** — `paymentDueDate` + `cutoffDate` inputs (with client validation: cutoff within period), server preview, fee-type selector column + reinclude column, `onSubmit` sends `includedUserIds` + `feeTypeOverrides`. +- **IncassoDialog.vue** — same as Reminder but `expectedIncassoDate` and the INCASSO_MISMATCH warning row. + +### 5.3 Host + +**`MemberManager.vue`** renders `` bound to the selection + period + memberships it already holds. Each dialog owns its own open/close + reset; `MemberManager` loses the giant per-action branching it currently threads to the shared modal. `BulkActionsMenu.vue` unchanged (still emits the chosen action). + +**Why 6 dialogs and not a strategy object inside one modal:** the modal-ux critique argued a strategy pattern is cheaper. But the maintainer explicitly asked for one modal per action, and with the scaffold + `useBulkPreview` doing ~80% of the work each dialog is ~80 lines with **zero action-type conditionals**. The critique's "you just move branching into the composable" is avoided because `useBulkPreview` is genuinely action-agnostic (it never switches on action) — the differences are the `loadPreview`/`onSubmit` closures each dialog supplies, which is composition, not branching. + +--- + +## 6. What stays server-side and why (money/rules source of truth) + +- **Fee tier resolution** (`resolveFeeType`) — stays server-side; input (latest-membership start) not reliably in FE. FE only maps a *known* type to €. +- **`alreadyPaid`, `lastSentOn`, incasso-flag** — server (DB/audit). `alreadyPaid` for mark-paid/unpaid *is* mirrored (that's the whole point of `paidUserIds`), so those two go FE; for reminder/incasso the same fact is combined with fee/audit data that isn't in FE, so it stays server. +- **Resume basis period** (`findLatest`) + membership classification — server. +- **All authz** — `@PreAuthorize` on every endpoint incl. the retained previews; FE preview is display-only and never a security boundary (auth enforced at execute). +- **All mutations + amount actually recorded** — server, in `@Transactional` execute handlers via `decide()`. + +Staleness/concurrency (a board colleague edits state mid-dialog): accepted as eventual-consistency for previews, but **execute always re-runs `decide()` against live DB**, so it never acts on stale preview — it acts on truth. `BulkActionResult` already returns `applied/skipped/queued`, so the FE can surface "N rows changed since preview" from the delta. No checksum/version mechanism (the correctness lens proposed one but never defined it; the critique correctly called it placeholder — re-running `decide()` on execute is the real guarantee and it already exists). + +--- + +## 7. Tests (maintainer item 3: readability; testing-migration, tempered by its critique) + +- **Backend unit:** one `decide*Test.kt` per action testing the decision function across boundaries (cutoff edge `start == cutoff`, ALUMNI, HONORARY→EXCLUDED, blank email→NO_EMAIL, resume basis-period in/out, end started-today). This is where logic coverage concentrates now. +- **Backend IT:** keep per-endpoint but slim to contract + authz (403 non-board, 400 invalid override, happy path). **Add an invariant IT per server-preview action asserting preview disposition counts == execute outcome counts** for an unchanged DB — this is the regression net against the class of bug the maintainer is reacting to. +- **FE unit (vitest):** test the three FE-preview compute functions (mark-paid/unpaid/end) against `useMemberRows` snapshots incl. the `serverToday` boundary; test `useBulkPreview` reinclude/includedUserIds math once (shared). +- **E2E / system:** keep flows; for FE-preview actions drop the preview mock and only mock execute; keep preview mocks for reminder/incasso/resume. Update `MemberManagerBulkHelper` selectors for the new dialog components. + +OpenAPI: paths + DTOs change ⇒ regen required. Run `generate-openapi-local.sh` (H2, DB-free per memory) then `lint:gen`, else byte-diff fails. Verify `BulkRowReason` now includes `NO_EMAIL` and that `BulkActionType`/`BulkRowDisposition` export as `$ref` enums (endpoints lens flagged possible inlining — check `@Schema` present, which it already is on these enums). + +--- + +## 8. Sequencing (single PR, safe internal order) + +1. Backend: add `decide*()` functions; refactor existing preview+execute handlers to call them (behavior-preserving except the NO_EMAIL fix). Add `NO_EMAIL` reason. Add override validation guard. Add `serverToday` to end-preview envelope. +2. Backend: rename endpoints to action paths; drop mark-paid/unpaid preview endpoints; drop `includedUserIds`/`feeTypeOverrides` from preview requests. +3. Regen OpenAPI + client; `lint:gen`. +4. FE: extract scaffold + `useBulkPreview` + `bulkDisposition` + `feePreview` from the monolith. +5. FE: build 6 dialogs; wire `MemberManager` host; delete `BulkActionConfirmDialog.vue`. +6. Tests: decision unit tests, preview==execute invariants, FE compute unit tests, update e2e/system. diff --git a/services/api/openapi.json b/services/api/openapi.json index 888dbbb5b..e0d012f52 100644 --- a/services/api/openapi.json +++ b/services/api/openapi.json @@ -1 +1 @@ -{"components":{"schemas":{"ActionActorType":{"enum":["USER","SYSTEM"],"type":"string"},"Actor":{"properties":{"role":{"$ref":"#/components/schemas/Role"},"type":{"$ref":"#/components/schemas/ActionActorType"},"userId":{"format":"int64","type":"integer"}},"required":["role","type"],"type":"object"},"AddBoardMemberRequest":{"properties":{"endDate":{"format":"date","type":"string"},"role":{"minLength":1,"type":"string"},"startDate":{"format":"date","type":"string"},"userId":{"format":"int64","type":"integer"}},"required":["role","startDate","userId"],"type":"object"},"AddressResponse":{"properties":{"city":{"type":"string"},"country":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"houseNumber":{"type":"string"},"id":{"format":"int64","type":"integer"},"street":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"},"userId":{"format":"int64","type":"integer"},"version":{"format":"int64","type":"integer"},"zipCode":{"type":"string"}},"required":["createdAt","id","updatedAt","version"],"type":"object"},"AnswerRequest":{"properties":{"optionSelections":{"items":{"type":"boolean"},"type":"array"},"questionId":{"format":"int64","type":"integer"},"textResponse":{"type":"string"}},"required":["questionId"],"type":"object"},"AnswerResponse":{"properties":{"createdAt":{"format":"date-time","type":"string"},"id":{"format":"int64","type":"integer"},"optionSelections":{"items":{"type":"boolean"},"type":"array"},"questionId":{"format":"int64","type":"integer"},"textResponse":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"},"version":{"format":"int64","type":"integer"}},"required":["createdAt","id","questionId","updatedAt","version"],"type":"object"},"ApiError":{"description":"Problem Details for HTTP APIs including validation errors.","properties":{"detail":{"description":"Human-readable explanation specific to this occurrence.","example":"Validation failed for request.","type":"string"},"errors":{"description":"List of field/object validation errors (present when binding/validation fails).","items":{"$ref":"#/components/schemas/FieldValidationError"},"type":"array"},"instance":{"description":"A URI reference that identifies the specific occurrence.","example":"/api/v1/users","format":"uri","type":"string"},"status":{"description":"HTTP status code.","example":400,"format":"int32","type":"integer"},"title":{"description":"Short, human-readable summary of the problem.","example":"Bad Request","type":"string"},"traceId":{"description":"Trace or correlation id if available (Spring may add this via problem detail handlers).","example":"a8c0c4e5f1c24a7e","type":"string"},"type":{"description":"Problem type URI (RFC 7807).","example":"about:blank","type":"string"}}},"BlogResponse":{"properties":{"createdAt":{"format":"date-time","type":"string"},"html":{"type":"string"},"id":{"format":"int64","type":"integer"},"publishedAt":{"format":"date-time","type":"string"},"title":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"},"url":{"type":"string"},"version":{"format":"int64","type":"integer"}},"required":["createdAt","html","id","publishedAt","title","updatedAt","url","version"],"type":"object"},"BoardCreateMembershipRequest":{"properties":{"endDate":{"format":"date","type":"string"},"incasso":{"type":"boolean"},"memberType":{"$ref":"#/components/schemas/MemberType"},"startDate":{"format":"date","type":"string"},"userId":{"format":"int64","type":"integer"}},"required":["incasso","memberType","userId"],"type":"object"},"BoardMemberResponse":{"properties":{"boardId":{"format":"int64","type":"integer"},"createdAt":{"format":"date-time","type":"string"},"endDate":{"format":"date","type":"string"},"role":{"type":"string"},"startDate":{"format":"date","type":"string"},"updatedAt":{"format":"date-time","type":"string"},"userId":{"format":"int64","type":"integer"},"version":{"format":"int64","type":"integer"}},"required":["boardId","createdAt","role","startDate","updatedAt","userId","version"],"type":"object"},"BoardResponse":{"properties":{"candidate":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"endDate":{"format":"date","type":"string"},"id":{"format":"int64","type":"integer"},"members":{"items":{"$ref":"#/components/schemas/BoardMemberResponse"},"type":"array"},"name":{"type":"string"},"pictureId":{"format":"int64","type":"integer"},"startDate":{"format":"date","type":"string"},"updatedAt":{"format":"date-time","type":"string"},"version":{"format":"int64","type":"integer"}},"required":["candidate","createdAt","id","members","name","startDate","updatedAt","version"],"type":"object"},"CohortDetail":{"properties":{"externalId":{"type":"string"},"folder":{"type":"string"},"id":{"format":"int64","type":"integer"},"kind":{"$ref":"#/components/schemas/CohortKind"},"label":{"type":"string"},"memberCount":{"format":"int32","type":"integer"},"members":{"items":{"$ref":"#/components/schemas/CohortMemberRow"},"type":"array"},"rules":{"items":{"$ref":"#/components/schemas/CohortRule"},"type":"array"},"system":{"type":"string"}},"required":["id","kind","label","memberCount","members","rules","system"],"type":"object"},"CohortFactKind":{"enum":["ROLE","COMMITTEE","CONTRIBUTION_PAID","MEMBER_IN_PERIOD","NEWSLETTER","ACTIVE_IN_PERIOD"],"type":"string"},"CohortKind":{"enum":["LIST","ROLE","GROUP"],"type":"string"},"CohortMapping":{"properties":{"cohortId":{"format":"int64","type":"integer"},"externalId":{"type":"string"},"kind":{"$ref":"#/components/schemas/CohortKind"},"label":{"type":"string"},"system":{"$ref":"#/components/schemas/TargetSystem","description":"External system this mapping targets"}},"required":["cohortId","kind","label","system"],"type":"object"},"CohortMemberRow":{"properties":{"cohortMemberId":{"format":"int64","type":"integer"},"isUserDeleted":{"type":"boolean"},"joinedAt":{"format":"date-time","type":"string"},"userEmail":{"type":"string"},"userFullName":{"type":"string"},"userId":{"format":"int64","type":"integer"}},"required":["cohortMemberId","isUserDeleted","joinedAt","userId"],"type":"object"},"CohortRepair":{"properties":{"cohortId":{"format":"int64","type":"integer"},"enqueuedAdds":{"format":"int32","type":"integer"}},"required":["cohortId","enqueuedAdds"],"type":"object"},"CohortRule":{"properties":{"enabled":{"type":"boolean"},"factKey":{"type":"string"},"factKind":{"$ref":"#/components/schemas/CohortFactKind"},"id":{"format":"int64","type":"integer"}},"required":["enabled","factKey","factKind","id"],"type":"object"},"CohortSubjectCategory":{"enum":["COMMITTEES","PERIODS","MEMBERS","OTHER"],"type":"string"},"CohortSubjectDetail":{"properties":{"category":{"$ref":"#/components/schemas/CohortSubjectCategory"},"description":{"type":"string"},"id":{"format":"int64","type":"integer"},"label":{"type":"string"},"mappings":{"items":{"$ref":"#/components/schemas/CohortMapping"},"type":"array"},"members":{"items":{"$ref":"#/components/schemas/CohortSubjectMember"},"type":"array"},"rules":{"items":{"$ref":"#/components/schemas/CohortSubjectRule"},"type":"array"},"type":{"$ref":"#/components/schemas/CohortSubjectType"}},"required":["category","id","label","mappings","members","rules","type"],"type":"object"},"CohortSubjectMember":{"properties":{"cohortMemberId":{"format":"int64","type":"integer"},"isUserDeleted":{"type":"boolean"},"joinedAt":{"format":"date-time","type":"string"},"userEmail":{"type":"string"},"userFullName":{"type":"string"},"userId":{"format":"int64","type":"integer"}},"required":["cohortMemberId","isUserDeleted","joinedAt","userId"],"type":"object"},"CohortSubjectRule":{"properties":{"enabled":{"type":"boolean"},"factKey":{"type":"string"},"factKind":{"$ref":"#/components/schemas/CohortFactKind"},"id":{"format":"int64","type":"integer"}},"required":["enabled","factKey","factKind","id"],"type":"object"},"CohortSubjectSummary":{"properties":{"category":{"$ref":"#/components/schemas/CohortSubjectCategory"},"id":{"format":"int64","type":"integer"},"label":{"type":"string"},"mappingCount":{"format":"int32","type":"integer"},"memberCount":{"format":"int32","type":"integer"},"type":{"$ref":"#/components/schemas/CohortSubjectType"}},"required":["category","id","label","mappingCount","memberCount","type"],"type":"object"},"CohortSubjectType":{"enum":["COMMITTEE_MEMBERS","PERIOD_PAYERS","PERIOD_MEMBERS","PERIOD_ACTIVE_MEMBERS","NEWSLETTER_SUBSCRIBERS","CUSTOM"],"type":"string"},"CohortSummary":{"properties":{"externalId":{"type":"string"},"folder":{"type":"string"},"id":{"format":"int64","type":"integer"},"kind":{"$ref":"#/components/schemas/CohortKind"},"label":{"type":"string"},"memberCount":{"format":"int32","type":"integer"},"system":{"type":"string"}},"required":["id","kind","label","memberCount","system"],"type":"object"},"CommitteeDetailResponse":{"properties":{"createdAt":{"format":"date-time","type":"string"},"description":{"maxLength":4095,"minLength":0,"type":"string"},"id":{"format":"int64","type":"integer"},"members":{"items":{"$ref":"#/components/schemas/CommitteeMemberResponse"},"minItems":1,"type":"array"},"name":{"maxLength":255,"minLength":0,"type":"string"},"updatedAt":{"format":"date-time","type":"string"},"version":{"format":"int64","type":"integer"}},"required":["createdAt","description","id","members","name","updatedAt","version"],"type":"object"},"CommitteeMemberRequest":{"properties":{"role":{"minLength":1,"type":"string"},"userId":{"format":"int64","type":"integer"}},"required":["role","userId"],"type":"object"},"CommitteeMemberResponse":{"properties":{"committeeId":{"format":"int64","type":"integer"},"createdAt":{"format":"date-time","type":"string"},"role":{"minLength":1,"type":"string"},"updatedAt":{"format":"date-time","type":"string"},"userId":{"format":"int64","type":"integer"},"version":{"format":"int64","type":"integer"}},"required":["committeeId","createdAt","role","updatedAt","userId","version"],"type":"object"},"CommitteeResponse":{},"ContactSystem":{"enum":["BREVO"],"type":"string"},"ContributionPeriodResponse":{"properties":{"alumniFee":{"format":"double","type":"number"},"contactListId":{"format":"int64","type":"integer"},"createdAt":{"format":"date-time","type":"string"},"endDate":{"format":"date","type":"string"},"fullYearFee":{"format":"double","type":"number"},"halfYearFee":{"format":"double","type":"number"},"id":{"format":"int64","type":"integer"},"startDate":{"format":"date","type":"string"},"updatedAt":{"format":"date-time","type":"string"},"version":{"format":"int64","type":"integer"}},"required":["alumniFee","createdAt","endDate","fullYearFee","halfYearFee","id","startDate","updatedAt","version"],"type":"object"},"ContributionReminderResponse":{"properties":{"contributionPeriodId":{"format":"int64","type":"integer"},"createdAt":{"format":"date-time","type":"string"},"remindedAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"},"userId":{"format":"int64","type":"integer"},"version":{"format":"int64","type":"integer"}},"required":["contributionPeriodId","createdAt","updatedAt","userId","version"],"type":"object"},"ContributionResponse":{"properties":{"contributionPeriodId":{"format":"int64","type":"integer"},"createdAt":{"format":"date-time","type":"string"},"remindedAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"},"userId":{"format":"int64","type":"integer"},"version":{"format":"int64","type":"integer"}},"required":["contributionPeriodId","createdAt","updatedAt","userId","version"],"type":"object"},"CreateAddressRequest":{"properties":{"city":{"minLength":1,"type":"string"},"country":{"minLength":1,"type":"string"},"houseNumber":{"minLength":1,"type":"string"},"street":{"minLength":1,"type":"string"},"userId":{"format":"int64","type":"integer"},"zipCode":{"minLength":1,"type":"string"}},"required":["city","country","houseNumber","street","userId","zipCode"],"type":"object"},"CreateBlogRequest":{"properties":{"html":{"minLength":1,"type":"string"},"publishedAt":{"format":"date-time","type":"string"},"title":{"minLength":1,"type":"string"}},"required":["html","publishedAt","title"],"type":"object"},"CreateBoardRequest":{"properties":{"candidate":{"minLength":1,"type":"string"},"endDate":{"format":"date","type":"string"},"name":{"maxLength":100,"minLength":1,"type":"string"},"pictureId":{"format":"int64","type":"integer"},"startDate":{"format":"date","type":"string"}},"required":["candidate","name","startDate"],"type":"object"},"CreateCommitteeRequest":{"properties":{"description":{"maxLength":4095,"minLength":0,"type":"string"},"members":{"items":{"$ref":"#/components/schemas/CommitteeMemberRequest"},"minItems":1,"type":"array"},"name":{"maxLength":255,"minLength":0,"type":"string"}},"required":["description","members","name"],"type":"object"},"CreateContributionPeriodRequest":{"properties":{"alumniFee":{"format":"double","type":"number"},"contactListId":{"format":"int64","type":"integer"},"endDate":{"format":"date","type":"string"},"fullYearFee":{"format":"double","type":"number"},"halfYearFee":{"format":"double","type":"number"},"startDate":{"format":"date","type":"string"}},"required":["alumniFee","endDate","fullYearFee","halfYearFee","startDate"],"type":"object"},"CreateContributionReminderRequest":{"properties":{"contributionPeriodId":{"format":"int64","type":"integer"},"userId":{"format":"int64","type":"integer"}},"required":["contributionPeriodId","userId"],"type":"object"},"CreateContributionRequest":{"properties":{"contributionPeriodId":{"format":"int64","type":"integer"},"userId":{"format":"int64","type":"integer"}},"required":["contributionPeriodId","userId"],"type":"object"},"CreateEventRequest":{"properties":{"approved":{"type":"boolean"},"banner":{"$ref":"#/components/schemas/EventBannerRequest"},"committeeId":{"format":"int64","type":"integer"},"description":{"maxLength":4095,"minLength":0,"type":"string"},"endTime":{"format":"date-time","type":"string"},"location":{"type":"string"},"memberPrice":{"format":"double","type":"number"},"membersOnly":{"type":"boolean"},"publicPrice":{"format":"double","type":"number"},"signUp":{"type":"boolean"},"signUpDeadline":{"format":"date-time","type":"string"},"signUpForm":{"$ref":"#/components/schemas/SurveyRequest"},"signUpLimit":{"format":"int32","minimum":1,"type":"integer"},"startTime":{"format":"date-time","type":"string"},"title":{"maxLength":255,"minLength":0,"type":"string"}},"required":["approved","committeeId","description","endTime","membersOnly","signUp","startTime","title"],"type":"object"},"CreateEventSignUpRequest":{"properties":{"answers":{"items":{"$ref":"#/components/schemas/AnswerRequest"},"type":"array"},"guest":{"$ref":"#/components/schemas/CreateGuestRequest"},"userId":{"format":"int64","type":"integer"}},"type":"object"},"CreateGuestRequest":{"properties":{"discord":{"minLength":1,"type":"string"},"email":{"minLength":1,"type":"string"},"name":{"minLength":1,"type":"string"},"phoneNumber":{"type":"string"},"version":{"format":"int64","type":"integer"}},"required":["discord","email","name"],"type":"object"},"CreateMemberProfileRequest":{"properties":{"bhv":{"type":"boolean"},"dateOfBirth":{"format":"date","type":"string"},"ehbo":{"type":"boolean"},"gender":{"type":"string"},"nationality":{"minLength":1,"type":"string"},"studentNumber":{"type":"string"},"userId":{"format":"int64","type":"integer"}},"required":["bhv","dateOfBirth","ehbo","nationality","userId"],"type":"object"},"CreateSponsorRequest":{"properties":{"description":{"maxLength":4095,"minLength":0,"type":"string"},"name":{"maxLength":255,"minLength":0,"type":"string"}},"required":["description","name"],"type":"object"},"CreateTargetRequest":{"properties":{"folderHint":{"type":"string"},"label":{"minLength":1,"type":"string"},"system":{"$ref":"#/components/schemas/TargetSystem"}},"required":["label","system"],"type":"object"},"CreateTelemetryRequest":{"properties":{"platform":{"$ref":"#/components/schemas/PlatformType"},"url":{"minLength":1,"type":"string"}},"required":["platform","url"],"type":"object"},"CreateUserRequest":{"properties":{"consentPrivacy":{"type":"boolean"},"discord":{"minLength":1,"type":"string"},"email":{"minLength":1,"type":"string"},"firstName":{"minLength":1,"type":"string"},"fullName":{"type":"string"},"initials":{"minLength":1,"type":"string"},"lastName":{"minLength":1,"type":"string"},"memberProfile":{"$ref":"#/components/schemas/UpsertMemberProfileRequest"},"newsletter":{"type":"boolean"},"password":{"type":"string"},"phoneNumber":{"minLength":1,"type":"string"},"photoConsent":{"type":"boolean"},"prefix":{"type":"string"},"username":{"minLength":1,"type":"string"}},"required":["discord","email","firstName","initials","lastName","newsletter","phoneNumber","username"],"type":"object"},"CsrfToken":{"properties":{"headerName":{"type":"string"},"parameterName":{"type":"string"},"token":{"type":"string"}},"type":"object"},"DriftReport":{"properties":{"cohortId":{"format":"int64","type":"integer"},"externalCohortId":{"type":"string"},"extras":{"items":{"$ref":"#/components/schemas/ExtraRow"},"type":"array"},"lastReconciledAt":{"format":"date-time","type":"string"},"missing":{"items":{"$ref":"#/components/schemas/MissingRow"},"type":"array"},"system":{"$ref":"#/components/schemas/TargetSystem"}},"required":["cohortId","extras","missing","system"],"type":"object"},"Email":{"properties":{"attempts":{"format":"int32","type":"integer"},"createdAt":{"format":"date-time","type":"string"},"deliveredAt":{"format":"date-time","type":"string"},"deliveryStatus":{"$ref":"#/components/schemas/EmailDeliveryStatus"},"emailType":{"type":"string"},"errorReason":{"type":"string"},"errorType":{"type":"string"},"id":{"format":"int64","type":"integer"},"jobExecutionId":{"format":"int64","type":"integer"},"messageId":{"type":"string"},"openedAt":{"format":"date-time","type":"string"},"recipientEmail":{"type":"string"},"recipientName":{"type":"string"},"sentAt":{"format":"date-time","type":"string"},"subject":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"type":"object"},"EmailDeliveryStatus":{"enum":["PENDING","SENT","DELIVERED","OPENED","BOUNCED","FAILED"],"type":"string"},"EmailStats":{"properties":{"bouncedCount":{"format":"int64","type":"integer"},"deliveredCount":{"format":"int64","type":"integer"},"failedCount":{"format":"int64","type":"integer"},"openedCount":{"format":"int64","type":"integer"},"pendingCount":{"format":"int64","type":"integer"},"sentCount":{"format":"int64","type":"integer"},"totalCount":{"format":"int64","type":"integer"}},"required":["bouncedCount","deliveredCount","failedCount","openedCount","pendingCount","sentCount","totalCount"],"type":"object"},"EnqueueJobRequest":{"properties":{"jobType":{"minLength":1,"type":"string"},"payload":{"additionalProperties":{},"description":"Job payload fields keyed by name; shape depends on the job type","type":"object"}},"required":["jobType"],"type":"object"},"EventBannerRequest":{"properties":{"fileId":{"format":"int64","type":"integer"},"version":{"format":"int64","type":"integer"}},"required":["fileId"],"type":"object"},"EventBannerResponse":{"properties":{"createdAt":{"format":"date-time","type":"string"},"eventId":{"format":"int64","type":"integer"},"fileId":{"format":"int64","type":"integer"},"updatedAt":{"format":"date-time","type":"string"},"version":{"format":"int64","type":"integer"}},"required":["createdAt","eventId","fileId","updatedAt","version"],"type":"object"},"EventResponse":{"properties":{"approved":{"type":"boolean"},"banner":{"$ref":"#/components/schemas/EventBannerResponse"},"committeeId":{"format":"int64","type":"integer"},"createdAt":{"format":"date-time","type":"string"},"description":{"maxLength":4095,"minLength":0,"type":"string"},"endTime":{"format":"date-time","type":"string"},"id":{"format":"int64","type":"integer"},"location":{"type":"string"},"memberPrice":{"format":"double","type":"number"},"membersOnly":{"type":"boolean"},"publicPrice":{"format":"double","type":"number"},"signUp":{"type":"boolean"},"signUpCount":{"format":"int64","type":"integer"},"signUpDeadline":{"format":"date-time","type":"string"},"signUpForm":{"$ref":"#/components/schemas/SurveyResponse"},"signUpLimit":{"format":"int32","type":"integer"},"startTime":{"format":"date-time","type":"string"},"title":{"maxLength":255,"minLength":0,"type":"string"},"updatedAt":{"format":"date-time","type":"string"},"version":{"format":"int64","type":"integer"}},"required":["approved","createdAt","description","endTime","id","membersOnly","signUp","signUpCount","startTime","title","updatedAt","version"],"type":"object"},"EventSignUpResponse":{"properties":{"answers":{"items":{"$ref":"#/components/schemas/AnswerResponse"},"type":"array"},"createdAt":{"format":"date-time","type":"string"},"eventId":{"format":"int64","type":"integer"},"guest":{"$ref":"#/components/schemas/GuestResponse"},"id":{"format":"int64","type":"integer"},"updatedAt":{"format":"date-time","type":"string"},"user":{"$ref":"#/components/schemas/UserSummaryResponse"},"version":{"format":"int64","type":"integer"}},"required":["answers","createdAt","eventId","id","updatedAt","version"],"type":"object"},"ExternalTarget":{"properties":{"externalId":{"type":"string"},"folderLabel":{"type":"string"},"kind":{"$ref":"#/components/schemas/CohortKind"},"label":{"type":"string"},"linkedCohortId":{"format":"int64","type":"integer"},"memberCount":{"format":"int64","type":"integer"},"system":{"$ref":"#/components/schemas/TargetSystem"}},"required":["externalId","kind","label","system"],"type":"object"},"ExtraRow":{"properties":{"email":{"type":"string"},"externalUserId":{"type":"string"},"fullName":{"type":"string"},"kind":{"enum":["KNOWN_LOCAL_USER","UNKNOWN_EXTERNAL"],"type":"string"},"label":{"type":"string"},"softDeleted":{"type":"boolean"},"userId":{"format":"int64","type":"integer"}},"required":["externalUserId","kind"],"type":"object"},"FieldValidationError":{"description":"Details about a single field/object validation error.","properties":{"code":{"description":"Validation code / constraint key.","example":"Email","type":"string"},"field":{"description":"Field that failed validation (null for global errors).","example":"email","type":"string"},"message":{"description":"Human-readable validation message.","example":"must be a well-formed email address","type":"string"},"objectName":{"description":"Object (target) name that failed validation.","example":"createUserRequest","type":"string"}}},"FileResponse":{"properties":{"createdAt":{"format":"date-time","type":"string"},"id":{"format":"int64","type":"integer"},"mediaType":{"type":"string"},"name":{"maxLength":255,"minLength":0,"type":"string"},"path":{"type":"string"},"size":{"format":"int64","type":"integer"},"type":{"$ref":"#/components/schemas/FileType"},"updatedAt":{"format":"date-time","type":"string"},"version":{"format":"int64","type":"integer"}},"required":["createdAt","id","mediaType","name","path","type","updatedAt","version"],"type":"object"},"FileType":{"enum":["DOCUMENT","PROFILE_PICTURE","EVENT_BANNER","EVENT_PICTURE","SPONSOR_PICTURE"],"type":"string"},"GuestResponse":{"properties":{"createdAt":{"format":"date-time","type":"string"},"discord":{"type":"string"},"email":{"type":"string"},"id":{"format":"int64","type":"integer"},"name":{"type":"string"},"phoneNumber":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"},"version":{"format":"int64","type":"integer"}},"required":["createdAt","discord","email","id","name","updatedAt","version"],"type":"object"},"InboundReconcileApplyRequest":{"properties":{"previewToken":{"type":"string"},"selectedExternalUserIds":{"items":{"type":"string"},"type":"array"}},"required":["previewToken","selectedExternalUserIds"],"type":"object"},"InboundReconcileApplyResponse":{"properties":{"acceptedCount":{"format":"int32","type":"integer"},"jobId":{"format":"int64","type":"integer"},"skippedCount":{"format":"int32","type":"integer"}},"required":["acceptedCount","skippedCount"],"type":"object"},"InboundReconcilePreview":{"properties":{"fact":{"$ref":"#/components/schemas/SubjectFact"},"matched":{"items":{"$ref":"#/components/schemas/InboundReconcileRow"},"type":"array"},"previewToken":{"type":"string"},"remoteCount":{"format":"int32","type":"integer"},"skipped":{"items":{"$ref":"#/components/schemas/InboundReconcileRow"},"type":"array"},"writerSupported":{"type":"boolean"}},"required":["fact","matched","previewToken","remoteCount","skipped","writerSupported"],"type":"object"},"InboundReconcileRow":{"properties":{"alreadyTrue":{"type":"boolean"},"externalLabel":{"type":"string"},"externalUserId":{"type":"string"},"reason":{"enum":["DUPLICATE_REMOTE_ID","MAPPING_CONFLICT","DUPLICATE_USER_MATCH","MAPPED_USER_INACTIVE","UNMATCHED"],"type":"string"},"userEmail":{"type":"string"},"userFullName":{"type":"string"},"userId":{"format":"int64","type":"integer"},"writable":{"type":"boolean"}},"required":["alreadyTrue","externalUserId","writable"],"type":"object"},"JobExecution":{"properties":{"actor":{"$ref":"#/components/schemas/Actor"},"attempts":{"format":"int32","type":"integer"},"category":{"$ref":"#/components/schemas/JobExecutionCategory"},"createdAt":{"format":"date-time","type":"string"},"dedupKey":{"type":"string"},"errorMessage":{"type":"string"},"errorReason":{"type":"string"},"errorType":{"type":"string"},"finishedAt":{"format":"date-time","type":"string"},"id":{"format":"int64","type":"integer"},"initiatedByDisplay":{"type":"string"},"initiatedByFullName":{"type":"string"},"initiatedByRole":{"$ref":"#/components/schemas/Role"},"initiatedByType":{"$ref":"#/components/schemas/ActionActorType"},"initiatedByUserId":{"format":"int64","type":"integer"},"initiatedByUsername":{"type":"string"},"jobType":{"minLength":1,"type":"string"},"nextAttemptAt":{"format":"date-time","type":"string"},"payload":{"additionalProperties":{},"type":"object"},"queuedAt":{"format":"date-time","type":"string"},"relatedEntities":{"items":{"$ref":"#/components/schemas/JobExecutionRelatedEntity"},"type":"array"},"stackTrace":{"type":"string"},"startedAt":{"format":"date-time","type":"string"},"status":{"$ref":"#/components/schemas/JobExecutionStatus"},"targetSystem":{"$ref":"#/components/schemas/ContactSystem"},"updatedAt":{"format":"date-time","type":"string"}},"required":["attempts","jobType","relatedEntities","status"],"type":"object"},"JobExecutionCategory":{"enum":["calendar","contact","cohort","email","other"],"type":"string"},"JobExecutionRelatedEntity":{"properties":{"id":{"format":"int64","type":"integer"},"label":{"type":"string"},"type":{"type":"string"}},"required":["label","type"],"type":"object"},"JobExecutionStatus":{"enum":["QUEUED","RUNNING","SUCCESS","FAILED","DEAD"],"type":"string"},"JobPayloadField":{"properties":{"enumValues":{"items":{"type":"string"},"type":"array"},"kind":{"$ref":"#/components/schemas/JobPayloadFieldKind"},"name":{"type":"string"},"required":{"type":"boolean"},"type":{"type":"string"}},"required":["kind","name","required","type"],"type":"object"},"JobPayloadFieldKind":{"enum":["PRIMITIVE","ENUM","OBJECT"],"type":"string"},"JobStatsDTO":{"properties":{"avgSuccessDurationSeconds":{"format":"double","type":"number"},"deadCount":{"format":"int64","type":"integer"},"deadSinceStartup":{"format":"double","type":"number"},"failedCount":{"format":"int64","type":"integer"},"failedSinceStartup":{"format":"double","type":"number"},"queuedCount":{"format":"int64","type":"integer"},"recoveriesSinceStartup":{"format":"double","type":"number"},"runningCount":{"format":"int64","type":"integer"},"successCount":{"format":"int64","type":"integer"},"totalCount":{"format":"int64","type":"integer"}},"required":["avgSuccessDurationSeconds","deadCount","deadSinceStartup","failedCount","failedSinceStartup","queuedCount","recoveriesSinceStartup","runningCount","successCount","totalCount"],"type":"object"},"JobTypeDescriptor":{"properties":{"payloadFields":{"items":{"$ref":"#/components/schemas/JobPayloadField"},"type":"array"},"type":{"type":"string"}},"required":["payloadFields","type"],"type":"object"},"JwtRequest":{"properties":{"password":{"minLength":1,"type":"string"},"username":{"minLength":1,"type":"string"}},"required":["password","username"],"type":"object"},"LinkExistingTargetRequest":{"properties":{"externalId":{"minLength":1,"type":"string"},"system":{"$ref":"#/components/schemas/TargetSystem"}},"required":["externalId","system"],"type":"object"},"LinkUserRequest":{"properties":{"externalUserId":{"minLength":1,"type":"string"},"system":{"$ref":"#/components/schemas/TargetSystem"},"userId":{"format":"int64","type":"integer"}},"required":["externalUserId","system","userId"],"type":"object"},"LinkedUser":{"properties":{"externalUserId":{"type":"string"},"system":{"$ref":"#/components/schemas/TargetSystem"},"userId":{"format":"int64","type":"integer"}},"required":["externalUserId","system","userId"],"type":"object"},"LoginResponse":{"properties":{"addressId":{"format":"int64","type":"integer"},"expiration":{"format":"int64","type":"integer"},"roles":{"items":{"$ref":"#/components/schemas/Role"},"minItems":1,"type":"array"},"token":{"minLength":1,"type":"string"},"userId":{"format":"int64","type":"integer"},"username":{"minLength":1,"type":"string"}},"required":["expiration","roles","token","userId","username"],"type":"object"},"MemberActivationRequest":{"properties":{"password":{"maxLength":100,"minLength":8,"pattern":"^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]+$","type":"string"},"token":{"minLength":1,"type":"string"},"username":{"minLength":1,"type":"string"}},"required":["password","token","username"],"type":"object"},"MemberProfileResponse":{"properties":{"bhv":{"type":"boolean"},"createdAt":{"format":"date-time","type":"string"},"dateOfBirth":{"format":"date","type":"string"},"ehbo":{"type":"boolean"},"gender":{"type":"string"},"id":{"format":"int64","type":"integer"},"nationality":{"type":"string"},"studentNumber":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"},"userId":{"format":"int64","type":"integer"},"version":{"format":"int64","type":"integer"}},"required":["bhv","createdAt","ehbo","id","updatedAt","userId","version"]},"MemberType":{"enum":["ALUMNI","HONORARY","REGULAR","NONE"],"type":"string"},"MembershipResponse":{"properties":{"createdAt":{"format":"date-time","type":"string"},"endDate":{"format":"date","type":"string"},"id":{"format":"int64","type":"integer"},"incasso":{"type":"boolean"},"memberType":{"$ref":"#/components/schemas/MemberType"},"startDate":{"format":"date","type":"string"},"updatedAt":{"format":"date-time","type":"string"},"userId":{"format":"int64","type":"integer"},"version":{"format":"int64","type":"integer"}},"required":["createdAt","id","incasso","memberType","startDate","updatedAt","userId","version"],"type":"object"},"MissingRow":{"properties":{"hasExternalMapping":{"type":"boolean"},"userId":{"format":"int64","type":"integer"}},"required":["hasExternalMapping","userId"],"type":"object"},"PageMetadata":{"properties":{"number":{"format":"int64","type":"integer"},"size":{"format":"int64","type":"integer"},"totalElements":{"format":"int64","type":"integer"},"totalPages":{"format":"int64","type":"integer"}},"type":"object"},"PagedModelEmail":{"properties":{"content":{"items":{"$ref":"#/components/schemas/Email"},"type":"array"},"page":{"$ref":"#/components/schemas/PageMetadata"}},"type":"object"},"PagedModelEventResponse":{"properties":{"content":{"items":{"$ref":"#/components/schemas/EventResponse"},"type":"array"},"page":{"$ref":"#/components/schemas/PageMetadata"}},"type":"object"},"PagedModelJobExecution":{"properties":{"content":{"items":{"$ref":"#/components/schemas/JobExecution"},"type":"array"},"page":{"$ref":"#/components/schemas/PageMetadata"}},"type":"object"},"PagedModelUserDetailResponse":{"properties":{"content":{"items":{"$ref":"#/components/schemas/UserDetailResponse"},"type":"array"},"page":{"$ref":"#/components/schemas/PageMetadata"}},"type":"object"},"PasswordResetRequest":{"properties":{"password":{"maxLength":100,"minLength":8,"pattern":"^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]+$","type":"string"},"token":{"minLength":1,"type":"string"}},"required":["password","token"],"type":"object"},"PlatformType":{"enum":["FACEBOOK","LINKEDIN","TWITTER","INSTAGRAM"],"type":"string"},"QuestionRequest":{"properties":{"choiceLabels":{"items":{"type":"string"},"type":"array"},"idx":{"format":"int64","type":"integer"},"label":{"maxLength":2055,"minLength":0,"type":"string"},"required":{"type":"boolean"},"type":{"$ref":"#/components/schemas/QuestionType"}},"required":["idx","label","type"],"type":"object"},"QuestionResponse":{"properties":{"choiceLabels":{"items":{"type":"string"},"type":"array"},"createdAt":{"format":"date-time","type":"string"},"id":{"format":"int64","type":"integer"},"idx":{"format":"int64","type":"integer"},"label":{"maxLength":2055,"minLength":0,"type":"string"},"required":{"type":"boolean"},"surveyId":{"format":"int64","type":"integer"},"type":{"$ref":"#/components/schemas/QuestionType"},"updatedAt":{"format":"date-time","type":"string"},"version":{"format":"int64","type":"integer"}},"required":["createdAt","id","idx","label","surveyId","type","updatedAt","version"],"type":"object"},"QuestionType":{"enum":["OPEN","RADIO","CHECKBOX","DESCRIPTION"],"type":"string"},"RedirectResponse":{"properties":{"path":{"type":"string"}},"required":["path"],"type":"object"},"Role":{"enum":["ANONYMOUS","VEGAN","GUEST","COMPANY","MEMBER","COMMITTEE","BOARD","TREASURER","ADMIN","SYSTEM"],"type":"string"},"ServiceEntry":{"properties":{"description":{"type":"string"},"iconUrl":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"}},"required":["description","iconUrl","id","name","url"],"type":"object"},"SponsorResponse":{"properties":{"createdAt":{"format":"date-time","type":"string"},"description":{"type":"string"},"id":{"format":"int64","type":"integer"},"name":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"},"version":{"format":"int64","type":"integer"}},"required":["createdAt","description","id","name","updatedAt","version"],"type":"object"},"SubjectFact":{"properties":{"key":{"type":"string"},"kind":{"$ref":"#/components/schemas/CohortFactKind"}},"required":["key","kind"],"type":"object"},"SurveyRequest":{"properties":{"questions":{"items":{"$ref":"#/components/schemas/QuestionRequest"},"minItems":1,"type":"array"}},"required":["questions"],"type":"object"},"SurveyResponse":{"properties":{"createdAt":{"format":"date-time","type":"string"},"id":{"format":"int64","type":"integer"},"questions":{"items":{"$ref":"#/components/schemas/QuestionResponse"},"minItems":1,"type":"array"},"responseCount":{"format":"int64","type":"integer"},"updatedAt":{"format":"date-time","type":"string"},"version":{"format":"int64","type":"integer"}},"required":["createdAt","id","questions","responseCount","updatedAt","version"],"type":"object"},"SwitchTargetRequest":{"properties":{"deletePrevious":{"type":"boolean"},"externalId":{"minLength":1,"type":"string"},"reconcileNow":{"type":"boolean"}},"required":["deletePrevious","externalId","reconcileNow"],"type":"object"},"TargetDescriptor":{"properties":{"capabilities":{"items":{"enum":["CATALOG","CREATE","READ_MEMBERS","WRITE_MEMBERS","DELETE"],"type":"string"},"type":"array","uniqueItems":true},"folderLabel":{"type":"string"},"idLabel":{"type":"string"},"kind":{"$ref":"#/components/schemas/CohortKind"},"system":{"$ref":"#/components/schemas/TargetSystem"},"systemLabel":{"type":"string"},"targetLabel":{"type":"string"}},"required":["capabilities","idLabel","kind","system","systemLabel","targetLabel"],"type":"object"},"TargetSystem":{"enum":["BREVO","GOOGLE_CALENDAR"],"type":"string"},"TelemetryResponse":{"properties":{"createdAt":{"format":"date-time","type":"string"},"id":{"format":"int64","type":"integer"},"platform":{"$ref":"#/components/schemas/PlatformType"},"updatedAt":{"format":"date-time","type":"string"},"url":{"type":"string"},"version":{"format":"int64","type":"integer"}},"required":["createdAt","id","platform","updatedAt","url","version"],"type":"object"},"UpdateAddressRequest":{"properties":{"city":{"minLength":1,"type":"string"},"country":{"minLength":1,"type":"string"},"houseNumber":{"minLength":1,"type":"string"},"street":{"minLength":1,"type":"string"},"version":{"format":"int64","type":"integer"},"zipCode":{"minLength":1,"type":"string"}},"required":["city","country","houseNumber","street","version","zipCode"],"type":"object"},"UpdateBlogRequest":{"properties":{"html":{"minLength":1,"type":"string"},"publishedAt":{"format":"date-time","type":"string"},"title":{"minLength":1,"type":"string"},"version":{"format":"int64","type":"integer"}},"required":["html","publishedAt","title","version"],"type":"object"},"UpdateBoardRequest":{"properties":{"candidate":{"minLength":1,"type":"string"},"endDate":{"format":"date","type":"string"},"name":{"maxLength":100,"minLength":1,"type":"string"},"pictureId":{"format":"int64","type":"integer"},"startDate":{"format":"date","type":"string"},"version":{"format":"int64","type":"integer"}},"required":["candidate","name","startDate","version"],"type":"object"},"UpdateCommitteeRequest":{"properties":{"description":{"maxLength":4095,"minLength":0,"type":"string"},"members":{"items":{"$ref":"#/components/schemas/CommitteeMemberRequest"},"minItems":1,"type":"array"},"name":{"maxLength":255,"minLength":0,"type":"string"},"version":{"format":"int64","type":"integer"}},"required":["description","members","name","version"],"type":"object"},"UpdateContributionPeriodRequest":{"properties":{"alumniFee":{"format":"double","type":"number"},"contactListId":{"format":"int64","type":"integer"},"endDate":{"format":"date","type":"string"},"fullYearFee":{"format":"double","type":"number"},"halfYearFee":{"format":"double","type":"number"},"startDate":{"format":"date","type":"string"},"version":{"format":"int64","type":"integer"}},"required":["alumniFee","endDate","fullYearFee","halfYearFee","startDate","version"],"type":"object"},"UpdateEventRequest":{"properties":{"approved":{"type":"boolean"},"banner":{"$ref":"#/components/schemas/EventBannerRequest"},"committeeId":{"format":"int64","type":"integer"},"description":{"maxLength":4095,"minLength":0,"type":"string"},"endTime":{"format":"date-time","type":"string"},"location":{"type":"string"},"memberPrice":{"format":"double","type":"number"},"membersOnly":{"type":"boolean"},"publicPrice":{"format":"double","type":"number"},"removeExistingSignUps":{"type":"boolean"},"signUp":{"type":"boolean"},"signUpDeadline":{"format":"date-time","type":"string"},"signUpForm":{"$ref":"#/components/schemas/SurveyRequest"},"signUpLimit":{"format":"int32","minimum":1,"type":"integer"},"startTime":{"format":"date-time","type":"string"},"title":{"maxLength":255,"minLength":0,"type":"string"},"version":{"format":"int64","type":"integer"}},"required":["approved","committeeId","description","endTime","membersOnly","signUp","startTime","title","version"],"type":"object"},"UpdateEventSignUpRequest":{"properties":{"answers":{"items":{"$ref":"#/components/schemas/AnswerRequest"},"type":"array"},"guest":{"$ref":"#/components/schemas/CreateGuestRequest"},"userId":{"format":"int64","type":"integer"},"version":{"format":"int64","type":"integer"}},"type":"object"},"UpdateMemberProfileRequest":{"properties":{"bhv":{"type":"boolean"},"dateOfBirth":{"format":"date","type":"string"},"ehbo":{"type":"boolean"},"gender":{"type":"string"},"nationality":{"minLength":1,"type":"string"},"studentNumber":{"type":"string"},"version":{"format":"int64","type":"integer"}},"required":["bhv","dateOfBirth","ehbo","nationality","version"],"type":"object"},"UpdateMembershipRequest":{"properties":{"endDate":{"format":"date","type":"string"},"incasso":{"type":"boolean"},"memberType":{"$ref":"#/components/schemas/MemberType"},"startDate":{"format":"date","type":"string"},"userId":{"format":"int64","type":"integer"},"version":{"format":"int64","type":"integer"}},"required":["userId","version"],"type":"object"},"UpdateSponsorRequest":{"properties":{"description":{"maxLength":4095,"minLength":0,"type":"string"},"name":{"maxLength":255,"minLength":0,"type":"string"},"version":{"format":"int64","type":"integer"}},"required":["description","name","version"],"type":"object"},"UpdateUserRequest":{"properties":{"discord":{"minLength":1,"type":"string"},"memberProfile":{"$ref":"#/components/schemas/UpsertMemberProfileRequest"},"newsletter":{"type":"boolean"},"phoneNumber":{"minLength":1,"type":"string"},"photoConsent":{"type":"boolean"},"version":{"format":"int64","type":"integer"}},"required":["discord","newsletter","phoneNumber","version"],"type":"object"},"UpsertMemberProfileRequest":{"properties":{"bhv":{"type":"boolean"},"dateOfBirth":{"format":"date","type":"string"},"ehbo":{"type":"boolean"},"gender":{"type":"string"},"nationality":{"minLength":1,"type":"string"},"studentNumber":{"type":"string"},"version":{"format":"int64","type":"integer"}},"required":["bhv","dateOfBirth","ehbo","nationality"],"type":"object"},"UserActivationRequest":{"properties":{"token":{"minLength":1,"type":"string"}},"required":["token"],"type":"object"},"UserDetailResponse":{"properties":{"addressId":{"format":"int64","type":"integer"},"createdAt":{"format":"date-time","type":"string"},"discord":{"type":"string"},"email":{"type":"string"},"enabled":{"type":"boolean"},"firstName":{"type":"string"},"fullName":{"type":"string"},"id":{"format":"int64","type":"integer"},"initials":{"type":"string"},"lastName":{"type":"string"},"newsletter":{"type":"boolean"},"phoneNumber":{"type":"string"},"photoConsent":{"type":"boolean"},"prefix":{"type":"string"},"restoreUntilAt":{"format":"date-time","type":"string"},"roles":{"items":{"$ref":"#/components/schemas/Role"},"type":"array"},"updatedAt":{"format":"date-time","type":"string"},"username":{"type":"string"},"version":{"format":"int64","type":"integer"}},"required":["createdAt","email","enabled","firstName","fullName","id","initials","lastName","newsletter","photoConsent","roles","updatedAt","username","version"],"type":"object"},"UserSummaryResponse":{"properties":{"createdAt":{"format":"date-time","type":"string"},"discord":{"type":"string"},"email":{"type":"string"},"fullName":{"type":"string"},"id":{"format":"int64","type":"integer"},"phoneNumber":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"},"version":{"format":"int64","type":"integer"}},"required":["createdAt","email","fullName","id","updatedAt","version"],"type":"object"}}},"info":{"title":"OpenAPI definition","version":"v0"},"openapi":"3.1.0","paths":{"/addresses":{"get":{"operationId":"findAllAddresses","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/AddressResponse"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Addresses"]},"post":{"operationId":"createAddress","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAddressRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddressResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Addresses"]}},"/addresses/{id}":{"delete":{"operationId":"deleteAddressById","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Addresses"]},"get":{"operationId":"findAddressById","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddressResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Addresses"]},"put":{"operationId":"updateAddress","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAddressRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddressResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Addresses"]}},"/auth":{"post":{"operationId":"authenticate","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JwtRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Authentication"]}},"/auth/logout":{"post":{"operationId":"logout","responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Authentication"]}},"/blogs":{"get":{"operationId":"findBlogs","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/BlogResponse"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Blogs"]},"post":{"operationId":"createBlog","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateBlogRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlogResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Blogs"]}},"/blogs/{id}":{"delete":{"operationId":"deleteById","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Blogs"]},"get":{"operationId":"findBlogById","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlogResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Blogs"]},"post":{"operationId":"updateBlog","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateBlogRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlogResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Blogs"]}},"/boards":{"get":{"operationId":"findAllBoards","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/BoardResponse"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Boards"]},"post":{"operationId":"createBoard","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateBoardRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BoardResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Boards"]}},"/boards/{boardId}/members":{"post":{"operationId":"addMember","parameters":[{"in":"path","name":"boardId","required":true,"schema":{"format":"int64","type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddBoardMemberRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BoardMemberResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Boards"]}},"/boards/{boardId}/members/{userId}":{"delete":{"operationId":"removeMember","parameters":[{"in":"path","name":"boardId","required":true,"schema":{"format":"int64","type":"integer"}},{"in":"path","name":"userId","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Boards"]}},"/boards/{id}":{"delete":{"operationId":"deleteBoard","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Boards"]},"get":{"operationId":"findBoardById","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BoardResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Boards"]},"put":{"operationId":"updateBoard","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateBoardRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BoardResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Boards"]}},"/committeeMembers/committees":{"get":{"operationId":"findCommitteesByUserId","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/CommitteeResponse"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Committees"]}},"/committees":{"get":{"operationId":"findCommittees","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/CommitteeResponse"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Committees"]},"post":{"operationId":"createCommittee","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCommitteeRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommitteeDetailResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Committees"]}},"/committees/{committeeId}":{"get":{"operationId":"findCommitteeById","parameters":[{"in":"path","name":"committeeId","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommitteeResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Committees"]}},"/committees/{id}":{"delete":{"operationId":"deleteCommitteeById","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Committees"]},"put":{"operationId":"updateCommittee","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCommitteeRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommitteeDetailResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Committees"]}},"/contributionPeriods":{"get":{"operationId":"findContributionPeriods","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/ContributionPeriodResponse"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["ContributionPeriods"]},"post":{"operationId":"createContributionPeriod","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateContributionPeriodRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContributionPeriodResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["ContributionPeriods"]}},"/contributionPeriods/current":{"get":{"operationId":"findCurrentContributionPeriod","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContributionPeriodResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["ContributionPeriods"]}},"/contributionPeriods/{contributionPeriodId}/users/{userId}/contributions":{"delete":{"operationId":"deleteContribution","parameters":[{"in":"path","name":"userId","required":true,"schema":{"format":"int64","type":"integer"}},{"in":"path","name":"contributionPeriodId","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Contributions"]}},"/contributionPeriods/{id}":{"delete":{"operationId":"deleteContributionPeriodById","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["ContributionPeriods"]},"put":{"operationId":"updateContributionPeriod","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateContributionPeriodRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContributionPeriodResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["ContributionPeriods"]}},"/contributionPeriods/{periodId}/contributions":{"get":{"operationId":"findContributionsByPeriodId","parameters":[{"in":"path","name":"periodId","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/ContributionResponse"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Contributions"]}},"/contributionReminders":{"get":{"operationId":"findContributionReminders","parameters":[{"in":"query","name":"contributionPeriodId","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/ContributionReminderResponse"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["ContributionReminders"]},"post":{"operationId":"sendContributionReminder","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateContributionReminderRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContributionReminderResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["ContributionReminders"]}},"/contributionReminders/batch":{"post":{"operationId":"sendContributionReminderBatch","requestBody":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/CreateContributionReminderRequest"},"type":"array"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/ContributionReminderResponse"},"type":"array"}}},"description":"Created"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["ContributionReminders"]}},"/contributions":{"get":{"operationId":"findContributions","parameters":[{"in":"query","name":"contributionPeriodId","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/ContributionResponse"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Contributions"]},"post":{"operationId":"createContribution","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateContributionRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContributionResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Contributions"]}},"/csrf":{"get":{"operationId":"csrf","parameters":[{"in":"query","name":"csrfToken","required":true,"schema":{"$ref":"#/components/schemas/CsrfToken"}}],"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Security"]}},"/events":{"get":{"operationId":"findEvents","parameters":[{"description":"Zero-based page index (0..N)","in":"query","name":"page","required":false,"schema":{"default":0,"minimum":0,"type":"integer"}},{"description":"The size of the page to be returned","in":"query","name":"size","required":false,"schema":{"default":20,"minimum":1,"type":"integer"}},{"description":"Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported.","in":"query","name":"sort","required":false,"schema":{"items":{"type":"string"},"type":"array"}},{"in":"query","name":"from","required":false,"schema":{"format":"date-time","type":"string"}},{"in":"query","name":"to","required":false,"schema":{"format":"date-time","type":"string"}},{"in":"query","name":"approved","required":false,"schema":{"type":"boolean"}},{"in":"query","name":"committeeId","required":false,"schema":{"format":"int64","type":"integer"}},{"in":"query","name":"titleContains","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PagedModelEventResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Events"]},"post":{"operationId":"createEvent","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateEventRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Events"]}},"/events/banners":{"post":{"operationId":"uploadEventBanner","requestBody":{"content":{"multipart/form-data":{"schema":{"properties":{"file":{"format":"binary","type":"string"}},"required":["file"],"type":"object"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FileResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Files"]}},"/events/signups":{"get":{"operationId":"findEventSignUps","parameters":[{"in":"query","name":"from","required":false,"schema":{"format":"date-time","type":"string"}},{"in":"query","name":"to","required":false,"schema":{"format":"date-time","type":"string"}},{"in":"query","name":"userId","required":false,"schema":{"format":"int64","type":"integer"}},{"in":"query","name":"committeeId","required":false,"schema":{"format":"int64","type":"integer"}},{"in":"query","name":"approved","required":false,"schema":{"type":"boolean"}},{"in":"query","name":"eventId","required":false,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/EventSignUpResponse"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["EventSignUps"]}},"/events/signups/byAccessToken":{"get":{"operationId":"findEventSignUpsByAccessToken","parameters":[{"in":"header","name":"X-Guest-Access-Token","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/EventSignUpResponse"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["EventSignUps"]}},"/events/signups/{id}":{"delete":{"operationId":"deleteEventSignup","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}},{"in":"header","name":"X-Guest-Access-Token","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["EventSignUps"]}},"/events/{eventId}":{"delete":{"operationId":"deleteEventById","parameters":[{"in":"path","name":"eventId","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Events"]}},"/events/{eventId}/banners":{"get":{"operationId":"downloadEventBanner","parameters":[{"in":"path","name":"eventId","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"format":"binary","type":"string"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Files"]}},"/events/{eventId}/signups":{"get":{"operationId":"findEventSignUpsByEventId","parameters":[{"in":"path","name":"eventId","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/EventSignUpResponse"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["EventSignUps"]},"post":{"operationId":"createEventSignup","parameters":[{"in":"path","name":"eventId","required":true,"schema":{"format":"int64","type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateEventSignUpRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventSignUpResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["EventSignUps"]},"put":{"operationId":"updateEventSignUp","parameters":[{"in":"path","name":"eventId","required":true,"schema":{"format":"int64","type":"integer"}},{"in":"header","name":"X-Guest-Access-Token","required":false,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateEventSignUpRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventSignUpResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["EventSignUps"]}},"/events/{id}":{"get":{"operationId":"findEventById","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Events"]},"put":{"operationId":"updateEvent","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateEventRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Events"]}},"/events/{id}/approve":{"put":{"operationId":"approveEvent","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}},{"in":"query","name":"approved","required":true,"schema":{"type":"boolean"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Events"]}},"/health":{"get":{"operationId":"healthCheck","responses":{"200":{"content":{"application/json":{"schema":{"type":"boolean"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Health"]}},"/management/cohort-subjects":{"get":{"operationId":"findCohortSubjects","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/CohortSubjectSummary"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Cohort Subjects"]}},"/management/cohort-subjects/{id}":{"get":{"operationId":"findCohortSubjectById","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CohortSubjectDetail"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Cohort Subjects"]}},"/management/cohort-subjects/{id}/drift":{"get":{"operationId":"getDrift","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}},{"in":"query","name":"system","required":true,"schema":{"$ref":"#/components/schemas/TargetSystem"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DriftReport"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Cohort Subjects"]}},"/management/cohort-subjects/{id}/drift/link-user":{"post":{"operationId":"linkUser","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkUserRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkedUser"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Cohort Subjects"]}},"/management/cohort-subjects/{id}/targets/existing":{"post":{"operationId":"linkExistingTarget","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkExistingTargetRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CohortMapping"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Cohort Subjects"]}},"/management/cohort-subjects/{id}/targets/new":{"post":{"operationId":"createTarget","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTargetRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CohortMapping"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Cohort Subjects"]}},"/management/cohort-subjects/{id}/targets/{cohortId}":{"put":{"operationId":"switchTarget","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}},{"in":"path","name":"cohortId","required":true,"schema":{"format":"int64","type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SwitchTargetRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CohortMapping"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Cohort Subjects"]}},"/management/cohort-subjects/{id}/targets/{cohortId}/inbound-reconcile/apply":{"post":{"operationId":"applyInboundReconcile","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}},{"in":"path","name":"cohortId","required":true,"schema":{"format":"int64","type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundReconcileApplyRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundReconcileApplyResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Cohort Subjects"]}},"/management/cohort-subjects/{id}/targets/{cohortId}/inbound-reconcile/preview":{"post":{"operationId":"previewInboundReconcile","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}},{"in":"path","name":"cohortId","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundReconcilePreview"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Cohort Subjects"]}},"/management/cohort-targets/systems":{"get":{"operationId":"listCohortTargetSystems","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/TargetDescriptor"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Cohort Targets"]}},"/management/cohort-targets/{system}":{"get":{"operationId":"searchCohortTargets","parameters":[{"in":"path","name":"system","required":true,"schema":{"$ref":"#/components/schemas/TargetSystem"}},{"in":"query","name":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/ExternalTarget"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Cohort Targets"]}},"/management/cohorts":{"get":{"operationId":"findCohorts","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/CohortSummary"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Cohorts"]}},"/management/cohorts/{id}":{"get":{"operationId":"findCohortById","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CohortDetail"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Cohorts"]}},"/management/cohorts/{id}/repair-missing-adds":{"post":{"operationId":"repairMissingAdds","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CohortRepair"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Cohorts"]}},"/management/emails":{"get":{"operationId":"list_1","parameters":[{"description":"Zero-based page index (0..N)","in":"query","name":"page","required":false,"schema":{"default":0,"minimum":0,"type":"integer"}},{"description":"The size of the page to be returned","in":"query","name":"size","required":false,"schema":{"default":50,"minimum":1,"type":"integer"}},{"description":"Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported.","in":"query","name":"sort","required":false,"schema":{"default":["createdAt,DESC"],"items":{"type":"string"},"type":"array"}},{"in":"query","name":"deliveryStatus","required":false,"schema":{"$ref":"#/components/schemas/EmailDeliveryStatus"}},{"in":"query","name":"emailType","required":false,"schema":{"type":"string"}},{"in":"query","name":"search","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PagedModelEmail"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Email Management"]}},"/management/emails/stats":{"get":{"operationId":"getStats_1","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailStats"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Email Management"]}},"/management/emails/{id}/retry":{"post":{"operationId":"retry_1","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Email"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Email Management"]}},"/management/jobs":{"get":{"operationId":"list","parameters":[{"description":"Zero-based page index (0..N)","in":"query","name":"page","required":false,"schema":{"default":0,"minimum":0,"type":"integer"}},{"description":"The size of the page to be returned","in":"query","name":"size","required":false,"schema":{"default":50,"minimum":1,"type":"integer"}},{"description":"Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported.","in":"query","name":"sort","required":false,"schema":{"default":["updatedAt,DESC"],"items":{"type":"string"},"type":"array"}},{"in":"query","name":"status","required":false,"schema":{"$ref":"#/components/schemas/JobExecutionStatus"}},{"in":"query","name":"category","required":false,"schema":{"$ref":"#/components/schemas/JobExecutionCategory"}},{"in":"query","name":"search","required":false,"schema":{"type":"string"}},{"in":"query","name":"initiatedByType","required":false,"schema":{"$ref":"#/components/schemas/ActionActorType"}},{"in":"query","name":"jobType","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PagedModelJobExecution"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Job Management"]}},"/management/jobs/enqueue":{"post":{"operationId":"enqueue","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnqueueJobRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobExecution"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Job Management"]}},"/management/jobs/stats":{"get":{"operationId":"getStats","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobStatsDTO"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Job Management"]}},"/management/jobs/types":{"get":{"operationId":"jobTypes","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/JobTypeDescriptor"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Job Management"]}},"/management/jobs/{id}/retry":{"post":{"operationId":"retry","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobExecution"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Job Management"]}},"/me/services":{"get":{"operationId":"myServices","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/ServiceEntry"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["My Services"]}},"/memberProfiles":{"post":{"operationId":"createMemberProfile","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateMemberProfileRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MemberProfileResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Member Profiles"]}},"/memberships":{"get":{"operationId":"findMemberships","parameters":[{"in":"query","name":"from","required":false,"schema":{"format":"date","type":"string"}},{"in":"query","name":"to","required":false,"schema":{"format":"date","type":"string"}},{"in":"query","name":"userId","required":false,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/MembershipResponse"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Memberships"]},"post":{"operationId":"createMembership","responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MembershipResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Memberships"]}},"/memberships/{id}":{"delete":{"operationId":"deleteMembership","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Memberships"]},"get":{"operationId":"findMembershipById","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MembershipResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Memberships"]},"put":{"operationId":"updateMembership","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateMembershipRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MembershipResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Memberships"]}},"/memberships/{id}/end":{"post":{"operationId":"endMembership","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MembershipResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Memberships"]}},"/memberships/{id}/reopen":{"post":{"operationId":"reopenMembership","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MembershipResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Memberships"]}},"/memberships/{id}/restore":{"put":{"operationId":"restoreMembership","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MembershipResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Memberships"]}},"/oauth2/forward-auth":{"get":{"operationId":"forwardAuth","responses":{"200":{"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Forward Auth"]}},"/recovery/member/activate":{"post":{"operationId":"memberActivate","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MemberActivationRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RedirectResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Recovery"]}},"/recovery/password":{"post":{"operationId":"setPassword","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PasswordResetRequest"}}},"required":true},"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Recovery"]}},"/recovery/password/reset/{username}":{"post":{"operationId":"resetPassword","parameters":[{"in":"path","name":"username","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Recovery"]}},"/recovery/user/activate":{"post":{"operationId":"userActivate","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserActivationRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RedirectResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Recovery"]}},"/recovery/user/activate/resend/{username}":{"post":{"operationId":"resendUserActivation","parameters":[{"in":"path","name":"username","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Recovery"]}},"/recovery/users/{userId}/resend/recovery":{"post":{"operationId":"resendMemberActivationEmail","parameters":[{"in":"path","name":"userId","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Recovery"]}},"/sponsors":{"get":{"operationId":"findSponsors","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/SponsorResponse"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Sponsors"]},"post":{"operationId":"createSponsor","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSponsorRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SponsorResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Sponsors"]}},"/sponsors/{id}":{"delete":{"operationId":"deleteSponsorById","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Sponsors"]},"get":{"operationId":"findSponsorById","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SponsorResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Sponsors"]},"put":{"operationId":"updateSponsor","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSponsorRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SponsorResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Sponsors"]}},"/telemetry":{"post":{"operationId":"createTelemetry","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTelemetryRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TelemetryResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Telemetries"]}},"/telemetry/{id}":{"get":{"operationId":"findTelemetryById","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TelemetryResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Telemetries"]}},"/users":{"get":{"operationId":"findUsers","parameters":[{"in":"query","name":"username","required":false,"schema":{"type":"string"}},{"in":"query","name":"enabled","required":false,"schema":{"type":"boolean"}},{"description":"Zero-based page index (0..N)","in":"query","name":"page","required":false,"schema":{"default":0,"minimum":0,"type":"integer"}},{"description":"The size of the page to be returned","in":"query","name":"size","required":false,"schema":{"default":20,"minimum":1,"type":"integer"}},{"description":"Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported.","in":"query","name":"sort","required":false,"schema":{"items":{"type":"string"},"type":"array"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PagedModelUserDetailResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Users"]},"post":{"operationId":"createUser","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUserRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserDetailResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Users"]}},"/users/deleted":{"get":{"operationId":"findDeletedUsers","parameters":[{"description":"Zero-based page index (0..N)","in":"query","name":"page","required":false,"schema":{"default":0,"minimum":0,"type":"integer"}},{"description":"The size of the page to be returned","in":"query","name":"size","required":false,"schema":{"default":20,"minimum":1,"type":"integer"}},{"description":"Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported.","in":"query","name":"sort","required":false,"schema":{"items":{"type":"string"},"type":"array"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PagedModelUserDetailResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Users"]}},"/users/{id}":{"put":{"operationId":"updateUser","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUserRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserDetailResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Users"]}},"/users/{userId}":{"delete":{"operationId":"deleteUserById","parameters":[{"in":"path","name":"userId","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Users"]},"get":{"operationId":"findUserById","parameters":[{"in":"path","name":"userId","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserDetailResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Users"]}},"/users/{userId}/memberProfiles":{"get":{"operationId":"findMemberProfileByUserId","parameters":[{"in":"path","name":"userId","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MemberProfileResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Member Profiles"]},"put":{"operationId":"updateMemberProfile","parameters":[{"in":"path","name":"userId","required":true,"schema":{"format":"int64","type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateMemberProfileRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MemberProfileResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Member Profiles"]}},"/users/{userId}/memberships":{"post":{"operationId":"boardCreateMembership","parameters":[{"in":"path","name":"userId","required":true,"schema":{"format":"int64","type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BoardCreateMembershipRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MembershipResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Memberships"]}},"/users/{userId}/memberships/deleted":{"get":{"operationId":"findDeletedMemberships","parameters":[{"in":"path","name":"userId","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/MembershipResponse"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Memberships"]}},"/users/{userId}/restore":{"put":{"operationId":"restoreDeletedUserById","parameters":[{"in":"path","name":"userId","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Users"]}},"/users/{userId}/roles":{"put":{"operationId":"toggleUserRole","parameters":[{"in":"path","name":"userId","required":true,"schema":{"format":"int64","type":"integer"}},{"in":"query","name":"role","required":true,"schema":{"$ref":"#/components/schemas/Role"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserDetailResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Users"]}}},"servers":[{"description":"Generated server url","url":"http://localhost:8080"}],"tags":[{"description":"Admin cohort listings + detail","name":"Cohorts"},{"description":"API for managing outbound emails","name":"Email Management"},{"description":"Admin: external cohort target catalog","name":"Cohort Targets"},{"description":"Admin: logical subjects + their per-system mappings","name":"Cohort Subjects"},{"description":"API for managing job executions","name":"Job Management"}]} +{"components":{"schemas":{"ActionActorType":{"enum":["USER","SYSTEM"],"type":"string"},"Actor":{"properties":{"role":{"$ref":"#/components/schemas/Role"},"type":{"$ref":"#/components/schemas/ActionActorType"},"userId":{"format":"int64","type":"integer"}},"required":["role","type"],"type":"object"},"AddBoardMemberRequest":{"properties":{"endDate":{"format":"date","type":"string"},"role":{"minLength":1,"type":"string"},"startDate":{"format":"date","type":"string"},"userId":{"format":"int64","type":"integer"}},"required":["role","startDate","userId"],"type":"object"},"AddressResponse":{"properties":{"city":{"type":"string"},"country":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"houseNumber":{"type":"string"},"id":{"format":"int64","type":"integer"},"street":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"},"userId":{"format":"int64","type":"integer"},"version":{"format":"int64","type":"integer"},"zipCode":{"type":"string"}},"required":["createdAt","id","updatedAt","version"],"type":"object"},"AnswerRequest":{"properties":{"optionSelections":{"items":{"type":"boolean"},"type":"array"},"questionId":{"format":"int64","type":"integer"},"textResponse":{"type":"string"}},"required":["questionId"],"type":"object"},"AnswerResponse":{"properties":{"createdAt":{"format":"date-time","type":"string"},"id":{"format":"int64","type":"integer"},"optionSelections":{"items":{"type":"boolean"},"type":"array"},"questionId":{"format":"int64","type":"integer"},"textResponse":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"},"version":{"format":"int64","type":"integer"}},"required":["createdAt","id","questionId","updatedAt","version"],"type":"object"},"ApiError":{"description":"Problem Details for HTTP APIs including validation errors.","properties":{"detail":{"description":"Human-readable explanation specific to this occurrence.","example":"Validation failed for request.","type":"string"},"errors":{"description":"List of field/object validation errors (present when binding/validation fails).","items":{"$ref":"#/components/schemas/FieldValidationError"},"type":"array"},"instance":{"description":"A URI reference that identifies the specific occurrence.","example":"/api/v1/users","format":"uri","type":"string"},"status":{"description":"HTTP status code.","example":400,"format":"int32","type":"integer"},"title":{"description":"Short, human-readable summary of the problem.","example":"Bad Request","type":"string"},"traceId":{"description":"Trace or correlation id if available (Spring may add this via problem detail handlers).","example":"a8c0c4e5f1c24a7e","type":"string"},"type":{"description":"Problem type URI (RFC 7807).","example":"about:blank","type":"string"}}},"BlogResponse":{"properties":{"createdAt":{"format":"date-time","type":"string"},"html":{"type":"string"},"id":{"format":"int64","type":"integer"},"publishedAt":{"format":"date-time","type":"string"},"title":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"},"url":{"type":"string"},"version":{"format":"int64","type":"integer"}},"required":["createdAt","html","id","publishedAt","title","updatedAt","url","version"],"type":"object"},"BoardCreateMembershipRequest":{"properties":{"endDate":{"format":"date","type":"string"},"incasso":{"type":"boolean"},"memberType":{"$ref":"#/components/schemas/MemberType"},"startDate":{"format":"date","type":"string"},"userId":{"format":"int64","type":"integer"}},"required":["incasso","memberType","userId"],"type":"object"},"BoardMemberResponse":{"properties":{"boardId":{"format":"int64","type":"integer"},"createdAt":{"format":"date-time","type":"string"},"endDate":{"format":"date","type":"string"},"role":{"type":"string"},"startDate":{"format":"date","type":"string"},"updatedAt":{"format":"date-time","type":"string"},"userId":{"format":"int64","type":"integer"},"version":{"format":"int64","type":"integer"}},"required":["boardId","createdAt","role","startDate","updatedAt","userId","version"],"type":"object"},"BoardResponse":{"properties":{"candidate":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"endDate":{"format":"date","type":"string"},"id":{"format":"int64","type":"integer"},"members":{"items":{"$ref":"#/components/schemas/BoardMemberResponse"},"type":"array"},"name":{"type":"string"},"pictureId":{"format":"int64","type":"integer"},"startDate":{"format":"date","type":"string"},"updatedAt":{"format":"date-time","type":"string"},"version":{"format":"int64","type":"integer"}},"required":["candidate","createdAt","id","members","name","startDate","updatedAt","version"],"type":"object"},"BulkActionResult":{"properties":{"applied":{"format":"int32","type":"integer"},"queued":{"format":"int32","type":"integer"},"skipped":{"format":"int32","type":"integer"}},"required":["applied","queued","skipped"],"type":"object"},"BulkContributionReminderExecuteRequest":{"properties":{"contributionPeriodId":{"format":"int64","type":"integer"},"cutoffDate":{"format":"date","type":"string"},"feeTypeOverrides":{"additionalProperties":{"enum":["FULL_YEAR_FEE","HALF_YEAR_FEE","ALUMNI_FEE"],"type":"string"},"type":"object"},"includedUserIds":{"items":{"format":"int64","type":"integer"},"maxItems":1000,"minItems":0,"type":"array","uniqueItems":true},"paymentDueDate":{"format":"date","type":"string"},"userIds":{"items":{"format":"int64","type":"integer"},"maxItems":1000,"minItems":1,"type":"array"}},"required":["contributionPeriodId","cutoffDate","feeTypeOverrides","includedUserIds","paymentDueDate","userIds"],"type":"object"},"BulkEndMembershipRequest":{"properties":{"userIds":{"items":{"format":"int64","type":"integer"},"maxItems":1000,"minItems":1,"type":"array"}},"required":["userIds"],"type":"object"},"BulkIncassoNotificationExecuteRequest":{"properties":{"contributionPeriodId":{"format":"int64","type":"integer"},"cutoffDate":{"format":"date","type":"string"},"expectedIncassoDate":{"format":"date","type":"string"},"feeTypeOverrides":{"additionalProperties":{"enum":["FULL_YEAR_FEE","HALF_YEAR_FEE","ALUMNI_FEE"],"type":"string"},"type":"object"},"includedUserIds":{"items":{"format":"int64","type":"integer"},"maxItems":1000,"minItems":0,"type":"array","uniqueItems":true},"userIds":{"items":{"format":"int64","type":"integer"},"maxItems":1000,"minItems":1,"type":"array"}},"required":["contributionPeriodId","cutoffDate","expectedIncassoDate","feeTypeOverrides","includedUserIds","userIds"],"type":"object"},"BulkMarkPaidRequest":{"properties":{"contributionPeriodId":{"format":"int64","type":"integer"},"userIds":{"items":{"format":"int64","type":"integer"},"maxItems":1000,"minItems":1,"type":"array"}},"required":["contributionPeriodId","userIds"],"type":"object"},"BulkMarkUnpaidRequest":{"properties":{"contributionPeriodId":{"format":"int64","type":"integer"},"userIds":{"items":{"format":"int64","type":"integer"},"maxItems":1000,"minItems":1,"type":"array"}},"required":["contributionPeriodId","userIds"],"type":"object"},"BulkResumeMembershipRequest":{"properties":{"userIds":{"items":{"format":"int64","type":"integer"},"maxItems":1000,"minItems":1,"type":"array"}},"required":["userIds"],"type":"object"},"CohortDetail":{"properties":{"externalId":{"type":"string"},"folder":{"type":"string"},"id":{"format":"int64","type":"integer"},"kind":{"$ref":"#/components/schemas/CohortKind"},"label":{"type":"string"},"memberCount":{"format":"int32","type":"integer"},"members":{"items":{"$ref":"#/components/schemas/CohortMemberRow"},"type":"array"},"rules":{"items":{"$ref":"#/components/schemas/CohortRule"},"type":"array"},"system":{"type":"string"}},"required":["id","kind","label","memberCount","members","rules","system"],"type":"object"},"CohortFactKind":{"enum":["ROLE","COMMITTEE","CONTRIBUTION_PAID","MEMBER_IN_PERIOD","NEWSLETTER","ACTIVE_IN_PERIOD"],"type":"string"},"CohortKind":{"enum":["LIST","ROLE","GROUP"],"type":"string"},"CohortMapping":{"properties":{"cohortId":{"format":"int64","type":"integer"},"externalId":{"type":"string"},"kind":{"$ref":"#/components/schemas/CohortKind"},"label":{"type":"string"},"system":{"$ref":"#/components/schemas/TargetSystem","description":"External system this mapping targets"}},"required":["cohortId","kind","label","system"],"type":"object"},"CohortMemberRow":{"properties":{"cohortMemberId":{"format":"int64","type":"integer"},"isUserDeleted":{"type":"boolean"},"joinedAt":{"format":"date-time","type":"string"},"userEmail":{"type":"string"},"userFullName":{"type":"string"},"userId":{"format":"int64","type":"integer"}},"required":["cohortMemberId","isUserDeleted","joinedAt","userId"],"type":"object"},"CohortRepair":{"properties":{"cohortId":{"format":"int64","type":"integer"},"enqueuedAdds":{"format":"int32","type":"integer"}},"required":["cohortId","enqueuedAdds"],"type":"object"},"CohortRule":{"properties":{"enabled":{"type":"boolean"},"factKey":{"type":"string"},"factKind":{"$ref":"#/components/schemas/CohortFactKind"},"id":{"format":"int64","type":"integer"}},"required":["enabled","factKey","factKind","id"],"type":"object"},"CohortSubjectCategory":{"enum":["COMMITTEES","PERIODS","MEMBERS","OTHER"],"type":"string"},"CohortSubjectDetail":{"properties":{"category":{"$ref":"#/components/schemas/CohortSubjectCategory"},"description":{"type":"string"},"id":{"format":"int64","type":"integer"},"label":{"type":"string"},"mappings":{"items":{"$ref":"#/components/schemas/CohortMapping"},"type":"array"},"members":{"items":{"$ref":"#/components/schemas/CohortSubjectMember"},"type":"array"},"rules":{"items":{"$ref":"#/components/schemas/CohortSubjectRule"},"type":"array"},"type":{"$ref":"#/components/schemas/CohortSubjectType"}},"required":["category","id","label","mappings","members","rules","type"],"type":"object"},"CohortSubjectMember":{"properties":{"cohortMemberId":{"format":"int64","type":"integer"},"isUserDeleted":{"type":"boolean"},"joinedAt":{"format":"date-time","type":"string"},"userEmail":{"type":"string"},"userFullName":{"type":"string"},"userId":{"format":"int64","type":"integer"}},"required":["cohortMemberId","isUserDeleted","joinedAt","userId"],"type":"object"},"CohortSubjectRule":{"properties":{"enabled":{"type":"boolean"},"factKey":{"type":"string"},"factKind":{"$ref":"#/components/schemas/CohortFactKind"},"id":{"format":"int64","type":"integer"}},"required":["enabled","factKey","factKind","id"],"type":"object"},"CohortSubjectSummary":{"properties":{"category":{"$ref":"#/components/schemas/CohortSubjectCategory"},"id":{"format":"int64","type":"integer"},"label":{"type":"string"},"mappingCount":{"format":"int32","type":"integer"},"memberCount":{"format":"int32","type":"integer"},"type":{"$ref":"#/components/schemas/CohortSubjectType"}},"required":["category","id","label","mappingCount","memberCount","type"],"type":"object"},"CohortSubjectType":{"enum":["COMMITTEE_MEMBERS","PERIOD_PAYERS","PERIOD_MEMBERS","PERIOD_ACTIVE_MEMBERS","NEWSLETTER_SUBSCRIBERS","CUSTOM"],"type":"string"},"CohortSummary":{"properties":{"externalId":{"type":"string"},"folder":{"type":"string"},"id":{"format":"int64","type":"integer"},"kind":{"$ref":"#/components/schemas/CohortKind"},"label":{"type":"string"},"memberCount":{"format":"int32","type":"integer"},"system":{"type":"string"}},"required":["id","kind","label","memberCount","system"],"type":"object"},"CommitteeDetailResponse":{"properties":{"createdAt":{"format":"date-time","type":"string"},"description":{"maxLength":4095,"minLength":0,"type":"string"},"id":{"format":"int64","type":"integer"},"members":{"items":{"$ref":"#/components/schemas/CommitteeMemberResponse"},"minItems":1,"type":"array"},"name":{"maxLength":255,"minLength":0,"type":"string"},"updatedAt":{"format":"date-time","type":"string"},"version":{"format":"int64","type":"integer"}},"required":["createdAt","description","id","members","name","updatedAt","version"],"type":"object"},"CommitteeMemberRequest":{"properties":{"role":{"minLength":1,"type":"string"},"userId":{"format":"int64","type":"integer"}},"required":["role","userId"],"type":"object"},"CommitteeMemberResponse":{"properties":{"committeeId":{"format":"int64","type":"integer"},"createdAt":{"format":"date-time","type":"string"},"role":{"minLength":1,"type":"string"},"updatedAt":{"format":"date-time","type":"string"},"userId":{"format":"int64","type":"integer"},"version":{"format":"int64","type":"integer"}},"required":["committeeId","createdAt","role","updatedAt","userId","version"],"type":"object"},"CommitteeResponse":{},"ContactSystem":{"enum":["BREVO"],"type":"string"},"ContributionPeriodResponse":{"properties":{"alumniFee":{"format":"double","type":"number"},"contactListId":{"format":"int64","type":"integer"},"createdAt":{"format":"date-time","type":"string"},"endDate":{"format":"date","type":"string"},"fullYearFee":{"format":"double","type":"number"},"halfYearFee":{"format":"double","type":"number"},"id":{"format":"int64","type":"integer"},"startDate":{"format":"date","type":"string"},"updatedAt":{"format":"date-time","type":"string"},"version":{"format":"int64","type":"integer"}},"required":["alumniFee","createdAt","endDate","fullYearFee","halfYearFee","id","startDate","updatedAt","version"],"type":"object"},"ContributionReminderPreviewRequest":{"properties":{"contributionPeriodId":{"format":"int64","type":"integer"},"feeType":{"enum":["FULL_YEAR_FEE","HALF_YEAR_FEE","ALUMNI_FEE"],"type":"string"},"paymentDueDate":{"format":"date","type":"string"},"userId":{"format":"int64","type":"integer"}},"required":["contributionPeriodId","feeType","paymentDueDate","userId"],"type":"object"},"ContributionReminderResponse":{"properties":{"contributionPeriodId":{"format":"int64","type":"integer"},"createdAt":{"format":"date-time","type":"string"},"remindedAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"},"userId":{"format":"int64","type":"integer"},"version":{"format":"int64","type":"integer"}},"required":["contributionPeriodId","createdAt","updatedAt","userId","version"],"type":"object"},"ContributionResponse":{"properties":{"contributionPeriodId":{"format":"int64","type":"integer"},"createdAt":{"format":"date-time","type":"string"},"remindedAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"},"userId":{"format":"int64","type":"integer"},"version":{"format":"int64","type":"integer"}},"required":["contributionPeriodId","createdAt","updatedAt","userId","version"],"type":"object"},"CreateAddressRequest":{"properties":{"city":{"minLength":1,"type":"string"},"country":{"minLength":1,"type":"string"},"houseNumber":{"minLength":1,"type":"string"},"street":{"minLength":1,"type":"string"},"userId":{"format":"int64","type":"integer"},"zipCode":{"minLength":1,"type":"string"}},"required":["city","country","houseNumber","street","userId","zipCode"],"type":"object"},"CreateBlogRequest":{"properties":{"html":{"minLength":1,"type":"string"},"publishedAt":{"format":"date-time","type":"string"},"title":{"minLength":1,"type":"string"}},"required":["html","publishedAt","title"],"type":"object"},"CreateBoardRequest":{"properties":{"candidate":{"minLength":1,"type":"string"},"endDate":{"format":"date","type":"string"},"name":{"maxLength":100,"minLength":1,"type":"string"},"pictureId":{"format":"int64","type":"integer"},"startDate":{"format":"date","type":"string"}},"required":["candidate","name","startDate"],"type":"object"},"CreateCommitteeRequest":{"properties":{"description":{"maxLength":4095,"minLength":0,"type":"string"},"members":{"items":{"$ref":"#/components/schemas/CommitteeMemberRequest"},"minItems":1,"type":"array"},"name":{"maxLength":255,"minLength":0,"type":"string"}},"required":["description","members","name"],"type":"object"},"CreateContributionPeriodRequest":{"properties":{"alumniFee":{"format":"double","type":"number"},"contactListId":{"format":"int64","type":"integer"},"endDate":{"format":"date","type":"string"},"fullYearFee":{"format":"double","type":"number"},"halfYearFee":{"format":"double","type":"number"},"startDate":{"format":"date","type":"string"}},"required":["alumniFee","endDate","fullYearFee","halfYearFee","startDate"],"type":"object"},"CreateContributionReminderRequest":{"properties":{"contributionPeriodId":{"format":"int64","type":"integer"},"userId":{"format":"int64","type":"integer"}},"required":["contributionPeriodId","userId"],"type":"object"},"CreateContributionRequest":{"properties":{"contributionPeriodId":{"format":"int64","type":"integer"},"userId":{"format":"int64","type":"integer"}},"required":["contributionPeriodId","userId"],"type":"object"},"CreateEventRequest":{"properties":{"approved":{"type":"boolean"},"banner":{"$ref":"#/components/schemas/EventBannerRequest"},"committeeId":{"format":"int64","type":"integer"},"description":{"maxLength":4095,"minLength":0,"type":"string"},"endTime":{"format":"date-time","type":"string"},"location":{"type":"string"},"memberPrice":{"format":"double","type":"number"},"membersOnly":{"type":"boolean"},"publicPrice":{"format":"double","type":"number"},"signUp":{"type":"boolean"},"signUpDeadline":{"format":"date-time","type":"string"},"signUpForm":{"$ref":"#/components/schemas/SurveyRequest"},"signUpLimit":{"format":"int32","minimum":1,"type":"integer"},"startTime":{"format":"date-time","type":"string"},"title":{"maxLength":255,"minLength":0,"type":"string"}},"required":["approved","committeeId","description","endTime","membersOnly","signUp","startTime","title"],"type":"object"},"CreateEventSignUpRequest":{"properties":{"answers":{"items":{"$ref":"#/components/schemas/AnswerRequest"},"type":"array"},"guest":{"$ref":"#/components/schemas/CreateGuestRequest"},"userId":{"format":"int64","type":"integer"}},"type":"object"},"CreateGuestRequest":{"properties":{"discord":{"minLength":1,"type":"string"},"email":{"minLength":1,"type":"string"},"name":{"minLength":1,"type":"string"},"phoneNumber":{"type":"string"},"version":{"format":"int64","type":"integer"}},"required":["discord","email","name"],"type":"object"},"CreateMemberProfileRequest":{"properties":{"bhv":{"type":"boolean"},"dateOfBirth":{"format":"date","type":"string"},"ehbo":{"type":"boolean"},"gender":{"type":"string"},"nationality":{"minLength":1,"type":"string"},"studentNumber":{"type":"string"},"userId":{"format":"int64","type":"integer"}},"required":["bhv","dateOfBirth","ehbo","nationality","userId"],"type":"object"},"CreateSponsorRequest":{"properties":{"description":{"maxLength":4095,"minLength":0,"type":"string"},"name":{"maxLength":255,"minLength":0,"type":"string"}},"required":["description","name"],"type":"object"},"CreateTargetRequest":{"properties":{"folderHint":{"type":"string"},"label":{"minLength":1,"type":"string"},"system":{"$ref":"#/components/schemas/TargetSystem"}},"required":["label","system"],"type":"object"},"CreateTelemetryRequest":{"properties":{"platform":{"$ref":"#/components/schemas/PlatformType"},"url":{"minLength":1,"type":"string"}},"required":["platform","url"],"type":"object"},"CreateUserRequest":{"properties":{"consentPrivacy":{"type":"boolean"},"discord":{"minLength":1,"type":"string"},"email":{"minLength":1,"type":"string"},"firstName":{"minLength":1,"type":"string"},"fullName":{"type":"string"},"initials":{"minLength":1,"type":"string"},"lastName":{"minLength":1,"type":"string"},"memberProfile":{"$ref":"#/components/schemas/UpsertMemberProfileRequest"},"newsletter":{"type":"boolean"},"password":{"type":"string"},"phoneNumber":{"minLength":1,"type":"string"},"photoConsent":{"type":"boolean"},"prefix":{"type":"string"},"username":{"minLength":1,"type":"string"}},"required":["discord","email","firstName","initials","lastName","newsletter","phoneNumber","username"],"type":"object"},"CsrfToken":{"properties":{"headerName":{"type":"string"},"parameterName":{"type":"string"},"token":{"type":"string"}},"type":"object"},"DriftReport":{"properties":{"cohortId":{"format":"int64","type":"integer"},"externalCohortId":{"type":"string"},"extras":{"items":{"$ref":"#/components/schemas/ExtraRow"},"type":"array"},"lastReconciledAt":{"format":"date-time","type":"string"},"missing":{"items":{"$ref":"#/components/schemas/MissingRow"},"type":"array"},"system":{"$ref":"#/components/schemas/TargetSystem"}},"required":["cohortId","extras","missing","system"],"type":"object"},"Email":{"properties":{"attempts":{"format":"int32","type":"integer"},"createdAt":{"format":"date-time","type":"string"},"deliveredAt":{"format":"date-time","type":"string"},"deliveryStatus":{"$ref":"#/components/schemas/EmailDeliveryStatus"},"emailType":{"type":"string"},"errorReason":{"type":"string"},"errorType":{"type":"string"},"id":{"format":"int64","type":"integer"},"jobExecutionId":{"format":"int64","type":"integer"},"messageId":{"type":"string"},"openedAt":{"format":"date-time","type":"string"},"recipientEmail":{"type":"string"},"recipientName":{"type":"string"},"sentAt":{"format":"date-time","type":"string"},"subject":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"type":"object"},"EmailDeliveryStatus":{"enum":["PENDING","SENT","DELIVERED","OPENED","BOUNCED","FAILED"],"type":"string"},"EmailPreviewResponse":{"properties":{"html":{"type":"string"},"subject":{"type":"string"}},"required":["html","subject"],"type":"object"},"EmailStats":{"properties":{"bouncedCount":{"format":"int64","type":"integer"},"deliveredCount":{"format":"int64","type":"integer"},"failedCount":{"format":"int64","type":"integer"},"openedCount":{"format":"int64","type":"integer"},"pendingCount":{"format":"int64","type":"integer"},"sentCount":{"format":"int64","type":"integer"},"totalCount":{"format":"int64","type":"integer"}},"required":["bouncedCount","deliveredCount","failedCount","openedCount","pendingCount","sentCount","totalCount"],"type":"object"},"EnqueueJobRequest":{"properties":{"jobType":{"minLength":1,"type":"string"},"payload":{"additionalProperties":{},"description":"Job payload fields keyed by name; shape depends on the job type","type":"object"}},"required":["jobType"],"type":"object"},"EventBannerRequest":{"properties":{"fileId":{"format":"int64","type":"integer"},"version":{"format":"int64","type":"integer"}},"required":["fileId"],"type":"object"},"EventBannerResponse":{"properties":{"createdAt":{"format":"date-time","type":"string"},"eventId":{"format":"int64","type":"integer"},"fileId":{"format":"int64","type":"integer"},"updatedAt":{"format":"date-time","type":"string"},"version":{"format":"int64","type":"integer"}},"required":["createdAt","eventId","fileId","updatedAt","version"],"type":"object"},"EventResponse":{"properties":{"approved":{"type":"boolean"},"banner":{"$ref":"#/components/schemas/EventBannerResponse"},"committeeId":{"format":"int64","type":"integer"},"createdAt":{"format":"date-time","type":"string"},"description":{"maxLength":4095,"minLength":0,"type":"string"},"endTime":{"format":"date-time","type":"string"},"id":{"format":"int64","type":"integer"},"location":{"type":"string"},"memberPrice":{"format":"double","type":"number"},"membersOnly":{"type":"boolean"},"publicPrice":{"format":"double","type":"number"},"signUp":{"type":"boolean"},"signUpCount":{"format":"int64","type":"integer"},"signUpDeadline":{"format":"date-time","type":"string"},"signUpForm":{"$ref":"#/components/schemas/SurveyResponse"},"signUpLimit":{"format":"int32","type":"integer"},"startTime":{"format":"date-time","type":"string"},"title":{"maxLength":255,"minLength":0,"type":"string"},"updatedAt":{"format":"date-time","type":"string"},"version":{"format":"int64","type":"integer"}},"required":["approved","createdAt","description","endTime","id","membersOnly","signUp","signUpCount","startTime","title","updatedAt","version"],"type":"object"},"EventSignUpResponse":{"properties":{"answers":{"items":{"$ref":"#/components/schemas/AnswerResponse"},"type":"array"},"createdAt":{"format":"date-time","type":"string"},"eventId":{"format":"int64","type":"integer"},"guest":{"$ref":"#/components/schemas/GuestResponse"},"id":{"format":"int64","type":"integer"},"updatedAt":{"format":"date-time","type":"string"},"user":{"$ref":"#/components/schemas/UserSummaryResponse"},"version":{"format":"int64","type":"integer"}},"required":["answers","createdAt","eventId","id","updatedAt","version"],"type":"object"},"ExternalTarget":{"properties":{"externalId":{"type":"string"},"folderLabel":{"type":"string"},"kind":{"$ref":"#/components/schemas/CohortKind"},"label":{"type":"string"},"linkedCohortId":{"format":"int64","type":"integer"},"memberCount":{"format":"int64","type":"integer"},"system":{"$ref":"#/components/schemas/TargetSystem"}},"required":["externalId","kind","label","system"],"type":"object"},"ExtraRow":{"properties":{"email":{"type":"string"},"externalUserId":{"type":"string"},"fullName":{"type":"string"},"kind":{"enum":["KNOWN_LOCAL_USER","UNKNOWN_EXTERNAL"],"type":"string"},"label":{"type":"string"},"softDeleted":{"type":"boolean"},"userId":{"format":"int64","type":"integer"}},"required":["externalUserId","kind"],"type":"object"},"FieldValidationError":{"description":"Details about a single field/object validation error.","properties":{"code":{"description":"Validation code / constraint key.","example":"Email","type":"string"},"field":{"description":"Field that failed validation (null for global errors).","example":"email","type":"string"},"message":{"description":"Human-readable validation message.","example":"must be a well-formed email address","type":"string"},"objectName":{"description":"Object (target) name that failed validation.","example":"createUserRequest","type":"string"}}},"FileResponse":{"properties":{"createdAt":{"format":"date-time","type":"string"},"id":{"format":"int64","type":"integer"},"mediaType":{"type":"string"},"name":{"maxLength":255,"minLength":0,"type":"string"},"path":{"type":"string"},"size":{"format":"int64","type":"integer"},"type":{"$ref":"#/components/schemas/FileType"},"updatedAt":{"format":"date-time","type":"string"},"version":{"format":"int64","type":"integer"}},"required":["createdAt","id","mediaType","name","path","type","updatedAt","version"],"type":"object"},"FileType":{"enum":["DOCUMENT","PROFILE_PICTURE","EVENT_BANNER","EVENT_PICTURE","SPONSOR_PICTURE"],"type":"string"},"GuestResponse":{"properties":{"createdAt":{"format":"date-time","type":"string"},"discord":{"type":"string"},"email":{"type":"string"},"id":{"format":"int64","type":"integer"},"name":{"type":"string"},"phoneNumber":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"},"version":{"format":"int64","type":"integer"}},"required":["createdAt","discord","email","id","name","updatedAt","version"],"type":"object"},"InboundReconcileApplyRequest":{"properties":{"previewToken":{"type":"string"},"selectedExternalUserIds":{"items":{"type":"string"},"type":"array"}},"required":["previewToken","selectedExternalUserIds"],"type":"object"},"InboundReconcileApplyResponse":{"properties":{"acceptedCount":{"format":"int32","type":"integer"},"jobId":{"format":"int64","type":"integer"},"skippedCount":{"format":"int32","type":"integer"}},"required":["acceptedCount","skippedCount"],"type":"object"},"InboundReconcilePreview":{"properties":{"fact":{"$ref":"#/components/schemas/SubjectFact"},"matched":{"items":{"$ref":"#/components/schemas/InboundReconcileRow"},"type":"array"},"previewToken":{"type":"string"},"remoteCount":{"format":"int32","type":"integer"},"skipped":{"items":{"$ref":"#/components/schemas/InboundReconcileRow"},"type":"array"},"writerSupported":{"type":"boolean"}},"required":["fact","matched","previewToken","remoteCount","skipped","writerSupported"],"type":"object"},"InboundReconcileRow":{"properties":{"alreadyTrue":{"type":"boolean"},"externalLabel":{"type":"string"},"externalUserId":{"type":"string"},"reason":{"enum":["DUPLICATE_REMOTE_ID","MAPPING_CONFLICT","DUPLICATE_USER_MATCH","MAPPED_USER_INACTIVE","UNMATCHED"],"type":"string"},"userEmail":{"type":"string"},"userFullName":{"type":"string"},"userId":{"format":"int64","type":"integer"},"writable":{"type":"boolean"}},"required":["alreadyTrue","externalUserId","writable"],"type":"object"},"IncassoNotificationPreviewRequest":{"properties":{"contributionPeriodId":{"format":"int64","type":"integer"},"expectedIncassoDate":{"format":"date","type":"string"},"feeType":{"enum":["FULL_YEAR_FEE","HALF_YEAR_FEE","ALUMNI_FEE"],"type":"string"},"userId":{"format":"int64","type":"integer"}},"required":["contributionPeriodId","expectedIncassoDate","feeType","userId"],"type":"object"},"JobExecution":{"properties":{"actor":{"$ref":"#/components/schemas/Actor"},"attempts":{"format":"int32","type":"integer"},"category":{"$ref":"#/components/schemas/JobExecutionCategory"},"createdAt":{"format":"date-time","type":"string"},"dedupKey":{"type":"string"},"errorMessage":{"type":"string"},"errorReason":{"type":"string"},"errorType":{"type":"string"},"finishedAt":{"format":"date-time","type":"string"},"id":{"format":"int64","type":"integer"},"initiatedByDisplay":{"type":"string"},"initiatedByFullName":{"type":"string"},"initiatedByRole":{"$ref":"#/components/schemas/Role"},"initiatedByType":{"$ref":"#/components/schemas/ActionActorType"},"initiatedByUserId":{"format":"int64","type":"integer"},"initiatedByUsername":{"type":"string"},"jobType":{"minLength":1,"type":"string"},"nextAttemptAt":{"format":"date-time","type":"string"},"payload":{"additionalProperties":{},"type":"object"},"queuedAt":{"format":"date-time","type":"string"},"relatedEntities":{"items":{"$ref":"#/components/schemas/JobExecutionRelatedEntity"},"type":"array"},"stackTrace":{"type":"string"},"startedAt":{"format":"date-time","type":"string"},"status":{"$ref":"#/components/schemas/JobExecutionStatus"},"targetSystem":{"$ref":"#/components/schemas/ContactSystem"},"updatedAt":{"format":"date-time","type":"string"}},"required":["attempts","jobType","relatedEntities","status"],"type":"object"},"JobExecutionCategory":{"enum":["calendar","contact","cohort","email","other"],"type":"string"},"JobExecutionRelatedEntity":{"properties":{"id":{"format":"int64","type":"integer"},"label":{"type":"string"},"type":{"type":"string"}},"required":["label","type"],"type":"object"},"JobExecutionStatus":{"enum":["QUEUED","RUNNING","SUCCESS","FAILED","DEAD"],"type":"string"},"JobPayloadField":{"properties":{"enumValues":{"items":{"type":"string"},"type":"array"},"kind":{"$ref":"#/components/schemas/JobPayloadFieldKind"},"name":{"type":"string"},"required":{"type":"boolean"},"type":{"type":"string"}},"required":["kind","name","required","type"],"type":"object"},"JobPayloadFieldKind":{"enum":["PRIMITIVE","ENUM","OBJECT"],"type":"string"},"JobStatsDTO":{"properties":{"avgSuccessDurationSeconds":{"format":"double","type":"number"},"deadCount":{"format":"int64","type":"integer"},"deadSinceStartup":{"format":"double","type":"number"},"failedCount":{"format":"int64","type":"integer"},"failedSinceStartup":{"format":"double","type":"number"},"queuedCount":{"format":"int64","type":"integer"},"recoveriesSinceStartup":{"format":"double","type":"number"},"runningCount":{"format":"int64","type":"integer"},"successCount":{"format":"int64","type":"integer"},"totalCount":{"format":"int64","type":"integer"}},"required":["avgSuccessDurationSeconds","deadCount","deadSinceStartup","failedCount","failedSinceStartup","queuedCount","recoveriesSinceStartup","runningCount","successCount","totalCount"],"type":"object"},"JobTypeDescriptor":{"properties":{"payloadFields":{"items":{"$ref":"#/components/schemas/JobPayloadField"},"type":"array"},"type":{"type":"string"}},"required":["payloadFields","type"],"type":"object"},"JwtRequest":{"properties":{"password":{"minLength":1,"type":"string"},"username":{"minLength":1,"type":"string"}},"required":["password","username"],"type":"object"},"LinkExistingTargetRequest":{"properties":{"externalId":{"minLength":1,"type":"string"},"system":{"$ref":"#/components/schemas/TargetSystem"}},"required":["externalId","system"],"type":"object"},"LinkUserRequest":{"properties":{"externalUserId":{"minLength":1,"type":"string"},"system":{"$ref":"#/components/schemas/TargetSystem"},"userId":{"format":"int64","type":"integer"}},"required":["externalUserId","system","userId"],"type":"object"},"LinkedUser":{"properties":{"externalUserId":{"type":"string"},"system":{"$ref":"#/components/schemas/TargetSystem"},"userId":{"format":"int64","type":"integer"}},"required":["externalUserId","system","userId"],"type":"object"},"LoginResponse":{"properties":{"addressId":{"format":"int64","type":"integer"},"expiration":{"format":"int64","type":"integer"},"roles":{"items":{"$ref":"#/components/schemas/Role"},"minItems":1,"type":"array"},"token":{"minLength":1,"type":"string"},"userId":{"format":"int64","type":"integer"},"username":{"minLength":1,"type":"string"}},"required":["expiration","roles","token","userId","username"],"type":"object"},"MemberActivationRequest":{"properties":{"password":{"maxLength":100,"minLength":8,"pattern":"^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]+$","type":"string"},"token":{"minLength":1,"type":"string"},"username":{"minLength":1,"type":"string"}},"required":["password","token","username"],"type":"object"},"MemberProfileResponse":{"properties":{"bhv":{"type":"boolean"},"createdAt":{"format":"date-time","type":"string"},"dateOfBirth":{"format":"date","type":"string"},"ehbo":{"type":"boolean"},"gender":{"type":"string"},"id":{"format":"int64","type":"integer"},"nationality":{"type":"string"},"studentNumber":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"},"userId":{"format":"int64","type":"integer"},"version":{"format":"int64","type":"integer"}},"required":["bhv","createdAt","ehbo","id","updatedAt","userId","version"]},"MemberType":{"enum":["ALUMNI","HONORARY","REGULAR","NONE"],"type":"string"},"MembershipResponse":{"properties":{"createdAt":{"format":"date-time","type":"string"},"endDate":{"format":"date","type":"string"},"id":{"format":"int64","type":"integer"},"incasso":{"type":"boolean"},"memberType":{"$ref":"#/components/schemas/MemberType"},"startDate":{"format":"date","type":"string"},"updatedAt":{"format":"date-time","type":"string"},"userId":{"format":"int64","type":"integer"},"version":{"format":"int64","type":"integer"}},"required":["createdAt","id","incasso","memberType","startDate","updatedAt","userId","version"],"type":"object"},"MissingRow":{"properties":{"hasExternalMapping":{"type":"boolean"},"userId":{"format":"int64","type":"integer"}},"required":["hasExternalMapping","userId"],"type":"object"},"PageMetadata":{"properties":{"number":{"format":"int64","type":"integer"},"size":{"format":"int64","type":"integer"},"totalElements":{"format":"int64","type":"integer"},"totalPages":{"format":"int64","type":"integer"}},"type":"object"},"PagedModelEmail":{"properties":{"content":{"items":{"$ref":"#/components/schemas/Email"},"type":"array"},"page":{"$ref":"#/components/schemas/PageMetadata"}},"type":"object"},"PagedModelEventResponse":{"properties":{"content":{"items":{"$ref":"#/components/schemas/EventResponse"},"type":"array"},"page":{"$ref":"#/components/schemas/PageMetadata"}},"type":"object"},"PagedModelJobExecution":{"properties":{"content":{"items":{"$ref":"#/components/schemas/JobExecution"},"type":"array"},"page":{"$ref":"#/components/schemas/PageMetadata"}},"type":"object"},"PagedModelUserDetailResponse":{"properties":{"content":{"items":{"$ref":"#/components/schemas/UserDetailResponse"},"type":"array"},"page":{"$ref":"#/components/schemas/PageMetadata"}},"type":"object"},"PasswordResetRequest":{"properties":{"password":{"maxLength":100,"minLength":8,"pattern":"^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]+$","type":"string"},"token":{"minLength":1,"type":"string"}},"required":["password","token"],"type":"object"},"PlatformType":{"enum":["FACEBOOK","LINKEDIN","TWITTER","INSTAGRAM"],"type":"string"},"QuestionRequest":{"properties":{"choiceLabels":{"items":{"type":"string"},"type":"array"},"idx":{"format":"int64","type":"integer"},"label":{"maxLength":2055,"minLength":0,"type":"string"},"required":{"type":"boolean"},"type":{"$ref":"#/components/schemas/QuestionType"}},"required":["idx","label","type"],"type":"object"},"QuestionResponse":{"properties":{"choiceLabels":{"items":{"type":"string"},"type":"array"},"createdAt":{"format":"date-time","type":"string"},"id":{"format":"int64","type":"integer"},"idx":{"format":"int64","type":"integer"},"label":{"maxLength":2055,"minLength":0,"type":"string"},"required":{"type":"boolean"},"surveyId":{"format":"int64","type":"integer"},"type":{"$ref":"#/components/schemas/QuestionType"},"updatedAt":{"format":"date-time","type":"string"},"version":{"format":"int64","type":"integer"}},"required":["createdAt","id","idx","label","surveyId","type","updatedAt","version"],"type":"object"},"QuestionType":{"enum":["OPEN","RADIO","CHECKBOX","DESCRIPTION"],"type":"string"},"RedirectResponse":{"properties":{"path":{"type":"string"}},"required":["path"],"type":"object"},"Role":{"enum":["ANONYMOUS","VEGAN","GUEST","COMPANY","MEMBER","COMMITTEE","BOARD","TREASURER","ADMIN","SYSTEM"],"type":"string"},"ServiceEntry":{"properties":{"description":{"type":"string"},"iconUrl":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"}},"required":["description","iconUrl","id","name","url"],"type":"object"},"SponsorResponse":{"properties":{"createdAt":{"format":"date-time","type":"string"},"description":{"type":"string"},"id":{"format":"int64","type":"integer"},"name":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"},"version":{"format":"int64","type":"integer"}},"required":["createdAt","description","id","name","updatedAt","version"],"type":"object"},"SubjectFact":{"properties":{"key":{"type":"string"},"kind":{"$ref":"#/components/schemas/CohortFactKind"}},"required":["key","kind"],"type":"object"},"SurveyRequest":{"properties":{"questions":{"items":{"$ref":"#/components/schemas/QuestionRequest"},"minItems":1,"type":"array"}},"required":["questions"],"type":"object"},"SurveyResponse":{"properties":{"createdAt":{"format":"date-time","type":"string"},"id":{"format":"int64","type":"integer"},"questions":{"items":{"$ref":"#/components/schemas/QuestionResponse"},"minItems":1,"type":"array"},"responseCount":{"format":"int64","type":"integer"},"updatedAt":{"format":"date-time","type":"string"},"version":{"format":"int64","type":"integer"}},"required":["createdAt","id","questions","responseCount","updatedAt","version"],"type":"object"},"SwitchTargetRequest":{"properties":{"deletePrevious":{"type":"boolean"},"externalId":{"minLength":1,"type":"string"},"reconcileNow":{"type":"boolean"}},"required":["deletePrevious","externalId","reconcileNow"],"type":"object"},"TargetDescriptor":{"properties":{"capabilities":{"items":{"enum":["CATALOG","CREATE","READ_MEMBERS","WRITE_MEMBERS","DELETE"],"type":"string"},"type":"array","uniqueItems":true},"folderLabel":{"type":"string"},"idLabel":{"type":"string"},"kind":{"$ref":"#/components/schemas/CohortKind"},"system":{"$ref":"#/components/schemas/TargetSystem"},"systemLabel":{"type":"string"},"targetLabel":{"type":"string"}},"required":["capabilities","idLabel","kind","system","systemLabel","targetLabel"],"type":"object"},"TargetSystem":{"enum":["BREVO","GOOGLE_CALENDAR"],"type":"string"},"TelemetryResponse":{"properties":{"createdAt":{"format":"date-time","type":"string"},"id":{"format":"int64","type":"integer"},"platform":{"$ref":"#/components/schemas/PlatformType"},"updatedAt":{"format":"date-time","type":"string"},"url":{"type":"string"},"version":{"format":"int64","type":"integer"}},"required":["createdAt","id","platform","updatedAt","url","version"],"type":"object"},"UpdateAddressRequest":{"properties":{"city":{"minLength":1,"type":"string"},"country":{"minLength":1,"type":"string"},"houseNumber":{"minLength":1,"type":"string"},"street":{"minLength":1,"type":"string"},"version":{"format":"int64","type":"integer"},"zipCode":{"minLength":1,"type":"string"}},"required":["city","country","houseNumber","street","version","zipCode"],"type":"object"},"UpdateBlogRequest":{"properties":{"html":{"minLength":1,"type":"string"},"publishedAt":{"format":"date-time","type":"string"},"title":{"minLength":1,"type":"string"},"version":{"format":"int64","type":"integer"}},"required":["html","publishedAt","title","version"],"type":"object"},"UpdateBoardRequest":{"properties":{"candidate":{"minLength":1,"type":"string"},"endDate":{"format":"date","type":"string"},"name":{"maxLength":100,"minLength":1,"type":"string"},"pictureId":{"format":"int64","type":"integer"},"startDate":{"format":"date","type":"string"},"version":{"format":"int64","type":"integer"}},"required":["candidate","name","startDate","version"],"type":"object"},"UpdateCommitteeRequest":{"properties":{"description":{"maxLength":4095,"minLength":0,"type":"string"},"members":{"items":{"$ref":"#/components/schemas/CommitteeMemberRequest"},"minItems":1,"type":"array"},"name":{"maxLength":255,"minLength":0,"type":"string"},"version":{"format":"int64","type":"integer"}},"required":["description","members","name","version"],"type":"object"},"UpdateContributionPeriodRequest":{"properties":{"alumniFee":{"format":"double","type":"number"},"contactListId":{"format":"int64","type":"integer"},"endDate":{"format":"date","type":"string"},"fullYearFee":{"format":"double","type":"number"},"halfYearFee":{"format":"double","type":"number"},"startDate":{"format":"date","type":"string"},"version":{"format":"int64","type":"integer"}},"required":["alumniFee","endDate","fullYearFee","halfYearFee","startDate","version"],"type":"object"},"UpdateEventRequest":{"properties":{"approved":{"type":"boolean"},"banner":{"$ref":"#/components/schemas/EventBannerRequest"},"committeeId":{"format":"int64","type":"integer"},"description":{"maxLength":4095,"minLength":0,"type":"string"},"endTime":{"format":"date-time","type":"string"},"location":{"type":"string"},"memberPrice":{"format":"double","type":"number"},"membersOnly":{"type":"boolean"},"publicPrice":{"format":"double","type":"number"},"removeExistingSignUps":{"type":"boolean"},"signUp":{"type":"boolean"},"signUpDeadline":{"format":"date-time","type":"string"},"signUpForm":{"$ref":"#/components/schemas/SurveyRequest"},"signUpLimit":{"format":"int32","minimum":1,"type":"integer"},"startTime":{"format":"date-time","type":"string"},"title":{"maxLength":255,"minLength":0,"type":"string"},"version":{"format":"int64","type":"integer"}},"required":["approved","committeeId","description","endTime","membersOnly","signUp","startTime","title","version"],"type":"object"},"UpdateEventSignUpRequest":{"properties":{"answers":{"items":{"$ref":"#/components/schemas/AnswerRequest"},"type":"array"},"guest":{"$ref":"#/components/schemas/CreateGuestRequest"},"userId":{"format":"int64","type":"integer"},"version":{"format":"int64","type":"integer"}},"type":"object"},"UpdateMemberProfileRequest":{"properties":{"bhv":{"type":"boolean"},"dateOfBirth":{"format":"date","type":"string"},"ehbo":{"type":"boolean"},"gender":{"type":"string"},"nationality":{"minLength":1,"type":"string"},"studentNumber":{"type":"string"},"version":{"format":"int64","type":"integer"}},"required":["bhv","dateOfBirth","ehbo","nationality","version"],"type":"object"},"UpdateMembershipRequest":{"properties":{"endDate":{"format":"date","type":"string"},"incasso":{"type":"boolean"},"memberType":{"$ref":"#/components/schemas/MemberType"},"startDate":{"format":"date","type":"string"},"userId":{"format":"int64","type":"integer"},"version":{"format":"int64","type":"integer"}},"required":["userId","version"],"type":"object"},"UpdateSponsorRequest":{"properties":{"description":{"maxLength":4095,"minLength":0,"type":"string"},"name":{"maxLength":255,"minLength":0,"type":"string"},"version":{"format":"int64","type":"integer"}},"required":["description","name","version"],"type":"object"},"UpdateUserRequest":{"properties":{"discord":{"minLength":1,"type":"string"},"memberProfile":{"$ref":"#/components/schemas/UpsertMemberProfileRequest"},"newsletter":{"type":"boolean"},"phoneNumber":{"minLength":1,"type":"string"},"photoConsent":{"type":"boolean"},"version":{"format":"int64","type":"integer"}},"required":["discord","newsletter","phoneNumber","version"],"type":"object"},"UpsertMemberProfileRequest":{"properties":{"bhv":{"type":"boolean"},"dateOfBirth":{"format":"date","type":"string"},"ehbo":{"type":"boolean"},"gender":{"type":"string"},"nationality":{"minLength":1,"type":"string"},"studentNumber":{"type":"string"},"version":{"format":"int64","type":"integer"}},"required":["bhv","dateOfBirth","ehbo","nationality"],"type":"object"},"UserActivationRequest":{"properties":{"token":{"minLength":1,"type":"string"}},"required":["token"],"type":"object"},"UserDetailResponse":{"properties":{"addressId":{"format":"int64","type":"integer"},"createdAt":{"format":"date-time","type":"string"},"discord":{"type":"string"},"email":{"type":"string"},"enabled":{"type":"boolean"},"firstName":{"type":"string"},"fullName":{"type":"string"},"id":{"format":"int64","type":"integer"},"initials":{"type":"string"},"lastName":{"type":"string"},"newsletter":{"type":"boolean"},"phoneNumber":{"type":"string"},"photoConsent":{"type":"boolean"},"prefix":{"type":"string"},"restoreUntilAt":{"format":"date-time","type":"string"},"roles":{"items":{"$ref":"#/components/schemas/Role"},"type":"array"},"updatedAt":{"format":"date-time","type":"string"},"username":{"type":"string"},"version":{"format":"int64","type":"integer"}},"required":["createdAt","email","enabled","firstName","fullName","id","initials","lastName","newsletter","photoConsent","roles","updatedAt","username","version"],"type":"object"},"UserSummaryResponse":{"properties":{"createdAt":{"format":"date-time","type":"string"},"discord":{"type":"string"},"email":{"type":"string"},"fullName":{"type":"string"},"id":{"format":"int64","type":"integer"},"phoneNumber":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"},"version":{"format":"int64","type":"integer"}},"required":["createdAt","email","fullName","id","updatedAt","version"],"type":"object"}}},"info":{"title":"OpenAPI definition","version":"v0"},"openapi":"3.1.0","paths":{"/addresses":{"get":{"operationId":"findAllAddresses","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/AddressResponse"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Addresses"]},"post":{"operationId":"createAddress","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAddressRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddressResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Addresses"]}},"/addresses/{id}":{"delete":{"operationId":"deleteAddressById","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Addresses"]},"get":{"operationId":"findAddressById","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddressResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Addresses"]},"put":{"operationId":"updateAddress","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAddressRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddressResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Addresses"]}},"/auth":{"post":{"operationId":"authenticate","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JwtRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Authentication"]}},"/auth/logout":{"post":{"operationId":"logout","responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Authentication"]}},"/blogs":{"get":{"operationId":"findBlogs","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/BlogResponse"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Blogs"]},"post":{"operationId":"createBlog","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateBlogRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlogResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Blogs"]}},"/blogs/{id}":{"delete":{"operationId":"deleteById","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Blogs"]},"get":{"operationId":"findBlogById","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlogResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Blogs"]},"post":{"operationId":"updateBlog","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateBlogRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlogResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Blogs"]}},"/boards":{"get":{"operationId":"findAllBoards","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/BoardResponse"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Boards"]},"post":{"operationId":"createBoard","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateBoardRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BoardResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Boards"]}},"/boards/{boardId}/members":{"post":{"operationId":"addMember","parameters":[{"in":"path","name":"boardId","required":true,"schema":{"format":"int64","type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddBoardMemberRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BoardMemberResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Boards"]}},"/boards/{boardId}/members/{userId}":{"delete":{"operationId":"removeMember","parameters":[{"in":"path","name":"boardId","required":true,"schema":{"format":"int64","type":"integer"}},{"in":"path","name":"userId","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Boards"]}},"/boards/{id}":{"delete":{"operationId":"deleteBoard","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Boards"]},"get":{"operationId":"findBoardById","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BoardResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Boards"]},"put":{"operationId":"updateBoard","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateBoardRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BoardResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Boards"]}},"/committeeMembers/committees":{"get":{"operationId":"findCommitteesByUserId","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/CommitteeResponse"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Committees"]}},"/committees":{"get":{"operationId":"findCommittees","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/CommitteeResponse"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Committees"]},"post":{"operationId":"createCommittee","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCommitteeRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommitteeDetailResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Committees"]}},"/committees/{committeeId}":{"get":{"operationId":"findCommitteeById","parameters":[{"in":"path","name":"committeeId","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommitteeResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Committees"]}},"/committees/{id}":{"delete":{"operationId":"deleteCommitteeById","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Committees"]},"put":{"operationId":"updateCommittee","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCommitteeRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommitteeDetailResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Committees"]}},"/contributionPeriods":{"get":{"operationId":"findContributionPeriods","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/ContributionPeriodResponse"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["ContributionPeriods"]},"post":{"operationId":"createContributionPeriod","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateContributionPeriodRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContributionPeriodResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["ContributionPeriods"]}},"/contributionPeriods/current":{"get":{"operationId":"findCurrentContributionPeriod","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContributionPeriodResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["ContributionPeriods"]}},"/contributionPeriods/{contributionPeriodId}/users/{userId}/contributions":{"delete":{"operationId":"deleteContribution","parameters":[{"in":"path","name":"userId","required":true,"schema":{"format":"int64","type":"integer"}},{"in":"path","name":"contributionPeriodId","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Contributions"]}},"/contributionPeriods/{id}":{"delete":{"operationId":"deleteContributionPeriodById","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["ContributionPeriods"]},"put":{"operationId":"updateContributionPeriod","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateContributionPeriodRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContributionPeriodResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["ContributionPeriods"]}},"/contributionPeriods/{periodId}/contributions":{"get":{"operationId":"findContributionsByPeriodId","parameters":[{"in":"path","name":"periodId","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/ContributionResponse"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Contributions"]}},"/contributionReminders":{"get":{"operationId":"findContributionReminders","parameters":[{"in":"query","name":"contributionPeriodId","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/ContributionReminderResponse"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["ContributionReminders"]},"post":{"operationId":"sendContributionReminder","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateContributionReminderRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContributionReminderResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["ContributionReminders"]}},"/contributionReminders/batch":{"post":{"operationId":"sendContributionReminderBatch","requestBody":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/CreateContributionReminderRequest"},"type":"array"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/ContributionReminderResponse"},"type":"array"}}},"description":"Created"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["ContributionReminders"]}},"/contributionReminders/bulk/execute":{"post":{"operationId":"executeBulkReminder","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkContributionReminderExecuteRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkActionResult"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Contributions"]}},"/contributionReminders/preview":{"post":{"operationId":"previewReminder","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContributionReminderPreviewRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailPreviewResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Contributions"]}},"/contributions":{"get":{"operationId":"findContributions","parameters":[{"in":"query","name":"contributionPeriodId","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/ContributionResponse"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Contributions"]},"post":{"operationId":"createContribution","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateContributionRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContributionResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Contributions"]}},"/contributions/bulk/mark-paid":{"post":{"operationId":"markPaid","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkMarkPaidRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkActionResult"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Contributions"]}},"/contributions/bulk/mark-unpaid":{"post":{"operationId":"markUnpaid","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkMarkUnpaidRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkActionResult"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Contributions"]}},"/csrf":{"get":{"operationId":"csrf","parameters":[{"in":"query","name":"csrfToken","required":true,"schema":{"$ref":"#/components/schemas/CsrfToken"}}],"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Security"]}},"/events":{"get":{"operationId":"findEvents","parameters":[{"description":"Zero-based page index (0..N)","in":"query","name":"page","required":false,"schema":{"default":0,"minimum":0,"type":"integer"}},{"description":"The size of the page to be returned","in":"query","name":"size","required":false,"schema":{"default":20,"minimum":1,"type":"integer"}},{"description":"Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported.","in":"query","name":"sort","required":false,"schema":{"items":{"type":"string"},"type":"array"}},{"in":"query","name":"from","required":false,"schema":{"format":"date-time","type":"string"}},{"in":"query","name":"to","required":false,"schema":{"format":"date-time","type":"string"}},{"in":"query","name":"approved","required":false,"schema":{"type":"boolean"}},{"in":"query","name":"committeeId","required":false,"schema":{"format":"int64","type":"integer"}},{"in":"query","name":"titleContains","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PagedModelEventResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Events"]},"post":{"operationId":"createEvent","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateEventRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Events"]}},"/events/banners":{"post":{"operationId":"uploadEventBanner","requestBody":{"content":{"multipart/form-data":{"schema":{"properties":{"file":{"format":"binary","type":"string"}},"required":["file"],"type":"object"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FileResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Files"]}},"/events/signups":{"get":{"operationId":"findEventSignUps","parameters":[{"in":"query","name":"from","required":false,"schema":{"format":"date-time","type":"string"}},{"in":"query","name":"to","required":false,"schema":{"format":"date-time","type":"string"}},{"in":"query","name":"userId","required":false,"schema":{"format":"int64","type":"integer"}},{"in":"query","name":"committeeId","required":false,"schema":{"format":"int64","type":"integer"}},{"in":"query","name":"approved","required":false,"schema":{"type":"boolean"}},{"in":"query","name":"eventId","required":false,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/EventSignUpResponse"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["EventSignUps"]}},"/events/signups/byAccessToken":{"get":{"operationId":"findEventSignUpsByAccessToken","parameters":[{"in":"header","name":"X-Guest-Access-Token","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/EventSignUpResponse"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["EventSignUps"]}},"/events/signups/{id}":{"delete":{"operationId":"deleteEventSignup","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}},{"in":"header","name":"X-Guest-Access-Token","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["EventSignUps"]}},"/events/{eventId}":{"delete":{"operationId":"deleteEventById","parameters":[{"in":"path","name":"eventId","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Events"]}},"/events/{eventId}/banners":{"get":{"operationId":"downloadEventBanner","parameters":[{"in":"path","name":"eventId","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"format":"binary","type":"string"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Files"]}},"/events/{eventId}/signups":{"get":{"operationId":"findEventSignUpsByEventId","parameters":[{"in":"path","name":"eventId","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/EventSignUpResponse"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["EventSignUps"]},"post":{"operationId":"createEventSignup","parameters":[{"in":"path","name":"eventId","required":true,"schema":{"format":"int64","type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateEventSignUpRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventSignUpResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["EventSignUps"]},"put":{"operationId":"updateEventSignUp","parameters":[{"in":"path","name":"eventId","required":true,"schema":{"format":"int64","type":"integer"}},{"in":"header","name":"X-Guest-Access-Token","required":false,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateEventSignUpRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventSignUpResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["EventSignUps"]}},"/events/{id}":{"get":{"operationId":"findEventById","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Events"]},"put":{"operationId":"updateEvent","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateEventRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Events"]}},"/events/{id}/approve":{"put":{"operationId":"approveEvent","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}},{"in":"query","name":"approved","required":true,"schema":{"type":"boolean"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Events"]}},"/health":{"get":{"operationId":"healthCheck","responses":{"200":{"content":{"application/json":{"schema":{"type":"boolean"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Health"]}},"/incassoNotifications/bulk/execute":{"post":{"operationId":"executeBulkIncassoNotification","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkIncassoNotificationExecuteRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkActionResult"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Contributions"]}},"/incassoNotifications/preview":{"post":{"operationId":"previewIncassoNotification","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IncassoNotificationPreviewRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailPreviewResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Contributions"]}},"/management/cohort-subjects":{"get":{"operationId":"findCohortSubjects","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/CohortSubjectSummary"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Cohort Subjects"]}},"/management/cohort-subjects/{id}":{"get":{"operationId":"findCohortSubjectById","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CohortSubjectDetail"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Cohort Subjects"]}},"/management/cohort-subjects/{id}/drift":{"get":{"operationId":"getDrift","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}},{"in":"query","name":"system","required":true,"schema":{"$ref":"#/components/schemas/TargetSystem"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DriftReport"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Cohort Subjects"]}},"/management/cohort-subjects/{id}/drift/link-user":{"post":{"operationId":"linkUser","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkUserRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkedUser"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Cohort Subjects"]}},"/management/cohort-subjects/{id}/targets/existing":{"post":{"operationId":"linkExistingTarget","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkExistingTargetRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CohortMapping"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Cohort Subjects"]}},"/management/cohort-subjects/{id}/targets/new":{"post":{"operationId":"createTarget","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTargetRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CohortMapping"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Cohort Subjects"]}},"/management/cohort-subjects/{id}/targets/{cohortId}":{"put":{"operationId":"switchTarget","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}},{"in":"path","name":"cohortId","required":true,"schema":{"format":"int64","type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SwitchTargetRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CohortMapping"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Cohort Subjects"]}},"/management/cohort-subjects/{id}/targets/{cohortId}/inbound-reconcile/apply":{"post":{"operationId":"applyInboundReconcile","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}},{"in":"path","name":"cohortId","required":true,"schema":{"format":"int64","type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundReconcileApplyRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundReconcileApplyResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Cohort Subjects"]}},"/management/cohort-subjects/{id}/targets/{cohortId}/inbound-reconcile/preview":{"post":{"operationId":"previewInboundReconcile","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}},{"in":"path","name":"cohortId","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundReconcilePreview"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Cohort Subjects"]}},"/management/cohort-targets/systems":{"get":{"operationId":"listCohortTargetSystems","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/TargetDescriptor"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Cohort Targets"]}},"/management/cohort-targets/{system}":{"get":{"operationId":"searchCohortTargets","parameters":[{"in":"path","name":"system","required":true,"schema":{"$ref":"#/components/schemas/TargetSystem"}},{"in":"query","name":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/ExternalTarget"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Cohort Targets"]}},"/management/cohorts":{"get":{"operationId":"findCohorts","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/CohortSummary"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Cohorts"]}},"/management/cohorts/{id}":{"get":{"operationId":"findCohortById","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CohortDetail"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Cohorts"]}},"/management/cohorts/{id}/repair-missing-adds":{"post":{"operationId":"repairMissingAdds","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CohortRepair"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Cohorts"]}},"/management/emails":{"get":{"operationId":"list_1","parameters":[{"description":"Zero-based page index (0..N)","in":"query","name":"page","required":false,"schema":{"default":0,"minimum":0,"type":"integer"}},{"description":"The size of the page to be returned","in":"query","name":"size","required":false,"schema":{"default":50,"minimum":1,"type":"integer"}},{"description":"Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported.","in":"query","name":"sort","required":false,"schema":{"default":["createdAt,DESC"],"items":{"type":"string"},"type":"array"}},{"in":"query","name":"deliveryStatus","required":false,"schema":{"$ref":"#/components/schemas/EmailDeliveryStatus"}},{"in":"query","name":"emailType","required":false,"schema":{"type":"string"}},{"in":"query","name":"search","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PagedModelEmail"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Email Management"]}},"/management/emails/stats":{"get":{"operationId":"getStats_1","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailStats"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Email Management"]}},"/management/emails/{id}/retry":{"post":{"operationId":"retry_1","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Email"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Email Management"]}},"/management/jobs":{"get":{"operationId":"list","parameters":[{"description":"Zero-based page index (0..N)","in":"query","name":"page","required":false,"schema":{"default":0,"minimum":0,"type":"integer"}},{"description":"The size of the page to be returned","in":"query","name":"size","required":false,"schema":{"default":50,"minimum":1,"type":"integer"}},{"description":"Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported.","in":"query","name":"sort","required":false,"schema":{"default":["updatedAt,DESC"],"items":{"type":"string"},"type":"array"}},{"in":"query","name":"status","required":false,"schema":{"$ref":"#/components/schemas/JobExecutionStatus"}},{"in":"query","name":"category","required":false,"schema":{"$ref":"#/components/schemas/JobExecutionCategory"}},{"in":"query","name":"search","required":false,"schema":{"type":"string"}},{"in":"query","name":"initiatedByType","required":false,"schema":{"$ref":"#/components/schemas/ActionActorType"}},{"in":"query","name":"jobType","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PagedModelJobExecution"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Job Management"]}},"/management/jobs/enqueue":{"post":{"operationId":"enqueue","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnqueueJobRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobExecution"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Job Management"]}},"/management/jobs/stats":{"get":{"operationId":"getStats","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobStatsDTO"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Job Management"]}},"/management/jobs/types":{"get":{"operationId":"jobTypes","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/JobTypeDescriptor"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Job Management"]}},"/management/jobs/{id}/retry":{"post":{"operationId":"retry","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobExecution"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Job Management"]}},"/me/services":{"get":{"operationId":"myServices","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/ServiceEntry"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["My Services"]}},"/memberProfiles":{"post":{"operationId":"createMemberProfile","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateMemberProfileRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MemberProfileResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Member Profiles"]}},"/memberships":{"get":{"operationId":"findMemberships","parameters":[{"in":"query","name":"from","required":false,"schema":{"format":"date","type":"string"}},{"in":"query","name":"to","required":false,"schema":{"format":"date","type":"string"}},{"in":"query","name":"userId","required":false,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/MembershipResponse"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Memberships"]},"post":{"operationId":"createMembership","responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MembershipResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Memberships"]}},"/memberships/bulk/end/execute":{"post":{"operationId":"executeBulkEnd","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkEndMembershipRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkActionResult"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Memberships"]}},"/memberships/bulk/resume/execute":{"post":{"operationId":"executeBulkResume","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkResumeMembershipRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkActionResult"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Memberships"]}},"/memberships/{id}":{"delete":{"operationId":"deleteMembership","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Memberships"]},"get":{"operationId":"findMembershipById","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MembershipResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Memberships"]},"put":{"operationId":"updateMembership","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateMembershipRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MembershipResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Memberships"]}},"/memberships/{id}/end":{"post":{"operationId":"endMembership","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MembershipResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Memberships"]}},"/memberships/{id}/reopen":{"post":{"operationId":"reopenMembership","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MembershipResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Memberships"]}},"/memberships/{id}/restore":{"put":{"operationId":"restoreMembership","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MembershipResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Memberships"]}},"/oauth2/forward-auth":{"get":{"operationId":"forwardAuth","responses":{"200":{"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Forward Auth"]}},"/recovery/member/activate":{"post":{"operationId":"memberActivate","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MemberActivationRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RedirectResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Recovery"]}},"/recovery/password":{"post":{"operationId":"setPassword","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PasswordResetRequest"}}},"required":true},"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Recovery"]}},"/recovery/password/reset/{username}":{"post":{"operationId":"resetPassword","parameters":[{"in":"path","name":"username","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Recovery"]}},"/recovery/user/activate":{"post":{"operationId":"userActivate","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserActivationRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RedirectResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Recovery"]}},"/recovery/user/activate/resend/{username}":{"post":{"operationId":"resendUserActivation","parameters":[{"in":"path","name":"username","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Recovery"]}},"/recovery/users/{userId}/resend/recovery":{"post":{"operationId":"resendMemberActivationEmail","parameters":[{"in":"path","name":"userId","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Recovery"]}},"/sponsors":{"get":{"operationId":"findSponsors","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/SponsorResponse"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Sponsors"]},"post":{"operationId":"createSponsor","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSponsorRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SponsorResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Sponsors"]}},"/sponsors/{id}":{"delete":{"operationId":"deleteSponsorById","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Sponsors"]},"get":{"operationId":"findSponsorById","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SponsorResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Sponsors"]},"put":{"operationId":"updateSponsor","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSponsorRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SponsorResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Sponsors"]}},"/telemetry":{"post":{"operationId":"createTelemetry","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTelemetryRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TelemetryResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Telemetries"]}},"/telemetry/{id}":{"get":{"operationId":"findTelemetryById","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TelemetryResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Telemetries"]}},"/users":{"get":{"operationId":"findUsers","parameters":[{"in":"query","name":"username","required":false,"schema":{"type":"string"}},{"in":"query","name":"enabled","required":false,"schema":{"type":"boolean"}},{"description":"Zero-based page index (0..N)","in":"query","name":"page","required":false,"schema":{"default":0,"minimum":0,"type":"integer"}},{"description":"The size of the page to be returned","in":"query","name":"size","required":false,"schema":{"default":20,"minimum":1,"type":"integer"}},{"description":"Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported.","in":"query","name":"sort","required":false,"schema":{"items":{"type":"string"},"type":"array"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PagedModelUserDetailResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Users"]},"post":{"operationId":"createUser","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUserRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserDetailResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Users"]}},"/users/deleted":{"get":{"operationId":"findDeletedUsers","parameters":[{"description":"Zero-based page index (0..N)","in":"query","name":"page","required":false,"schema":{"default":0,"minimum":0,"type":"integer"}},{"description":"The size of the page to be returned","in":"query","name":"size","required":false,"schema":{"default":20,"minimum":1,"type":"integer"}},{"description":"Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported.","in":"query","name":"sort","required":false,"schema":{"items":{"type":"string"},"type":"array"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PagedModelUserDetailResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Users"]}},"/users/{id}":{"put":{"operationId":"updateUser","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUserRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserDetailResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Users"]}},"/users/{userId}":{"delete":{"operationId":"deleteUserById","parameters":[{"in":"path","name":"userId","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Users"]},"get":{"operationId":"findUserById","parameters":[{"in":"path","name":"userId","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserDetailResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Users"]}},"/users/{userId}/memberProfiles":{"get":{"operationId":"findMemberProfileByUserId","parameters":[{"in":"path","name":"userId","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MemberProfileResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Member Profiles"]},"put":{"operationId":"updateMemberProfile","parameters":[{"in":"path","name":"userId","required":true,"schema":{"format":"int64","type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateMemberProfileRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MemberProfileResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Member Profiles"]}},"/users/{userId}/memberships":{"post":{"operationId":"boardCreateMembership","parameters":[{"in":"path","name":"userId","required":true,"schema":{"format":"int64","type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BoardCreateMembershipRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MembershipResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Memberships"]}},"/users/{userId}/memberships/deleted":{"get":{"operationId":"findDeletedMemberships","parameters":[{"in":"path","name":"userId","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/MembershipResponse"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Memberships"]}},"/users/{userId}/restore":{"put":{"operationId":"restoreDeletedUserById","parameters":[{"in":"path","name":"userId","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Users"]}},"/users/{userId}/roles":{"put":{"operationId":"toggleUserRole","parameters":[{"in":"path","name":"userId","required":true,"schema":{"format":"int64","type":"integer"}},{"in":"query","name":"role","required":true,"schema":{"$ref":"#/components/schemas/Role"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserDetailResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Bad Request\",\n \"status\": 400,\n \"detail\": \"Validation failed for request.\",\n \"instance\": \"/api/users\",\n \"errors\": [\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"email\",\n \"message\": \"must be a well-formed email address\",\n \"code\": \"Email\"\n },\n {\n \"objectName\": \"createUserRequest\",\n \"field\": \"age\",\n \"message\": \"must be greater than or equal to 0\",\n \"code\": \"Min\"\n }\n ],\n \"traceId\": \"a8c0c4e5f1c24a7e\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Unauthorized\",\n \"status\": 401,\n \"detail\": \"Full authentication is required to access this resource\",\n \"instance\": \"/api/users\",\n \"traceId\": \"401401401401\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Forbidden\",\n \"status\": 403,\n \"detail\": \"Access is denied\",\n \"instance\": \"/api/users\",\n \"traceId\": \"403403403403\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Forbidden (access denied)"},"404":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Not Found\",\n \"status\": 404,\n \"detail\": \"User not found with id: 42\",\n \"instance\": \"/api/users/42\",\n \"traceId\": \"cdef1234abcd5678\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Not Found"},"500":{"content":{"application/json":{"example":"{\n \"type\": \"about:blank\",\n \"title\": \"Internal Server Error\",\n \"status\": 500,\n \"detail\": \"An unexpected error occurred.\",\n \"instance\": \"/api/users\",\n \"traceId\": \"ab12cd34ef56\"\n}\n","schema":{"$ref":"#/components/schemas/ApiError"}}},"description":"Server error"}},"tags":["Users"]}}},"servers":[{"description":"Generated server url","url":"http://localhost:8080"}],"tags":[{"description":"Admin cohort listings + detail","name":"Cohorts"},{"description":"API for managing outbound emails","name":"Email Management"},{"description":"Admin: external cohort target catalog","name":"Cohort Targets"},{"description":"Admin: logical subjects + their per-system mappings","name":"Cohort Subjects"},{"description":"API for managing job executions","name":"Job Management"}]} diff --git a/services/api/src/integrationTest/kotlin/net/blueshell/api/domain/contribution/web/BulkEmailControllerITBase.kt b/services/api/src/integrationTest/kotlin/net/blueshell/api/domain/contribution/web/BulkEmailControllerITBase.kt new file mode 100644 index 000000000..eaff4a030 --- /dev/null +++ b/services/api/src/integrationTest/kotlin/net/blueshell/api/domain/contribution/web/BulkEmailControllerITBase.kt @@ -0,0 +1,381 @@ +package net.blueshell.api.domain.contribution.web + +import net.blueshell.api.domain.contribution.application.ContributionService +import net.blueshell.api.domain.contribution.persistence.Contribution +import net.blueshell.api.domain.contribution.persistence.ContributionPeriod +import net.blueshell.api.domain.user.persistence.Membership +import net.blueshell.api.domain.user.persistence.User +import net.blueshell.api.shared.dto.bulk.BulkFeeType +import net.blueshell.api.shared.enums.MemberType +import net.blueshell.api.shared.enums.Role +import net.blueshell.api.testsupport.UserTestSupport +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.http.MediaType +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status +import java.time.LocalDate + +/** + * Abstract base for IT tests of bulk-email endpoints (contribution reminders, incasso notifications). + * Centralizes shared fixture setup, JSON builders, and test logic to reduce duplication. + * Subclasses define the specific endpoint paths and date field names. + */ +@SpringBootTest +abstract class BulkEmailControllerITBase( + val executeEndpoint: String, + val previewEndpoint: String, + val dateParamName: String, // "paymentDueDate" or "expectedIncassoDate" + /** Incasso flag the shared tests put on fixture memberships: the incasso endpoint only applies to incasso members. */ + val defaultIncasso: Boolean = false, +) : UserTestSupport() { + + @Autowired + protected lateinit var contributionService: ContributionService + + /** + * Build the JSON body for execute/preview requests. + * The dateParam is substituted under the key dateParamName in the JSON. + */ + protected fun body( + userIds: List, + periodId: Long, + cutoffDate: LocalDate, + dateParam: LocalDate, + includedUserIds: Set = emptySet(), + feeTypeOverrides: Map = emptyMap() + ): String { + val includedJson = if (includedUserIds.isEmpty()) "[]" else includedUserIds.joinToString(",", "[", "]") + val overridesJson = if (feeTypeOverrides.isEmpty()) "{}" else { + feeTypeOverrides.entries.joinToString(",", "{", "}") { (k, v) -> "\"$k\":\"$v\"" } + } + return """{ + "userIds":[${userIds.joinToString(",")}], + "contributionPeriodId":$periodId, + "cutoffDate":"$cutoffDate", + "$dateParamName":"$dateParam", + "includedUserIds":$includedJson, + "feeTypeOverrides":$overridesJson + }""" + } + + /** + * Build the JSON body for preview requests. + * The dateParam is substituted under the key dateParamName in the JSON. + */ + protected fun previewBody( + userId: Long, + periodId: Long, + feeType: BulkFeeType, + dateParam: LocalDate, + ): String = """{ + "userId":$userId, + "contributionPeriodId":$periodId, + "feeType":"$feeType", + "$dateParamName":"$dateParam" + }""" + + protected fun markPaid(user: User, period: ContributionPeriod) = persist( + Contribution(id = Contribution.Id(user.id, period.id), user = user, contributionPeriod = period) + ) + + protected fun createMembership( + user: User, + memberType: MemberType, + startDate: LocalDate = LocalDate.of(2024, 1, 1), + incasso: Boolean = false + ): Membership = persist( + Membership( + user = user, + memberType = memberType, + startDate = startDate, + endDate = null, + incasso = incasso + ) + ) + + @Nested + inner class Execute { + + @Test + fun `sends email and writes audit row`() { + val board = createUserWithRole(Role.BOARD) + val regular = createUserWithRole(Role.MEMBER) + val period = createContributionPeriodFixture() + createMembership(regular, MemberType.REGULAR, LocalDate.of(2024, 1, 1), incasso = defaultIncasso) + + val cutoffDate = LocalDate.now() + val dateParam = LocalDate.now().plusDays(30) + + mvc.perform( + post(executeEndpoint) + .with(bearer(board)) + .contentType(MediaType.APPLICATION_JSON) + .content(body(listOf(regular.id!!), period.id!!, cutoffDate, dateParam)) + ) + .andExpect(status().isOk) + .andExpect(jsonPath("$.applied").value(1)) + .andExpect(jsonPath("$.skipped").value(0)) + .andExpect(jsonPath("$.queued").value(1)) + } + + @Test + fun `honors fee type overrides in audit and email`() { + val board = createUserWithRole(Role.BOARD) + val member = createUserWithRole(Role.MEMBER) + val period = createContributionPeriodFixture() + createMembership(member, MemberType.REGULAR, LocalDate.of(2024, 1, 1), incasso = defaultIncasso) + + val cutoffDate = LocalDate.now() + val dateParam = LocalDate.now().plusDays(30) + + mvc.perform( + post(executeEndpoint) + .with(bearer(board)) + .contentType(MediaType.APPLICATION_JSON) + .content( + body( + listOf(member.id!!), + period.id!!, + cutoffDate, + dateParam, + includedUserIds = setOf(member.id!!), + feeTypeOverrides = mapOf(member.id!! to BulkFeeType.HALF_YEAR_FEE) + ) + ) + ) + .andExpect(status().isOk) + .andExpect(jsonPath("$.applied").value(1)) + .andExpect(jsonPath("$.queued").value(1)) + } + + @Test + fun `excludes honorary members and never sends them`() { + val board = createUserWithRole(Role.BOARD) + val honorary = createUserWithRole(Role.MEMBER) + val period = createContributionPeriodFixture() + createMembership(honorary, MemberType.HONORARY, incasso = defaultIncasso) + + val cutoffDate = LocalDate.now() + val dateParam = LocalDate.now().plusDays(30) + + mvc.perform( + post(executeEndpoint) + .with(bearer(board)) + .contentType(MediaType.APPLICATION_JSON) + .content(body(listOf(honorary.id!!), period.id!!, cutoffDate, dateParam)) + ) + .andExpect(status().isOk) + .andExpect(jsonPath("$.applied").value(0)) + .andExpect(jsonPath("$.skipped").value(1)) + .andExpect(jsonPath("$.queued").value(0)) + } + + @Test + fun `already-paid excluded by default but can be re-included`() { + val board = createUserWithRole(Role.BOARD) + val member = createUserWithRole(Role.MEMBER) + val period = createContributionPeriodFixture() + createMembership(member, MemberType.REGULAR, incasso = defaultIncasso) + markPaid(member, period) + + val cutoffDate = LocalDate.now() + val dateParam = LocalDate.now().plusDays(30) + + mvc.perform( + post(executeEndpoint) + .with(bearer(board)) + .contentType(MediaType.APPLICATION_JSON) + .content(body(listOf(member.id!!), period.id!!, cutoffDate, dateParam)) + ) + .andExpect(status().isOk) + .andExpect(jsonPath("$.applied").value(0)) + .andExpect(jsonPath("$.skipped").value(1)) + + mvc.perform( + post(executeEndpoint) + .with(bearer(board)) + .contentType(MediaType.APPLICATION_JSON) + .content( + body( + listOf(member.id!!), + period.id!!, + cutoffDate, + dateParam, + includedUserIds = setOf(member.id!!) + ) + ) + ) + .andExpect(status().isOk) + .andExpect(jsonPath("$.applied").value(1)) + .andExpect(jsonPath("$.skipped").value(0)) + } + + @Test + fun `skips members without email`() { + val board = createUserWithRole(Role.BOARD) + val noEmail = createUserWithRole(Role.MEMBER) + noEmail.email = "" + persist(noEmail) + + val period = createContributionPeriodFixture() + createMembership(noEmail, MemberType.REGULAR, incasso = defaultIncasso) + + val cutoffDate = LocalDate.now() + val dateParam = LocalDate.now().plusDays(30) + + mvc.perform( + post(executeEndpoint) + .with(bearer(board)) + .contentType(MediaType.APPLICATION_JSON) + .content(body(listOf(noEmail.id!!), period.id!!, cutoffDate, dateParam)) + ) + .andExpect(status().isOk) + .andExpect(jsonPath("$.applied").value(0)) + .andExpect(jsonPath("$.skipped").value(1)) + } + } + + @Nested + inner class MixedCohort { + + // FE preview is client-side (bulkCompute.ts); execute mirrors its decision logic for mixed cohorts. + @Test + fun `execute applies includable members and skips the excluded honorary`() { + val board = createUserWithRole(Role.BOARD) + val regular = createUserWithRole(Role.MEMBER) + val alumni = createUserWithRole(Role.MEMBER) + val honorary = createUserWithRole(Role.MEMBER) + val period = createContributionPeriodFixture() + createMembership(regular, MemberType.REGULAR, LocalDate.of(2024, 1, 1), incasso = defaultIncasso) + createMembership(alumni, MemberType.ALUMNI, incasso = defaultIncasso) + createMembership(honorary, MemberType.HONORARY, incasso = defaultIncasso) + + val userIds = listOf(regular.id!!, alumni.id!!, honorary.id!!) + val cutoffDate = LocalDate.now() + val dateParam = LocalDate.now().plusDays(30) + + mvc.perform( + post(executeEndpoint) + .with(bearer(board)) + .contentType(MediaType.APPLICATION_JSON) + .content(body(userIds, period.id!!, cutoffDate, dateParam)) + ) + .andExpect(status().isOk) + .andExpect(jsonPath("$.applied").value(2)) + .andExpect(jsonPath("$.skipped").value(1)) + } + + @Test + fun `execute rejects a fee override for an excluded honorary user`() { + val board = createUserWithRole(Role.BOARD) + val honorary = createUserWithRole(Role.MEMBER) + val period = createContributionPeriodFixture() + createMembership(honorary, MemberType.HONORARY, incasso = defaultIncasso) + + mvc.perform( + post(executeEndpoint) + .with(bearer(board)) + .contentType(MediaType.APPLICATION_JSON) + .content( + body( + listOf(honorary.id!!), + period.id!!, + LocalDate.of(2024, 7, 1), + LocalDate.now().plusDays(30), + includedUserIds = setOf(honorary.id!!), + feeTypeOverrides = mapOf(honorary.id!! to BulkFeeType.FULL_YEAR_FEE), + ) + ) + ) + .andExpect(status().isBadRequest) + } + } + + @Nested + inner class Preview { + + @Test + fun `returns non-empty subject and html and renders neither entity nor job`() { + val board = createUserWithRole(Role.BOARD) + val member = createUserWithRole(Role.MEMBER) + val period = createContributionPeriodFixture() + createMembership(member, MemberType.REGULAR, LocalDate.of(2024, 1, 1), incasso = defaultIncasso) + + val dateParam = LocalDate.now().plusDays(30) + + mvc.perform( + post(previewEndpoint) + .with(bearer(board)) + .contentType(MediaType.APPLICATION_JSON) + .content(previewBody(member.id!!, period.id!!, BulkFeeType.FULL_YEAR_FEE, dateParam)) + ) + .andExpect(status().isOk) + .andExpect(jsonPath("$.subject").isNotEmpty) + .andExpect(jsonPath("$.html").isNotEmpty) + // Template images are inlined as data URIs so the iframe shows them regardless of hosting. + .andExpect( + jsonPath("$.html").value( + org.hamcrest.Matchers.containsString("data:image/png;base64,") + ) + ) + } + + @Test + fun `honors the requested fee type and date param in the rendered html`() { + val board = createUserWithRole(Role.BOARD) + val member = createUserWithRole(Role.MEMBER) + val period = createContributionPeriodFixture() + createMembership(member, MemberType.REGULAR, LocalDate.of(2024, 1, 1), incasso = defaultIncasso) + + val dateParam = LocalDate.now().plusDays(30) + + mvc.perform( + post(previewEndpoint) + .with(bearer(board)) + .contentType(MediaType.APPLICATION_JSON) + .content(previewBody(member.id!!, period.id!!, BulkFeeType.HALF_YEAR_FEE, dateParam)) + ) + .andExpect(status().isOk) + .andExpect( + jsonPath("$.html").value(org.hamcrest.Matchers.containsString("%.2f".format(period.halfYearFee))) + ) + } + } + + @Nested + inner class Authorization { + + @Test + fun `non-board is forbidden`() { + val member = createUserWithRole(Role.MEMBER) + val period = createContributionPeriodFixture() + + mvc.perform( + post(executeEndpoint) + .with(bearer(member)) + .contentType(MediaType.APPLICATION_JSON) + .content(body(listOf(member.id!!), period.id!!, LocalDate.now(), LocalDate.now().plusDays(30))) + ) + .andExpect(status().isForbidden) + } + + @Test + fun `non-board is forbidden from preview`() { + val member = createUserWithRole(Role.MEMBER) + val period = createContributionPeriodFixture() + + mvc.perform( + post(previewEndpoint) + .with(bearer(member)) + .contentType(MediaType.APPLICATION_JSON) + .content(previewBody(member.id!!, period.id!!, BulkFeeType.FULL_YEAR_FEE, LocalDate.now().plusDays(30))) + ) + .andExpect(status().isForbidden) + } + } +} diff --git a/services/api/src/integrationTest/kotlin/net/blueshell/api/domain/contribution/web/ContributionBulkControllerIT.kt b/services/api/src/integrationTest/kotlin/net/blueshell/api/domain/contribution/web/ContributionBulkControllerIT.kt new file mode 100644 index 000000000..54585097e --- /dev/null +++ b/services/api/src/integrationTest/kotlin/net/blueshell/api/domain/contribution/web/ContributionBulkControllerIT.kt @@ -0,0 +1,139 @@ +package net.blueshell.api.domain.contribution.web + +import net.blueshell.api.domain.contribution.application.ContributionService +import net.blueshell.api.domain.contribution.persistence.Contribution +import net.blueshell.api.domain.contribution.persistence.ContributionPeriod +import net.blueshell.api.domain.user.persistence.User +import net.blueshell.api.shared.enums.Role +import net.blueshell.api.testsupport.UserTestSupport +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.http.MediaType +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status + +/** + * Contract + authz for the execute-only mark-paid / mark-unpaid bulk endpoints. Preview + * for these actions is computed frontend-side, so there is no server preview endpoint to + * test; the redesign's regression net is the shared decide() unit tests plus the + * preview==execute invariants on the reminder/incasso/resume ITs. + * See docs/proposals/bulk-actions/REDESIGN.md §2 & §7. + */ +@SpringBootTest +class ContributionBulkControllerIT : UserTestSupport() { + + @Autowired + private lateinit var contributionService: ContributionService + + private fun markPaid(user: User, period: ContributionPeriod) = persist( + Contribution(id = Contribution.Id(user.id, period.id), user = user, contributionPeriod = period) + ) + + private fun body(userIds: List, periodId: Long) = + """{"userIds":[${userIds.joinToString(",")}],"contributionPeriodId":$periodId}""" + + @Nested + inner class Execute { + + @Test + fun `mark-paid creates contributions only for unpaid users`() { + val board = createUserWithRole(Role.BOARD) + val a = createUserWithRole(Role.MEMBER) + val b = createUserWithRole(Role.MEMBER) + val period = createContributionPeriodFixture() + markPaid(a, period) + + mvc.perform( + post("/contributions/bulk/mark-paid") + .with(bearer(board)) + .contentType(MediaType.APPLICATION_JSON) + .content(body(listOf(a.id!!, b.id!!), period.id!!)) + ) + .andExpect(status().isOk) + .andExpect(jsonPath("$.applied").value(1)) + .andExpect(jsonPath("$.skipped").value(1)) + + val bothPaid = transactionTemplate.execute { + entityManager.clear() + contributionService.existsByUserIdAndPeriodId(a.id!!, period.id!!) && + contributionService.existsByUserIdAndPeriodId(b.id!!, period.id!!) + } + assertThat(bothPaid).isTrue() + } + + @Test + fun `mark-unpaid deletes existing contributions only`() { + val board = createUserWithRole(Role.BOARD) + val a = createUserWithRole(Role.MEMBER) + val b = createUserWithRole(Role.MEMBER) + val period = createContributionPeriodFixture() + markPaid(a, period) + + mvc.perform( + post("/contributions/bulk/mark-unpaid") + .with(bearer(board)) + .contentType(MediaType.APPLICATION_JSON) + .content(body(listOf(a.id!!, b.id!!), period.id!!)) + ) + .andExpect(status().isOk) + .andExpect(jsonPath("$.applied").value(1)) + .andExpect(jsonPath("$.skipped").value(1)) + + val aStillPaid = transactionTemplate.execute { + entityManager.clear() + contributionService.existsByUserIdAndPeriodId(a.id!!, period.id!!) + } + assertThat(aStillPaid).isFalse() + } + + @Test + fun `rejects empty selection`() { + val board = createUserWithRole(Role.BOARD) + val period = createContributionPeriodFixture() + + mvc.perform( + post("/contributions/bulk/mark-paid") + .with(bearer(board)) + .contentType(MediaType.APPLICATION_JSON) + .content("""{"userIds":[],"contributionPeriodId":${period.id}}""") + ) + .andExpect(status().isBadRequest) + } + + @Test + fun `returns not found when period is unknown`() { + val board = createUserWithRole(Role.BOARD) + val user = createUserWithRole(Role.MEMBER) + + mvc.perform( + post("/contributions/bulk/mark-paid") + .with(bearer(board)) + .contentType(MediaType.APPLICATION_JSON) + .content(body(listOf(user.id!!), 999999)) + ) + .andExpect(status().isNotFound) + } + } + + @Nested + inner class Authorization { + + @Test + fun `non-board is forbidden`() { + val member = createUserWithRole(Role.MEMBER) + val period = createContributionPeriodFixture() + + mvc.perform( + post("/contributions/bulk/mark-paid") + .with(bearer(member)) + .contentType(MediaType.APPLICATION_JSON) + .content(body(listOf(member.id!!), period.id!!)) + ) + .andExpect(status().isForbidden) + } + } +} diff --git a/services/api/src/integrationTest/kotlin/net/blueshell/api/domain/contribution/web/ContributionReminderBulkControllerIT.kt b/services/api/src/integrationTest/kotlin/net/blueshell/api/domain/contribution/web/ContributionReminderBulkControllerIT.kt new file mode 100644 index 000000000..e8401b1f2 --- /dev/null +++ b/services/api/src/integrationTest/kotlin/net/blueshell/api/domain/contribution/web/ContributionReminderBulkControllerIT.kt @@ -0,0 +1,271 @@ +package net.blueshell.api.domain.contribution.web + +import net.blueshell.api.domain.contribution.persistence.ContributionReminder +import net.blueshell.api.shared.dto.bulk.BulkFeeType +import net.blueshell.api.shared.enums.MemberType +import net.blueshell.api.shared.enums.Role +import net.blueshell.api.shared.job.EmailJobs +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.springframework.http.MediaType +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status +import java.time.LocalDate +import java.time.format.DateTimeFormatter + +class ContributionReminderBulkControllerIT : + BulkEmailControllerITBase( + executeEndpoint = "/contributionReminders/bulk/execute", + previewEndpoint = "/contributionReminders/preview", + dateParamName = "paymentDueDate", + ) { + + @Nested + inner class ReminderExecute { + + @Test + fun `sends reminders to included members and writes audit rows`() { + val board = createUserWithRole(Role.BOARD) + val regular = createUserWithRole(Role.MEMBER) + val period = createContributionPeriodFixture() + createMembership(regular, MemberType.REGULAR, LocalDate.of(2024, 1, 1), incasso = false) + + val cutoffDate = LocalDate.now() + val paymentDueDate = LocalDate.now().plusDays(30) + + mvc.perform( + post(executeEndpoint) + .with(bearer(board)) + .contentType(MediaType.APPLICATION_JSON) + .content(body(listOf(regular.id!!), period.id!!, cutoffDate, paymentDueDate)) + ) + .andExpect(status().isOk) + .andExpect(jsonPath("$.applied").value(1)) + .andExpect(jsonPath("$.skipped").value(0)) + .andExpect(jsonPath("$.queued").value(1)) + + // Verify audit record was written + transactionTemplate.execute { + entityManager.clear() + val reminder = entityManager.find( + ContributionReminder::class.java, + ContributionReminder.Id(regular.id, period.id) + ) + assertThat(reminder).isNotNull + assertThat(reminder.amount).isEqualTo(period.fullYearFee) + assertThat(reminder.paymentDueDate).isEqualTo(paymentDueDate) + } + } + + @Test + fun `honors fee type overrides in audit and email`() { + val board = createUserWithRole(Role.BOARD) + // Member started before cutoff → default would be FULL_YEAR_FEE; override to HALF_YEAR_FEE + val member = createUserWithRole(Role.MEMBER) + val period = createContributionPeriodFixture() + createMembership(member, MemberType.REGULAR, LocalDate.of(2024, 1, 1), incasso = false) + + val cutoffDate = LocalDate.now() + val paymentDueDate = LocalDate.now().plusDays(30) + + mvc.perform( + post(executeEndpoint) + .with(bearer(board)) + .contentType(MediaType.APPLICATION_JSON) + .content( + body( + listOf(member.id!!), + period.id!!, + cutoffDate, + paymentDueDate, + // The member is INCLUDED, so a real client sends them in includedUserIds + // (FE set = INCLUDED ∪ re-included WARNING); overrides require membership there. + includedUserIds = setOf(member.id!!), + feeTypeOverrides = mapOf(member.id!! to BulkFeeType.HALF_YEAR_FEE) + ) + ) + ) + .andExpect(status().isOk) + .andExpect(jsonPath("$.applied").value(1)) + .andExpect(jsonPath("$.queued").value(1)) + + // Verify audit record has the half-year fee amount (from the override) + transactionTemplate.execute { + entityManager.clear() + val reminder = entityManager.find( + ContributionReminder::class.java, + ContributionReminder.Id(member.id, period.id) + ) + assertThat(reminder).isNotNull + assertThat(reminder.amount).isEqualTo(period.halfYearFee) + } + } + + @Test + fun `excludes honorary members and never sends them`() { + val board = createUserWithRole(Role.BOARD) + val honorary = createUserWithRole(Role.MEMBER) + val period = createContributionPeriodFixture() + createMembership(honorary, MemberType.HONORARY, incasso = false) + + val cutoffDate = LocalDate.now() + val paymentDueDate = LocalDate.now().plusDays(30) + + mvc.perform( + post(executeEndpoint) + .with(bearer(board)) + .contentType(MediaType.APPLICATION_JSON) + .content(body(listOf(honorary.id!!), period.id!!, cutoffDate, paymentDueDate)) + ) + .andExpect(status().isOk) + .andExpect(jsonPath("$.applied").value(0)) + .andExpect(jsonPath("$.skipped").value(1)) + .andExpect(jsonPath("$.queued").value(0)) + } + + @Test + fun `already-paid excluded by default but can be re-included`() { + val board = createUserWithRole(Role.BOARD) + val member = createUserWithRole(Role.MEMBER) + val period = createContributionPeriodFixture() + createMembership(member, MemberType.REGULAR, incasso = false) + markPaid(member, period) + + val cutoffDate = LocalDate.now() + val paymentDueDate = LocalDate.now().plusDays(30) + + // Execute without re-including: should skip + mvc.perform( + post(executeEndpoint) + .with(bearer(board)) + .contentType(MediaType.APPLICATION_JSON) + .content(body(listOf(member.id!!), period.id!!, cutoffDate, paymentDueDate)) + ) + .andExpect(status().isOk) + .andExpect(jsonPath("$.applied").value(0)) + .andExpect(jsonPath("$.skipped").value(1)) + + // Execute with re-including: should send + mvc.perform( + post(executeEndpoint) + .with(bearer(board)) + .contentType(MediaType.APPLICATION_JSON) + .content( + body( + listOf(member.id!!), + period.id!!, + cutoffDate, + paymentDueDate, + includedUserIds = setOf(member.id!!) + ) + ) + ) + .andExpect(status().isOk) + .andExpect(jsonPath("$.applied").value(1)) + .andExpect(jsonPath("$.skipped").value(0)) + } + + @Test + fun `skips members without email`() { + val board = createUserWithRole(Role.BOARD) + val noEmail = createUserWithRole(Role.MEMBER) + noEmail.email = "" + persist(noEmail) + + val period = createContributionPeriodFixture() + createMembership(noEmail, MemberType.REGULAR, incasso = false) + + val cutoffDate = LocalDate.now() + val paymentDueDate = LocalDate.now().plusDays(30) + + mvc.perform( + post(executeEndpoint) + .with(bearer(board)) + .contentType(MediaType.APPLICATION_JSON) + .content(body(listOf(noEmail.id!!), period.id!!, cutoffDate, paymentDueDate)) + ) + .andExpect(status().isOk) + .andExpect(jsonPath("$.applied").value(0)) + .andExpect(jsonPath("$.skipped").value(1)) + } + } + + @Nested + inner class ReminderPreview { + + @Test + fun `returns non-empty subject and html and renders neither a reminder nor a job`() { + val board = createUserWithRole(Role.BOARD) + val member = createUserWithRole(Role.MEMBER) + val period = createContributionPeriodFixture() + createMembership(member, MemberType.REGULAR, LocalDate.of(2024, 1, 1), incasso = false) + + val paymentDueDate = LocalDate.now().plusDays(30) + + mvc.perform( + post(previewEndpoint) + .with(bearer(board)) + .contentType(MediaType.APPLICATION_JSON) + .content(previewBody(member.id!!, period.id!!, BulkFeeType.FULL_YEAR_FEE, paymentDueDate)) + ) + .andExpect(status().isOk) + .andExpect( + jsonPath("$.subject").value( + org.hamcrest.Matchers.containsString("Please pay your Blueshell contribution") + ) + ) + .andExpect(jsonPath("$.subject").isNotEmpty) + .andExpect(jsonPath("$.html").isNotEmpty) + // Template images are inlined as data URIs so the iframe shows them regardless of hosting. + .andExpect( + jsonPath("$.html").value( + org.hamcrest.Matchers.containsString("data:image/png;base64,") + ) + ) + + // Preview must not persist a reminder … + transactionTemplate.execute { + entityManager.clear() + val reminder = entityManager.find( + ContributionReminder::class.java, + ContributionReminder.Id(member.id, period.id) + ) + assertThat(reminder).isNull() + } + // … nor enqueue a send. + assertThat(findJobsByType(EmailJobs.ContributionReminder.type)).isEmpty() + } + + @Test + fun `honors the requested fee type and payment due date in the rendered html`() { + val board = createUserWithRole(Role.BOARD) + val member = createUserWithRole(Role.MEMBER) + val period = createContributionPeriodFixture() + createMembership(member, MemberType.REGULAR, LocalDate.of(2024, 1, 1), incasso = false) + + val paymentDueDate = LocalDate.now().plusDays(30) + val formatted = paymentDueDate.format(DateTimeFormatter.ofPattern("dd MMMM yyyy")) + + mvc.perform( + post(previewEndpoint) + .with(bearer(board)) + .contentType(MediaType.APPLICATION_JSON) + .content(previewBody(member.id!!, period.id!!, BulkFeeType.HALF_YEAR_FEE, paymentDueDate)) + ) + .andExpect(status().isOk) + .andExpect( + jsonPath("$.html").value(org.hamcrest.Matchers.containsString("%.2f".format(period.halfYearFee))) + ) + .andExpect(jsonPath("$.html").value(org.hamcrest.Matchers.containsString(formatted))) + .andExpect( + jsonPath("$.html").value( + org.hamcrest.Matchers.containsString( + "the half-year fee, as your membership started during the second half of the year" + ) + ) + ) + } + } +} diff --git a/services/api/src/integrationTest/kotlin/net/blueshell/api/domain/contribution/web/IncassoNotificationBulkControllerIT.kt b/services/api/src/integrationTest/kotlin/net/blueshell/api/domain/contribution/web/IncassoNotificationBulkControllerIT.kt new file mode 100644 index 000000000..8d064bfed --- /dev/null +++ b/services/api/src/integrationTest/kotlin/net/blueshell/api/domain/contribution/web/IncassoNotificationBulkControllerIT.kt @@ -0,0 +1,351 @@ +package net.blueshell.api.domain.contribution.web + +import net.blueshell.api.domain.contribution.persistence.ContributionPeriod +import net.blueshell.api.domain.contribution.persistence.IncassoNotification +import net.blueshell.api.domain.user.persistence.Membership +import net.blueshell.api.domain.user.persistence.User +import net.blueshell.api.platform.integration.email.application.service.EmailSenderService +import net.blueshell.api.shared.dto.bulk.BulkFeeType +import net.blueshell.api.shared.enums.MemberType +import net.blueshell.api.shared.enums.Role +import net.blueshell.api.shared.job.EmailJobs +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.http.MediaType +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status +import java.time.LocalDate +import java.time.format.DateTimeFormatter + +class IncassoNotificationBulkControllerIT : + BulkEmailControllerITBase( + executeEndpoint = "/incassoNotifications/bulk/execute", + previewEndpoint = "/incassoNotifications/preview", + dateParamName = "expectedIncassoDate", + // The incasso handler only applies to members with incasso enabled; the + // inherited shared tests must create their fixture memberships accordingly. + defaultIncasso = true, + ) { + + @Autowired + private lateinit var emailSenderService: EmailSenderService + + private fun expectedAcademicYear(periodId: Long): String { + val period = entityManager.find(ContributionPeriod::class.java, periodId) + val startYear = period.startDate.year + val endYear = period.endDate.year + return if (endYear > startYear) "$startYear/$endYear" else "$startYear" + } + + @Nested + inner class IncassoExecute { + + @Test + fun `sends notification, writes audit row and enqueues email`() { + val board = createUserWithRole(Role.BOARD) + val regular = createUserWithRole(Role.MEMBER) + val period = createContributionPeriodFixture() + createMembership(regular, MemberType.REGULAR, LocalDate.of(2024, 1, 1), incasso = true) + + val cutoffDate = LocalDate.now() + val expectedIncassoDate = LocalDate.now().plusDays(30) + + mvc.perform( + post(executeEndpoint) + .with(bearer(board)) + .contentType(MediaType.APPLICATION_JSON) + .content(body(listOf(regular.id!!), period.id!!, cutoffDate, expectedIncassoDate)) + ) + .andExpect(status().isOk) + .andExpect(jsonPath("$.applied").value(1)) + .andExpect(jsonPath("$.skipped").value(0)) + .andExpect(jsonPath("$.queued").value(1)) + + // Verify audit record was written + transactionTemplate.execute { + entityManager.clear() + val notification = entityManager.find( + IncassoNotification::class.java, + IncassoNotification.Id(regular.id, period.id) + ) + assertThat(notification).isNotNull + assertThat(notification.amount).isEqualTo(period.fullYearFee) + assertThat(notification.expectedIncassoDate).isEqualTo(expectedIncassoDate) + } + + // Verify email job enqueued with correct payload + val jobs = findJobsByType(EmailJobs.IncassoNotification.type) + assertThat(jobs).hasSize(1) + assertThat(jobs.first().payload) + .contains("\"userId\":${regular.id}") + .contains("\"contributionPeriodId\":${period.id}") + } + + @Test + fun `honors fee type overrides in audit and email`() { + // Member started before cutoff → default would be FULL_YEAR_FEE; override to ALUMNI_FEE + val board = createUserWithRole(Role.BOARD) + val member = createUserWithRole(Role.MEMBER) + val period = createContributionPeriodFixture() + createMembership(member, MemberType.REGULAR, LocalDate.of(2024, 1, 1), incasso = true) + + val cutoffDate = LocalDate.now() + val expectedIncassoDate = LocalDate.now().plusDays(30) + + mvc.perform( + post(executeEndpoint) + .with(bearer(board)) + .contentType(MediaType.APPLICATION_JSON) + .content( + body( + listOf(member.id!!), + period.id!!, + cutoffDate, + expectedIncassoDate, + // The member is INCLUDED, so a real client sends them in includedUserIds + // (FE set = INCLUDED ∪ re-included WARNING); overrides require membership there. + includedUserIds = setOf(member.id!!), + feeTypeOverrides = mapOf(member.id!! to BulkFeeType.ALUMNI_FEE) + ) + ) + ) + .andExpect(status().isOk) + .andExpect(jsonPath("$.applied").value(1)) + .andExpect(jsonPath("$.queued").value(1)) + + // Verify audit record has the alumni fee amount (from the override) + transactionTemplate.execute { + entityManager.clear() + val notification = entityManager.find( + IncassoNotification::class.java, + IncassoNotification.Id(member.id, period.id) + ) + assertThat(notification).isNotNull + assertThat(notification.amount).isEqualTo(period.alumniFee) + } + + // Verify rendered mail carries the overridden amount and formatted incasso date + emailTransportClient.reset() + emailSenderService.sendIncassoNotificationEmail(member.id!!, period.id!!) + val formatted = expectedIncassoDate.format( + DateTimeFormatter.ofPattern("EEEE d MMMM yyyy", java.util.Locale.ENGLISH) + ) + val refreshed = refreshUser(member) + val academicYear = expectedAcademicYear(period.id!!) + assertEmailSent( + toEmail = refreshed.email, + subject = "Your Blueshell contribution will be collected automatically ($academicYear)", + bodyContains = "%.2f".format(period.alumniFee) + ) + assertThat(emailTransportClient.sentEmails.first().htmlContent).contains(formatted) + } + + @Test + fun `excludes honorary members and never sends them`() { + val board = createUserWithRole(Role.BOARD) + val honorary = createUserWithRole(Role.MEMBER) + val period = createContributionPeriodFixture() + createMembership(honorary, MemberType.HONORARY, incasso = true) + + val cutoffDate = LocalDate.now() + val expectedIncassoDate = LocalDate.now().plusDays(30) + + mvc.perform( + post(executeEndpoint) + .with(bearer(board)) + .contentType(MediaType.APPLICATION_JSON) + .content(body(listOf(honorary.id!!), period.id!!, cutoffDate, expectedIncassoDate)) + ) + .andExpect(status().isOk) + .andExpect(jsonPath("$.applied").value(0)) + .andExpect(jsonPath("$.skipped").value(1)) + .andExpect(jsonPath("$.queued").value(0)) + + assertThat(findJobsByType(EmailJobs.IncassoNotification.type)).isEmpty() + } + + @Test + fun `incasso-mismatch excluded by default but sent when re-included`() { + val board = createUserWithRole(Role.BOARD) + val member = createUserWithRole(Role.MEMBER) + val period = createContributionPeriodFixture() + createMembership(member, MemberType.REGULAR, LocalDate.of(2024, 1, 1), incasso = false) + + val cutoffDate = LocalDate.now() + val expectedIncassoDate = LocalDate.now().plusDays(30) + + // Execute without re-including: should skip + mvc.perform( + post(executeEndpoint) + .with(bearer(board)) + .contentType(MediaType.APPLICATION_JSON) + .content(body(listOf(member.id!!), period.id!!, cutoffDate, expectedIncassoDate)) + ) + .andExpect(status().isOk) + .andExpect(jsonPath("$.applied").value(0)) + .andExpect(jsonPath("$.skipped").value(1)) + + // Execute with re-including: should send + mvc.perform( + post(executeEndpoint) + .with(bearer(board)) + .contentType(MediaType.APPLICATION_JSON) + .content( + body( + listOf(member.id!!), + period.id!!, + cutoffDate, + expectedIncassoDate, + includedUserIds = setOf(member.id!!) + ) + ) + ) + .andExpect(status().isOk) + .andExpect(jsonPath("$.applied").value(1)) + .andExpect(jsonPath("$.skipped").value(0)) + .andExpect(jsonPath("$.queued").value(1)) + } + + @Test + fun `already-paid excluded by default but sent when re-included`() { + val board = createUserWithRole(Role.BOARD) + val member = createUserWithRole(Role.MEMBER) + val period = createContributionPeriodFixture() + createMembership(member, MemberType.REGULAR, incasso = true) + markPaid(member, period) + + val cutoffDate = LocalDate.now() + val expectedIncassoDate = LocalDate.now().plusDays(30) + + // Execute without re-including: should skip + mvc.perform( + post(executeEndpoint) + .with(bearer(board)) + .contentType(MediaType.APPLICATION_JSON) + .content(body(listOf(member.id!!), period.id!!, cutoffDate, expectedIncassoDate)) + ) + .andExpect(status().isOk) + .andExpect(jsonPath("$.applied").value(0)) + .andExpect(jsonPath("$.skipped").value(1)) + + // Execute with re-including: should send + mvc.perform( + post(executeEndpoint) + .with(bearer(board)) + .contentType(MediaType.APPLICATION_JSON) + .content( + body( + listOf(member.id!!), + period.id!!, + cutoffDate, + expectedIncassoDate, + includedUserIds = setOf(member.id!!) + ) + ) + ) + .andExpect(status().isOk) + .andExpect(jsonPath("$.applied").value(1)) + .andExpect(jsonPath("$.skipped").value(0)) + .andExpect(jsonPath("$.queued").value(1)) + } + + @Test + fun `skips members without email`() { + val board = createUserWithRole(Role.BOARD) + val noEmail = createUserWithRole(Role.MEMBER) + noEmail.email = "" + persist(noEmail) + + val period = createContributionPeriodFixture() + createMembership(noEmail, MemberType.REGULAR, incasso = true) + + val cutoffDate = LocalDate.now() + val expectedIncassoDate = LocalDate.now().plusDays(30) + + mvc.perform( + post(executeEndpoint) + .with(bearer(board)) + .contentType(MediaType.APPLICATION_JSON) + .content(body(listOf(noEmail.id!!), period.id!!, cutoffDate, expectedIncassoDate)) + ) + .andExpect(status().isOk) + .andExpect(jsonPath("$.applied").value(0)) + .andExpect(jsonPath("$.skipped").value(1)) + .andExpect(jsonPath("$.queued").value(0)) + } + } + + @Nested + inner class IncassoPreview { + + @Test + fun `returns non-empty subject and html and renders neither a notification nor a job`() { + val board = createUserWithRole(Role.BOARD) + val member = createUserWithRole(Role.MEMBER) + val period = createContributionPeriodFixture() + createMembership(member, MemberType.REGULAR, LocalDate.of(2024, 1, 1), incasso = true) + + val expectedIncassoDate = LocalDate.now().plusDays(30) + + mvc.perform( + post(previewEndpoint) + .with(bearer(board)) + .contentType(MediaType.APPLICATION_JSON) + .content(previewBody(member.id!!, period.id!!, BulkFeeType.FULL_YEAR_FEE, expectedIncassoDate)) + ) + .andExpect(status().isOk) + .andExpect( + jsonPath("$.subject").value( + org.hamcrest.Matchers.containsString("Your Blueshell contribution will be collected automatically") + ) + ) + .andExpect(jsonPath("$.subject").isNotEmpty) + .andExpect(jsonPath("$.html").isNotEmpty) + + // Preview must not persist a notification … + transactionTemplate.execute { + entityManager.clear() + val notification = entityManager.find( + IncassoNotification::class.java, + IncassoNotification.Id(member.id, period.id) + ) + assertThat(notification).isNull() + } + // … nor enqueue a send. + assertThat(findJobsByType(EmailJobs.IncassoNotification.type)).isEmpty() + } + + @Test + fun `honors the requested fee type and expected incasso date in the rendered html`() { + val board = createUserWithRole(Role.BOARD) + val member = createUserWithRole(Role.MEMBER) + val period = createContributionPeriodFixture() + createMembership(member, MemberType.REGULAR, LocalDate.of(2024, 1, 1), incasso = true) + + val expectedIncassoDate = LocalDate.now().plusDays(30) + val formatted = expectedIncassoDate.format( + DateTimeFormatter.ofPattern("EEEE d MMMM yyyy", java.util.Locale.ENGLISH) + ) + + mvc.perform( + post(previewEndpoint) + .with(bearer(board)) + .contentType(MediaType.APPLICATION_JSON) + .content(previewBody(member.id!!, period.id!!, BulkFeeType.ALUMNI_FEE, expectedIncassoDate)) + ) + .andExpect(status().isOk) + .andExpect( + jsonPath("$.html").value(org.hamcrest.Matchers.containsString("%.2f".format(period.alumniFee))) + ) + .andExpect(jsonPath("$.html").value(org.hamcrest.Matchers.containsString(formatted))) + .andExpect( + jsonPath("$.html").value( + org.hamcrest.Matchers.containsString("the alumni fee, as you are an alumni member") + ) + ) + } + } +} diff --git a/services/api/src/integrationTest/kotlin/net/blueshell/api/domain/user/web/MembershipBulkEndControllerIT.kt b/services/api/src/integrationTest/kotlin/net/blueshell/api/domain/user/web/MembershipBulkEndControllerIT.kt new file mode 100644 index 000000000..18aacadb5 --- /dev/null +++ b/services/api/src/integrationTest/kotlin/net/blueshell/api/domain/user/web/MembershipBulkEndControllerIT.kt @@ -0,0 +1,97 @@ +package net.blueshell.api.domain.user.web + +import net.blueshell.api.domain.user.persistence.repository.MemberRepository +import net.blueshell.api.shared.enums.MemberType +import net.blueshell.api.shared.enums.Role +import net.blueshell.api.testsupport.UserTestSupport +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.http.MediaType +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status +import java.time.LocalDate + +@SpringBootTest +class MembershipBulkEndControllerIT : UserTestSupport() { + + @Autowired + private lateinit var memberRepository: MemberRepository + + private fun body(userIds: List) = """{"userIds":[${userIds.joinToString(",")}]}""" + + @Nested + inner class Execute { + + @Test + fun `ends active memberships effective today and skips users without one`() { + val board = createUserWithRole(Role.BOARD) + val withMembership = createUserWithRole(Role.MEMBER) + val membership = createMembershipFixture(user = withMembership) + val without = createUserWithRole(Role.MEMBER) + + mvc.perform( + post("/memberships/bulk/end/execute") + .with(bearer(board)) + .contentType(MediaType.APPLICATION_JSON) + .content(body(listOf(withMembership.id!!, without.id!!))) + ) + .andExpect(status().isOk) + .andExpect(jsonPath("$.applied").value(1)) + .andExpect(jsonPath("$.skipped").value(1)) + + val ended = memberRepository.findById(membership.id!!).orElseThrow() + assertThat(ended.endDate).isEqualTo(LocalDate.now()) + } + + @Test + fun `protected users keep their membership - committee, board, admin and honorary are skipped`() { + val board = createUserWithRole(Role.BOARD) + + val committee = createUserWithRole(Role.COMMITTEE) + val committeeMembership = createMembershipFixture(user = committee) + val boardUser = createUserWithRole(Role.BOARD) + val boardMembership = createMembershipFixture(user = boardUser) + val admin = createUserWithRole(Role.ADMIN) + val adminMembership = createMembershipFixture(user = admin) + val honorary = createUserWithRole(Role.MEMBER) + val honoraryMembership = createMembershipFixture(user = honorary, memberType = MemberType.HONORARY) + + mvc.perform( + post("/memberships/bulk/end/execute") + .with(bearer(board)) + .contentType(MediaType.APPLICATION_JSON) + .content(body(listOf(committee.id!!, boardUser.id!!, admin.id!!, honorary.id!!))) + ) + .andExpect(status().isOk) + .andExpect(jsonPath("$.applied").value(0)) + .andExpect(jsonPath("$.skipped").value(4)) + + // None of the protected memberships were ended. + listOf(committeeMembership, boardMembership, adminMembership, honoraryMembership).forEach { + val reloaded = memberRepository.findById(it.id!!).orElseThrow() + assertThat(reloaded.endDate).isNull() + } + } + } + + @Nested + inner class Authorization { + + @Test + fun `non-board is forbidden`() { + val member = createUserWithRole(Role.MEMBER) + + mvc.perform( + post("/memberships/bulk/end/execute") + .with(bearer(member)) + .contentType(MediaType.APPLICATION_JSON) + .content(body(listOf(member.id!!))) + ) + .andExpect(status().isForbidden) + } + } +} diff --git a/services/api/src/integrationTest/kotlin/net/blueshell/api/domain/user/web/MembershipBulkResumeControllerIT.kt b/services/api/src/integrationTest/kotlin/net/blueshell/api/domain/user/web/MembershipBulkResumeControllerIT.kt new file mode 100644 index 000000000..3269259aa --- /dev/null +++ b/services/api/src/integrationTest/kotlin/net/blueshell/api/domain/user/web/MembershipBulkResumeControllerIT.kt @@ -0,0 +1,164 @@ +package net.blueshell.api.domain.user.web + +import net.blueshell.api.domain.user.persistence.repository.MemberRepository +import net.blueshell.api.shared.enums.MemberType +import net.blueshell.api.shared.enums.Role +import net.blueshell.api.testsupport.UserTestSupport +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.http.MediaType +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status +import java.time.LocalDate + +@SpringBootTest +class MembershipBulkResumeControllerIT : UserTestSupport() { + + @Autowired + private lateinit var memberRepository: MemberRepository + + private fun body(userIds: List) = """{"userIds":[${userIds.joinToString(",")}]}""" + + @Nested + inner class Execute { + + @Test + fun `resumes membership by clearing endDate`() { + val board = createUserWithRole(Role.BOARD) + val periodStart = LocalDate.now().minusDays(15) + val periodEnd = LocalDate.now().plusDays(345) + createContributionPeriodFixture(startDate = periodStart, endDate = periodEnd) + + val member = createUserWithRole(Role.MEMBER) + val membership = createMembershipFixture( + user = member, + memberType = MemberType.REGULAR, + startDate = LocalDate.now().minusDays(100), + endDate = LocalDate.now().minusDays(5), // within basis period + ) + + mvc.perform( + post("/memberships/bulk/resume/execute") + .with(bearer(board)) + .contentType(MediaType.APPLICATION_JSON) + .content(body(listOf(member.id!!))) + ) + .andExpect(status().isOk) + .andExpect(jsonPath("$.applied").value(1)) + .andExpect(jsonPath("$.skipped").value(0)) + + val resumed = memberRepository.findById(membership.id!!).orElseThrow() + assertThat(resumed.endDate).isNull() + } + + @Test + fun `inserts new membership for user with no resumable membership`() { + val board = createUserWithRole(Role.BOARD) + val periodStart = LocalDate.now().minusDays(15) + val periodEnd = LocalDate.now().plusDays(345) + createContributionPeriodFixture(startDate = periodStart, endDate = periodEnd) + + // Member has no membership at all + val member = createUserWithRole(Role.MEMBER) + + mvc.perform( + post("/memberships/bulk/resume/execute") + .with(bearer(board)) + .contentType(MediaType.APPLICATION_JSON) + .content(body(listOf(member.id!!))) + ) + .andExpect(status().isOk) + .andExpect(jsonPath("$.applied").value(1)) + + val newMemberships = memberRepository.findByUser_Id(member.id!!) + assertThat(newMemberships).hasSize(1) + val newMembership = newMemberships.first() + assertThat(newMembership.endDate).isNull() + assertThat(newMembership.startDate).isEqualTo(LocalDate.now()) + assertThat(newMembership.memberType).isEqualTo(MemberType.REGULAR) + assertThat(newMembership.incasso).isFalse() + } + + @Test + fun `skips already-active membership`() { + val board = createUserWithRole(Role.BOARD) + val periodStart = LocalDate.now().minusDays(15) + val periodEnd = LocalDate.now().plusDays(345) + createContributionPeriodFixture(startDate = periodStart, endDate = periodEnd) + + val member = createUserWithRole(Role.MEMBER) + createMembershipFixture(user = member, endDate = null) + + mvc.perform( + post("/memberships/bulk/resume/execute") + .with(bearer(board)) + .contentType(MediaType.APPLICATION_JSON) + .content(body(listOf(member.id!!))) + ) + .andExpect(status().isOk) + .andExpect(jsonPath("$.applied").value(0)) + .andExpect(jsonPath("$.skipped").value(1)) + } + } + + @Nested + inner class MixedCohort { + + // The preview is now computed entirely client-side (bulkCompute.ts), so there is no + // server preview endpoint to compare against. This test still pins execute's shared + // classifyUser decision for the exact mixed cohort the FE preview classifies as + // 2 includable (WILL_RESUME + WILL_START_NEW) and 1 skipped (ALREADY_ACTIVE): + // execute must apply 2 and skip 1. See docs/proposals/bulk-actions/REDESIGN.md §7. + @Test + fun `execute applies resumable and start-new members and skips the already-active`() { + val board = createUserWithRole(Role.BOARD) + val periodStart = LocalDate.now().minusDays(15) + val periodEnd = LocalDate.now().plusDays(345) + createContributionPeriodFixture(startDate = periodStart, endDate = periodEnd) + + val resumable = createUserWithRole(Role.MEMBER) + createMembershipFixture( + user = resumable, + memberType = MemberType.REGULAR, + startDate = LocalDate.now().minusDays(100), + endDate = LocalDate.now().minusDays(5), // within basis period → WILL_RESUME + ) + val startNew = createUserWithRole(Role.MEMBER) // no membership → WILL_START_NEW + val active = createUserWithRole(Role.MEMBER) + createMembershipFixture(user = active, endDate = null) // ALREADY_ACTIVE → skipped + + val userIds = listOf(resumable.id!!, startNew.id!!, active.id!!) + + mvc.perform( + post("/memberships/bulk/resume/execute") + .with(bearer(board)) + .contentType(MediaType.APPLICATION_JSON) + .content(body(userIds)) + ) + .andExpect(status().isOk) + .andExpect(jsonPath("$.applied").value(2)) + .andExpect(jsonPath("$.skipped").value(1)) + } + } + + @Nested + inner class Authorization { + + @Test + fun `non-board is forbidden`() { + val member = createUserWithRole(Role.MEMBER) + + mvc.perform( + post("/memberships/bulk/resume/execute") + .with(bearer(member)) + .contentType(MediaType.APPLICATION_JSON) + .content(body(listOf(member.id!!))) + ) + .andExpect(status().isForbidden) + } + } +} diff --git a/services/api/src/main/kotlin/net/blueshell/api/ApiApplication.kt b/services/api/src/main/kotlin/net/blueshell/api/ApiApplication.kt index e352db7aa..690a1308a 100644 --- a/services/api/src/main/kotlin/net/blueshell/api/ApiApplication.kt +++ b/services/api/src/main/kotlin/net/blueshell/api/ApiApplication.kt @@ -1,5 +1,6 @@ package net.blueshell.api +import net.blueshell.api.platform.config.BankProperties import net.blueshell.api.platform.config.JobQueueProperties import net.blueshell.api.platform.config.StorageConfig import org.springframework.boot.autoconfigure.SpringBootApplication @@ -10,7 +11,7 @@ import org.springframework.scheduling.annotation.EnableAsync import org.springframework.scheduling.annotation.EnableScheduling @SpringBootApplication -@EnableConfigurationProperties(StorageConfig::class, JobQueueProperties::class) +@EnableConfigurationProperties(StorageConfig::class, JobQueueProperties::class, BankProperties::class) @EnableJpaAuditing @EnableAsync @EnableRetry diff --git a/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/application/ContributionReminderService.kt b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/application/ContributionReminderService.kt index 3c25c0535..fad9c8dfa 100644 --- a/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/application/ContributionReminderService.kt +++ b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/application/ContributionReminderService.kt @@ -21,6 +21,12 @@ class ContributionReminderService @Autowired constructor( return repository.findByIdContributionPeriodId(contributionPeriodId) } + @Transactional(readOnly = true) + fun findLastReminderForUserAndPeriod(userId: Long, contributionPeriodId: Long): ContributionReminder? { + return repository.findByIdContributionPeriodId(contributionPeriodId) + .firstOrNull { it.userId == userId } + } + fun sendReminder(reminder: ContributionReminder) { val reminderId = reminder.id jobs.enqueue( diff --git a/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/application/EmailPreviewService.kt b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/application/EmailPreviewService.kt new file mode 100644 index 000000000..42a0aee40 --- /dev/null +++ b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/application/EmailPreviewService.kt @@ -0,0 +1,132 @@ +package net.blueshell.api.domain.contribution.application + +import net.blueshell.api.domain.contribution.application.email.createContributionReminderEmail +import net.blueshell.api.domain.contribution.application.email.createIncassoNotificationEmail +import net.blueshell.api.domain.contribution.domain.service.resolveFeeAmount +import net.blueshell.api.domain.user.application.UserService +import net.blueshell.api.platform.config.BankProperties +import net.blueshell.api.platform.integration.email.application.service.EmailSenderService +import net.blueshell.api.shared.dto.bulk.BulkFeeType +import net.blueshell.api.shared.email.EmailContent +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import java.time.LocalDate + +/** + * Rendered email preview: the exact subject + HTML body that a reminder / incasso + * notification would carry if sent, so an operator can double-check before a bulk run. + */ +data class RenderedEmailPreview(val subject: String, val html: String) + +/** + * Renders reminder / incasso-notification emails for ONE user WITHOUT sending or + * persisting anything. It reuses the same pure email builders and the same template + * render step the real send path uses (via [EmailSenderService.renderEmailHtml]), so the + * preview is faithful to what would actually be delivered. It never creates a + * ContributionReminder / IncassoNotification and never enqueues an email job. + * + * The effective fee type mirrors the execute path: an operator-supplied [feeType] + * override is honored, otherwise the recommended type is resolved from the member's + * latest-membership start relative to the half-year cutoff. + */ +@Service +class EmailPreviewService( + private val users: UserService, + private val periods: ContributionPeriodService, + private val emailSender: EmailSenderService, + private val bank: BankProperties, +) { + /** Render the contribution-reminder email for [userId] using [paymentDueDate]. */ + @Transactional(readOnly = true) + fun previewReminder( + userId: Long, + contributionPeriodId: Long, + feeType: BulkFeeType, + paymentDueDate: LocalDate, + ): RenderedEmailPreview { + val user = users.findById(userId) + val period = periods.findById(contributionPeriodId) + val amount = resolveFeeAmount(feeType, period) + val content = createContributionReminderEmail(user, period, amount, paymentDueDate, bank, feeType) + return render(content) + } + + /** Render the incasso-notification email for [userId] using [expectedIncassoDate]. */ + @Transactional(readOnly = true) + fun previewIncassoNotification( + userId: Long, + contributionPeriodId: Long, + feeType: BulkFeeType, + expectedIncassoDate: LocalDate, + ): RenderedEmailPreview { + val user = users.findById(userId) + val period = periods.findById(contributionPeriodId) + val amount = resolveFeeAmount(feeType, period) + val content = createIncassoNotificationEmail(user, period, amount, expectedIncassoDate, feeType) + return render(content) + } + + private fun render(content: EmailContent): RenderedEmailPreview = + RenderedEmailPreview(subject = content.subject, html = inlineEmailAssets(emailSender.renderEmailHtml(content))) + + /** + * Preview-only: replace the hosted email-asset URLs with base64 data URIs read from + * the classpath, so the preview iframe always shows the logo/watermark regardless of + * whether the configured frontend URL is reachable from the operator's browser + * (e.g. docker-internal hostnames in dev, or assets not yet deployed). The real send + * path is untouched, so mail clients keep the hosted URLs. + */ + private fun inlineEmailAssets(html: String): String { + var result = html + INLINEABLE_ASSETS.forEach { (urlSuffix, dataUri) -> + if (dataUri != null) { + result = replaceUrlsEndingWith(result, urlSuffix, dataUri) + } + } + return result + } + + /** + * Replace every URL ending in [suffix] (absolute or relative, as found in src / + * background attributes and CSS url(...) values) with [replacement]. + * + * Deliberately NOT a regex: a pattern like `[^"'()\s]*suffix` backtracks + * quadratically once the first replacement inserts a ~180KB base64 data URI (one + * unbroken token), which made each preview render take minutes. This is a linear + * scan: find the suffix, walk back to the URL's start delimiter, splice. + */ + private fun replaceUrlsEndingWith(html: String, suffix: String, replacement: String): String { + var searchFrom = html.indexOf(suffix) + if (searchFrom < 0) return html + val sb = StringBuilder(html.length + replacement.length) + var emitted = 0 + var hit = searchFrom + while (hit >= 0) { + var start = hit + while (start > emitted && html[start - 1] !in URL_DELIMITERS) start-- + sb.append(html, emitted, start).append(replacement) + emitted = hit + suffix.length + hit = html.indexOf(suffix, emitted) + } + sb.append(html, emitted, html.length) + return sb.toString() + } + + private companion object { + /** Characters that terminate a URL token when scanning backwards from the suffix. */ + private val URL_DELIMITERS = setOf('"', '\'', '(', ')', '>', '=', ',', ' ', '\t', '\n', '\r') + + /** Hosted-URL suffix -> data URI (null when the classpath asset is missing). */ + private val INLINEABLE_ASSETS: Map by lazy { + mapOf( + "/img/email/blueshell-logo.png" to classpathDataUri("templates/assets/BSLOGO.png"), + "/img/email/watermark.png" to classpathDataUri("templates/assets/BackdropBlack.png"), + ) + } + + private fun classpathDataUri(path: String): String? = + EmailPreviewService::class.java.classLoader.getResourceAsStream(path)?.use { + "data:image/png;base64," + java.util.Base64.getEncoder().encodeToString(it.readBytes()) + } + } +} diff --git a/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/application/IncassoNotificationService.kt b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/application/IncassoNotificationService.kt new file mode 100644 index 000000000..f7752defa --- /dev/null +++ b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/application/IncassoNotificationService.kt @@ -0,0 +1,43 @@ +package net.blueshell.api.domain.contribution.application + +import net.blueshell.api.domain.contribution.persistence.IncassoNotification +import net.blueshell.api.domain.contribution.persistence.repository.IncassoNotificationRepository +import net.blueshell.api.shared.job.EmailJobs +import net.blueshell.api.shared.job.TrackedJobDispatcher +import net.blueshell.api.shared.service.BaseModelService +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +class IncassoNotificationService @Autowired constructor( + repository: IncassoNotificationRepository, + private val periodService: ContributionPeriodService, + private val jobs: TrackedJobDispatcher +) : BaseModelService(repository) { + @Transactional(readOnly = true) + fun findByContributionPeriodId(contributionPeriodId: Long): MutableList { + periodService.findById(contributionPeriodId) + return repository.findByIdContributionPeriodId(contributionPeriodId) + } + + @Transactional(readOnly = true) + fun findLastNotificationForUserAndPeriod(userId: Long, contributionPeriodId: Long): IncassoNotification? { + return repository.findByIdContributionPeriodId(contributionPeriodId) + .firstOrNull { it.userId == userId } + } + + fun sendNotification(notification: IncassoNotification) { + val notificationId = notification.id + jobs.enqueue( + EmailJobs.IncassoNotification, + EmailJobs.IncassoNotificationPayload(notificationId.userId!!, notificationId.contributionPeriodId!!) + ) + } + + fun sendNotifications(notifications: MutableList) { + for (notification in notifications) { + sendNotification(notification) + } + } +} diff --git a/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/application/command/BulkContributionCommandHandlers.kt b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/application/command/BulkContributionCommandHandlers.kt new file mode 100644 index 000000000..b84c51306 --- /dev/null +++ b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/application/command/BulkContributionCommandHandlers.kt @@ -0,0 +1,69 @@ +package net.blueshell.api.domain.contribution.application.command + +import net.blueshell.api.domain.contribution.application.ContributionPeriodService +import net.blueshell.api.domain.contribution.application.ContributionService +import net.blueshell.api.domain.contribution.command.BulkContributionOperation +import net.blueshell.api.domain.contribution.command.ExecuteBulkContributionCommand +import net.blueshell.api.domain.contribution.persistence.Contribution +import net.blueshell.api.domain.user.application.MembershipService +import net.blueshell.api.domain.user.application.UserService +import net.blueshell.api.shared.command.CommandHandler +import net.blueshell.api.shared.dto.bulk.BulkActionResult +import net.blueshell.api.shared.enums.MemberType +import org.springframework.stereotype.Component +import org.springframework.transaction.annotation.Transactional + +/** + * Mark-paid / mark-unpaid are now execute-only: their entire decision input + * (`userId ∈ paidUserIds`?) already lives in the frontend, so the preview is + * computed client-side and there is no server preview endpoint. Execute stays + * idempotent — it creates a contribution only if one does not exist (paid) or + * deletes only if one does (unpaid), so re-running is a no-op for settled rows. + * See docs/proposals/bulk-actions/REDESIGN.md §1 (preview tiering) & §2. + */ +@Component +class ExecuteBulkContributionHandler( + private val service: ContributionService, + private val users: UserService, + private val memberships: MembershipService, + private val periods: ContributionPeriodService, +) : CommandHandler { + override val commandType = ExecuteBulkContributionCommand::class + + @Transactional + override fun handle(command: ExecuteBulkContributionCommand): BulkActionResult { + val periodId = command.contributionPeriodId!! + val period = periods.findById(periodId) + val paid = command.operation == BulkContributionOperation.PAID + var applied = 0 + var skipped = 0 + command.userIds.distinct().forEach { userId -> + // Poisoned-batch guard: a userId with no user must not abort the whole batch + // (users.findById throws 404). Treat unknown ids as skipped and continue. + if (!users.existsById(userId)) { + skipped++ + return@forEach + } + // Honorary members are never marked paid/unpaid — mirror of the FE's + // SKIPPED(HONORARY). Resolve the most-recent membership by start date. + val activeMembership = memberships.findByUserId(userId).maxByOrNull { it.startDate } + if (activeMembership?.memberType == MemberType.HONORARY) { + skipped++ + return@forEach + } + val exists = service.existsByUserIdAndPeriodId(userId, periodId) + when { + paid && !exists -> { + service.create(Contribution(user = users.findById(userId), contributionPeriod = period)) + applied++ + } + !paid && exists -> { + service.deleteById(Contribution.Id(userId, periodId)) + applied++ + } + else -> skipped++ + } + } + return BulkActionResult(applied = applied, skipped = skipped, queued = 0) + } +} diff --git a/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/application/command/BulkContributionReminderHandlers.kt b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/application/command/BulkContributionReminderHandlers.kt new file mode 100644 index 000000000..5d6168a39 --- /dev/null +++ b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/application/command/BulkContributionReminderHandlers.kt @@ -0,0 +1,152 @@ +package net.blueshell.api.domain.contribution.application.command + +import net.blueshell.api.domain.contribution.application.ContributionPeriodService +import net.blueshell.api.domain.contribution.application.ContributionReminderService +import net.blueshell.api.domain.contribution.application.ContributionService +import net.blueshell.api.domain.contribution.command.ExecuteBulkContributionReminderCommand +import net.blueshell.api.domain.contribution.domain.service.resolveFeeAmount +import net.blueshell.api.domain.contribution.domain.service.resolveFeeType +import net.blueshell.api.domain.contribution.persistence.ContributionPeriod +import net.blueshell.api.domain.contribution.persistence.ContributionReminder +import net.blueshell.api.domain.user.application.MembershipService +import net.blueshell.api.domain.user.application.UserService +import net.blueshell.api.shared.command.CommandHandler +import net.blueshell.api.shared.dto.bulk.BulkActionResult +import net.blueshell.api.shared.dto.bulk.BulkFeeType +import net.blueshell.api.shared.dto.bulk.BulkRowDisposition +import net.blueshell.api.shared.dto.bulk.BulkRowReason +import net.blueshell.api.shared.enums.MemberType +import org.springframework.stereotype.Component +import org.springframework.transaction.annotation.Transactional +import java.time.LocalDate +import java.time.ZoneOffset + +@Component +class ExecuteBulkContributionReminderHandler( + private val users: UserService, + private val memberships: MembershipService, + private val periods: ContributionPeriodService, + private val contributions: ContributionService, + private val reminders: ContributionReminderService, +) : CommandHandler { + override val commandType = ExecuteBulkContributionReminderCommand::class + + @Transactional + override fun handle(command: ExecuteBulkContributionReminderCommand): BulkActionResult { + val periodId = command.contributionPeriodId!! + val period = periods.findById(periodId) + val cutoffDate = command.cutoffDate!! + requireCutoffWithinPeriod(cutoffDate, period) + val includedUserIds = command.includedUserIds + + val requestedUserIds = command.userIds.distinct() + + // Decide once per user (same as preview); poisoned-batch guard prevents one bad id aborting mid-transaction. + val decisions = requestedUserIds.mapNotNull { userId -> + if (!users.existsById(userId)) return@mapNotNull null + userId to decideReminder(userId, periodId, period, cutoffDate, users, memberships, contributions, reminders) + }.toMap() + + validateFeeTypeOverrides(command.feeTypeOverrides, includedUserIds, decisions) + + var applied = 0 + // Unknown ids (dropped above) are skips too. + var skipped = requestedUserIds.size - decisions.size + var queued = 0 + + decisions.forEach { (userId, decision) -> + val shouldSend = when (decision.disposition) { + BulkRowDisposition.INCLUDED -> true + BulkRowDisposition.WARNING -> userId in includedUserIds + else -> false // EXCLUDED / SKIPPED (incl. NO_EMAIL) + } + if (!shouldSend) { + skipped++ + return@forEach + } + + val user = users.findById(userId) + val effectiveFeeType = command.feeTypeOverrides[userId] ?: decision.recommendedFeeType!! + val amountToSend = resolveFeeAmount(effectiveFeeType, period) + + val reminder = reminders.create( + ContributionReminder( + user = user, + contributionPeriod = period, + amount = amountToSend, + paymentDueDate = command.paymentDueDate, + ) + ) + reminders.sendReminder(reminder) + + applied++ + queued++ + } + + return BulkActionResult(applied = applied, skipped = skipped, queued = queued) + } +} + +/** + * Decision function for the contribution-reminder bulk action. Pure with respect to the + * DB reads it performs (no writes). Called by both preview and execute. + */ +internal fun decideReminder( + userId: Long, + periodId: Long, + period: ContributionPeriod, + cutoffDate: LocalDate, + users: UserService, + memberships: MembershipService, + contributions: ContributionService, + reminders: ContributionReminderService, +): EmailBulkDecision { + val user = users.findById(userId) + + // Current (active) membership — most recent by start date. This LATEST start is + // what fee resolution keys off (NOT the earliest the FE derives), which is exactly + // why reminder preview cannot be safely computed client-side. + val activeMembership = memberships.findByUserId(userId).maxByOrNull { it.startDate } + val memberType = activeMembership?.memberType ?: MemberType.REGULAR + val membershipStart = activeMembership?.startDate + val recommendedFeeType = resolveFeeType(memberType, membershipStart, cutoffDate) + + val alreadyPaid = contributions.existsByUserIdAndPeriodId(userId, periodId) + val emailMissing = user.email.isBlank() + val lastSent = reminders.findLastReminderForUserAndPeriod(userId, periodId)?.createdAt + ?.atZone(ZoneOffset.UTC)?.toLocalDate() + + val disposition: BulkRowDisposition + val reason: BulkRowReason? + when { + recommendedFeeType == null -> { + disposition = BulkRowDisposition.EXCLUDED + reason = BulkRowReason.HONORARY + } + emailMissing -> { + disposition = BulkRowDisposition.SKIPPED + reason = BulkRowReason.NO_EMAIL + } + alreadyPaid -> { + disposition = BulkRowDisposition.WARNING + reason = BulkRowReason.ALREADY_PAID + } + else -> { + disposition = BulkRowDisposition.INCLUDED + reason = null + } + } + + return EmailBulkDecision( + userId = userId, + name = user.fullName, + memberType = memberType, + memberSince = membershipStart, + disposition = disposition, + reason = reason, + recommendedFeeType = recommendedFeeType, + amount = recommendedFeeType?.let { resolveFeeAmount(it, period) }, + lastSentOn = lastSent, + emailMissing = emailMissing, + ) +} diff --git a/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/application/command/BulkEmailActionHelpers.kt b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/application/command/BulkEmailActionHelpers.kt new file mode 100644 index 000000000..43c84031a --- /dev/null +++ b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/application/command/BulkEmailActionHelpers.kt @@ -0,0 +1,73 @@ +package net.blueshell.api.domain.contribution.application.command + +import net.blueshell.api.domain.contribution.persistence.ContributionPeriod +import net.blueshell.api.shared.dto.bulk.BulkActionResult +import net.blueshell.api.shared.dto.bulk.BulkFeeType +import net.blueshell.api.shared.dto.bulk.BulkRowDisposition +import net.blueshell.api.shared.dto.bulk.BulkRowReason +import net.blueshell.api.shared.enums.MemberType +import org.springframework.web.server.ResponseStatusException +import org.springframework.http.HttpStatus +import java.time.LocalDate + +/** + * The single decision an email-style bulk action reaches for one user. + * Computed by the execute handler to determine side effects. + * The [decideReminder]/[decideIncasso] functions are shared decision logic. + * See docs/proposals/bulk-actions/REDESIGN.md §3. + */ +data class EmailBulkDecision( + val userId: Long, + val name: String, + val memberType: MemberType, + val memberSince: LocalDate?, + val disposition: BulkRowDisposition, + val reason: BulkRowReason?, + val recommendedFeeType: BulkFeeType?, + val amount: Double?, + val lastSentOn: LocalDate?, + /** True when the user has no email; execute must skip even if operator re-includes. */ + val emailMissing: Boolean, +) + +/** + * Guard: the cutoff date must fall within the contribution period's [startDate, endDate] + * (inclusive). Mirrors the frontend rule so a direct API call cannot pick a cutoff outside + * the period and skew fee-type resolution. See docs/proposals/bulk-actions/REDESIGN.md §3. + */ +internal fun requireCutoffWithinPeriod(cutoffDate: LocalDate, period: ContributionPeriod) { + if (cutoffDate.isBefore(period.startDate) || cutoffDate.isAfter(period.endDate)) { + throw ResponseStatusException( + HttpStatus.BAD_REQUEST, + "cutoffDate must fall within the contribution period [${period.startDate}, ${period.endDate}]", + ) + } +} + +/** + * Validate operator-supplied fee-type overrides against the computed decisions. + * Rejects (HTTP 400) an override for a user who is EXCLUDED/HONORARY (no fee applies) + * or who is not in the operator's included set. Missing override → recommended type. + * See docs/proposals/bulk-actions/REDESIGN.md §3 (fee-override validation). + */ +internal fun validateFeeTypeOverrides( + feeTypeOverrides: Map, + includedUserIds: Set, + decisionsByUser: Map, +) { + feeTypeOverrides.keys.forEach { userId -> + val decision = decisionsByUser[userId] + if (decision == null || decision.recommendedFeeType == null) { + throw ResponseStatusException( + HttpStatus.BAD_REQUEST, + "Fee-type override supplied for user $userId who is excluded from this action", + ) + } + if (userId !in includedUserIds) { + throw ResponseStatusException( + HttpStatus.BAD_REQUEST, + "Fee-type override supplied for user $userId who is not included in this action", + ) + } + } +} diff --git a/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/application/command/BulkIncassoNotificationHandlers.kt b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/application/command/BulkIncassoNotificationHandlers.kt new file mode 100644 index 000000000..ce0e17748 --- /dev/null +++ b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/application/command/BulkIncassoNotificationHandlers.kt @@ -0,0 +1,157 @@ +package net.blueshell.api.domain.contribution.application.command + +import net.blueshell.api.domain.contribution.application.ContributionPeriodService +import net.blueshell.api.domain.contribution.application.ContributionService +import net.blueshell.api.domain.contribution.application.IncassoNotificationService +import net.blueshell.api.domain.contribution.command.ExecuteBulkIncassoNotificationCommand +import net.blueshell.api.domain.contribution.domain.service.resolveFeeAmount +import net.blueshell.api.domain.contribution.domain.service.resolveFeeType +import net.blueshell.api.domain.contribution.persistence.ContributionPeriod +import net.blueshell.api.domain.contribution.persistence.IncassoNotification +import net.blueshell.api.domain.user.application.MembershipService +import net.blueshell.api.domain.user.application.UserService +import net.blueshell.api.shared.command.CommandHandler +import net.blueshell.api.shared.dto.bulk.BulkActionResult +import net.blueshell.api.shared.dto.bulk.BulkRowDisposition +import net.blueshell.api.shared.dto.bulk.BulkRowReason +import net.blueshell.api.shared.enums.MemberType +import org.springframework.stereotype.Component +import org.springframework.transaction.annotation.Transactional +import java.time.LocalDate +import java.time.ZoneOffset +import net.blueshell.api.domain.contribution.application.command.EmailBulkDecision +import net.blueshell.api.domain.contribution.application.command.requireCutoffWithinPeriod +import net.blueshell.api.domain.contribution.application.command.validateFeeTypeOverrides + +@Component +class ExecuteBulkIncassoNotificationHandler( + private val users: UserService, + private val memberships: MembershipService, + private val periods: ContributionPeriodService, + private val contributions: ContributionService, + private val notifications: IncassoNotificationService, +) : CommandHandler { + override val commandType = ExecuteBulkIncassoNotificationCommand::class + + @Transactional + override fun handle(command: ExecuteBulkIncassoNotificationCommand): BulkActionResult { + val periodId = command.contributionPeriodId!! + val period = periods.findById(periodId) + val cutoffDate = command.cutoffDate!! + requireCutoffWithinPeriod(cutoffDate, period) + val includedUserIds = command.includedUserIds + + val requestedUserIds = command.userIds.distinct() + + // Decide once per user (same as preview); poisoned-batch guard prevents one bad id aborting mid-transaction. + val decisions = requestedUserIds.mapNotNull { userId -> + if (!users.existsById(userId)) return@mapNotNull null + userId to decideIncasso(userId, periodId, period, cutoffDate, users, memberships, contributions, notifications) + }.toMap() + + validateFeeTypeOverrides(command.feeTypeOverrides, includedUserIds, decisions) + + var applied = 0 + // Unknown ids (dropped above) are skips too. + var skipped = requestedUserIds.size - decisions.size + var queued = 0 + + decisions.forEach { (userId, decision) -> + val shouldSend = when (decision.disposition) { + BulkRowDisposition.INCLUDED -> true + BulkRowDisposition.WARNING -> userId in includedUserIds + else -> false // EXCLUDED / SKIPPED (incl. NO_EMAIL) + } + if (!shouldSend) { + skipped++ + return@forEach + } + + val user = users.findById(userId) + val effectiveFeeType = command.feeTypeOverrides[userId] ?: decision.recommendedFeeType!! + val amountToSend = resolveFeeAmount(effectiveFeeType, period) + + val notification = notifications.create( + IncassoNotification( + user = user, + contributionPeriod = period, + amount = amountToSend, + expectedIncassoDate = command.expectedIncassoDate, + ) + ) + notifications.sendNotification(notification) + + applied++ + queued++ + } + + return BulkActionResult(applied = applied, skipped = skipped, queued = queued) + } +} + +/** + * Decision function for the incasso-notification bulk action. Mirrors [decideReminder] + * but adds the incasso-flag check. Called by both preview and execute. + * See docs/proposals/bulk-actions/REDESIGN.md §3. + */ +internal fun decideIncasso( + userId: Long, + periodId: Long, + period: ContributionPeriod, + cutoffDate: LocalDate, + users: UserService, + memberships: MembershipService, + contributions: ContributionService, + notifications: IncassoNotificationService, +): EmailBulkDecision { + val user = users.findById(userId) + + val activeMembership = memberships.findByUserId(userId).maxByOrNull { it.startDate } + val memberType = activeMembership?.memberType ?: MemberType.REGULAR + val membershipStart = activeMembership?.startDate + val recommendedFeeType = resolveFeeType(memberType, membershipStart, cutoffDate) + + val hasIncassoEnabled = activeMembership?.incasso ?: false + val alreadyPaid = contributions.existsByUserIdAndPeriodId(userId, periodId) + val emailMissing = user.email.isBlank() + val lastSent = notifications.findLastNotificationForUserAndPeriod(userId, periodId)?.createdAt + ?.atZone(ZoneOffset.UTC)?.toLocalDate() + + val disposition: BulkRowDisposition + val reason: BulkRowReason? + when { + recommendedFeeType == null -> { + disposition = BulkRowDisposition.EXCLUDED + reason = BulkRowReason.HONORARY + } + emailMissing -> { + disposition = BulkRowDisposition.SKIPPED + reason = BulkRowReason.NO_EMAIL + } + !hasIncassoEnabled -> { + disposition = BulkRowDisposition.WARNING + reason = BulkRowReason.INCASSO_MISMATCH + } + alreadyPaid -> { + disposition = BulkRowDisposition.WARNING + reason = BulkRowReason.ALREADY_PAID + } + else -> { + disposition = BulkRowDisposition.INCLUDED + reason = null + } + } + + return EmailBulkDecision( + userId = userId, + name = user.fullName, + memberType = memberType, + memberSince = membershipStart, + disposition = disposition, + reason = reason, + recommendedFeeType = recommendedFeeType, + amount = recommendedFeeType?.let { resolveFeeAmount(it, period) }, + lastSentOn = lastSent, + emailMissing = emailMissing, + ) +} diff --git a/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/application/email/ContributionReminderEmailBuilder.kt b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/application/email/ContributionReminderEmailBuilder.kt index 2158c695a..183cce561 100644 --- a/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/application/email/ContributionReminderEmailBuilder.kt +++ b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/application/email/ContributionReminderEmailBuilder.kt @@ -1,44 +1,147 @@ package net.blueshell.api.domain.contribution.application.email +import net.blueshell.api.domain.contribution.domain.service.feeReason import net.blueshell.api.domain.contribution.persistence.ContributionPeriod import net.blueshell.api.domain.user.persistence.User +import net.blueshell.api.platform.config.BankProperties +import net.blueshell.api.shared.dto.bulk.BulkFeeType import net.blueshell.api.shared.email.EmailContent +import java.time.LocalDate +import java.time.format.DateTimeFormatter /** * Email builder for contribution payment reminders. * * Builds EmailContent DTO that serves as Anti-Corruption Layer (ADR-019) * between the contribution domain and the platform email system. + * + * Members pay their membership contribution by BANK TRANSFER to the Blueshell + * account, not via the website. The bank details come from configuration + * (see BankProperties / blueshell.bank.*). + * + * The Markdown body is assembled from a list of column-0 lines joined with + * newlines rather than a `trimIndent()`-ed raw string. Interpolating a + * multi-line value (the bank-transfer block) into an indented raw string + * defeats `trimIndent()`: the interpolated lines carry no indentation, so the + * common indent computed by `trimIndent()` collapses to 0 and every other line + * keeps its 8-space source indentation. Markdown then renders the whole body as + * an indented code block. Joining unindented lines is immune to that. + */ + +private val DATE_FORMATTER: DateTimeFormatter = DateTimeFormatter.ofPattern("dd MMMM yyyy") + +/** + * Derives an academic-year label (e.g. "2025/2026") from a contribution period. + * When the period spans two calendar years the label uses both, otherwise it + * falls back to a single year. + */ +internal fun academicYearLabel(period: ContributionPeriod): String { + val startYear = period.startDate.year + val endYear = period.endDate.year + return if (endYear > startYear) "$startYear/$endYear" else "$startYear" +} + +/** Bank-transfer block as unindented Markdown lines (no leading whitespace). */ +private fun bankTransferLines(bank: BankProperties): List = listOf( + "**Bank transfer**", + "Account: ${bank.iban}, in the name of ${bank.accountName}.", + "For foreign bank accounts, the BIC code is ${bank.bic}.", +) + +private const val SIGN_OFF = "Secretary & Treasurer of ESA Blueshell" + +/** + * Bulk reminder email: quote a single resolved amount and due date, and ask the + * member to pay by bank transfer to the Blueshell account. */ fun createContributionReminderEmail( recipient: User, contributionPeriod: ContributionPeriod, - frontendUrl: String + amount: Double, + paymentDueDate: LocalDate, + bank: BankProperties, + feeType: BulkFeeType, ): EmailContent { - val markdownContent = """ - Dear ${recipient.fullName}, - - This is a friendly reminder that your contribution payment for the period ${contributionPeriod.startDate} to ${contributionPeriod.endDate} is due. - - Payment options: - - Half year fee: €${"%.2f".format(contributionPeriod.halfYearFee)} - - Full year fee: €${"%.2f".format(contributionPeriod.fullYearFee)} - - Alumni fee: €${"%.2f".format(contributionPeriod.alumniFee)} + val academicYear = academicYearLabel(contributionPeriod) + val markdownContent = buildList { + add("Dear ${recipient.fullName},") + add("") + add( + "In order to retain your membership you will need to pay the contribution fee for " + + "$academicYear. This fee must be paid before **${paymentDueDate.format(DATE_FORMATTER)}**. " + + "If the payment is not received before this time, your membership role in our Discord and " + + "on the website will be revoked." + ) + add("") + add( + "The contribution may be paid by transferring the fee directly to the Blueshell bank account. " + + "Details are given below." + ) + add("") + add("**Amount due: €${"%.2f".format(amount)}** (${feeReason(feeType)})") + add("") + addAll(bankTransferLines(bank)) + add("") + add("If you have already paid, please disregard this message.") + add("") + add("Kind regards,") + add(SIGN_OFF) + }.joinToString("\n") - Please make your payment at your earliest convenience via our [website]($frontendUrl). - - If you have already made your payment, please disregard this message. + return EmailContent( + recipientEmail = recipient.email, + recipientName = recipient.fullName, + subject = "Please pay your Blueshell contribution ($academicYear)", + markdownContent = markdownContent, + senderNameOverride = SIGN_OFF, + replyToOverride = "board@blueshell.utwente.nl" + ) +} - Kind regards, - Treasurer of Blueshell Esports - """.trimIndent() +/** + * Single-user reminder email: the resolved amount is not known up front, so the + * available fee options are listed. The member still pays by bank transfer to the + * Blueshell account. + */ +fun createContributionReminderEmail( + recipient: User, + contributionPeriod: ContributionPeriod, + bank: BankProperties, +): EmailContent { + val academicYear = academicYearLabel(contributionPeriod) + val markdownContent = buildList { + add("Dear ${recipient.fullName},") + add("") + add( + "In order to retain your membership you will need to pay the contribution fee for " + + "$academicYear. If the payment is not received in time, your membership role in our Discord " + + "and on the website will be revoked." + ) + add("") + add( + "The contribution may be paid by transferring the fee directly to the Blueshell bank account. " + + "The fee that applies to you is one of the following." + ) + add("") + add("**Fee options**") + add("- Half year fee: €${"%.2f".format(contributionPeriod.halfYearFee)}") + add("- Full year fee: €${"%.2f".format(contributionPeriod.fullYearFee)}") + add("- Alumni fee: €${"%.2f".format(contributionPeriod.alumniFee)}") + add("") + addAll(bankTransferLines(bank)) + add("") + add("If you have already paid, please disregard this message.") + add("") + add("Kind regards,") + add(SIGN_OFF) + }.joinToString("\n") return EmailContent( recipientEmail = recipient.email, recipientName = recipient.fullName, - subject = "Contribution Payment Reminder - Blueshell Esports", + subject = "Please pay your Blueshell contribution ($academicYear)", markdownContent = markdownContent, - senderNameOverride = "Treasurer of Blueshell", + senderNameOverride = SIGN_OFF, replyToOverride = "board@blueshell.utwente.nl" ) } diff --git a/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/application/email/IncassoNotificationEmailBuilder.kt b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/application/email/IncassoNotificationEmailBuilder.kt new file mode 100644 index 000000000..954d2b71a --- /dev/null +++ b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/application/email/IncassoNotificationEmailBuilder.kt @@ -0,0 +1,67 @@ +package net.blueshell.api.domain.contribution.application.email + +import net.blueshell.api.domain.contribution.domain.service.feeReason +import net.blueshell.api.domain.contribution.persistence.ContributionPeriod +import net.blueshell.api.domain.user.persistence.User +import net.blueshell.api.shared.dto.bulk.BulkFeeType +import net.blueshell.api.shared.email.EmailContent +import java.time.LocalDate +import java.time.format.DateTimeFormatter +import java.util.Locale + +/** + * Email builder for incasso notification (bulk collection notice). + * + * Builds EmailContent DTO that serves as Anti-Corruption Layer (ADR-019) + * between the contribution domain and the platform email system. + * + * Incasso members are collected by DIRECT DEBIT (SEPA incasso), so this email + * does NOT ask them to transfer any money. It notifies them that the fee will be + * debited automatically. We know the applied fee, so a single amount is stated + * together with the reason it applies, rather than listing every fee option. + */ + +private val INCASSO_DATE_FORMATTER: DateTimeFormatter = + DateTimeFormatter.ofPattern("EEEE d MMMM yyyy", Locale.ENGLISH) + +fun createIncassoNotificationEmail( + recipient: User, + contributionPeriod: ContributionPeriod, + amount: Double, + expectedIncassoDate: LocalDate, + feeType: BulkFeeType, +): EmailContent { + val academicYear = academicYearLabel(contributionPeriod) + val formattedDate = expectedIncassoDate.format(INCASSO_DATE_FORMATTER) + // Assembled from column-0 lines (not a trimIndent()-ed raw string) so no line + // ever reaches the Markdown converter with leading whitespace, which would be + // rendered as an indented code block. + val markdownContent = buildList { + add("Dear ${recipient.fullName},") + add("") + add( + "Your membership fee for your $academicYear membership of ESA Blueshell will be automatically " + + "subtracted from your bank account on or around **$formattedDate**. Please make sure there " + + "are sufficient funds in your account on that date." + ) + add("") + add("**Amount to be collected: €${"%.2f".format(amount)}** (${feeReason(feeType)})") + add("") + add( + "If you wish to terminate your membership, please respond to this email before $formattedDate " + + "so we can remove you from the incasso list." + ) + add("") + add("Kind regards,") + add("Secretary & Treasurer of ESA Blueshell") + }.joinToString("\n") + + return EmailContent( + recipientEmail = recipient.email, + recipientName = recipient.fullName, + subject = "Your Blueshell contribution will be collected automatically ($academicYear)", + markdownContent = markdownContent, + senderNameOverride = "Secretary & Treasurer of ESA Blueshell", + replyToOverride = "board@blueshell.utwente.nl" + ) +} diff --git a/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/command/BulkContributionCommands.kt b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/command/BulkContributionCommands.kt new file mode 100644 index 000000000..4a7eb09d3 --- /dev/null +++ b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/command/BulkContributionCommands.kt @@ -0,0 +1,20 @@ +package net.blueshell.api.domain.contribution.command + +import jakarta.validation.constraints.NotEmpty +import jakarta.validation.constraints.NotNull +import jakarta.validation.constraints.Positive +import net.blueshell.api.shared.command.Command +import net.blueshell.api.shared.dto.bulk.BulkActionResult + +/** Which paid-state a bulk contribution action drives. */ +enum class BulkContributionOperation { PAID, UNPAID } + +data class ExecuteBulkContributionCommand( + @field:NotEmpty(message = "At least one user ID is required") + val userIds: List<@Positive Long>, + @field:NotNull(message = "Contribution period ID is required") + @field:Positive(message = "Contribution period ID must be positive") + val contributionPeriodId: Long?, + @field:NotNull(message = "Operation is required") + val operation: BulkContributionOperation?, +) : Command diff --git a/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/command/BulkContributionReminderCommands.kt b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/command/BulkContributionReminderCommands.kt new file mode 100644 index 000000000..f83d115ce --- /dev/null +++ b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/command/BulkContributionReminderCommands.kt @@ -0,0 +1,29 @@ +package net.blueshell.api.domain.contribution.command + +import jakarta.validation.constraints.NotEmpty +import jakarta.validation.constraints.NotNull +import jakarta.validation.constraints.Positive +import net.blueshell.api.shared.command.Command +import net.blueshell.api.shared.dto.bulk.BulkActionResult +import net.blueshell.api.shared.dto.bulk.BulkFeeType +import java.time.LocalDate + +data class ExecuteBulkContributionReminderCommand( + @field:NotEmpty(message = "At least one user ID is required") + val userIds: List<@Positive Long>, + @field:NotNull(message = "Contribution period ID is required") + @field:Positive(message = "Contribution period ID must be positive") + val contributionPeriodId: Long?, + @field:NotNull(message = "Cutoff date is required") + val cutoffDate: LocalDate?, + @field:NotNull(message = "Payment due date is required") + val paymentDueDate: LocalDate?, + /** User IDs to include (re-includes those marked as already-paid by default). */ + val includedUserIds: Set = emptySet(), + /** + * Per-user fee type overrides: userId -> BulkFeeType. + * The handler resolves the € from the period's fee for the chosen type. + * If a user has no override, their recommended fee type is used. + */ + val feeTypeOverrides: Map = emptyMap(), +) : Command diff --git a/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/command/BulkIncassoNotificationCommands.kt b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/command/BulkIncassoNotificationCommands.kt new file mode 100644 index 000000000..99ca14f58 --- /dev/null +++ b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/command/BulkIncassoNotificationCommands.kt @@ -0,0 +1,29 @@ +package net.blueshell.api.domain.contribution.command + +import jakarta.validation.constraints.NotEmpty +import jakarta.validation.constraints.NotNull +import jakarta.validation.constraints.Positive +import net.blueshell.api.shared.command.Command +import net.blueshell.api.shared.dto.bulk.BulkActionResult +import net.blueshell.api.shared.dto.bulk.BulkFeeType +import java.time.LocalDate + +data class ExecuteBulkIncassoNotificationCommand( + @field:NotEmpty(message = "At least one user ID is required") + val userIds: List<@Positive Long>, + @field:NotNull(message = "Contribution period ID is required") + @field:Positive(message = "Contribution period ID must be positive") + val contributionPeriodId: Long?, + @field:NotNull(message = "Cutoff date is required") + val cutoffDate: LocalDate?, + @field:NotNull(message = "Expected incasso date is required") + val expectedIncassoDate: LocalDate?, + /** User IDs to include (re-includes those marked as non-incasso/already-paid by default). */ + val includedUserIds: Set = emptySet(), + /** + * Per-user fee type overrides: userId -> BulkFeeType. + * The handler resolves the € from the period's fee for the chosen type. + * If a user has no override, their recommended fee type is used. + */ + val feeTypeOverrides: Map = emptyMap(), +) : Command diff --git a/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/domain/service/FeeResolution.kt b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/domain/service/FeeResolution.kt new file mode 100644 index 000000000..20c563a35 --- /dev/null +++ b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/domain/service/FeeResolution.kt @@ -0,0 +1,100 @@ +package net.blueshell.api.domain.contribution.domain.service + +import net.blueshell.api.domain.contribution.persistence.ContributionPeriod +import net.blueshell.api.shared.dto.bulk.BulkFeeType +import net.blueshell.api.shared.enums.MemberType +import java.time.LocalDate + +/** + * Resolves the fee amount owed by a member for a contribution period, + * accounting for member type and membership start date relative to cutoff. + * + * Rules: + * - REGULAR: half-year fee if membership started AFTER the cutoff, else full-year (boundary start == cutoff pays full year, matching the frontend rule) + * - ALUMNI: alumni fee + * - HONORARY: excluded (returns null) + * + * @param memberType the member's type + * @param membershipStartDate the date the membership started (null = unresolvable) + * @param cutoffDate the date used to determine half-year vs full-year for REGULAR members + * @param period the contribution period carrying the fees + * @return the resolved fee in euros, or null if excluded (honorary) + */ +fun resolveMemberFee( + memberType: MemberType, + membershipStartDate: LocalDate?, + cutoffDate: LocalDate, + period: ContributionPeriod, +): Double? = when (memberType) { + MemberType.REGULAR -> { + if (membershipStartDate != null && membershipStartDate > cutoffDate) { + period.halfYearFee + } else { + period.fullYearFee + } + } + MemberType.ALUMNI -> period.alumniFee + MemberType.HONORARY -> null // Excluded + MemberType.NONE -> period.fullYearFee // Fallback for no membership +} + +/** + * Resolves the [BulkFeeType] recommended for a member based on their type and + * membership start date relative to the half-year cutoff. Returns null for + * HONORARY members (they are excluded). + * + * Rules (mirror of [resolveMemberFee]): + * - REGULAR started on or before cutoff → FULL_YEAR_FEE + * - REGULAR started after cutoff → HALF_YEAR_FEE + * - ALUMNI → ALUMNI_FEE + * - HONORARY → null (excluded) + */ +fun resolveFeeType( + memberType: MemberType, + membershipStartDate: LocalDate?, + cutoffDate: LocalDate, +): BulkFeeType? = when (memberType) { + MemberType.REGULAR -> { + if (membershipStartDate != null && membershipStartDate > cutoffDate) { + BulkFeeType.HALF_YEAR_FEE + } else { + BulkFeeType.FULL_YEAR_FEE + } + } + MemberType.ALUMNI -> BulkFeeType.ALUMNI_FEE + MemberType.HONORARY -> null // Excluded + MemberType.NONE -> BulkFeeType.FULL_YEAR_FEE // Fallback for no membership +} + +/** + * Resolves the € amount for a given [BulkFeeType] from the contribution period. + * This is a pure function with no side effects. + */ +fun resolveFeeAmount(feeType: BulkFeeType, period: ContributionPeriod): Double = when (feeType) { + BulkFeeType.FULL_YEAR_FEE -> period.fullYearFee + BulkFeeType.HALF_YEAR_FEE -> period.halfYearFee + BulkFeeType.ALUMNI_FEE -> period.alumniFee +} + +/** + * Best-effort recovery of the [BulkFeeType] that produced a persisted [amount] for a + * given [period], by matching the amount against the period's fee options. Used by the + * send path, where only the resolved amount is stored on the reminder / incasso record + * (the fee type itself is not persisted). Falls back to [BulkFeeType.FULL_YEAR_FEE] when + * no fee option matches, so the email always states a reason. + */ +fun resolveFeeTypeFromAmount(amount: Double, period: ContributionPeriod): BulkFeeType = when (amount) { + period.halfYearFee -> BulkFeeType.HALF_YEAR_FEE + period.alumniFee -> BulkFeeType.ALUMNI_FEE + else -> BulkFeeType.FULL_YEAR_FEE +} + +/** + * Human-readable reason for why a specific [BulkFeeType] applies to a member, stated + * inline in reminder / incasso emails so the amount is never quoted without context. + */ +fun feeReason(feeType: BulkFeeType): String = when (feeType) { + BulkFeeType.ALUMNI_FEE -> "the alumni fee, as you are an alumni member" + BulkFeeType.HALF_YEAR_FEE -> "the half-year fee, as your membership started during the second half of the year" + BulkFeeType.FULL_YEAR_FEE -> "the full-year fee" +} diff --git a/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/persistence/ContributionReminder.kt b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/persistence/ContributionReminder.kt index 56428326a..f8b9d5b92 100644 --- a/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/persistence/ContributionReminder.kt +++ b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/persistence/ContributionReminder.kt @@ -7,6 +7,7 @@ import net.blueshell.api.shared.model.Identifiable import org.hibernate.Hibernate import org.hibernate.annotations.SQLDelete import org.hibernate.annotations.SQLRestriction +import java.time.LocalDate @Entity @Table( @@ -48,6 +49,12 @@ class ContributionReminder( @ManyToOne(fetch = FetchType.LAZY, optional = false) @JoinColumn(name = "contribution_period_id", nullable = false) var contributionPeriod: ContributionPeriod, + + @Column(name = "amount", nullable = true) + var amount: Double? = null, + + @Column(name = "payment_due_date", nullable = true) + var paymentDueDate: LocalDate? = null, ) : AuditedSoftDeleteEntity(), Identifiable { val userId: Long diff --git a/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/persistence/IncassoNotification.kt b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/persistence/IncassoNotification.kt new file mode 100644 index 000000000..b74e7ed5b --- /dev/null +++ b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/persistence/IncassoNotification.kt @@ -0,0 +1,82 @@ +package net.blueshell.api.domain.contribution.persistence + +import jakarta.persistence.* +import net.blueshell.api.domain.user.persistence.User +import net.blueshell.api.shared.model.AuditedSoftDeleteEntity +import net.blueshell.api.shared.model.Identifiable +import org.hibernate.Hibernate +import org.hibernate.annotations.SQLDelete +import org.hibernate.annotations.SQLRestriction +import java.time.LocalDate + +@Entity +@Table( + name = "incasso_notifications", + uniqueConstraints = [ + UniqueConstraint( + name = "uk_incasso_notifications_user_period_deleted_at", + columnNames = ["user_id", "contribution_period_id", "deleted_at"] + ), + ], + indexes = [ + Index(name = "idx_incasso_notifications_deleted_at", columnList = "deleted_at"), + Index(name = "idx_incasso_notifications_created_at", columnList = "created_at"), + Index(name = "idx_incasso_notifications_user_id", columnList = "user_id, deleted_at"), + Index( + name = "idx_incasso_notifications_contribution_period_id", + columnList = "contribution_period_id, deleted_at" + ) + ] +) +@SQLDelete( + sql = """ + UPDATE incasso_notifications + SET deleted_at = NOW(), version = version + 1 + WHERE contribution_period_id = ? AND user_id = ? AND version = ? + """ +) +@SQLRestriction("deleted_at = '9999-12-31 23:59:59'") +class IncassoNotification( + @EmbeddedId + override var id: Id = Id(), + + @MapsId("userId") + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "user_id", nullable = false) + var user: User, + + @MapsId("contributionPeriodId") + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "contribution_period_id", nullable = false) + var contributionPeriod: ContributionPeriod, + + @Column(name = "amount", nullable = true) + var amount: Double? = null, + + @Column(name = "expected_incasso_date", nullable = true) + var expectedIncassoDate: LocalDate? = null, +) : AuditedSoftDeleteEntity(), Identifiable { + + val userId: Long + get() = id.userId ?: user.id ?: 0 + + val contributionPeriodId: Long + get() = id.contributionPeriodId ?: contributionPeriod.id ?: 0 + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null) return false + if (Hibernate.getClass(this) != Hibernate.getClass(other)) return false + other as IncassoNotification + return id == other.id + } + + override fun hashCode(): Int = id.hashCode() + + @Embeddable + data class Id( + var userId: Long? = null, + var contributionPeriodId: Long? = null + ) : java.io.Serializable + +} diff --git a/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/persistence/repository/IncassoNotificationRepository.kt b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/persistence/repository/IncassoNotificationRepository.kt new file mode 100644 index 000000000..ad22759d3 --- /dev/null +++ b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/persistence/repository/IncassoNotificationRepository.kt @@ -0,0 +1,10 @@ +package net.blueshell.api.domain.contribution.persistence.repository + +import net.blueshell.api.domain.contribution.persistence.IncassoNotification +import net.blueshell.api.shared.repository.BaseRepository +import org.springframework.stereotype.Repository + +@Repository +interface IncassoNotificationRepository : BaseRepository { + fun findByIdContributionPeriodId(contributionPeriodId: Long): MutableList +} diff --git a/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/web/ContributionBulkController.kt b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/web/ContributionBulkController.kt new file mode 100644 index 000000000..bb85fc8a5 --- /dev/null +++ b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/web/ContributionBulkController.kt @@ -0,0 +1,132 @@ +package net.blueshell.api.domain.contribution.web + +import io.swagger.v3.oas.annotations.tags.Tag +import jakarta.validation.Valid +import net.blueshell.api.domain.contribution.application.EmailPreviewService +import net.blueshell.api.domain.contribution.command.BulkContributionOperation +import net.blueshell.api.domain.contribution.command.ExecuteBulkContributionCommand +import net.blueshell.api.domain.contribution.command.ExecuteBulkContributionReminderCommand +import net.blueshell.api.domain.contribution.command.ExecuteBulkIncassoNotificationCommand +import net.blueshell.api.domain.contribution.web.dto.request.BulkContributionReminderExecuteRequest +import net.blueshell.api.domain.contribution.web.dto.request.BulkIncassoNotificationExecuteRequest +import net.blueshell.api.domain.contribution.web.dto.request.BulkMarkPaidRequest +import net.blueshell.api.domain.contribution.web.dto.request.BulkMarkUnpaidRequest +import net.blueshell.api.domain.contribution.web.dto.request.ContributionReminderPreviewRequest +import net.blueshell.api.domain.contribution.web.dto.request.IncassoNotificationPreviewRequest +import net.blueshell.api.domain.contribution.web.dto.response.EmailPreviewResponse +import net.blueshell.api.shared.command.CommandBus +import net.blueshell.api.shared.dto.bulk.BulkActionResult +import org.springframework.security.access.prepost.PreAuthorize +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RestController + +/** + * Bulk actions over members: mark-paid/unpaid, send contribution reminders, send incasso + * notifications. One execute endpoint per action with action-named paths. Preview of the + * row dispositions happens frontend-side; the execute endpoints are the source of truth + * and re-validate against the live DB. A separate email-preview endpoint per email action + * renders (never sends) the actual email for one user so an operator can double-check it. + * Board-only. See docs/proposals/bulk-actions/REDESIGN.md §2. + */ +@RestController +@Tag(name = "Contributions") +class ContributionBulkController( + private val commandBus: CommandBus, + private val emailPreviewService: EmailPreviewService, +) { + + // ===== Mark Paid / Unpaid (execute-only; preview is frontend-computed) ===== + + @PreAuthorize("hasPermission('__NO_TARGET__', 'Contribution', 'write')") + @PostMapping("/contributions/bulk/mark-paid") + fun markPaid(@Valid @RequestBody request: BulkMarkPaidRequest): BulkActionResult = + commandBus.dispatch( + ExecuteBulkContributionCommand( + userIds = request.userIds, + contributionPeriodId = request.contributionPeriodId, + operation = BulkContributionOperation.PAID, + ) + ) + + @PreAuthorize("hasPermission('__NO_TARGET__', 'Contribution', 'write')") + @PostMapping("/contributions/bulk/mark-unpaid") + fun markUnpaid(@Valid @RequestBody request: BulkMarkUnpaidRequest): BulkActionResult = + commandBus.dispatch( + ExecuteBulkContributionCommand( + userIds = request.userIds, + contributionPeriodId = request.contributionPeriodId, + operation = BulkContributionOperation.UNPAID, + ) + ) + + // ===== Contribution Reminders ===== + + @PreAuthorize("hasPermission('__NO_TARGET__', 'ContributionReminder', 'write')") + @PostMapping("/contributionReminders/bulk/execute") + fun executeBulkReminder(@Valid @RequestBody request: BulkContributionReminderExecuteRequest): BulkActionResult = + commandBus.dispatch( + ExecuteBulkContributionReminderCommand( + userIds = request.userIds, + contributionPeriodId = request.contributionPeriodId, + cutoffDate = request.cutoffDate, + paymentDueDate = request.paymentDueDate, + includedUserIds = request.includedUserIds, + feeTypeOverrides = request.feeTypeOverrides, + ) + ) + + /** + * Render (never send) the contribution-reminder email for a single user, using the + * same fee type and payment-due date a bulk send would use, so an operator can + * double-check the actual email. Does NOT create a ContributionReminder or enqueue a + * send. Board-only. + */ + @PreAuthorize("hasPermission('__NO_TARGET__', 'ContributionReminder', 'write')") + @PostMapping("/contributionReminders/preview") + fun previewReminder(@Valid @RequestBody request: ContributionReminderPreviewRequest): EmailPreviewResponse { + val preview = emailPreviewService.previewReminder( + userId = request.userId!!, + contributionPeriodId = request.contributionPeriodId!!, + feeType = request.feeType!!, + paymentDueDate = request.paymentDueDate!!, + ) + return EmailPreviewResponse(subject = preview.subject, html = preview.html) + } + + // ===== Incasso Notifications ===== + + @PreAuthorize("hasPermission('__NO_TARGET__', 'Contribution', 'write')") + @PostMapping("/incassoNotifications/bulk/execute") + fun executeBulkIncassoNotification( + @Valid @RequestBody request: BulkIncassoNotificationExecuteRequest, + ): BulkActionResult = + commandBus.dispatch( + ExecuteBulkIncassoNotificationCommand( + userIds = request.userIds, + contributionPeriodId = request.contributionPeriodId, + cutoffDate = request.cutoffDate, + expectedIncassoDate = request.expectedIncassoDate, + includedUserIds = request.includedUserIds, + feeTypeOverrides = request.feeTypeOverrides, + ) + ) + + /** + * Render (never send) the incasso-notification email for a single user, using the + * same fee type and expected incasso date a bulk send would use, so an operator can + * double-check the actual email. Does NOT create an IncassoNotification or enqueue a + * send. Board-only. + */ + @PreAuthorize("hasPermission('__NO_TARGET__', 'Contribution', 'write')") + @PostMapping("/incassoNotifications/preview") + fun previewIncassoNotification(@Valid @RequestBody request: IncassoNotificationPreviewRequest): EmailPreviewResponse { + val preview = emailPreviewService.previewIncassoNotification( + userId = request.userId!!, + contributionPeriodId = request.contributionPeriodId!!, + feeType = request.feeType!!, + expectedIncassoDate = request.expectedIncassoDate!!, + ) + return EmailPreviewResponse(subject = preview.subject, html = preview.html) + } +} diff --git a/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/web/dto/request/BulkContributionReminderRequest.kt b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/web/dto/request/BulkContributionReminderRequest.kt new file mode 100644 index 000000000..3c55db972 --- /dev/null +++ b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/web/dto/request/BulkContributionReminderRequest.kt @@ -0,0 +1,44 @@ +package net.blueshell.api.domain.contribution.web.dto.request + +import io.swagger.v3.oas.annotations.media.Schema +import jakarta.validation.constraints.Future +import jakarta.validation.constraints.NotEmpty +import jakarta.validation.constraints.NotNull +import jakarta.validation.constraints.Positive +import jakarta.validation.constraints.Size +import net.blueshell.api.shared.dto.bulk.BulkFeeType +import java.time.LocalDate + +/** + * Execute request for the contribution-reminder bulk action. Carries the operator's + * re-include set and per-user fee-type overrides. The server re-decides against the + * live DB and validates the overrides (rejects excluded/non-included users). + */ +@Schema(name = "BulkContributionReminderExecuteRequest") +data class BulkContributionReminderExecuteRequest( + @field:NotEmpty(message = "At least one user ID is required") + @field:Size(min = 1, max = 1000, message = "userIds must contain between 1 and 1000 entries") + val userIds: List<@Positive Long> = emptyList(), + + @field:NotNull(message = "Contribution period ID is required") + @field:Positive(message = "Contribution period ID must be positive") + val contributionPeriodId: Long? = null, + + @field:NotNull(message = "Cutoff date is required") + val cutoffDate: LocalDate? = null, + + @field:NotNull(message = "Payment due date is required") + @field:Future(message = "Payment due date must be in the future") + val paymentDueDate: LocalDate? = null, + + /** User IDs to include (re-includes already-paid WARNING rows). */ + @field:Size(max = 1000, message = "includedUserIds must not exceed 1000 entries") + val includedUserIds: Set = emptySet(), + + /** + * Per-user fee type overrides (userId -> BulkFeeType). The server resolves the € + * from the period's fee for the chosen type; missing → recommended type. + */ + @field:Size(max = 1000, message = "feeTypeOverrides must not exceed 1000 entries") + val feeTypeOverrides: Map = emptyMap(), +) diff --git a/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/web/dto/request/BulkContributionRequest.kt b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/web/dto/request/BulkContributionRequest.kt new file mode 100644 index 000000000..ea4b4777a --- /dev/null +++ b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/web/dto/request/BulkContributionRequest.kt @@ -0,0 +1,39 @@ +package net.blueshell.api.domain.contribution.web.dto.request + +import io.swagger.v3.oas.annotations.media.Schema +import jakarta.validation.constraints.NotEmpty +import jakarta.validation.constraints.NotNull +import jakarta.validation.constraints.Positive +import jakarta.validation.constraints.Size + +/** + * Execute-only request for the mark-paid bulk action. The operation is implied by the + * action-named path (`/contributions/bulk/mark-paid`), so no `operation` field is sent. + * There is no preview endpoint — the frontend computes the mark-paid preview locally + * from its paid-user set. See docs/proposals/bulk-actions/REDESIGN.md §2. + */ +@Schema(name = "BulkMarkPaidRequest") +data class BulkMarkPaidRequest( + @field:NotEmpty(message = "At least one user ID is required") + @field:Size(min = 1, max = 1000, message = "userIds must contain between 1 and 1000 entries") + val userIds: List<@Positive Long> = emptyList(), + + @field:NotNull(message = "Contribution period ID is required") + @field:Positive(message = "Contribution period ID must be positive") + val contributionPeriodId: Long? = null, +) + +/** + * Execute-only request for the mark-unpaid bulk action. Mirror of [BulkMarkPaidRequest] + * for the `/contributions/bulk/mark-unpaid` path. + */ +@Schema(name = "BulkMarkUnpaidRequest") +data class BulkMarkUnpaidRequest( + @field:NotEmpty(message = "At least one user ID is required") + @field:Size(min = 1, max = 1000, message = "userIds must contain between 1 and 1000 entries") + val userIds: List<@Positive Long> = emptyList(), + + @field:NotNull(message = "Contribution period ID is required") + @field:Positive(message = "Contribution period ID must be positive") + val contributionPeriodId: Long? = null, +) diff --git a/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/web/dto/request/BulkIncassoNotificationRequest.kt b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/web/dto/request/BulkIncassoNotificationRequest.kt new file mode 100644 index 000000000..eeb8f6fc4 --- /dev/null +++ b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/web/dto/request/BulkIncassoNotificationRequest.kt @@ -0,0 +1,42 @@ +package net.blueshell.api.domain.contribution.web.dto.request + +import io.swagger.v3.oas.annotations.media.Schema +import jakarta.validation.constraints.Future +import jakarta.validation.constraints.NotEmpty +import jakarta.validation.constraints.NotNull +import jakarta.validation.constraints.Positive +import jakarta.validation.constraints.Size +import net.blueshell.api.shared.dto.bulk.BulkFeeType +import java.time.LocalDate + +/** + * Execute request for the incasso-notification bulk action. Carries the operator's + * re-include set and per-user fee-type overrides. + */ +@Schema(name = "BulkIncassoNotificationExecuteRequest") +data class BulkIncassoNotificationExecuteRequest( + @field:NotEmpty(message = "At least one user ID is required") + @field:Size(min = 1, max = 1000, message = "userIds must contain between 1 and 1000 entries") + val userIds: List<@Positive Long> = emptyList(), + + @field:NotNull(message = "Contribution period ID is required") + @field:Positive(message = "Contribution period ID must be positive") + val contributionPeriodId: Long? = null, + + @field:NotNull(message = "Cutoff date is required") + val cutoffDate: LocalDate? = null, + + @field:NotNull(message = "Expected incasso date is required") + @field:Future(message = "Expected incasso date must be in the future") + val expectedIncassoDate: LocalDate? = null, + + /** User IDs to include (re-includes non-incasso / already-paid WARNING rows). */ + @field:Size(max = 1000, message = "includedUserIds must not exceed 1000 entries") + val includedUserIds: Set = emptySet(), + + /** + * Per-user fee type overrides (userId -> BulkFeeType); missing → recommended type. + */ + @field:Size(max = 1000, message = "feeTypeOverrides must not exceed 1000 entries") + val feeTypeOverrides: Map = emptyMap(), +) diff --git a/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/web/dto/request/EmailPreviewRequest.kt b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/web/dto/request/EmailPreviewRequest.kt new file mode 100644 index 000000000..156773b1b --- /dev/null +++ b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/web/dto/request/EmailPreviewRequest.kt @@ -0,0 +1,52 @@ +package net.blueshell.api.domain.contribution.web.dto.request + +import io.swagger.v3.oas.annotations.media.Schema +import jakarta.validation.constraints.Future +import jakarta.validation.constraints.NotNull +import jakarta.validation.constraints.Positive +import net.blueshell.api.shared.dto.bulk.BulkFeeType +import java.time.LocalDate + +/** + * Preview request for the contribution-reminder email. Renders (never sends) the email + * for a single user, using the same fee type and payment-due date a bulk send would use. + */ +@Schema(name = "ContributionReminderPreviewRequest") +data class ContributionReminderPreviewRequest( + @field:NotNull(message = "User ID is required") + @field:Positive(message = "User ID must be positive") + val userId: Long? = null, + + @field:NotNull(message = "Contribution period ID is required") + @field:Positive(message = "Contribution period ID must be positive") + val contributionPeriodId: Long? = null, + + @field:NotNull(message = "Fee type is required") + val feeType: BulkFeeType? = null, + + @field:NotNull(message = "Payment due date is required") + @field:Future(message = "Payment due date must be in the future") + val paymentDueDate: LocalDate? = null, +) + +/** + * Preview request for the incasso-notification email. Renders (never sends) the email for + * a single user, using the same fee type and expected incasso date a bulk send would use. + */ +@Schema(name = "IncassoNotificationPreviewRequest") +data class IncassoNotificationPreviewRequest( + @field:NotNull(message = "User ID is required") + @field:Positive(message = "User ID must be positive") + val userId: Long? = null, + + @field:NotNull(message = "Contribution period ID is required") + @field:Positive(message = "Contribution period ID must be positive") + val contributionPeriodId: Long? = null, + + @field:NotNull(message = "Fee type is required") + val feeType: BulkFeeType? = null, + + @field:NotNull(message = "Expected incasso date is required") + @field:Future(message = "Expected incasso date must be in the future") + val expectedIncassoDate: LocalDate? = null, +) diff --git a/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/web/dto/response/EmailPreviewResponse.kt b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/web/dto/response/EmailPreviewResponse.kt new file mode 100644 index 000000000..561a558a8 --- /dev/null +++ b/services/api/src/main/kotlin/net/blueshell/api/domain/contribution/web/dto/response/EmailPreviewResponse.kt @@ -0,0 +1,14 @@ +package net.blueshell.api.domain.contribution.web.dto.response + +import io.swagger.v3.oas.annotations.media.Schema + +/** + * A rendered email preview: the subject line and the full HTML body that would be sent. + * Returned by the reminder / incasso-notification preview endpoints so an operator can + * double-check the actual email before a bulk send. + */ +@Schema(name = "EmailPreviewResponse") +data class EmailPreviewResponse( + val subject: String, + val html: String, +) diff --git a/services/api/src/main/kotlin/net/blueshell/api/domain/user/application/MembershipService.kt b/services/api/src/main/kotlin/net/blueshell/api/domain/user/application/MembershipService.kt index b228ba5a3..1857ad3b8 100644 --- a/services/api/src/main/kotlin/net/blueshell/api/domain/user/application/MembershipService.kt +++ b/services/api/src/main/kotlin/net/blueshell/api/domain/user/application/MembershipService.kt @@ -84,6 +84,10 @@ class MembershipService @Autowired constructor( return repository.existsByUser_IdAndEndDateIsNull(userId) } + fun findByUserId(userId: Long): MutableList { + return repository.findByUser_Id(userId) + } + fun findByQuery(query: MembershipQuery): MutableList { val spec = MembershipSpecifications.fromQuery( query, diff --git a/services/api/src/main/kotlin/net/blueshell/api/domain/user/application/command/BulkEndMembershipCommandHandlers.kt b/services/api/src/main/kotlin/net/blueshell/api/domain/user/application/command/BulkEndMembershipCommandHandlers.kt new file mode 100644 index 000000000..0b79bafdc --- /dev/null +++ b/services/api/src/main/kotlin/net/blueshell/api/domain/user/application/command/BulkEndMembershipCommandHandlers.kt @@ -0,0 +1,76 @@ +package net.blueshell.api.domain.user.application.command + +import net.blueshell.api.domain.user.application.MembershipService +import net.blueshell.api.domain.user.application.UserService +import net.blueshell.api.domain.user.application.query.MembershipQuery +import net.blueshell.api.domain.user.command.ExecuteBulkEndMembershipCommand +import net.blueshell.api.domain.user.persistence.Membership +import net.blueshell.api.shared.command.CommandHandler +import net.blueshell.api.shared.dto.bulk.BulkActionResult +import net.blueshell.api.shared.enums.MemberType +import net.blueshell.api.shared.enums.Role +import org.springframework.stereotype.Component +import org.springframework.transaction.annotation.Transactional +import java.time.LocalDate + +/** + * Shared decision for end-membership: a user's active (endDate=null) memberships that + * started before [actionDate] are endable. Both preview and execute filter with the + * same predicate and the same single [actionDate] so they cannot diverge across the + * midnight boundary within a request. See docs/proposals/bulk-actions/REDESIGN.md §3. + */ +private fun endableMemberships( + memberships: MembershipService, + userId: Long, + actionDate: LocalDate, +): List = + memberships.findByQuery(MembershipQuery(userId = userId)) + .filter { it.endDate == null && it.startDate.isBefore(actionDate) } + +@Component +class ExecuteBulkEndMembershipHandler( + private val memberships: MembershipService, + private val users: UserService, +) : CommandHandler { + override val commandType = ExecuteBulkEndMembershipCommand::class + + @Transactional + override fun handle(command: ExecuteBulkEndMembershipCommand): BulkActionResult { + val today = LocalDate.now() + var applied = 0 + var skipped = 0 + command.userIds.distinct().forEach { userId -> + // Poisoned-batch guard: an unknown userId must not abort the whole batch + // (users.findById throws 404). Treat it as skipped and continue. + if (!users.existsById(userId)) { + skipped++ + return@forEach + } + // Server-side mirror of the client preview's protection rules: users with a + // role above Member (committee/board/treasurer/admin) and honorary members + // keep their membership. The FE shows these rows as EXCLUDED; execute + // re-checks against live data so the protection cannot be bypassed. + val user = users.findById(userId) + if (user.roles.any { it in PROTECTED_ROLES }) { + skipped++ + return@forEach + } + val endable = endableMemberships(memberships, userId, today) + if (endable.isEmpty() || endable.any { it.memberType == MemberType.HONORARY }) { + skipped++ + } else { + endable.forEach { membership -> + membership.endDate = today + memberships.update(membership) + } + applied++ + } + } + return BulkActionResult(applied = applied, skipped = skipped, queued = 0) + } + + private companion object { + /** Roles whose holders cannot have their membership ended. */ + private val PROTECTED_ROLES = setOf(Role.COMMITTEE, Role.BOARD, Role.TREASURER, Role.ADMIN) + } +} diff --git a/services/api/src/main/kotlin/net/blueshell/api/domain/user/application/command/BulkResumeMembershipCommandHandlers.kt b/services/api/src/main/kotlin/net/blueshell/api/domain/user/application/command/BulkResumeMembershipCommandHandlers.kt new file mode 100644 index 000000000..4831d5f73 --- /dev/null +++ b/services/api/src/main/kotlin/net/blueshell/api/domain/user/application/command/BulkResumeMembershipCommandHandlers.kt @@ -0,0 +1,116 @@ +package net.blueshell.api.domain.user.application.command + +import net.blueshell.api.domain.contribution.application.ContributionPeriodService +import net.blueshell.api.domain.user.application.MembershipService +import net.blueshell.api.domain.user.application.UserService +import net.blueshell.api.domain.user.command.ExecuteBulkResumeMembershipCommand +import net.blueshell.api.domain.user.persistence.Membership +import net.blueshell.api.shared.command.CommandHandler +import net.blueshell.api.shared.dto.bulk.BulkActionResult +import net.blueshell.api.shared.enums.MemberType +import org.springframework.stereotype.Component +import org.springframework.transaction.annotation.Transactional +import java.time.LocalDate + +/** + * Determine how a single user's membership will be treated by the resume/start-new action. + * Called by both preview and execute handlers with the same inputs. + * + * @param memberships All (active, non-deleted) membership rows for the user. + * @param basisPeriodStart Start date of the globally most-recent ContributionPeriod. + * @param basisPeriodEnd End date of the globally most-recent ContributionPeriod. + * @return A sealed [ResumeOutcome] describing what will happen. + */ +private sealed class ResumeOutcome { + /** User already has an active membership — skip. */ + object AlreadyActive : ResumeOutcome() + + /** User's most-recent membership ended within the basis period — resume it. */ + data class Resume(val membership: Membership) : ResumeOutcome() + + /** No resumable membership found — insert a new one. */ + data class StartNew(val copyFrom: Membership?) : ResumeOutcome() +} + +private fun classifyUser( + memberships: List, + basisPeriodStart: LocalDate, + basisPeriodEnd: LocalDate, +): ResumeOutcome { + // Already active? + if (memberships.any { it.endDate == null }) return ResumeOutcome.AlreadyActive + + // Pick latest membership by startDate + val latest = memberships.maxByOrNull { it.startDate } + + // If latest membership ended within [basisPeriodStart, basisPeriodEnd], resume it + if (latest != null) { + val endDate = latest.endDate + if (endDate != null && !endDate.isBefore(basisPeriodStart) && !endDate.isAfter(basisPeriodEnd)) { + return ResumeOutcome.Resume(latest) + } + } + + // Otherwise start new — copy memberType/incasso from latest if available + return ResumeOutcome.StartNew(copyFrom = latest) +} + +@Component +class ExecuteBulkResumeMembershipHandler( + private val memberships: MembershipService, + private val users: UserService, + private val periods: ContributionPeriodService, +) : CommandHandler { + override val commandType = ExecuteBulkResumeMembershipCommand::class + + @Transactional + override fun handle(command: ExecuteBulkResumeMembershipCommand): BulkActionResult { + val basisPeriod = periods.findLatest() + if (basisPeriod == null) { + // All skipped — nothing to do + return BulkActionResult(applied = 0, skipped = command.userIds.distinct().size, queued = 0) + } + + val today = LocalDate.now() + var applied = 0 + var skipped = 0 + + command.userIds.distinct().forEach { userId -> + // Poisoned-batch guard: an unknown userId must not abort the whole batch + // (the StartNew branch calls users.findById, which throws 404). Treat it as + // skipped and continue. + if (!users.existsById(userId)) { + skipped++ + return@forEach + } + val userMemberships = memberships.findByUserId(userId) + val outcome = classifyUser(userMemberships, basisPeriod.startDate, basisPeriod.endDate) + + when (outcome) { + is ResumeOutcome.AlreadyActive -> skipped++ + is ResumeOutcome.Resume -> { + outcome.membership.endDate = null + memberships.update(outcome.membership) + applied++ + } + is ResumeOutcome.StartNew -> { + val user = users.findById(userId) + val memberType = outcome.copyFrom?.memberType ?: MemberType.REGULAR + val incasso = outcome.copyFrom?.incasso ?: false + memberships.create( + Membership( + user = user, + startDate = today, + endDate = null, + memberType = memberType, + incasso = incasso, + ) + ) + applied++ + } + } + } + + return BulkActionResult(applied = applied, skipped = skipped, queued = 0) + } +} diff --git a/services/api/src/main/kotlin/net/blueshell/api/domain/user/command/BulkEndMembershipCommands.kt b/services/api/src/main/kotlin/net/blueshell/api/domain/user/command/BulkEndMembershipCommands.kt new file mode 100644 index 000000000..fe2fb3678 --- /dev/null +++ b/services/api/src/main/kotlin/net/blueshell/api/domain/user/command/BulkEndMembershipCommands.kt @@ -0,0 +1,11 @@ +package net.blueshell.api.domain.user.command + +import jakarta.validation.constraints.NotEmpty +import jakarta.validation.constraints.Positive +import net.blueshell.api.shared.command.Command +import net.blueshell.api.shared.dto.bulk.BulkActionResult + +data class ExecuteBulkEndMembershipCommand( + @field:NotEmpty(message = "At least one user ID is required") + val userIds: List<@Positive Long>, +) : Command diff --git a/services/api/src/main/kotlin/net/blueshell/api/domain/user/command/BulkResumeMembershipCommands.kt b/services/api/src/main/kotlin/net/blueshell/api/domain/user/command/BulkResumeMembershipCommands.kt new file mode 100644 index 000000000..fe13ee32c --- /dev/null +++ b/services/api/src/main/kotlin/net/blueshell/api/domain/user/command/BulkResumeMembershipCommands.kt @@ -0,0 +1,11 @@ +package net.blueshell.api.domain.user.command + +import jakarta.validation.constraints.NotEmpty +import jakarta.validation.constraints.Positive +import net.blueshell.api.shared.command.Command +import net.blueshell.api.shared.dto.bulk.BulkActionResult + +data class ExecuteBulkResumeMembershipCommand( + @field:NotEmpty(message = "At least one user ID is required") + val userIds: List<@Positive Long>, +) : Command diff --git a/services/api/src/main/kotlin/net/blueshell/api/domain/user/web/MembershipBulkController.kt b/services/api/src/main/kotlin/net/blueshell/api/domain/user/web/MembershipBulkController.kt new file mode 100644 index 000000000..b75f73d75 --- /dev/null +++ b/services/api/src/main/kotlin/net/blueshell/api/domain/user/web/MembershipBulkController.kt @@ -0,0 +1,34 @@ +package net.blueshell.api.domain.user.web + +import io.swagger.v3.oas.annotations.tags.Tag +import jakarta.validation.Valid +import net.blueshell.api.domain.user.command.ExecuteBulkEndMembershipCommand +import net.blueshell.api.domain.user.command.ExecuteBulkResumeMembershipCommand +import net.blueshell.api.domain.user.web.dto.request.BulkEndMembershipRequest +import net.blueshell.api.domain.user.web.dto.request.BulkResumeMembershipRequest +import net.blueshell.api.shared.command.CommandBus +import net.blueshell.api.shared.dto.bulk.BulkActionResult +import org.springframework.security.access.prepost.PreAuthorize +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RestController + +/** + * Bulk end-membership and resume-membership actions over a set of users. + * Execute-only endpoints; previews are computed frontend-side. + * Board-only. Period-independent. See docs/proposals/bulk-actions/REDESIGN.md §2. + */ +@RestController +@Tag(name = "Memberships") +class MembershipBulkController(private val commandBus: CommandBus) { + + @PreAuthorize("hasPermission('__NO_TARGET__', 'Membership', 'write')") + @PostMapping("/memberships/bulk/end/execute") + fun executeBulkEnd(@Valid @RequestBody request: BulkEndMembershipRequest): BulkActionResult = + commandBus.dispatch(ExecuteBulkEndMembershipCommand(userIds = request.userIds)) + + @PreAuthorize("hasPermission('__NO_TARGET__', 'Membership', 'write')") + @PostMapping("/memberships/bulk/resume/execute") + fun executeBulkResume(@Valid @RequestBody request: BulkResumeMembershipRequest): BulkActionResult = + commandBus.dispatch(ExecuteBulkResumeMembershipCommand(userIds = request.userIds)) +} diff --git a/services/api/src/main/kotlin/net/blueshell/api/domain/user/web/dto/request/BulkEndMembershipRequest.kt b/services/api/src/main/kotlin/net/blueshell/api/domain/user/web/dto/request/BulkEndMembershipRequest.kt new file mode 100644 index 000000000..d3918b23f --- /dev/null +++ b/services/api/src/main/kotlin/net/blueshell/api/domain/user/web/dto/request/BulkEndMembershipRequest.kt @@ -0,0 +1,13 @@ +package net.blueshell.api.domain.user.web.dto.request + +import io.swagger.v3.oas.annotations.media.Schema +import jakarta.validation.constraints.NotEmpty +import jakarta.validation.constraints.Positive +import jakarta.validation.constraints.Size + +@Schema(name = "BulkEndMembershipRequest") +data class BulkEndMembershipRequest( + @field:NotEmpty(message = "At least one user ID is required") + @field:Size(min = 1, max = 1000, message = "userIds must contain between 1 and 1000 entries") + val userIds: List<@Positive Long> = emptyList(), +) diff --git a/services/api/src/main/kotlin/net/blueshell/api/domain/user/web/dto/request/BulkResumeMembershipRequest.kt b/services/api/src/main/kotlin/net/blueshell/api/domain/user/web/dto/request/BulkResumeMembershipRequest.kt new file mode 100644 index 000000000..bb076c725 --- /dev/null +++ b/services/api/src/main/kotlin/net/blueshell/api/domain/user/web/dto/request/BulkResumeMembershipRequest.kt @@ -0,0 +1,13 @@ +package net.blueshell.api.domain.user.web.dto.request + +import io.swagger.v3.oas.annotations.media.Schema +import jakarta.validation.constraints.NotEmpty +import jakarta.validation.constraints.Positive +import jakarta.validation.constraints.Size + +@Schema(name = "BulkResumeMembershipRequest") +data class BulkResumeMembershipRequest( + @field:NotEmpty(message = "At least one user ID is required") + @field:Size(min = 1, max = 1000, message = "userIds must contain between 1 and 1000 entries") + val userIds: List<@Positive Long> = emptyList(), +) diff --git a/services/api/src/main/kotlin/net/blueshell/api/platform/config/BankProperties.kt b/services/api/src/main/kotlin/net/blueshell/api/platform/config/BankProperties.kt new file mode 100644 index 000000000..0e75ae9f4 --- /dev/null +++ b/services/api/src/main/kotlin/net/blueshell/api/platform/config/BankProperties.kt @@ -0,0 +1,15 @@ +package net.blueshell.api.platform.config + +import org.springframework.boot.context.properties.ConfigurationProperties + +/** + * Blueshell bank account details used in contribution emails so that members can + * transfer their membership fee. Configured via `blueshell.bank.*` so the values + * live in configuration rather than being hardcoded in the email builders. + */ +@ConfigurationProperties(prefix = "blueshell.bank") +data class BankProperties( + val iban: String = "NL19 INGB 0008 0964 62", + val bic: String = "INGBNL2A", + val accountName: String = "Blueshell E-Sports Vereniging", +) diff --git a/services/api/src/main/kotlin/net/blueshell/api/platform/integration/email/application/service/EmailSenderService.kt b/services/api/src/main/kotlin/net/blueshell/api/platform/integration/email/application/service/EmailSenderService.kt index 0de0da81b..171d3ad42 100644 --- a/services/api/src/main/kotlin/net/blueshell/api/platform/integration/email/application/service/EmailSenderService.kt +++ b/services/api/src/main/kotlin/net/blueshell/api/platform/integration/email/application/service/EmailSenderService.kt @@ -4,11 +4,16 @@ import net.blueshell.api.domain.auth.application.email.createMemberActivationEma import net.blueshell.api.domain.auth.application.email.createPasswordResetEmail import net.blueshell.api.domain.auth.application.email.createUserActivationEmail import net.blueshell.api.domain.contribution.application.ContributionReminderService +import net.blueshell.api.domain.contribution.application.IncassoNotificationService import net.blueshell.api.domain.contribution.application.email.createContributionReminderEmail +import net.blueshell.api.domain.contribution.application.email.createIncassoNotificationEmail +import net.blueshell.api.domain.contribution.domain.service.resolveFeeTypeFromAmount import net.blueshell.api.domain.contribution.persistence.ContributionReminder +import net.blueshell.api.domain.contribution.persistence.IncassoNotification import net.blueshell.api.domain.event.application.EventSignUpService import net.blueshell.api.domain.event.application.email.createEventSignupEmail import net.blueshell.api.domain.user.application.UserService +import net.blueshell.api.platform.config.BankProperties import net.blueshell.api.platform.integration.email.adapter.EmailTransportClient import net.blueshell.api.platform.integration.email.application.service.EmailService import net.blueshell.api.shared.email.EmailContent @@ -26,8 +31,10 @@ class EmailSenderService( private val emailClient: EmailTransportClient, private val users: UserService, private val reminders: ContributionReminderService, + private val incassoNotifications: IncassoNotificationService, private val eventSignUps: EventSignUpService, private val emailService: EmailService, + private val bank: BankProperties, @param:Value($$"${frontend.url}") private val frontendUrl: String, @param:Value($$"${app.url}") private val appUrl: String, @param:Value($$"${email.from.name}") private val senderName: String, @@ -36,14 +43,40 @@ class EmailSenderService( ) { fun sendContributionReminderEmail(userId: Long, contributionPeriodId: Long, jobExecutionId: Long? = null) { val reminder = requireExists { reminders.findById(ContributionReminder.Id(userId, contributionPeriodId)) } - val emailContent = createContributionReminderEmail( - reminder.user, - reminder.contributionPeriod, - frontendUrl - ) + val emailContent = if (reminder.amount != null && reminder.paymentDueDate != null) { + // Bulk reminder: use specific amount and due date. The fee type is not + // persisted, so recover it from the resolved amount to state the reason. + createContributionReminderEmail( + reminder.user, + reminder.contributionPeriod, + reminder.amount!!, + reminder.paymentDueDate!!, + bank, + resolveFeeTypeFromAmount(reminder.amount!!, reminder.contributionPeriod) + ) + } else { + // Single-user reminder: use all options + createContributionReminderEmail( + reminder.user, + reminder.contributionPeriod, + bank + ) + } deliver(emailContent, "email.contribution-reminder", jobExecutionId) } + fun sendIncassoNotificationEmail(userId: Long, contributionPeriodId: Long, jobExecutionId: Long? = null) { + val notification = requireExists { incassoNotifications.findById(IncassoNotification.Id(userId, contributionPeriodId)) } + val emailContent = createIncassoNotificationEmail( + notification.user, + notification.contributionPeriod, + notification.amount!!, + notification.expectedIncassoDate!!, + resolveFeeTypeFromAmount(notification.amount!!, notification.contributionPeriod), + ) + deliver(emailContent, "email.incasso-notification", jobExecutionId) + } + fun sendEventSignupEmail(eventSignUpId: Long, guestAccessToken: String, jobExecutionId: Long? = null) { val eventSignUp = requireExists { eventSignUps.findById(eventSignUpId) } val emailContent = createEventSignupEmail(eventSignUp, frontendUrl, guestAccessToken) @@ -63,15 +96,25 @@ class EmailSenderService( deliver(emailContent, "email.recovery", jobExecutionId) } - /** Render template, inject tracking pixel, create the outbox record, then hand off to the transport. */ - private fun deliver(emailContent: EmailContent, emailType: String, jobExecutionId: Long? = null) { - val htmlContent = templateService.createEmail( + /** + * Render an [EmailContent] to its final HTML body exactly as the send path does + * (Markdown → HTML → Thymeleaf template), WITHOUT persisting an outbox record, + * injecting a tracking pixel, or transmitting anything. This is the reusable render + * step shared by [deliver] and the email-preview endpoints, so a preview is faithful + * to what would actually be sent. + */ + fun renderEmailHtml(emailContent: EmailContent): String = + templateService.createEmail( emailContent.recipientEmail, emailContent.recipientName, emailContent.subject, emailContent.markdownContent ) + /** Render template, inject tracking pixel, create the outbox record, then hand off to the transport. */ + private fun deliver(emailContent: EmailContent, emailType: String, jobExecutionId: Long? = null) { + val htmlContent = renderEmailHtml(emailContent) + val outbox = emailService.createPending(emailContent, emailType, jobExecutionId) val trackedHtml = outbox.trackingToken ?.let { token -> injectTrackingPixel(htmlContent, "$appUrl/track/email/open/$token") } diff --git a/services/api/src/main/kotlin/net/blueshell/api/platform/integration/email/application/service/EmailTemplateService.kt b/services/api/src/main/kotlin/net/blueshell/api/platform/integration/email/application/service/EmailTemplateService.kt index 1590c4c54..34a816465 100644 --- a/services/api/src/main/kotlin/net/blueshell/api/platform/integration/email/application/service/EmailTemplateService.kt +++ b/services/api/src/main/kotlin/net/blueshell/api/platform/integration/email/application/service/EmailTemplateService.kt @@ -52,6 +52,9 @@ class EmailTemplateService(templateEngine: TemplateEngine) { // Prepare template variables val variables: MutableMap = HashMap() variables["appUrl"] = appUrl + // Public site origin used to build absolute URLs to email image assets + // (logo, watermark) served from the frontend's public/ root. + variables["frontendUrl"] = appUrl variables["emailContent"] = htmlContent variables["sentTo"] = recipientEmail variables["fullName"] = recipientName diff --git a/services/api/src/main/kotlin/net/blueshell/api/shared/dto/bulk/BulkActionEnvelope.kt b/services/api/src/main/kotlin/net/blueshell/api/shared/dto/bulk/BulkActionEnvelope.kt new file mode 100644 index 000000000..f1455581e --- /dev/null +++ b/services/api/src/main/kotlin/net/blueshell/api/shared/dto/bulk/BulkActionEnvelope.kt @@ -0,0 +1,100 @@ +package net.blueshell.api.shared.dto.bulk + +import io.swagger.v3.oas.annotations.media.Schema + +/** + * Shared preview/execute envelope for member-manager bulk actions. + * + * Lives in the shared kernel so every domain's bulk endpoints (contributions, + * memberships, and — later — reminders/incasso) return the same shape and the + * frontend can drive one confirmation dialog. All business logic that produces + * these values stays in the per-domain command handlers; this is pure data. + */ + +/** + * Fee type used for contribution-reminder and incasso-notification bulk actions. + * The server resolves the € amount from the selected period's fee for the chosen type. + */ +@Schema(name = "BulkFeeType") +enum class BulkFeeType { + /** Full-year fee — for REGULAR members who started before the half-year cutoff. */ + FULL_YEAR_FEE, + + /** Half-year fee — for REGULAR members who started on or after the half-year cutoff. */ + HALF_YEAR_FEE, + + /** Alumni fee — for ALUMNI members. */ + ALUMNI_FEE, +} + +/** How a selected user will be treated by a bulk action. */ +enum class BulkRowDisposition { + /** Will be acted on / emailed. */ + INCLUDED, + + /** No-op for this action (e.g. already paid, no active membership). */ + SKIPPED, + + /** Hard-excluded by a business rule and NOT overridable (e.g. honorary). */ + EXCLUDED, + + /** Excluded by default but the operator may opt the user back in (e.g. already-paid / no incasso). */ + WARNING, +} + +/** Machine-readable reason code for a non-INCLUDED disposition. */ +@Schema(name = "BulkRowReason") +enum class BulkRowReason { + ALREADY_PAID, + NOT_PAID, + HONORARY, + INCASSO_MISMATCH, + NO_ACTIVE_MEMBERSHIP, + STARTED_TODAY, + + /** + * Email actions (reminder/incasso): the user has no email address on file, so + * nothing can be sent. Previously the execute handler skipped these silently and + * the preview never surfaced it — now it is a first-class SKIPPED reason visible + * in the preview. See docs/proposals/bulk-actions/REDESIGN.md §3. + */ + NO_EMAIL, + /** Resume/start-new: the user already has an active (endDate=null) membership. */ + ALREADY_ACTIVE, + /** Resume/start-new: no contribution period exists at all. */ + NO_CONTRIBUTION_PERIOD, + /** Preview outcome for INCLUDED rows: the most-recent membership will be resumed. */ + WILL_RESUME, + /** Preview outcome for INCLUDED rows: a new membership will be inserted starting today. */ + WILL_START_NEW, +} + +/** Type of bulk action being performed. */ +@Schema(name = "BulkActionType") +enum class BulkActionType { + MARK_PAID, + MARK_UNPAID, + CONTRIBUTION_REMINDER, + INCASSO_NOTIFICATION, + END_MEMBERSHIP, + RESUME_MEMBERSHIP, +} + +@Schema(name = "BulkActionCounts") +data class BulkActionCounts( + val selected: Int, + val willApply: Int, + val skipped: Int, + val excluded: Int, + val warned: Int, +) + +@Schema(name = "BulkActionResult") +data class BulkActionResult( + /** Rows changed (contributions created/deleted, memberships ended). */ + val applied: Int, + /** Rows deliberately not changed (no-ops / excluded). */ + val skipped: Int, + /** Emails queued (0 for non-email actions). */ + val queued: Int = 0, +) diff --git a/services/api/src/main/kotlin/net/blueshell/api/shared/job/JobDefinitions.kt b/services/api/src/main/kotlin/net/blueshell/api/shared/job/JobDefinitions.kt index 6e0f220aa..cdf9a3672 100644 --- a/services/api/src/main/kotlin/net/blueshell/api/shared/job/JobDefinitions.kt +++ b/services/api/src/main/kotlin/net/blueshell/api/shared/job/JobDefinitions.kt @@ -22,6 +22,12 @@ object EmailJobs { override fun dedupKey(payload: ContributionReminderPayload): String? = null } + object IncassoNotification : JobDefinition { + override val type: String = "email.incasso-notification" + override val payloadType: Class = IncassoNotificationPayload::class.java + override fun dedupKey(payload: IncassoNotificationPayload): String? = null + } + data class RecoveryPayload( val userId: Long, val token: String, @@ -37,6 +43,11 @@ object EmailJobs { val userId: Long, val contributionPeriodId: Long ) + + data class IncassoNotificationPayload( + val userId: Long, + val contributionPeriodId: Long + ) } object ContactJobs { diff --git a/services/api/src/main/resources/application.yaml b/services/api/src/main/resources/application.yaml index e3370ab58..adae17a6f 100644 --- a/services/api/src/main/resources/application.yaml +++ b/services/api/src/main/resources/application.yaml @@ -45,6 +45,13 @@ spring: host: ${VALKEY_HOST:localhost} port: ${VALKEY_PORT:6379} timeout: 500ms + # Valkey is used only for HTTP sessions and the cache layer, never as a + # Spring Data repository store. Disabling repository scanning stops the + # Redis module (present alongside JPA) from inspecting every JPA + # repository in strict mode and logging a "Could not safely identify + # store assignment" line for each one at startup. + repositories: + enabled: false jpa: hibernate: @@ -121,6 +128,14 @@ app: frontend: url: ${FRONTEND_URL:http://localhost:3000} +# Blueshell bank account details shown in contribution emails so members can +# transfer their membership fee. +blueshell: + bank: + iban: ${BLUESHELL_BANK_IBAN:NL19 INGB 0008 0964 62} + bic: ${BLUESHELL_BANK_BIC:INGBNL2A} + account-name: ${BLUESHELL_BANK_ACCOUNT_NAME:Blueshell E-Sports Vereniging} + # Valkey-backed HTTP sessions. The SESSION cookie domain defaults to the same # value as the auth cookie so the session travels to every subdomain in prod. session: diff --git a/services/api/src/main/resources/db/migration/V80__contribution_reminder_bulk_fields.sql b/services/api/src/main/resources/db/migration/V80__contribution_reminder_bulk_fields.sql new file mode 100644 index 000000000..992e6f8f5 --- /dev/null +++ b/services/api/src/main/resources/db/migration/V80__contribution_reminder_bulk_fields.sql @@ -0,0 +1,3 @@ +-- Add amount and payment_due_date fields to contribution_reminders for bulk reminder functionality +ALTER TABLE contribution_reminders ADD COLUMN amount DOUBLE PRECISION NULL; +ALTER TABLE contribution_reminders ADD COLUMN payment_due_date DATE NULL; diff --git a/services/api/src/main/resources/db/migration/V81__incasso_notification_audit_table.sql b/services/api/src/main/resources/db/migration/V81__incasso_notification_audit_table.sql new file mode 100644 index 000000000..45d954f64 --- /dev/null +++ b/services/api/src/main/resources/db/migration/V81__incasso_notification_audit_table.sql @@ -0,0 +1,43 @@ +-- Create incasso_notifications audit table for bulk incasso notification. +-- Mirrors contribution_reminders: composite (user_id, contribution_period_id) +-- identity, soft-delete sentinel, optimistic-lock version, and audit FKs. +CREATE TABLE incasso_notifications +( + user_id BIGINT NOT NULL, + contribution_period_id BIGINT NOT NULL, + amount DOUBLE NULL, + expected_incasso_date DATE NULL, + deleted_at datetime DEFAULT '9999-12-31 23:59:59' NOT NULL, + created_at datetime DEFAULT CURRENT_TIMESTAMP NOT NULL, + updated_at datetime DEFAULT CURRENT_TIMESTAMP NOT NULL, + version BIGINT DEFAULT 0 NOT NULL, + created_by_id BIGINT NULL, + updated_by_id BIGINT NULL, + CONSTRAINT PRIMARY KEY (user_id, contribution_period_id) +); + +ALTER TABLE incasso_notifications + ADD CONSTRAINT uk_incasso_notifications_user_period_deleted_at + UNIQUE (user_id, contribution_period_id, deleted_at); + +ALTER TABLE incasso_notifications + ADD CONSTRAINT fk_incasso_notifications_user_id + FOREIGN KEY (user_id) REFERENCES users (id); + +ALTER TABLE incasso_notifications + ADD CONSTRAINT fk_incasso_notifications_contribution_period_id + FOREIGN KEY (contribution_period_id) REFERENCES contribution_periods (id); + +ALTER TABLE incasso_notifications + ADD CONSTRAINT fk_incasso_notifications_created_by_id + FOREIGN KEY (created_by_id) REFERENCES users (id); + +ALTER TABLE incasso_notifications + ADD CONSTRAINT fk_incasso_notifications_updated_by_id + FOREIGN KEY (updated_by_id) REFERENCES users (id); + +CREATE INDEX idx_incasso_notifications_deleted_at ON incasso_notifications (deleted_at); +CREATE INDEX idx_incasso_notifications_created_at ON incasso_notifications (created_at); +CREATE INDEX idx_incasso_notifications_user_id ON incasso_notifications (user_id, deleted_at); +CREATE INDEX idx_incasso_notifications_contribution_period_id + ON incasso_notifications (contribution_period_id, deleted_at); diff --git a/services/api/src/main/resources/templates/emails/email-template.html b/services/api/src/main/resources/templates/emails/email-template.html index 4635b7875..5edb19d72 100644 --- a/services/api/src/main/resources/templates/emails/email-template.html +++ b/services/api/src/main/resources/templates/emails/email-template.html @@ -1,15 +1,32 @@ - + - - + + + Blueshell Esports + + @@ -203,240 +222,207 @@ h1, h2, h3, h4, h5, h6 { font-family: Arial, Helvetica, sans-serif !important; } - h1, h2 { text-transform: uppercase !important; } - + - + +
+ Blueshell Esports +
-
- - - - - + +
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + +
 
- Blueshell Esports -
 
-

- MAIN TITLE -

-
-
 
-
 
- -

- Follow us + + + - -
+ + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - -
- - - - - + +

+ + + + +
 
+
+

+ Follow us +

+ + + + + + + + + + +
+ + Discord + + + + Facebook + + + + Instagram + + + + Twitter / X + + + + Twitch + + + + + + + +
+ Website +
+
+
+
+

+ This email was sent to + [recipient-email] +
+ Need help? Visit + esa-blueshell.nl +

+
+ + + + +
- \ No newline at end of file + diff --git a/services/api/src/test/kotlin/net/blueshell/api/domain/contribution/application/command/BulkContributionCommandHandlersTest.kt b/services/api/src/test/kotlin/net/blueshell/api/domain/contribution/application/command/BulkContributionCommandHandlersTest.kt new file mode 100644 index 000000000..6c67c543f --- /dev/null +++ b/services/api/src/test/kotlin/net/blueshell/api/domain/contribution/application/command/BulkContributionCommandHandlersTest.kt @@ -0,0 +1,161 @@ +package net.blueshell.api.domain.contribution.application.command + +import net.blueshell.api.domain.contribution.application.ContributionPeriodService +import net.blueshell.api.domain.contribution.application.ContributionService +import net.blueshell.api.domain.contribution.command.BulkContributionOperation +import net.blueshell.api.domain.contribution.command.ExecuteBulkContributionCommand +import net.blueshell.api.domain.contribution.persistence.Contribution +import net.blueshell.api.domain.contribution.persistence.ContributionPeriod +import net.blueshell.api.domain.user.application.MembershipService +import net.blueshell.api.domain.user.application.UserService +import net.blueshell.api.domain.user.persistence.Membership +import net.blueshell.api.domain.user.persistence.User +import net.blueshell.api.shared.enums.MemberType +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.mockito.kotlin.any +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever +import java.time.Instant +import java.time.LocalDate + +class BulkContributionCommandHandlersTest { + + private val contributionService = mock() + private val userService = mock() + private val membershipService = mock() + private val periodService = mock() + + private val handler = ExecuteBulkContributionHandler( + contributionService, + userService, + membershipService, + periodService, + ) + + @Test + fun `mark-paid creates a contribution for a regular unpaid member`() { + val userId = 1L + val periodId = 100L + val user = mockUser(userId, "Alice") + val period = mockPeriod(periodId) + val membership = mockMembership(MemberType.REGULAR) + + whenever(periodService.findById(periodId)).thenReturn(period) + whenever(userService.existsById(userId)).thenReturn(true) + whenever(userService.findById(userId)).thenReturn(user) + whenever(membershipService.findByUserId(userId)).thenReturn(mutableListOf(membership)) + whenever(contributionService.existsByUserIdAndPeriodId(userId, periodId)).thenReturn(false) + + val result = handler.handle( + ExecuteBulkContributionCommand( + userIds = listOf(userId), + contributionPeriodId = periodId, + operation = BulkContributionOperation.PAID, + ) + ) + + assertThat(result.applied).isEqualTo(1) + assertThat(result.skipped).isEqualTo(0) + verify(contributionService).create(any()) + } + + @Test + fun `mark-paid skips a honorary member and creates no contribution`() { + val userId = 2L + val periodId = 100L + val user = mockUser(userId, "Bob") + val period = mockPeriod(periodId) + val honorary = mockMembership(MemberType.HONORARY) + + whenever(periodService.findById(periodId)).thenReturn(period) + whenever(userService.existsById(userId)).thenReturn(true) + whenever(userService.findById(userId)).thenReturn(user) + whenever(membershipService.findByUserId(userId)).thenReturn(mutableListOf(honorary)) + + val result = handler.handle( + ExecuteBulkContributionCommand( + userIds = listOf(userId), + contributionPeriodId = periodId, + operation = BulkContributionOperation.PAID, + ) + ) + + assertThat(result.applied).isEqualTo(0) + assertThat(result.skipped).isEqualTo(1) + verify(contributionService, never()).create(any()) + } + + @Test + fun `mark-paid skips an unknown user id without aborting the batch`() { + val validId = 1L + val unknownId = 999999L + val periodId = 100L + val user = mockUser(validId, "Alice") + val period = mockPeriod(periodId) + val membership = mockMembership(MemberType.REGULAR) + + whenever(periodService.findById(periodId)).thenReturn(period) + whenever(userService.existsById(validId)).thenReturn(true) + whenever(userService.existsById(unknownId)).thenReturn(false) + whenever(userService.findById(validId)).thenReturn(user) + whenever(membershipService.findByUserId(validId)).thenReturn(mutableListOf(membership)) + whenever(contributionService.existsByUserIdAndPeriodId(validId, periodId)).thenReturn(false) + + val result = handler.handle( + ExecuteBulkContributionCommand( + userIds = listOf(validId, unknownId), + contributionPeriodId = periodId, + operation = BulkContributionOperation.PAID, + ) + ) + + assertThat(result.applied).isEqualTo(1) + assertThat(result.skipped).isEqualTo(1) + } + + private fun mockUser(id: Long, name: String): User = User( + username = "user$id", + email = "user$id@example.com", + password = "hash", + initials = name.take(1).uppercase(), + firstName = name, + lastName = "", + ).apply { setField(this, "id", id) } + + private fun mockPeriod(id: Long): ContributionPeriod = ContributionPeriod( + startDate = LocalDate.of(2024, 1, 1), + endDate = LocalDate.of(2024, 12, 31), + halfYearFee = 50.0, + fullYearFee = 100.0, + alumniFee = 25.0, + ).apply { setField(this, "id", id) } + + private fun mockMembership(memberType: MemberType): Membership = Membership( + user = mock(), + startDate = LocalDate.of(2023, 1, 1), + endDate = null, + memberType = memberType, + incasso = false, + ).apply { + setField(this, "createdAt", Instant.parse("2024-01-01T00:00:00Z")) + setField(this, "updatedAt", Instant.parse("2024-01-01T00:00:00Z")) + } + + private fun setField(target: Any, name: String, value: Any?) { + var current: Class<*>? = target::class.java + while (current != null) { + try { + val field = current.getDeclaredField(name) + field.isAccessible = true + field.set(target, value) + return + } catch (_: NoSuchFieldException) { + current = current.superclass + } + } + error("Field $name not found on ${target::class.java.name}") + } +} diff --git a/services/api/src/test/kotlin/net/blueshell/api/domain/contribution/application/command/BulkIncassoNotificationHandlersTest.kt b/services/api/src/test/kotlin/net/blueshell/api/domain/contribution/application/command/BulkIncassoNotificationHandlersTest.kt new file mode 100644 index 000000000..53f29cfdf --- /dev/null +++ b/services/api/src/test/kotlin/net/blueshell/api/domain/contribution/application/command/BulkIncassoNotificationHandlersTest.kt @@ -0,0 +1,325 @@ +package net.blueshell.api.domain.contribution.application.command + +import net.blueshell.api.domain.contribution.application.ContributionPeriodService +import net.blueshell.api.domain.contribution.application.ContributionService +import net.blueshell.api.domain.contribution.application.IncassoNotificationService +import net.blueshell.api.domain.contribution.command.ExecuteBulkIncassoNotificationCommand +import net.blueshell.api.domain.contribution.persistence.ContributionPeriod +import net.blueshell.api.domain.contribution.persistence.IncassoNotification +import net.blueshell.api.domain.user.application.MembershipService +import net.blueshell.api.domain.user.application.UserService +import net.blueshell.api.domain.user.persistence.Membership +import net.blueshell.api.domain.user.persistence.User +import net.blueshell.api.shared.dto.bulk.BulkFeeType +import net.blueshell.api.shared.dto.bulk.BulkRowDisposition +import net.blueshell.api.shared.dto.bulk.BulkRowReason +import net.blueshell.api.shared.enums.MemberType +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever +import java.time.Instant +import java.time.LocalDate + +class BulkIncassoNotificationHandlersTest { + + private val userService = mock() + private val membershipService = mock() + private val periodService = mock() + private val contributionService = mock() + private val notificationService = mock() + + @Nested + inner class ExecuteBulkIncassoNotification { + + private val handler = ExecuteBulkIncassoNotificationHandler( + userService, + membershipService, + periodService, + contributionService, + notificationService, + ) + + @Test + fun `execute creates notification for included user`() { + val userId = 1L + val periodId = 100L + val cutoffDate = LocalDate.of(2024, 1, 1) + val expectedIncassoDate = LocalDate.of(2024, 2, 1) + + val user = mockUser(userId, "Alice", email = "alice@example.com") + val period = mockPeriod(periodId, 50.0, 100.0, 25.0) + val membership = mockMembership(incasso = true) + + whenever(userService.existsById(userId)).thenReturn(true) + whenever(userService.findById(userId)).thenReturn(user) + whenever(membershipService.findByUserId(userId)).thenReturn(mutableListOf(membership)) + whenever(periodService.findById(periodId)).thenReturn(period) + whenever(contributionService.existsByUserIdAndPeriodId(userId, periodId)).thenReturn(false) + + val capturedNotification = argumentCaptor() + val savedNotification = mockNotification(userId, periodId, 100.0, expectedIncassoDate) + whenever(notificationService.create(capturedNotification.capture())).thenReturn(savedNotification) + + val result = handler.handle( + ExecuteBulkIncassoNotificationCommand( + userIds = listOf(userId), + contributionPeriodId = periodId, + cutoffDate = cutoffDate, + expectedIncassoDate = expectedIncassoDate, + ) + ) + + assertThat(result.applied).isEqualTo(1) + assertThat(result.skipped).isEqualTo(0) + assertThat(result.queued).isEqualTo(1) + + assertThat(capturedNotification.firstValue.user).isSameAs(user) + assertThat(capturedNotification.firstValue.contributionPeriod).isSameAs(period) + assertThat(capturedNotification.firstValue.amount).isEqualTo(100.0) + assertThat(capturedNotification.firstValue.expectedIncassoDate).isEqualTo(expectedIncassoDate) + + verify(notificationService).sendNotification(savedNotification) + } + + @Test + fun `execute skips honorary members`() { + val userId = 2L + val periodId = 100L + val cutoffDate = LocalDate.of(2024, 1, 1) + val expectedIncassoDate = LocalDate.of(2024, 2, 1) + + val user = mockUser(userId, "Bob") + val period = mockPeriod(periodId, 50.0, 100.0, 25.0) + val membership = mockMembership(memberType = MemberType.HONORARY) + + whenever(userService.existsById(userId)).thenReturn(true) + whenever(userService.findById(userId)).thenReturn(user) + whenever(membershipService.findByUserId(userId)).thenReturn(mutableListOf(membership)) + whenever(periodService.findById(periodId)).thenReturn(period) + whenever(contributionService.existsByUserIdAndPeriodId(userId, periodId)).thenReturn(false) + + val result = handler.handle( + ExecuteBulkIncassoNotificationCommand( + userIds = listOf(userId), + contributionPeriodId = periodId, + cutoffDate = cutoffDate, + expectedIncassoDate = expectedIncassoDate, + ) + ) + + assertThat(result.applied).isEqualTo(0) + assertThat(result.skipped).isEqualTo(1) + assertThat(result.queued).isEqualTo(0) + } + + @Test + fun `execute skips users without incasso by default, re-includes via includedUserIds`() { + val userId = 3L + val periodId = 100L + val cutoffDate = LocalDate.of(2024, 1, 1) + val expectedIncassoDate = LocalDate.of(2024, 2, 1) + + val user = mockUser(userId, "Charlie", email = "charlie@example.com") + val period = mockPeriod(periodId, 50.0, 100.0, 25.0) + val membership = mockMembership(incasso = false) + + whenever(userService.existsById(userId)).thenReturn(true) + whenever(userService.findById(userId)).thenReturn(user) + whenever(membershipService.findByUserId(userId)).thenReturn(mutableListOf(membership)) + whenever(periodService.findById(periodId)).thenReturn(period) + whenever(contributionService.existsByUserIdAndPeriodId(userId, periodId)).thenReturn(false) + + val capturedNotification = argumentCaptor() + val savedNotification = mockNotification(userId, periodId, 100.0, expectedIncassoDate) + whenever(notificationService.create(capturedNotification.capture())).thenReturn(savedNotification) + + val result = handler.handle( + ExecuteBulkIncassoNotificationCommand( + userIds = listOf(userId), + contributionPeriodId = periodId, + cutoffDate = cutoffDate, + expectedIncassoDate = expectedIncassoDate, + includedUserIds = setOf(userId), + ) + ) + + assertThat(result.applied).isEqualTo(1) + assertThat(result.skipped).isEqualTo(0) + assertThat(result.queued).isEqualTo(1) + verify(notificationService).sendNotification(savedNotification) + } + + @Test + fun `execute applies fee type override`() { + val userId = 4L + val periodId = 100L + val cutoffDate = LocalDate.of(2024, 1, 1) + val expectedIncassoDate = LocalDate.of(2024, 2, 1) + // Regular member with incasso=true, default would be FULL_YEAR_FEE (100.0) + // Override to HALF_YEAR_FEE (50.0) + + val user = mockUser(userId, "Diana", email = "diana@example.com") + val period = mockPeriod(periodId, 50.0, 100.0, 25.0) + val membership = mockMembership(incasso = true) + + whenever(userService.existsById(userId)).thenReturn(true) + whenever(userService.findById(userId)).thenReturn(user) + whenever(membershipService.findByUserId(userId)).thenReturn(mutableListOf(membership)) + whenever(periodService.findById(periodId)).thenReturn(period) + whenever(contributionService.existsByUserIdAndPeriodId(userId, periodId)).thenReturn(false) + + val capturedNotification = argumentCaptor() + val savedNotification = mockNotification(userId, periodId, 50.0, expectedIncassoDate) + whenever(notificationService.create(capturedNotification.capture())).thenReturn(savedNotification) + + val result = handler.handle( + ExecuteBulkIncassoNotificationCommand( + userIds = listOf(userId), + contributionPeriodId = periodId, + cutoffDate = cutoffDate, + expectedIncassoDate = expectedIncassoDate, + includedUserIds = setOf(userId), + feeTypeOverrides = mapOf(userId to BulkFeeType.HALF_YEAR_FEE), + ) + ) + + assertThat(result.applied).isEqualTo(1) + // Half-year fee is 50.0 (from mockPeriod) + assertThat(capturedNotification.firstValue.amount).isEqualTo(50.0) + } + + @Test + fun `execute rejects a cutoff date outside the contribution period`() { + val userId = 5L + val periodId = 100L + // Period is 2024-01-01..2024-12-31; this cutoff is a day too late. + val cutoffDate = LocalDate.of(2025, 1, 1) + val expectedIncassoDate = LocalDate.of(2024, 2, 1) + + val period = mockPeriod(periodId, 50.0, 100.0, 25.0) + whenever(periodService.findById(periodId)).thenReturn(period) + + val ex = org.junit.jupiter.api.assertThrows { + handler.handle( + ExecuteBulkIncassoNotificationCommand( + userIds = listOf(userId), + contributionPeriodId = periodId, + cutoffDate = cutoffDate, + expectedIncassoDate = expectedIncassoDate, + ) + ) + } + assertThat(ex.statusCode.value()).isEqualTo(400) + } + + @Test + fun `execute skips an unknown user id without aborting the batch`() { + val validId = 1L + val unknownId = 999999L + val periodId = 100L + val cutoffDate = LocalDate.of(2024, 1, 1) + val expectedIncassoDate = LocalDate.of(2024, 2, 1) + + val user = mockUser(validId, "Alice", email = "alice@example.com") + val period = mockPeriod(periodId, 50.0, 100.0, 25.0) + val membership = mockMembership(incasso = true) + + whenever(userService.existsById(validId)).thenReturn(true) + whenever(userService.existsById(unknownId)).thenReturn(false) + whenever(userService.findById(validId)).thenReturn(user) + whenever(membershipService.findByUserId(validId)).thenReturn(mutableListOf(membership)) + whenever(periodService.findById(periodId)).thenReturn(period) + whenever(contributionService.existsByUserIdAndPeriodId(validId, periodId)).thenReturn(false) + + val savedNotification = mockNotification(validId, periodId, 100.0, expectedIncassoDate) + whenever(notificationService.create(org.mockito.kotlin.any())).thenReturn(savedNotification) + + val result = handler.handle( + ExecuteBulkIncassoNotificationCommand( + userIds = listOf(validId, unknownId), + contributionPeriodId = periodId, + cutoffDate = cutoffDate, + expectedIncassoDate = expectedIncassoDate, + ) + ) + + assertThat(result.applied).isEqualTo(1) + assertThat(result.skipped).isEqualTo(1) + } + } + + private fun mockUser(id: Long, name: String, email: String = "user@example.com"): User = User( + username = "user$id", + email = email, + password = "hash", + initials = name.take(1).uppercase(), + firstName = name, + lastName = "", + ).apply { + setField(this, "id", id) + } + + private fun mockPeriod( + id: Long, + halfFee: Double, + fullFee: Double, + alumniFee: Double + ): ContributionPeriod = ContributionPeriod( + startDate = LocalDate.of(2024, 1, 1), + endDate = LocalDate.of(2024, 12, 31), + halfYearFee = halfFee, + fullYearFee = fullFee, + alumniFee = alumniFee, + ).apply { + setField(this, "id", id) + } + + private fun mockMembership( + memberType: MemberType = MemberType.REGULAR, + incasso: Boolean = true + ): Membership = Membership( + user = mock(), + startDate = LocalDate.of(2023, 1, 1), + endDate = null, + memberType = memberType, + incasso = incasso, + ).apply { + setField(this, "createdAt", Instant.parse("2024-01-01T00:00:00Z")) + setField(this, "updatedAt", Instant.parse("2024-01-01T00:00:00Z")) + } + + private fun mockNotification( + userId: Long, + periodId: Long, + amount: Double, + expectedIncassoDate: LocalDate + ): IncassoNotification = IncassoNotification( + id = IncassoNotification.Id(userId, periodId), + user = mock(), + contributionPeriod = mock(), + amount = amount, + expectedIncassoDate = expectedIncassoDate, + ).apply { + setField(this, "createdAt", Instant.parse("2024-01-01T00:00:00Z")) + setField(this, "updatedAt", Instant.parse("2024-01-01T00:00:00Z")) + } + + private fun setField(target: Any, name: String, value: Any?) { + var current: Class<*>? = target::class.java + while (current != null) { + try { + val field = current.getDeclaredField(name) + field.isAccessible = true + field.set(target, value) + return + } catch (_: NoSuchFieldException) { + current = current.superclass + } + } + error("Field $name not found on ${target::class.java.name}") + } +} diff --git a/services/api/src/test/kotlin/net/blueshell/api/domain/contribution/application/email/ContributionReminderEmailBuilderTest.kt b/services/api/src/test/kotlin/net/blueshell/api/domain/contribution/application/email/ContributionReminderEmailBuilderTest.kt index 2c3e2fcf8..d6ab4bdd6 100644 --- a/services/api/src/test/kotlin/net/blueshell/api/domain/contribution/application/email/ContributionReminderEmailBuilderTest.kt +++ b/services/api/src/test/kotlin/net/blueshell/api/domain/contribution/application/email/ContributionReminderEmailBuilderTest.kt @@ -2,6 +2,8 @@ package net.blueshell.api.domain.contribution.application.email import net.blueshell.api.domain.contribution.persistence.ContributionPeriod import net.blueshell.api.domain.user.persistence.User +import net.blueshell.api.platform.config.BankProperties +import net.blueshell.api.shared.dto.bulk.BulkFeeType import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import java.time.LocalDate @@ -9,81 +11,126 @@ import java.time.LocalDate /** * Tests for contribution reminder email builder. * - * Verifies EmailContent is created correctly with all payment options (ADR-019, ADR-022). + * Verifies EmailContent is created correctly and instructs members to pay by + * bank transfer to the Blueshell account (ADR-019, ADR-022). */ class ContributionReminderEmailBuilderTest { - private val frontendUrl = "https://test-frontend.com" + private val bank = BankProperties( + iban = "NL19 INGB 0008 0964 62", + bic = "INGBNL2A", + accountName = "Blueshell E-Sports Vereniging", + ) @Test - fun `createContributionReminderEmail builds correct EmailContent`() { - // Given: User and contribution period + fun `bulk reminder builds correct EmailContent with amount and due date`() { + // Given: User and contribution period spanning an academic year val user = createTestUser("john.doe", "john.doe@example.com", "John", "Doe") val period = createTestPeriod( - startDate = LocalDate.of(2024, 1, 1), - endDate = LocalDate.of(2024, 12, 31), + startDate = LocalDate.of(2025, 9, 1), + endDate = LocalDate.of(2026, 8, 31), halfYearFee = 25.0, fullYearFee = 45.0, alumniFee = 10.0 ) - // When: Building contribution reminder email - val emailContent = createContributionReminderEmail(user, period, frontendUrl) + // When: Building the bulk contribution reminder email + val emailContent = createContributionReminderEmail( + user, + period, + amount = 45.0, + paymentDueDate = LocalDate.of(2025, 10, 1), + bank = bank, + feeType = BulkFeeType.FULL_YEAR_FEE, + ) - // Then: EmailContent has correct fields + // Then: EmailContent has correct fields and academic-year subject assertThat(emailContent.recipientEmail).isEqualTo(user.email) assertThat(emailContent.recipientName).isEqualTo(user.fullName) - assertThat(emailContent.subject).isEqualTo("Contribution Payment Reminder - Blueshell Esports") - assertThat(emailContent.senderNameOverride).isEqualTo("Treasurer of Blueshell") + assertThat(emailContent.subject).isEqualTo("Please pay your Blueshell contribution (2025/2026)") + assertThat(emailContent.senderNameOverride).isEqualTo("Secretary & Treasurer of ESA Blueshell") assertThat(emailContent.replyToOverride).isEqualTo("board@blueshell.utwente.nl") - // And: Body contains all payment options + // And: Body instructs a bank transfer with the configured details and no website payment assertThat(emailContent.markdownContent) .contains("Dear John Doe") - .contains("2024-01-01") - .contains("2024-12-31") - .contains("Half year fee: €25.00") - .contains("Full year fee: €45.00") - .contains("Alumni fee: €10.00") - .contains(frontendUrl) + .contains("2025/2026") + .contains("01 October 2025") + .contains("Amount due: €45.00") + .contains("(the full-year fee)") + .contains("NL19 INGB 0008 0964 62") + .contains("INGBNL2A") + .contains("Blueshell E-Sports Vereniging") + .contains("Secretary & Treasurer of ESA Blueshell") + // The old copy asked members to pay "via our [website](...)" — that link must be + // gone. ("website" alone appears legitimately in the role-revocation sentence.) + .doesNotContain("via our") + .doesNotContain("[website]") + assertThat(emailContent.markdownContent).doesNotContain("—") // no em-dashes + assertNoLeadingWhitespace(emailContent.markdownContent) } @Test - fun `email includes friendly reminder language`() { + fun `single-user reminder lists fee options and asks for bank transfer`() { // Given: User and period val user = createTestUser("jane", "jane@example.com", "Jane", "Smith") - val period = createTestPeriod() + val period = createTestPeriod( + startDate = LocalDate.of(2025, 9, 1), + endDate = LocalDate.of(2026, 8, 31), + halfYearFee = 25.0, + fullYearFee = 45.0, + alumniFee = 10.0, + ) - // When: Building email - val emailContent = createContributionReminderEmail(user, period, frontendUrl) + // When: Building the single-user email + val emailContent = createContributionReminderEmail(user, period, bank) - // Then: Email has friendly tone + // Then: Email lists fee options and points to the bank account + assertThat(emailContent.subject).isEqualTo("Please pay your Blueshell contribution (2025/2026)") assertThat(emailContent.markdownContent) - .contains("friendly reminder") - .contains("at your earliest convenience") - .contains("If you have already made your payment, please disregard this message") + .contains("Half year fee: €25.00") + .contains("Full year fee: €45.00") + .contains("Alumni fee: €10.00") + .contains("NL19 INGB 0008 0964 62") + .contains("If you have already paid, please disregard this message") .contains("Kind regards") - .contains("Treasurer of Blueshell Esports") + .contains("Secretary & Treasurer of ESA Blueshell") + // No pay-via-website link ("website" alone may appear in the revocation copy). + .doesNotContain("via our") + .doesNotContain("[website]") + assertNoLeadingWhitespace(emailContent.markdownContent) } @Test - fun `email formats currency correctly`() { - // Given: Period with precise decimal amounts + fun `bulk reminder formats currency correctly`() { + // Given: Precise decimal amount val user = createTestUser("test", "test@example.com", "Test", "User") - val period = createTestPeriod( - halfYearFee = 12.50, - fullYearFee = 20.00, - alumniFee = 5.99 - ) + val period = createTestPeriod() // When: Building email - val emailContent = createContributionReminderEmail(user, period, frontendUrl) + val emailContent = createContributionReminderEmail( + user, + period, + amount = 12.50, + paymentDueDate = LocalDate.now(), + bank = bank, + feeType = BulkFeeType.HALF_YEAR_FEE, + ) // Then: Currency is formatted with 2 decimals - assertThat(emailContent.markdownContent) - .contains("€12.50") - .contains("€20.00") - .contains("€5.99") + assertThat(emailContent.markdownContent).contains("€12.50") + } + + /** + * Guards against the "email renders as a code block" regression: any line + * reaching the Markdown converter with leading whitespace is parsed as an + * indented code block, so every line of the body must start at column 0. + */ + private fun assertNoLeadingWhitespace(markdown: String) { + val offending = markdown.lines().filter { it.isNotEmpty() && it.first().isWhitespace() } + assertThat(offending) + .withFailMessage("Markdown lines must not start with whitespace, found: %s", offending) + .isEmpty() } private fun createTestUser(username: String, email: String, firstName: String, lastName: String): User { @@ -100,8 +147,8 @@ class ContributionReminderEmailBuilderTest { } private fun createTestPeriod( - startDate: LocalDate = LocalDate.now(), - endDate: LocalDate = LocalDate.now().plusMonths(6), + startDate: LocalDate = LocalDate.of(2025, 9, 1), + endDate: LocalDate = LocalDate.of(2026, 8, 31), halfYearFee: Double = 25.0, fullYearFee: Double = 45.0, alumniFee: Double = 10.0 diff --git a/services/api/src/test/kotlin/net/blueshell/api/domain/contribution/domain/service/FeeResolutionTest.kt b/services/api/src/test/kotlin/net/blueshell/api/domain/contribution/domain/service/FeeResolutionTest.kt new file mode 100644 index 000000000..515b9a8f2 --- /dev/null +++ b/services/api/src/test/kotlin/net/blueshell/api/domain/contribution/domain/service/FeeResolutionTest.kt @@ -0,0 +1,82 @@ +package net.blueshell.api.domain.contribution.domain.service + +import net.blueshell.api.domain.contribution.persistence.ContributionPeriod +import net.blueshell.api.shared.enums.MemberType +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import java.time.LocalDate + +class FeeResolutionTest { + + private val period = ContributionPeriod( + startDate = LocalDate.of(2024, 1, 1), + endDate = LocalDate.of(2024, 12, 31), + halfYearFee = 50.0, + fullYearFee = 100.0, + alumniFee = 30.0, + ) + + private val cutoffDate = LocalDate.of(2024, 7, 1) + + @Nested + inner class RegularMembers { + + @Test + fun `regular member starting before cutoff pays full year fee`() { + val startDate = LocalDate.of(2024, 1, 1) + val resolved = resolveMemberFee(MemberType.REGULAR, startDate, cutoffDate, period) + assertThat(resolved).isEqualTo(100.0) + } + + @Test + fun `regular member starting exactly on cutoff pays full year fee (boundary matches the frontend rule)`() { + val startDate = LocalDate.of(2024, 7, 1) + val resolved = resolveMemberFee(MemberType.REGULAR, startDate, cutoffDate, period) + assertThat(resolved).isEqualTo(100.0) + } + + @Test + fun `regular member starting after cutoff pays half year fee`() { + val startDate = LocalDate.of(2024, 8, 15) + val resolved = resolveMemberFee(MemberType.REGULAR, startDate, cutoffDate, period) + assertThat(resolved).isEqualTo(50.0) + } + + @Test + fun `regular member with null start date pays full year fee`() { + val resolved = resolveMemberFee(MemberType.REGULAR, null, cutoffDate, period) + assertThat(resolved).isEqualTo(100.0) + } + } + + @Nested + inner class AlumniMembers { + + @Test + fun `alumni member pays alumni fee regardless of start date`() { + val resolved1 = resolveMemberFee(MemberType.ALUMNI, LocalDate.of(2023, 1, 1), cutoffDate, period) + val resolved2 = resolveMemberFee(MemberType.ALUMNI, LocalDate.of(2024, 8, 1), cutoffDate, period) + val resolved3 = resolveMemberFee(MemberType.ALUMNI, null, cutoffDate, period) + + assertThat(resolved1).isEqualTo(30.0) + assertThat(resolved2).isEqualTo(30.0) + assertThat(resolved3).isEqualTo(30.0) + } + } + + @Nested + inner class HonoraryMembers { + + @Test + fun `honorary member is excluded regardless of start date`() { + val resolved1 = resolveMemberFee(MemberType.HONORARY, LocalDate.of(2023, 1, 1), cutoffDate, period) + val resolved2 = resolveMemberFee(MemberType.HONORARY, LocalDate.of(2024, 8, 1), cutoffDate, period) + val resolved3 = resolveMemberFee(MemberType.HONORARY, null, cutoffDate, period) + + assertThat(resolved1).isNull() + assertThat(resolved2).isNull() + assertThat(resolved3).isNull() + } + } +} diff --git a/services/api/src/test/kotlin/net/blueshell/api/domain/contribution/web/ContributionBulkControllerValidationTest.kt b/services/api/src/test/kotlin/net/blueshell/api/domain/contribution/web/ContributionBulkControllerValidationTest.kt new file mode 100644 index 000000000..690a47407 --- /dev/null +++ b/services/api/src/test/kotlin/net/blueshell/api/domain/contribution/web/ContributionBulkControllerValidationTest.kt @@ -0,0 +1,120 @@ +package net.blueshell.api.domain.contribution.web + +import net.blueshell.api.shared.enums.Role +import net.blueshell.api.testsupport.UserTestSupport +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.http.MediaType +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status +import java.time.LocalDate + +/** + * Server-side hardening: every frontend guard on the bulk-action endpoints is mirrored + * with jakarta validation on the request DTOs or a handler guard, so a direct API call + * cannot bypass the rules. See docs/proposals/bulk-actions/REDESIGN.md §2 & §3. + */ +@SpringBootTest +class ContributionBulkControllerValidationTest : UserTestSupport() { + + private fun futureDate(): String = LocalDate.now().plusDays(20).toString() + private fun pastDate(): String = LocalDate.now().minusDays(1).toString() + + @Nested + inner class ReminderExecuteValidation { + + @Test + fun `rejects a past payment due date with 400`() { + val board = createUserWithRole(Role.BOARD) + val period = createContributionPeriodFixture() + val user = createUserWithRole(Role.MEMBER) + val cutoff = LocalDate.now().toString() + + mvc.perform( + post("/contributionReminders/bulk/execute") + .with(bearer(board)) + .contentType(MediaType.APPLICATION_JSON) + .content( + """{"userIds":[${user.id}],"contributionPeriodId":${period.id},"cutoffDate":"$cutoff","paymentDueDate":"${pastDate()}"}""" + ) + ).andExpect(status().isBadRequest) + } + + @Test + fun `rejects a cutoff date outside the contribution period with 400`() { + val board = createUserWithRole(Role.BOARD) + val period = createContributionPeriodFixture() + val user = createUserWithRole(Role.MEMBER) + // Period ends at now+1mo; this cutoff is far beyond it. + val cutoff = LocalDate.now().plusMonths(6).toString() + + mvc.perform( + post("/contributionReminders/bulk/execute") + .with(bearer(board)) + .contentType(MediaType.APPLICATION_JSON) + .content( + """{"userIds":[${user.id}],"contributionPeriodId":${period.id},"cutoffDate":"$cutoff","paymentDueDate":"${futureDate()}"}""" + ) + ).andExpect(status().isBadRequest) + } + + @Test + fun `rejects more than 1000 user ids with 400`() { + val board = createUserWithRole(Role.BOARD) + val period = createContributionPeriodFixture() + val cutoff = LocalDate.now().toString() + val ids = (1..1001).joinToString(",") + + mvc.perform( + post("/contributionReminders/bulk/execute") + .with(bearer(board)) + .contentType(MediaType.APPLICATION_JSON) + .content( + """{"userIds":[$ids],"contributionPeriodId":${period.id},"cutoffDate":"$cutoff","paymentDueDate":"${futureDate()}"}""" + ) + ).andExpect(status().isBadRequest) + } + } + + @Nested + inner class IncassoExecuteValidation { + + @Test + fun `rejects a past expected incasso date with 400`() { + val board = createUserWithRole(Role.BOARD) + val period = createContributionPeriodFixture() + val user = createUserWithRole(Role.MEMBER) + val cutoff = LocalDate.now().toString() + + mvc.perform( + post("/incassoNotifications/bulk/execute") + .with(bearer(board)) + .contentType(MediaType.APPLICATION_JSON) + .content( + """{"userIds":[${user.id}],"contributionPeriodId":${period.id},"cutoffDate":"$cutoff","expectedIncassoDate":"${pastDate()}"}""" + ) + ).andExpect(status().isBadRequest) + } + } + + @Nested + inner class PreviewValidation { + + @Test + fun `preview rejects a past payment due date with 400`() { + val board = createUserWithRole(Role.BOARD) + val period = createContributionPeriodFixture() + val user = createUserWithRole(Role.MEMBER) + + mvc.perform( + post("/contributionReminders/preview") + .with(bearer(board)) + .contentType(MediaType.APPLICATION_JSON) + .content( + """{"userId":${user.id},"contributionPeriodId":${period.id},"feeType":"FULL_YEAR_FEE","paymentDueDate":"${pastDate()}"}""" + ) + ).andExpect(status().isBadRequest) + } + } +} diff --git a/services/api/src/test/kotlin/net/blueshell/api/domain/user/application/command/BulkResumeMembershipHandlersTest.kt b/services/api/src/test/kotlin/net/blueshell/api/domain/user/application/command/BulkResumeMembershipHandlersTest.kt new file mode 100644 index 000000000..601c2df8f --- /dev/null +++ b/services/api/src/test/kotlin/net/blueshell/api/domain/user/application/command/BulkResumeMembershipHandlersTest.kt @@ -0,0 +1,212 @@ +package net.blueshell.api.domain.user.application.command + +import net.blueshell.api.domain.contribution.application.ContributionPeriodService +import net.blueshell.api.domain.contribution.persistence.ContributionPeriod +import net.blueshell.api.domain.user.application.MembershipService +import net.blueshell.api.domain.user.application.UserService +import net.blueshell.api.domain.user.command.ExecuteBulkResumeMembershipCommand +import net.blueshell.api.domain.user.persistence.Membership +import net.blueshell.api.domain.user.persistence.User +import net.blueshell.api.shared.dto.bulk.BulkRowDisposition +import net.blueshell.api.shared.dto.bulk.BulkRowReason +import net.blueshell.api.shared.enums.MemberType +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever +import java.time.Instant +import java.time.LocalDate + +class BulkResumeMembershipHandlersTest { + + private val userService = mock() + private val membershipService = mock() + private val periodService = mock() + + // Helper: basis period Jan 1 – Dec 31 2024 + private val basisPeriodStart = LocalDate.of(2024, 1, 1) + private val basisPeriodEnd = LocalDate.of(2024, 12, 31) + + private fun basisPeriod() = mockPeriod(100L, basisPeriodStart, basisPeriodEnd) + + @Nested + inner class ExecuteBulkResumeMembership { + + private val handler = ExecuteBulkResumeMembershipHandler(membershipService, userService, periodService) + + @Test + fun `resumes membership by clearing endDate`() { + val userId = 1L + val user = mockUser(userId, "Alice") + val membership = mockMembership( + memberType = MemberType.REGULAR, + startDate = LocalDate.of(2023, 9, 1), + endDate = LocalDate.of(2024, 6, 30), // within basis period + ) + whenever(userService.existsById(userId)).thenReturn(true) + whenever(userService.findById(userId)).thenReturn(user) + whenever(membershipService.findByUserId(userId)).thenReturn(mutableListOf(membership)) + whenever(periodService.findLatest()).thenReturn(basisPeriod()) + + val result = handler.handle(ExecuteBulkResumeMembershipCommand(listOf(userId))) + + assertThat(result.applied).isEqualTo(1) + assertThat(result.skipped).isEqualTo(0) + val captor = argumentCaptor() + verify(membershipService).update(captor.capture()) + assertThat(captor.firstValue.endDate).isNull() + } + + @Test + fun `inserts new membership copying memberType and incasso from prior`() { + val userId = 2L + val user = mockUser(userId, "Bob") + val priorMembership = mockMembership( + memberType = MemberType.ALUMNI, + startDate = LocalDate.of(2022, 1, 1), + endDate = LocalDate.of(2022, 12, 31), + incasso = true, + ) + whenever(userService.existsById(userId)).thenReturn(true) + whenever(userService.findById(userId)).thenReturn(user) + whenever(membershipService.findByUserId(userId)).thenReturn(mutableListOf(priorMembership)) + whenever(periodService.findLatest()).thenReturn(basisPeriod()) + + val result = handler.handle(ExecuteBulkResumeMembershipCommand(listOf(userId))) + + assertThat(result.applied).isEqualTo(1) + val captor = argumentCaptor() + verify(membershipService).create(captor.capture()) + val created = captor.firstValue + assertThat(created.memberType).isEqualTo(MemberType.ALUMNI) + assertThat(created.incasso).isTrue() + assertThat(created.startDate).isEqualTo(LocalDate.now()) + assertThat(created.endDate).isNull() + } + + @Test + fun `inserts REGULAR non-incasso membership when no prior membership`() { + val userId = 3L + val user = mockUser(userId, "Charlie") + whenever(userService.existsById(userId)).thenReturn(true) + whenever(userService.findById(userId)).thenReturn(user) + whenever(membershipService.findByUserId(userId)).thenReturn(mutableListOf()) + whenever(periodService.findLatest()).thenReturn(basisPeriod()) + + val result = handler.handle(ExecuteBulkResumeMembershipCommand(listOf(userId))) + + assertThat(result.applied).isEqualTo(1) + val captor = argumentCaptor() + verify(membershipService).create(captor.capture()) + val created = captor.firstValue + assertThat(created.memberType).isEqualTo(MemberType.REGULAR) + assertThat(created.incasso).isFalse() + } + + @Test + fun `skips user with already-active membership`() { + val userId = 4L + val user = mockUser(userId, "Diana") + val activeMembership = mockMembership(endDate = null) + whenever(userService.existsById(userId)).thenReturn(true) + whenever(userService.findById(userId)).thenReturn(user) + whenever(membershipService.findByUserId(userId)).thenReturn(mutableListOf(activeMembership)) + whenever(periodService.findLatest()).thenReturn(basisPeriod()) + + val result = handler.handle(ExecuteBulkResumeMembershipCommand(listOf(userId))) + + assertThat(result.applied).isEqualTo(0) + assertThat(result.skipped).isEqualTo(1) + verify(membershipService, never()).update(org.mockito.kotlin.any()) + verify(membershipService, never()).create(org.mockito.kotlin.any()) + } + + @Test + fun `skips an unknown user id without aborting the batch`() { + val validId = 6L + val unknownId = 999999L + val user = mockUser(validId, "Eve") + // Valid user has no prior membership -> StartNew branch (which calls findById). + whenever(userService.existsById(validId)).thenReturn(true) + whenever(userService.existsById(unknownId)).thenReturn(false) + whenever(userService.findById(validId)).thenReturn(user) + whenever(membershipService.findByUserId(validId)).thenReturn(mutableListOf()) + whenever(periodService.findLatest()).thenReturn(basisPeriod()) + + val result = handler.handle(ExecuteBulkResumeMembershipCommand(listOf(validId, unknownId))) + + assertThat(result.applied).isEqualTo(1) + assertThat(result.skipped).isEqualTo(1) + verify(membershipService).create(org.mockito.kotlin.any()) + } + + @Test + fun `returns all skipped when no contribution period`() { + val userId = 5L + whenever(periodService.findLatest()).thenReturn(null) + + val result = handler.handle(ExecuteBulkResumeMembershipCommand(listOf(userId))) + + assertThat(result.applied).isEqualTo(0) + assertThat(result.skipped).isEqualTo(1) + verify(membershipService, never()).update(org.mockito.kotlin.any()) + verify(membershipService, never()).create(org.mockito.kotlin.any()) + } + } + + // ── Helpers ─────────────────────────────────────────────────────────────── + + private fun mockUser(id: Long, name: String): User = User( + username = "user$id", + email = "user$id@example.com", + password = "hash", + initials = name.take(1).uppercase(), + firstName = name, + lastName = "", + ).apply { + setField(this, "id", id) + } + + private fun mockPeriod(id: Long, startDate: LocalDate, endDate: LocalDate): ContributionPeriod = + ContributionPeriod( + startDate = startDate, + endDate = endDate, + ).apply { + setField(this, "id", id) + } + + private fun mockMembership( + memberType: MemberType = MemberType.REGULAR, + startDate: LocalDate = LocalDate.of(2023, 1, 1), + endDate: LocalDate? = null, + incasso: Boolean = false, + ): Membership = Membership( + user = mock(), + startDate = startDate, + endDate = endDate, + memberType = memberType, + incasso = incasso, + ).apply { + setField(this, "createdAt", Instant.parse("2024-01-01T00:00:00Z")) + setField(this, "updatedAt", Instant.parse("2024-01-01T00:00:00Z")) + } + + private fun setField(target: Any, name: String, value: Any?) { + var current: Class<*>? = target::class.java + while (current != null) { + try { + val field = current.getDeclaredField(name) + field.isAccessible = true + field.set(target, value) + return + } catch (_: NoSuchFieldException) { + current = current.superclass + } + } + error("Field $name not found on ${target::class.java.name}") + } +} diff --git a/services/api/src/test/kotlin/net/blueshell/api/platform/integration/email/job/EmailJobHandlersTest.kt b/services/api/src/test/kotlin/net/blueshell/api/platform/integration/email/job/EmailJobHandlersTest.kt index abf6d2394..be1803bcf 100644 --- a/services/api/src/test/kotlin/net/blueshell/api/platform/integration/email/job/EmailJobHandlersTest.kt +++ b/services/api/src/test/kotlin/net/blueshell/api/platform/integration/email/job/EmailJobHandlersTest.kt @@ -166,7 +166,7 @@ class EmailJobHandlersTest : ServiceTestSupport() { val emails = emailClient.sentEmails assertThat(emails).hasSize(1) assertThat(emails.first().toEmail).isEqualTo("contributor@example.com") - assertThat(emails.first().subject).contains("Contribution Payment Reminder") + assertThat(emails.first().subject).contains("Please pay your Blueshell contribution") } @Test diff --git a/services/api/src/test/kotlin/net/blueshell/api/platform/integration/email/service/EmailServiceIntegrationTest.kt b/services/api/src/test/kotlin/net/blueshell/api/platform/integration/email/service/EmailServiceIntegrationTest.kt index e34b76322..1314aa65b 100644 --- a/services/api/src/test/kotlin/net/blueshell/api/platform/integration/email/service/EmailServiceIntegrationTest.kt +++ b/services/api/src/test/kotlin/net/blueshell/api/platform/integration/email/service/EmailServiceIntegrationTest.kt @@ -114,12 +114,13 @@ class EmailServiceIntegrationTest : ServiceTestSupport() { val email = emails.first() assertThat(email.toEmail).isEqualTo("contributor@example.com") - assertThat(email.subject).contains("Contribution Payment Reminder") + assertThat(email.subject).contains("Please pay your Blueshell contribution") assertThat(email.htmlContent) .contains("25.0") .contains("45.0") .contains("10.0") .contains("Treasurer") + .contains("NL19 INGB 0008 0964 62") } } diff --git a/services/api/src/test/kotlin/net/blueshell/api/platform/integration/email/service/EmailServiceMissingEntityTest.kt b/services/api/src/test/kotlin/net/blueshell/api/platform/integration/email/service/EmailServiceMissingEntityTest.kt index be39155f9..32520029d 100644 --- a/services/api/src/test/kotlin/net/blueshell/api/platform/integration/email/service/EmailServiceMissingEntityTest.kt +++ b/services/api/src/test/kotlin/net/blueshell/api/platform/integration/email/service/EmailServiceMissingEntityTest.kt @@ -2,10 +2,14 @@ package net.blueshell.api.platform.integration.email.service import io.mockk.every import io.mockk.mockk +import org.mockito.kotlin.any import net.blueshell.api.domain.contribution.application.ContributionReminderService +import net.blueshell.api.domain.contribution.application.IncassoNotificationService import net.blueshell.api.domain.contribution.persistence.ContributionReminder +import net.blueshell.api.domain.contribution.persistence.IncassoNotification import net.blueshell.api.domain.event.application.EventSignUpService import net.blueshell.api.domain.user.application.UserService +import net.blueshell.api.platform.config.BankProperties import net.blueshell.api.platform.integration.email.adapter.EmailTransportClient import net.blueshell.api.platform.integration.email.application.service.EmailSenderService import net.blueshell.api.platform.integration.email.application.service.EmailService @@ -27,6 +31,7 @@ class EmailServiceMissingEntityTest { private val emailClient = mockk(relaxed = true) private val users = mockk() private val reminders = mockk() + private val incassoNotifications = mockk() private val eventSignUps = mockk() private val emailService = mockk(relaxed = true) @@ -35,8 +40,10 @@ class EmailServiceMissingEntityTest { emailClient = emailClient, users = users, reminders = reminders, + incassoNotifications = incassoNotifications, eventSignUps = eventSignUps, emailService = emailService, + bank = BankProperties(), frontendUrl = "http://localhost:3000", appUrl = "http://localhost:8080", senderName = "Blueshell", @@ -69,6 +76,15 @@ class EmailServiceMissingEntityTest { .isInstanceOf(NonRetryableJobException::class.java) } + @Test + fun `sendIncassoNotificationEmail throws NonRetryableJobException when notification not found`() { + every { incassoNotifications.findById(any()) } throws + ResponseStatusException(HttpStatus.NOT_FOUND, "Notification not found") + + assertThatThrownBy { emailSenderService.sendIncassoNotificationEmail(1L, 2L) } + .isInstanceOf(NonRetryableJobException::class.java) + } + @Test fun `non-404 ResponseStatusException is not wrapped and remains retryable`() { every { users.findById(99L) } throws ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Server error") diff --git a/services/frontend/.yarn/install-state.gz b/services/frontend/.yarn/install-state.gz deleted file mode 100644 index 8a3f7531e..000000000 Binary files a/services/frontend/.yarn/install-state.gz and /dev/null differ diff --git a/services/frontend/public/img/email/blueshell-logo.png b/services/frontend/public/img/email/blueshell-logo.png new file mode 100644 index 000000000..ab128f7fa Binary files /dev/null and b/services/frontend/public/img/email/blueshell-logo.png differ diff --git a/services/frontend/public/img/email/watermark.png b/services/frontend/public/img/email/watermark.png new file mode 100644 index 000000000..bec6dcfbd Binary files /dev/null and b/services/frontend/public/img/email/watermark.png differ diff --git a/services/frontend/src/App.vue b/services/frontend/src/App.vue index b7a46610f..71e112d8c 100644 --- a/services/frontend/src/App.vue +++ b/services/frontend/src/App.vue @@ -211,15 +211,9 @@ - Manage contributions - - - Manage members + Manage users +defineOptions({name: "BulkActionsMenu"}) + +interface Props { + disabled?: boolean + /** True when no contribution period is selected (period-relative actions are disabled). */ + noPeriod?: boolean +} + +withDefaults(defineProps(), { + disabled: false, + noPeriod: false, +}) + +const emit = defineEmits<{ + (e: "markPaid"): void + (e: "markUnpaid"): void + (e: "sendReminder"): void + (e: "sendIncasso"): void + (e: "endMembership"): void + (e: "resumeMembership"): void +}>() + + + diff --git a/services/frontend/src/components/common/modals/BaseModal.vue b/services/frontend/src/components/common/modals/BaseModal.vue index e46c0c26e..a807fa556 100644 --- a/services/frontend/src/components/common/modals/BaseModal.vue +++ b/services/frontend/src/components/common/modals/BaseModal.vue @@ -6,6 +6,23 @@ import type {SubmitState} from "@/composables/formUtils" defineOptions({name: "BaseModal"}) +defineSlots<{ + /** Default slot for the modal body. */ + default(props: Record): unknown + /** Render extra affordances (e.g. a help button) inline in the header, to the right of the title. */ + "title-append"(props: Record): unknown + /** Fixed (non-scrolling) region at the top of the body; only the default slot below it scrolls. */ + "body-header"(props: Record): unknown + /** Full override of the entire footer contents; replaces all action buttons. */ + actions(props: Record): unknown + /** Secondary action(s) between Cancel and the primary Save button. */ + "actions-prepend"(props: Record): unknown + /** Override just the primary save button; falls back to SubmitButton or v-btn. */ + save(props: Record): unknown + /** Optional action(s) after the primary Save button. */ + "actions-append"(props: Record): unknown +}>() + interface Props { modelValue: boolean title: string @@ -100,15 +117,34 @@ const useSaveAsSubmitButton = computed( :scrollable="scrollable" > - - {{ title }} + + {{ title }} + + - - + + + + - + + + + + + + + + + + + + + + + + + {{ col.header }} + + + + {{ includeLabel }} + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/services/frontend/src/components/common/modals/bulk/EmailPreviewPanel.vue b/services/frontend/src/components/common/modals/bulk/EmailPreviewPanel.vue new file mode 100644 index 000000000..fb8334b2d --- /dev/null +++ b/services/frontend/src/components/common/modals/bulk/EmailPreviewPanel.vue @@ -0,0 +1,182 @@ + + +