Approvals
Some applications publish a document only after someone approved it. The File Service keeps that lifecycle on the file itself, as status, and enforces who may move it. This guide is for backend developers and administrators of document applications; an application that does not use approvals leaves every file at draft and can ignore it.
approveTurn it on
Section titled “Turn it on”Set approvals_enabled: true in the application’s settings. The console then shows the status column and the review actions. Then decide who approves: map files.approve onto a role on the Permissions page for the whole application, or grant the approve action with an entry on the folders that need review.
The statuses and who may move them
Section titled “The statuses and who may move them”| From | To | Needs |
|---|---|---|
draft |
in_review, archived |
edit |
draft |
approved, rejected |
approve |
in_review |
approved, rejected, draft |
approve |
in_review |
archived |
edit |
approved |
draft (reopen) |
approve |
approved |
archived |
edit |
rejected |
draft, in_review, archived |
edit |
archived |
draft |
edit |
Any other move answers status_locked. Change the status with PATCH P/files/:file_id and { "status": "…" }, or set it on the upload ticket.
What a status locks
Section titled “What a status locks”- A file
in_revieworapprovedrefuses new versions and metadata changes from people withoutapprove(status_locked). - An
archivedfile refuses changes until it is moved back todraft; it can still be trashed. - A new version of a file that is not a draft reopens it as
draft, so an approved document is never silently replaced.
Decide from your backend
Section titled “Decide from your backend”import { FilesConflictError, FilesPreconditionFailedError } from '@central-code-station/ccs-files-client';import { files } from './files-client';
// With approvals on (settings.approvals_enabled), a document moves draft -> in_review -> approved or rejected.// acting_user_id names the person your backend acts for, on the audit row.export async function submitForReview(fileId: string, userId: string) { const file = await files.files.get(fileId); return files.files.update(fileId, { status: 'in_review', acting_user_id: userId }, { ifMatch: file.revision });}
export async function decide(fileId: string, reviewerId: string, approve: boolean) { // Your backend may do anything; check that the reviewer holds `approve` on this file before acting for them. const { actions } = await files.files.effectivePermissions(fileId, reviewerId); if (!actions.includes('approve')) throw new Error('not a reviewer of this file'); const file = await files.files.get(fileId); try { return await files.files.update(fileId, { status: approve ? 'approved' : 'rejected', acting_user_id: reviewerId }, { ifMatch: file.revision }); } catch (error) { if (error instanceof FilesPreconditionFailedError) throw new Error('the file changed meanwhile: reload and decide again'); if (error instanceof FilesConflictError) throw new Error(`refused: ${error.code}`); // status_locked, file_locked throw error; }}Your machine client may set any status. The sample checks the reviewer’s own rights first with GET P/files/:file_id/effective-permissions, and sends the revision as If-Match, so two reviewers deciding at once cannot overwrite each other (revision_mismatch).
Who is told
Section titled “Who is told”Every change publishes file.status.changed with from, to and by_user_id. The owner receives it; on in_review the users who may approve the file receive it too, with a push notification when they are offline, and the owner gets a push on approved and rejected. Every change is in the audit log as file.status.changed. Search and listings filter by status.