Skip to content

Navigation Menu

Sign in
Sign up

πŸ—ƒοΈ Make archiving tables/applications a personal option - #2476

Open
AndyScherzinger wants to merge 46 commits into
main from
feat/2125/personal-archiving-option
Open

πŸ—ƒοΈ Make archiving tables/applications a personal option #2476
AndyScherzinger wants to merge 46 commits into
main from
feat/2125/personal-archiving-option

Conversation

@AndyScherzinger

@AndyScherzinger AndyScherzinger commented Apr 12, 2026
edited
Loading

Copy link
Copy Markdown
Member

πŸ–ΌοΈ Screenshots

Archived categories Archive app Archive table Unarchive app Unarchive table
2026εΉ΄04月12ζ—₯ 15_33_25-Tables - Nextcloud β€” Mozilla Firefox 2026εΉ΄04月12ζ—₯ 15_34_51-Tables - Nextcloud β€” Mozilla Firefox 2026εΉ΄04月12ζ—₯ 15_34_35-Tables - Nextcloud β€” Mozilla Firefox 2026εΉ΄04月12ζ—₯ 15_34_05-Tables - Nextcloud β€” Mozilla Firefox 2026εΉ΄04月12ζ—₯ 15_33_50-Tables - Nextcloud β€” Mozilla Firefox

The original ticket #2125 defined tables and views, while this PR tacked tables and apps. Given a clear view on how archived views should get displayed this could be added as a follow-up PR to not make this PR grow in review-size and complexity.


Summary

  • This change replaces the existing single-user global archived flag on tables with a two-layer per-user model: an owner-level flag on the entity and a new tables_archive_user table that stores individual user overrides, so archive state is resolved per-user at read time. When an owner archives a table or application, it is automatically treated as archived for every user with access, but any of those users may override this for themselves; an owner unarchiving resets the state for everyone by clearing all per-user overrides.
  • The existing tables_tables.archived column is repurposed from a simple global toggle into the owner-level signal, and the database migration converts all currently-archived tables into owner-archived entries to preserve existing data without disruption.
  • Application (context) archiving is introduced as a completely new capability end-to-end: covering database schema, entity, service, API endpoints, routes, and frontend navigation - while table archiving is substantially reworked from a simple owner toggle into the full per-user propagation system.
  • A new ArchiveService centralizes all business logic for both resource types, four new API endpoints are split across ApiTablesController and ContextController following existing naming conventions, and the frontend gains archive/unarchive actions in both navigation item menus alongside a new collapsible "Archived applications" section mirroring the already-existing "Archived tables" section.

⚠️ Beware

The database migration only covers the owner and direct user shares, not groups, etc. since they can't be resolved directly via SQL (think teams/groups). So in these cases archived tables would resurface until manually archived by the respective user. Also there is no cleanup for teams/groups, if a user is taken out of a team, the archive meta-info would not get deleted. Only simple but confusing way around it would be to prevent archiving/unarchiving for tables/apps where a user is neither the owner nor a user-share receiver. Expensive alternative would be a housekeeping job checking all these archive bits periodically, but that also sounds expensive with little use. Else we would need to consume a team/group changes event and check if some cleanup is needed. WDYT?

🏁 Checklist

  • ⛑️ Tests (unit and/or integration) are included or not needed
  • πŸ”™ Backport requests are created or not needed: /backport to stableX.X
  • πŸ“… Milestone is set
  • 🌸 PR title is meaningful (if it should be in the changelog: is it meaningful to users?)

πŸ€– AI (if applicable)

  • The content of this PR was partly or fully generated using AI

