"Temporary notes" with a death timer: created via a dedicated hourglass button
in the space-tree header, a note auto-moves to Trash after a configurable X
hours (default 24) unless explicitly made permanent ("structure or die").
Reuses existing mechanisms, mirroring is_template and the trash-cleanup job:
- New nullable column pages.temporary_expires_at (NULL = permanent; non-NULL =
frozen deadline) + partial index for the sweep; workspace column
temporary_note_hours (default via DEFAULT_TEMPORARY_NOTE_HOURS = 24).
- create-page DTO `temporary` flag; the deadline is frozen at creation so later
setting changes never reschedule existing notes.
- POST /pages/toggle-temporary (mirror of toggle-template): arm/clear the timer,
CASL-guarded via validateCanEdit, cross-workspace NotFound defense-in-depth.
- TemporaryNoteCleanupService: hourly @Interval sweep that soft-deletes expired
notes through the exact PageRepo.removePage path (recursive over children,
emits PAGE_SOFT_DELETED), attributed to the creator; idempotent via
deletedAt IS NULL filters.
- restorePage clears temporary_expires_at so a restored note can't be re-trashed.
- Workspace setting temporary_note_hours (audit-tracked) + a hours editor in
workspace General settings.
- Client: second create button, orange tree icon, tree + page-header menu toggle
("Make temporary"/"Make permanent"), an open-note banner with a rescue action,
and en/ru i18n.
Tests (unit): toggle-temporary controller (toggle/explicit/permission/cross-ws +
DTO validation), cleanup-job sweep (selection filters, per-note removePage,
error isolation), and a migration up/down sanity. Server tsc, client tsc -b,
and the page+workspace jest suites are green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
27 lines
1.1 KiB
TypeScript
27 lines
1.1 KiB
TypeScript
import SettingsTitle from "@/components/settings/settings-title.tsx";
|
|
import WorkspaceNameForm from "@/features/workspace/components/settings/components/workspace-name-form";
|
|
import WorkspaceIcon from "@/features/workspace/components/settings/components/workspace-icon.tsx";
|
|
import HtmlEmbedSettings from "@/features/workspace/components/settings/components/html-embed-settings.tsx";
|
|
import TrackerSettings from "@/features/workspace/components/settings/components/tracker-settings.tsx";
|
|
import TemporaryNoteSettings from "@/features/workspace/components/settings/components/temporary-note-settings.tsx";
|
|
import { useTranslation } from "react-i18next";
|
|
import { getAppName } from "@/lib/config.ts";
|
|
import { Helmet } from "react-helmet-async";
|
|
|
|
export default function WorkspaceSettings() {
|
|
const { t } = useTranslation();
|
|
return (
|
|
<>
|
|
<Helmet>
|
|
<title>Workspace Settings - {getAppName()}</title>
|
|
</Helmet>
|
|
<SettingsTitle title={t("General")} />
|
|
<WorkspaceIcon />
|
|
<WorkspaceNameForm />
|
|
<HtmlEmbedSettings />
|
|
<TrackerSettings />
|
|
<TemporaryNoteSettings />
|
|
</>
|
|
);
|
|
}
|