Attachments whose key ends in a trailing slash have no filename
component and cause yazl to throw, aborting the entire export. Skip
them with a warning and continue the export instead.
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Guard against null collaboratorIds when persisting collaborative
updates; the DB column has no default and can be NULL on older rows.
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* perf: Add missing indexes on foreign keys referencing documents
Cascade deletes on the documents table were forced into sequential scans
on file_operations, share_subscriptions, and access_requests because
their documentId columns lacked a usable single-column index.
Also fixes lint-staged to skip oxlint when every staged file matches an
.oxlintrc.json ignore pattern, since oxlint exits 1 in that case.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Handle oxlint no-files exit instead of mirroring ignorePatterns
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* chore: Update JSON importer to use zip streaming, new importer flow
* chore: Drop teamId from import urlId collision check and remove unused internal-id scaffolding
urlId is globally unique on Document/Collection so the team scope was wrong.
Also removes leftover internal-id generation in JSONAPIImportTask that was
never used in task input/output.
* Restore classes used upstream
* fix: documents.list with Draft status filter throws database error
The count() query referenced $memberships.id$ in WHERE but had no
membership include, causing "missing FROM-clause entry for table
memberships". The findAll path was also silently dropping drafts because
withMembershipScope defaulted to defaultScope (which filters publishedAt
!= null). Pre-fetch the user's UserMembership document IDs and filter by
id IN (...) on both find and count, and pass includeDrafts: true when
the Draft filter is active.
* Preserve template/trial filters when including drafts
* Move template/trial filters into withDrafts scope
* Revert withDrafts scope filters, apply at call site instead
Adding template/trial filters to withDrafts broke includes in places
like Share's withCollectionPermissions where the document include must
remain optional (LEFT JOIN) — adding a where promoted it to INNER JOIN
and dropped shares without a documentId.
* fix: Don't report upstream OAuth provider errors to Sentry
TokenError and AuthorizationError from passport-oauth2 represent
input problems from the upstream provider (expired or already-redeemed
codes, access_denied, etc) rather than server bugs. Log them at warn
level and redirect with the standard auth-error notice instead of
sending to the error reporter.
* Warn-only for OAuth provider errors, keep redirect flow shared
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* fix: Apply Postgres statement_timeout on request-handling processes
Sets `statement_timeout` to REQUEST_TIMEOUT on the Sequelize connection
pool when the process handles HTTP requests (web/api/collaboration/
websockets/admin) and does not also run worker/cron. Prevents a single
runaway query from saturating the shared Postgres instance and cascading
into timeouts across all endpoints.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Drop dead `api` service check
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Only apply statement_timeout in forked cluster workers
Skips the timeout in the master process so startup migrations driven
from `checkPendingMigrations` are not cancelled mid-execution.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Client disconnects mid-response surface as "Premature close" errors from
Node's stream end-of-stream helper. These are expected and add noise to
Sentry, similar to the EPIPE/ECONNRESET errors already filtered.
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* fix: Allow deleting failed and canceled imports
The delete policy only permitted imports in the Completed state, so the
overflow menu for Errored or Canceled imports rendered with no items.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* test: Cover Errored and Canceled in imports.delete
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* perf: Move Markdown importer to zip stream
* refactor
* refactor: Extract zip walk + tree builder into ZipHelper
Adds `ZipHelper.walk` and `ZipHelper.toFileTree` so other importers can
stream zip contents without extracting to disk. Tree construction uses
an O(1) path → node map; `./`-prefixed entries are normalized, while
dotfiles, `__MACOSX`, and `..` segments are filtered.
* PR feedback
* Weekly insights rollup
* fix: Avoid eager db instance creation in DocumentInsight model
Importing sequelize at the top level triggered createDatabaseInstance
during module load, which caused unrelated test suites that transitively
require the model to fail. Use the instance-bound this.sequelize in the
static method instead.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix: Skip soft-deleted documents in weekly insights rollup
The weekly task was deleting daily rows for soft-deleted documents
without creating a weekly replacement, since rollupPeriod filters them
out. Join to documents in both the week-discovery query and the DELETE
to keep behavior consistent — historical daily rows for deleted docs are
left for the cleanup task to remove at the retention boundary.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* refactor: Bind cutoff days param and add date predicate in weekly rollup
Moves CUTOFF_DAYS from string interpolation to a bound parameter and
adds a plain `date <` predicate so the planner can use the
(documentId, date, period) index before evaluating date_trunc.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* fix: Unable to link secondary auth provider on custom domain
* doc
* chore: Custom -> Apex transfer token
* Refactor, address security concerns
* Ensure OAuth intent is single-use
* Secure OAuth state actor binding
* Use scrypt for OAuth actor session binding
* fix: Disabling of authorization providers with env
* fix: type error in authenticationProviders delete test
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
The fullWidth field is already persisted on the Document model and
supported in the REST API schemas (DocumentsCreateSchema, DocumentsUpdateSchema),
but the built-in MCP server did not expose it.
This patch adds fullWidth as an optional boolean parameter to both
create_document and update_document MCP tools, passing it through to
documentCreator and documentUpdater respectively.
* feat: Inline comment support
* fix: wrap inline comment mark and creation in transaction with row lock
* fix: lock document row when anchoring, error on failed anchor, use uuid import
* fix: Batch document deletes when emptying trash
Splits the final parentDocumentId clear and destroy in documentPermanentDeleter
into chunks of 100 to keep the exclusive lock window on the documents table
short, preventing concurrent web SELECTs from queueing behind a single large
DELETE.
* fix: Skip parentDocumentId clear for documents restored mid-flight
Re-checks deletedAt in the database before clearing parentDocumentId on
children, so a parent restored between the caller's query and now keeps its
children attached.
Two comments built back-to-back could share a millisecond-precision
createdAt, leaving the DESC-ordered result non-deterministic. Pass
explicit createdAt values so the assertion on body.data[1] is stable.
* chore(deps-dev): bump oxlint-tsgolint from 0.14.2 to 0.22.1
Bumps [oxlint-tsgolint](https://github.com/oxc-project/tsgolint) from 0.14.2 to 0.22.1.
- [Release notes](https://github.com/oxc-project/tsgolint/releases)
- [Commits](https://github.com/oxc-project/tsgolint/compare/v0.14.2...v0.22.1)
---
updated-dependencies:
- dependency-name: oxlint-tsgolint
dependency-version: 0.22.1
dependency-type: direct:development
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot] <support@github.com>
* chore: Switch tsconfig to bundler resolution for tsgolint 0.22.1
oxlint-tsgolint 0.22.1 removed support for moduleResolution=node10
(the alias for "node"). Switch to "bundler" with resolvePackageJsonExports
disabled so packages whose exports field omits a types condition still
resolve. Update markdown-it type imports to sub-paths since the package's
.d.mts entry only re-exports a subset of named types.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix: Resolve type-aware lint errors caught by tsgolint 0.22.1
oxlint-tsgolint 0.22.1 catches several await-thenable, no-floating-promises,
and no-meaningless-void-operator cases the prior 0.14.2 missed:
- Drop redundant inner `await` from Promise.all([await x, await y]) call sites
so the array entries are real Promises rather than already-resolved values.
- Replace Promise.all wrappers around synchronous presenters (presentEvent,
presentTemplate, presentPublicTeam) with plain map / direct calls.
- Wrap non-promise branches of ternaries inside Promise.all with
Promise.resolve so the array remains thenable across both arms.
- Add `void` to the unawaited provider.connect() in the auth-failed retry
chain, and remove `void` from the disconnect() call which returns void.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Tom Moor <tom@getoutline.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* fix: Allow empty string in optional fields
* fix: Preserve empty strings for content fields in MCP tools
Address review feedback by reverting content/text fields (description, document
text, comment text) back to z.string().optional() so callers can intentionally
clear values via "". optionalString() is reserved for identifier and query
fields where "" is not a meaningful input.
* feat: Request document access
Allow users without permission to a document to request access. Notifies
document managers via in-app notification and email; managers can grant
or dismiss the request.
- Adds AccessRequest model, migration, policy, presenter
- Adds accessRequests.create/info/approve/dismiss endpoints
- Adds DocumentAccessRequestNotificationsTask + email
- Adds Error403 request flow with loading state and pending indicator
- Auto-opens notifications popover via ?notifications=true (used in email)
- Adds SplitButton primitive for permission selection in notifications
- Refactors useConsumeQueryParam hook
* refactor
* fix: Make approve/dismiss idempotent on access requests
Return success when the access request has already been dismissed, or
when the user already has document membership at approve time, instead
of throwing 400. Avoids racy double-clicks on notification actions
producing user-visible errors.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Minor fixes
---------
Co-authored-by: Tom Moor <tom@getoutline.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* Update popularity scoring to use document_insights as data source
* Use UTC dates and guard against future-dated insights
Derive threshold/today as UTC day boundaries to match how document_insights.date is written, and add an upper bound to prevent future-dated rollups from collapsing the decay denominator.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* Auto-subscribe mentioned users to documnet
* Add tests for mention auto-subscribe and a buildMention factory
* Add tests that prior unsubscribes are respected when mentioned
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Batch mention subscriptions into a single transaction
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* wip
* Remove obsolete snapshots
* simplify
* chore(test): Convert mocks to TypeScript and tighten fetch mock types
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Remove unneccessary patches
* Migrate to msw instead of custom fetch mock
* Address PR review comments
- Split chained vi.useFakeTimers().setSystemTime() into separate calls.
- Switch test setup to dynamic imports so EventEmitter.defaultMaxListeners
assignment runs before module init (static imports were hoisted above it).
- Drop redundant NODE_ENV guard in monkeyPatchSequelizeErrorsForJest; its
sole caller already gates on env.isTest.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* chore: Replace lodash with es-toolkit
Migrate all direct lodash imports to es-toolkit/compat for a smaller,
faster, lodash-compatible utility library. Transitive lodash usage from
other packages remains unchanged.
* fix: Restore isPlainObject semantics in CanCan policy
The lodash migration aliased `isObject` to `lodash/isPlainObject` and
the codemod incorrectly mapped the local name to es-toolkit's `isObject`,
which also returns true for arrays and functions. This caused condition
objects in policy definitions to be skipped, breaking authorization
checks across the codebase.
* fix: Restore unicode-aware length counting in validators
es-toolkit/compat's size() returns string.length, while lodash's _.size()
counts unicode code points. Switch to [...value].length to preserve the
previous behavior so multi-byte characters like emoji count as one.
* chore: Vendor request-filtering-agent
* fix: honor fetch timeout and undefined allow list in proxy pre-flight
Default allowIPAddressList to [] so an unset ALLOWED_PRIVATE_IP_ADDRESSES
env var doesn't overwrite the agent's default and crash on .length, and
race the pre-flight DNS lookup against the request's abort signal so the
configured fetch timeout applies to slow DNS resolution.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* Allow viewers to create and access API keys
Still guarded by their view permissions
* Drop Member role gate from apiKeys routes
Lets viewers reach the createApiKey/listApiKeys/delete policy checks now
that the policy itself permits them. Updates CleanupDemotedUserTask to
retain viewer keys and adds coverage for viewer create + guest reject.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Tighten apiKeys test assertions and broaden viewer coverage
- Use not.toBeNull() instead of toBeTruthy() for retention check
- Add viewer coverage for apiKeys.list and apiKeys.delete
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>