@AndyScherzinger AndyScherzinger added enhancement New feature or request 2. developing Work in progress labels Apr 12, 2026
@AndyScherzinger AndyScherzinger changed the title (ε‰Šι™€) Personal archiving option (ε‰Šι™€γ“γ“γΎγ§) (追記) πŸ—ƒοΈ Make archiving tables/applications personal option (θΏ½θ¨˜γ“γ“γΎγ§) Apr 12, 2026
AndyScherzinger force-pushed the feat/2125/personal-archiving-option branch 5 times, most recently from 008f4f3 to 0e2bbdc Compare April 12, 2026 15:34
@AndyScherzinger AndyScherzinger changed the title (ε‰Šι™€) πŸ—ƒοΈ Make archiving tables/applications personal option (ε‰Šι™€γ“γ“γΎγ§) (追記) πŸ—ƒοΈ Make archiving tables/applications a personal option (θΏ½θ¨˜γ“γ“γΎγ§) Apr 12, 2026
AndyScherzinger force-pushed the feat/2125/personal-archiving-option branch 2 times, most recently from d3fce3e to fd235a6 Compare April 12, 2026 18:56
AndyScherzinger marked this pull request as ready for review April 12, 2026 19:36
@AndyScherzinger AndyScherzinger added 3. to review Waiting for reviews and removed 2. developing Work in progress labels Apr 12, 2026

@jancborchardt jancborchardt left a comment
edited by AndyScherzinger
Loading

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks generally nice, 2 details

  • I’d say to use outline icons instead of filled for both cases, archiving and unarchiving
  • In the cases of "Archive app" and "Unarchive app", the sorting of actions in the menu is off. We always recommend the destructive action (Delete) to be the very last, and then the next most destructive like Archive could be directly above it.

Comment thread lib/Db/UserArchive.php Outdated
Comment thread lib/Db/UserArchiveMapper.php Outdated
AndyScherzinger force-pushed the feat/2125/personal-archiving-option branch 4 times, most recently from 840a1ab to 086aad3 Compare April 28, 2026 20:06
AndyScherzinger and others added 15 commits September 12, 2026 00:44
Setting the parameter inside the loop preventing rebuilding the query representation if this really loops a lot.
Co-authored-by: Christoph Wurst <1374172+ChristophWurst@users.noreply.github.com>
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
...ust getXXX
Co-authored-by: Christoph Wurst <1374172+ChristophWurst@users.noreply.github.com>
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
AI-assistant: Claude Code 2.1.119 (Claude Sonnet 4.6)
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
Signed-off-by: Enjeck C. <patrathewhiz@gmail.com>
The middleware only recognizes contexts when the type arrives as the
string 'context'; an integer NODE_TYPE_CONTEXT is rejected as an
invalid node type before the controller runs.
Assisted-by: ClaudeCode:claude-fable-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
TableService::find() returns the entity-level archived flag; use
getTableForUser() so GET /tables/{id} reflects the requesting user's
personal archive override.
Assisted-by: ClaudeCode:claude-fable-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
Resolve the per-user archive state for nav-bar contexts and skip
archived ones so archiving an application also declutters the global
navigation.
Assisted-by: ClaudeCode:claude-fable-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
The migration was created in April under version 2020 and would sort
before migrations already released on main (2200-2210); rename it to
2400 with a current date stamp.
Assisted-by: ClaudeCode:claude-fable-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
Replace hardcoded node type 0 with Application::NODE_TYPE_TABLE.
Assisted-by: ClaudeCode:claude-fable-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
Prepare the share lookup and the archive check/insert queries a single
time and rebind their parameters per iteration instead of recreating
query builders for every archived table and recipient.
Assisted-by: ClaudeCode:claude-fable-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
Replace the read-modify-write cycle with IDBConnection::setValues(),
which is portable across all supported databases and closes the race
between concurrent requests hitting the unique index.
Assisted-by: ClaudeCode:claude-fable-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
Assisted-by: ClaudeCode:claude-fable-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
The method had no production caller; tests now verify row counts with
a direct count query instead.
Assisted-by: ClaudeCode:claude-fable-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
Extract the shared load-overrides-and-merge logic of the table and
context enrichment methods into one private helper.
Assisted-by: ClaudeCode:claude-fable-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
AndyScherzinger force-pushed the feat/2125/personal-archiving-option branch from ebe72ba to 6e6fb96 Compare September 11, 2026 22:44
...r archive test
Assisted-by: ClaudeCode:claude-fable-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
...ler archive test
Assisted-by: ClaudeCode:claude-fable-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
Return early when the archived table or application is no longer in
the local store instead of dereferencing undefined.
Assisted-by: ClaudeCode:claude-fable-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
AndyScherzinger force-pushed the feat/2125/personal-archiving-option branch from ff45418 to 731dbf1 Compare September 11, 2026 22:53
Vue 3 proxy reactivity tracks array index assignment, so the splice
swap is unnecessary; drop the unrelated change.
Assisted-by: ClaudeCode:claude-fable-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
Align the archived-applications counter with the archived-tables one,
which already uses the count prop introduced with nextcloud/vue 9.
Assisted-by: ClaudeCode:claude-fable-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
Wrap the ArchiveService calls in the same OCP\DB\Exception-to-
InternalError conversion TableService uses so tables and contexts
report archive failures consistently.
Assisted-by: ClaudeCode:claude-fable-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
Assisted-by: ClaudeCode:claude-fable-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
...access
An ownership transfer preserved the outgoing owner's archive view with
a personal override even when they lose access to the node entirely,
leaving orphaned rows in tables_archive_user. Remove the override
inside the transfer transaction when the outgoing owner can no longer
read the table or access the context.
Assisted-by: ClaudeCode:claude-fable-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
tables_shares stores node_type as a string ('table'), so filtering with
the integer NODE_TYPE_TABLE constant never matched the direct user
shares (or, on MySQL loose casting, matched every node type). Recipient
archive records were therefore not migrated correctly.
Assisted-by: ClaudeCode:claude-fable-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
Add UserArchiveMapper::findUserIdsForNode() and deleteAllForUser(),
plus ShareMapper::findNodesByReceiver(), as building blocks for
cleaning up per-user archive overrides when access is lost.
Assisted-by: ClaudeCode:claude-fable-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
Follow the pattern established by server group shares and the circles
app: shares stay group/circle-targeted with dynamic access resolution,
and only the materialized per-user state is removed once membership or
sharing ends. The new ArchiveCleanupService drops a user's archive
override after verifying they no longer have access to the node, and is
triggered when
- a user is removed from a group (UserRemovedEvent),
- a member is removed from a circle (CircleMemberRemovedEvent,
 including inherited members of a removed nested circle),
