Merge branch 'feat/v1463-restore' into release/v1.46.3

This commit is contained in:
Marc-André Bombeck committed 2026-10-29 20:46:23 +01:00
commit 00d129ad42
28 files changed
+2504 -154

No files matched your search

+82 -20
View File
@@ -25090,10 +25090,52 @@ paths:
same pass; a file that fails any check leaves nothing behind. It is refused when it is not valid JSON, fails the
backup schema, was written by a newer schema version than this server reads, needs an encryption key this server
does not hold (`meta.errorCode` = `backup.key.missing`, `meta.keyIds` naming them), or belongs to an account
that does not exist here. Three uploads a minute per administrator. Admin session cookie required; Bearer tokens
cannot reach admin endpoints. A browser session needs a fresh proof as well as a live one: a sign-in or `POST
/api/auth/reproof` within the last five minutes (with a second factor, on an account that has one). Without it
the answer is 401 `auth.reproof.required`, with `meta.methods` naming the proofs the account can give."
that does not exist here (`meta.errorCode` = `backup.owner.missing`). A file of an account that no longer exists
here (erased, say) can be sent again with a target in the query: `into=existing&userId=<id>&confirm=<username>`
for an account that holds no records yet, or `into=new&username=<name>` for a new account created for the copy,
with no password and no email (an admin sets its first password with the existing password reset). The file's
owner is rewritten to that account as it is read, so the stored copy is that account's copy and restores like
any other; the erased account's id is not stored. A target is refused before the file is read when the account
does not exist, is a managed profile, is pending deletion, or holds records (a restore replaces records, it does
not merge them); a file whose own account is still here is refused with a target (`backup.owner.exists`). Three
uploads a minute per administrator. Admin session cookie required; Bearer tokens cannot reach admin endpoints. A
browser session needs a fresh proof as well as a live one: a sign-in or `POST /api/auth/reproof` within the last
five minutes (with a second factor, on an account that has one). Without it the answer is 401
`auth.reproof.required`, with `meta.methods` naming the proofs the account can give."
parameters:
- in: query
name: into
schema:
description: "Where the copy goes when the account the file names does not exist here. Absent: the file's own account,
which has to exist."
type: string
enum:
- existing
- new
description: "Where the copy goes when the account the file names does not exist here. Absent: the file's own account,
which has to exist."
- in: query
name: userId
schema:
description: "With `into=existing`: the account's id."
type: string
maxLength: 64
description: "With `into=existing`: the account's id."
- in: query
name: confirm
schema:
description: "With `into=existing`: the account's username, typed by the operator."
type: string
maxLength: 64
description: "With `into=existing`: the account's username, typed by the operator."
- in: query
name: username
schema:
description: "With `into=new`: the new account's username (letters, digits, `_` and `-`)."
type: string
minLength: 3
maxLength: 30
description: "With `into=new`: the new account's username (letters, digits, `_` and `-`)."
requestBody:
required: true
content:
@@ -25139,6 +25181,22 @@ paths:
schema:
$ref: "#/components/schemas/ErrorEnvelope"
"403": *a25
"404":
description: The account chosen with `into=existing` does not exist (`backup.target.not_found`). Nothing was stored.
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorEnvelope"
"409":
description: "The chosen target cannot take the copy, and nothing was stored: a managed profile
(`backup.target.managed_profile`), an account pending deletion (`backup.target.pending_deletion`), an
account that already holds records (`backup.target.holds_records`, `meta.models` naming them), a username
already taken (`backup.target.username_taken`), or a file whose own account is still here
(`backup.owner.exists`)."
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorEnvelope"
"413":
description: "Too large: over 512 MB as sent (10 MB in a form), a gzip file that expands past what any backup this
server wrote does, or larger than the stored-copy limit once encrypted (`meta.errorCode` =
@@ -25150,8 +25208,9 @@ paths:
"422":
description: "The file was refused and nothing was stored: no file in the request, not valid JSON or not readable as
gzip, failed the backup schema (`meta.issues`), an unsupported schema version, a key this server does not
hold (`meta.errorCode` = `backup.key.missing`, `meta.keyIds`), or an owner account that does not exist
here."
hold (`meta.errorCode` = `backup.key.missing`, `meta.keyIds`), an owner account that does not exist here
(`backup.owner.missing`), a target query that does not parse (`details.issues`), or an `into=existing` whose
`confirm` is not the account's username (`backup.target.unconfirmed`)."
content:
application/json:
schema:
@@ -38654,20 +38713,23 @@ components:
auth.refresh.reuse, auth.refresh.revoked, auth.reproof.failed, auth.reproof.required,
auth.reproof.too_weak, auth.scope.insufficient, auth.session.insecure_transport,
auth.session.unavailable, auth.stepup.required, auth.token.expired, auth.token.invalid; backup —
backup.foreign_reference, backup.key.missing, backup.payload.undecryptable, backup.restore.active,
backup.section.missing, backup.upload.too_large; biomarkers — biomarkers.unit.locked; body-site —
body-site.invalid; coach — coach.budget.exceeded, coach.conversation.invalidTitle,
coach.conversation.notFound, coach.fenced.attachmentConflict, coach.fenced.attachmentLimit,
coach.fenced.attachmentNotFound, coach.fenced.attachmentNotIndexed, coach.fenced.attachmentRequired,
coach.fenced.attachmentUnavailable, coach.fenced.invalid, coach.request.invalid; coach-prefs —
coach-prefs.body.invalid_json, coach-prefs.body.invalid_shape; consent — consent.ai.confirmRequired,
consent.ai.required; cycle — cycle.bulk.invalid, cycle.bulk.too_large, cycle.calendar.invalid,
cycle.calendar.range, cycle.cycles.invalid, cycle.day-log.conflict, cycle.day-log.invalid,
cycle.disabled, cycle.period.invalid, cycle.symptom.custom.invalid, cycle.symptom.custom.limit;
cycle-prefs — cycle-prefs.body.invalid_json, cycle-prefs.invalid; disable-coach —
disable-coach.body.invalid_json; data_wipe — data_wipe.copy_failed; dawarich —
dawarich.credentials_rejected, dawarich.home_place_unknown, dawarich.invalid, dawarich.not_connected,
dawarich.rate_limited, dawarich.unreachable; day — day.invalid; demo — demo.readOnly; documents —
backup.foreign_reference, backup.key.missing, backup.owner.exists, backup.owner.missing,
backup.payload.undecryptable, backup.restore.active, backup.section.missing,
backup.target.holds_records, backup.target.managed_profile, backup.target.not_found,
backup.target.pending_deletion, backup.target.unconfirmed, backup.target.username_taken,
backup.upload.too_large; biomarkers — biomarkers.unit.locked; body-site — body-site.invalid; coach —
coach.budget.exceeded, coach.conversation.invalidTitle, coach.conversation.notFound,
coach.fenced.attachmentConflict, coach.fenced.attachmentLimit, coach.fenced.attachmentNotFound,
coach.fenced.attachmentNotIndexed, coach.fenced.attachmentRequired, coach.fenced.attachmentUnavailable,
coach.fenced.invalid, coach.request.invalid; coach-prefs — coach-prefs.body.invalid_json,
coach-prefs.body.invalid_shape; consent — consent.ai.confirmRequired, consent.ai.required; cycle —
cycle.bulk.invalid, cycle.bulk.too_large, cycle.calendar.invalid, cycle.calendar.range,
cycle.cycles.invalid, cycle.day-log.conflict, cycle.day-log.invalid, cycle.disabled,
cycle.period.invalid, cycle.symptom.custom.invalid, cycle.symptom.custom.limit; cycle-prefs —
cycle-prefs.body.invalid_json, cycle-prefs.invalid; disable-coach — disable-coach.body.invalid_json;
data_wipe — data_wipe.copy_failed; dawarich — dawarich.credentials_rejected,
dawarich.home_place_unknown, dawarich.invalid, dawarich.not_connected, dawarich.rate_limited,
dawarich.unreachable; day — day.invalid; demo — demo.readOnly; documents —
documents.chat.conversationNotFound, documents.chat.invalid, documents.inbound.alreadyConfirmed,
documents.inbound.alreadyPartlyConfirmed, documents.inbound.budgetExceeded,
documents.inbound.decryptFailed, documents.inbound.encounterNotFound, documents.inbound.episodeNotFound,