- a group is deleted or a circle destroyed (the existing receiver
 cleanup now also purges stale overrides on the affected nodes),
- a share is deleted (single receiver check for user shares, full node
 purge for group and circle shares),
- a user account is deleted (all overrides of that user are removed).
Assisted-by: ClaudeCode:claude-fable-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
The v1 updateTable endpoint defaulted the archived parameter to false
instead of null, and the tables:update occ command declared the
--archived option as VALUE_NONE, which resolves to false when omitted.
Both callers therefore passed an explicit unarchive on every partial
update, silently resetting the archived state of a table on a plain
title or emoji change.
Default both to null so an omitted parameter leaves the state unchanged;
the occ option becomes negatable (--archived / --no-archived).
Assisted-by: ClaudeCode:claude-fable-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
AndyScherzinger force-pushed the feat/2125/personal-archiving-option branch from 731dbf1 to 86d5be7 Compare September 11, 2026 22:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@ChristophWurst ChristophWurst ChristophWurst left review comments
@samin-z samin-z samin-z left review comments
@enjeck enjeck enjeck requested changes
@jancborchardt jancborchardt jancborchardt approved these changes
@blizzz blizzz Awaiting requested review from blizzz blizzz is a code owner
@benjaminfrueh benjaminfrueh Awaiting requested review from benjaminfrueh
@marcoambrosini marcoambrosini Awaiting requested review from marcoambrosini

Requested changes must be addressed to merge this pull request.

Assignees

No one assigned

Labels

3. to review Waiting for reviews AI assisted enhancement New feature or request

Projects

Status: πŸ—οΈ At engineering

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

AltStyle γ«γ‚ˆγ£γ¦ε€‰ζ›γ•γ‚ŒγŸγƒšγƒΌγ‚Έ (->γ‚ͺγƒͺγ‚ΈγƒŠγƒ«) /