Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
980bcf7
Scaffold docs plugin from starter template
catalintomai Jun 23, 2026
78472a7
Remove starter-template boilerplate
catalintomai Jun 24, 2026
c784b7f
add Space and Page data model foundations
catalintomai Jun 25, 2026
284ec81
ci: provision Postgres for server tests
catalintomai Jun 25, 2026
0e14b11
address coderabbitai comments
catalintomai Jun 25, 2026
bcc5a48
add page soft-delete and restore with child promotion
catalintomai Jun 25, 2026
d87544e
ci: scope GitHub Actions job permissions to least privilege
catalintomai Jun 29, 2026
3ea32af
test: add schema-isolated Postgres test harness
catalintomai Jun 29, 2026
af7732b
feat: add page drafts model, store, and migration
catalintomai Jun 29, 2026
3a3b27a
refactor: push page update CAS into store; add space restore
catalintomai Jun 29, 2026
4169688
fix: shift siblings on full sort key when promoting children
catalintomai Jun 29, 2026
2e90de9
fix: reject self-referential page version snapshots
catalintomai Jun 29, 2026
432b649
test: close schema-scoped DB pool in cleanup
catalintomai Jun 29, 2026
48e9827
fix: keep 409 when conflict re-read races a delete
catalintomai Jun 29, 2026
ba46705
docs: clarify page space/patch validation comments
catalintomai Jun 29, 2026
5990dcb
Remove DraftPropsMaxBytes; reuse shared PagePropsMaxBytes for drafts
catalintomai Jun 29, 2026
6553db7
Clarify and tighten godoc, inline, and migration comments
catalintomai Jun 29, 2026
50a222d
Extract named flags in PagePatch.IsValid field-pairing check
catalintomai Jun 29, 2026
e7bb998
refactor: consolidate error keys into shared literals
catalintomai Jun 30, 2026
a3e3294
build: add i18n extraction tooling, translations, and bundle load
catalintomai Jun 30, 2026
0e72bc0
docs: clarify CreatePage space-check and depth comments
catalintomai Jun 30, 2026
64af3cc
update comments
catalintomai Jun 30, 2026
fc6859d
address coderabbitai comments
catalintomai Jun 30, 2026
50a6f3d
update comments
catalintomai Jun 30, 2026
cd41ac2
test: align DB test harness with storetest.MakeSqlSettings
catalintomai Jun 30, 2026
b9ac4c0
address coderabbitai comments
catalintomai Jun 30, 2026
7ad5bc0
address coderabbitai comments + comment updates
catalintomai Jun 30, 2026
0bfc9ea
address CC reviews + update comments
catalintomai Jul 2, 2026
5c73366
address CC reviews
catalintomai Jul 2, 2026
f9d1b9d
address lint failure
catalintomai Jul 2, 2026
03d18f0
address coderabbitai comments
catalintomai Jul 2, 2026
b76d7b4
address coderabbitai comments
catalintomai Jul 2, 2026
50c6914
MM-69268 - Page tree CRUD + URL API: spaces, pages, move, duplicate
catalintomai Jul 6, 2026
3b2d2e9
some cleanup + update comments
catalintomai Jul 6, 2026
4c7977d
address cx comment
catalintomai Jul 7, 2026
fe9622a
address reviewers comments
catalintomai Jul 7, 2026
d7c4a82
address coderabbitai comments
catalintomai Jul 7, 2026
d0f6eeb
Merge remote-tracking branch 'origin/MM-69267-foundations-data-model'…
catalintomai Jul 7, 2026
91f1291
updates following parent branch changes
catalintomai Jul 7, 2026
7992107
clean-ups
catalintomai Jul 7, 2026
d514f35
fix test
catalintomai Jul 7, 2026
1cdc221
Add feature flag gate, space restore route, and review fixes
catalintomai Jul 8, 2026
035ca8c
Add userID to DeletePage and RestorePage; simplify comments
catalintomai Jul 8, 2026
bdbb151
Fix space-scoping gaps and tighten comments
catalintomai Jul 8, 2026
ce5f3af
address coderabbitai comments + update comments
catalintomai Jul 8, 2026
c4eabb4
address cx comments
catalintomai Jul 8, 2026
38726ee
Sanitize error responses and handle max depth in duplicate
catalintomai Jul 8, 2026
ad5a6cd
address coderabbitai comment
catalintomai Jul 8, 2026
7577f5e
address coderabbitai comment
catalintomai Jul 8, 2026
90491a6
address comments
catalintomai Jul 8, 2026
75d766a
update Auditable interface
catalintomai Jul 8, 2026
7ee87f2
Merge branch 'MM-69267-foundations-data-model' into MM-69268-page-tre…
catalintomai Jul 9, 2026
29e6d33
use mmmodel.Auditable, drop local duplicate
catalintomai Jul 9, 2026
56755b4
add SpacePatch type and icon sanitization to Space
catalintomai Jul 9, 2026
2ab2fa5
Merge branch 'MM-69267-foundations-data-model' into MM-69268-page-tre…
catalintomai Jul 9, 2026
db0b1e8
Add space membership checks, WS events, and per-user space filtering
catalintomai Jul 9, 2026
1998e8e
fix lint
catalintomai Jul 9, 2026
ab30de9
address coderabbitai comments
catalintomai Jul 9, 2026
fad6154
Merge remote-tracking branch 'origin/master' into MM-69268-page-tree-…
catalintomai Jul 9, 2026
a00dff1
address coderabbitai comments
catalintomai Jul 10, 2026
755565b
address coderabbitai comments
catalintomai Jul 10, 2026
722eab3
address coderabbitai cmoments
catalintomai Jul 10, 2026
cc82004
Page move/duplicate store ops, WS events, pagination, review fixes
catalintomai Jul 13, 2026
44d6312
fix lint
catalintomai Jul 13, 2026
69942cd
fix test
catalintomai Jul 13, 2026
7526876
update comments, small fixes
catalintomai Jul 13, 2026
5f724e1
address coderabbitai comments
catalintomai Jul 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ jobs:
uses: mattermost/actions/plugin-ci/build@0256d363493a1e6b9e361ea788c62a0a73bc00be

delivery:
if: ${{ github.repository_owner == 'mattermost' && github.event_name != 'schedule' && (github.ref_name == 'master' || github.ref_name == 'main') }}
if: ${{ github.repository_owner == 'mattermost' && github.event_name != 'schedule' && github.ref_name == 'master' }}
runs-on: ubuntu-latest
needs: [lint, test, build]
permissions:
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ make test
make check-style
```

`make test` requires a reachable Postgres instance; point `TEST_DATABASE_POSTGRESQL_DSN` at it, or run against the standard local dev Postgres if unset. Tests fail (rather than skip) when the DSN is unreachable.

## Documentation

See the [Mattermost plugin development guide](https://developers.mattermost.com/integrate/plugins/) for plugin structure, server/webapp hooks, and the release process.
288 changes: 258 additions & 30 deletions assets/i18n/en.json
Original file line number Diff line number Diff line change
@@ -1,35 +1,47 @@
[
{
"id": "app.page.create.conflict.app_error",
"translation": "A page with this ID already exists."
"id": "api.docs_not_enabled.app_error",
"translation": "Docs is not enabled."
},
{
"id": "app.page.create.invalid_id.app_error",
"translation": "Invalid page ID."
"id": "api.invalid_json.app_error",
"translation": "The request body could not be parsed."
},
{
"id": "app.page.create.invalid_parent.app_error",
"translation": "The parent page does not exist."
"id": "api.page.duplicate.invalid_target_space_id.app_error",
"translation": "The target_space_id field is not a valid ID."
},
{
"id": "app.page.create.invalid_parent_id.app_error",
"translation": "Invalid parent page ID."
"id": "api.page.move_to_space.invalid_target_space_id.app_error",
"translation": "The target_space_id field is not a valid ID."
},
{
"id": "app.page.create.invalid_space_id.app_error",
"translation": "Invalid space ID."
"id": "api.page.move_to_space.missing_target_space_id.app_error",
"translation": "The target_space_id field is required."
},
{
"id": "app.page.create.invalid_user_id.app_error",
"translation": "Invalid user ID."
"id": "api.request_too_large.app_error",
"translation": "The request body exceeds the maximum allowed size of {{.MaxBytes}} bytes."
},
{
"id": "app.optimistic_lock.baseline_required.app_error",
"translation": "The {{.Field}} field is required unless force is set."
},
{
"id": "app.page.create.max_depth_exceeded.app_error",
"translation": "Pages cannot be nested more than {{.MaxDepth}} levels deep."
"id": "app.page.circular_reference.app_error",
"translation": "A page cannot be placed under itself or one of its descendants."
},
{
"id": "app.page.create.parent_different_space.app_error",
"translation": "The parent page belongs to a different space."
"id": "app.page.create.conflict.app_error",
"translation": "A page with this ID already exists."
},
{
"id": "app.page.create.invalid_space_id.app_error",
"translation": "Invalid space ID."
},
{
"id": "app.page.create.invalid_user_id.app_error",
"translation": "Invalid user ID."
},
{
"id": "app.page.create.search_text_without_content.app_error",
Expand All @@ -40,13 +52,45 @@
"translation": "The space could not be found."
},
{
"id": "app.page.create.store_error.app_error",
"translation": "An error occurred while creating the page."
"id": "app.page.delete.invalid_id.app_error",
"translation": "Invalid page ID."
},
{
"id": "app.page.delete.invalid_id.app_error",
"id": "app.page.delete.invalid_space_id.app_error",
"translation": "Invalid space ID."
},
{
"id": "app.page.delete.invalid_user_id.app_error",
"translation": "Invalid user ID."
},
{
"id": "app.page.duplicate.cross_team.app_error",
"translation": "A page cannot be duplicated into a space on a different team."
},
{
"id": "app.page.duplicate.dest_not_found.app_error",
"translation": "The destination space or parent page could not be found."
},
{
"id": "app.page.duplicate.invalid_id.app_error",
"translation": "Invalid page ID."
},
{
"id": "app.page.duplicate.invalid_space_id.app_error",
"translation": "Invalid space ID."
},
{
"id": "app.page.duplicate.invalid_user_id.app_error",
"translation": "Invalid user ID."
},
{
"id": "app.page.duplicate.not_found.app_error",
"translation": "The page could not be found."
},
{
"id": "app.page.duplicate.subtree_max_depth_exceeded.app_error",
"translation": "Duplicating this page and its sub-pages here would exceed the maximum nesting depth of {{.MaxDepth}}."
},
{
"id": "app.page.get.invalid_id.app_error",
"translation": "Invalid page ID."
Expand All @@ -56,21 +100,81 @@
"translation": "The page could not be found."
},
{
"id": "app.page.get_ancestors.invalid_id.app_error",
"id": "app.page.get_children.invalid_id.app_error",
"translation": "Invalid page ID."
},
{
"id": "app.page.get_children.invalid_id.app_error",
"id": "app.page.invalid_parent.app_error",
"translation": "The destination parent page does not exist."
},
{
"id": "app.page.invalid_space_id.app_error",
"translation": "Invalid space ID."
},
{
"id": "app.page.max_depth_exceeded.app_error",
"translation": "Placing the page here would exceed the maximum nesting depth of {{.MaxDepth}}."
},
{
"id": "app.page.move.invalid_id.app_error",
"translation": "Invalid page ID."
},
{
"id": "app.page.get_descendants.invalid_id.app_error",
"id": "app.page.move.invalid_space_id.app_error",
"translation": "Invalid space ID."
},
{
"id": "app.page.move.invalid_user_id.app_error",
"translation": "Invalid user ID."
},
{
"id": "app.page.move_to_space.cross_team.app_error",
"translation": "A page can only be moved to a space in the same team."
},
{
"id": "app.page.move_to_space.invalid_id.app_error",
"translation": "Invalid page ID."
},
{
"id": "app.page.move_to_space.invalid_source_space.app_error",
"translation": "Invalid source space ID."
},
{
"id": "app.page.move_to_space.invalid_target_space.app_error",
"translation": "Invalid target space ID."
},
{
"id": "app.page.move_to_space.invalid_user_id.app_error",
"translation": "Invalid user ID."
},
{
"id": "app.page.not_found.app_error",
"translation": "The page could not be found."
},
{
"id": "app.page.restore.invalid_id.app_error",
"translation": "Invalid page ID."
},
{
"id": "app.page.restore.invalid_space_id.app_error",
"translation": "Invalid space ID."
},
{
"id": "app.page.restore.invalid_user_id.app_error",
"translation": "Invalid user ID."
},
{
"id": "app.page.restore.not_deleted.app_error",
"translation": "The page is not deleted and cannot be restored."
},
{
"id": "app.page.restore.not_restorable.app_error",
"translation": "The page cannot be restored because it is a version snapshot."
},
{
"id": "app.page.subtree_max_depth_exceeded.app_error",
"translation": "Placing this page and its sub-pages here would exceed the maximum nesting depth of {{.MaxDepth}}."
},
{
"id": "app.page.update.conflict.app_error",
"translation": "The page was changed by someone else. Reload and try again."
Expand All @@ -79,6 +183,10 @@
"id": "app.page.update.invalid_id.app_error",
"translation": "Invalid page ID."
},
{
"id": "app.page.update.invalid_space_id.app_error",
"translation": "Invalid space ID."
},
{
"id": "app.page.update.invalid_user_id.app_error",
"translation": "Invalid user ID."
Expand All @@ -91,6 +199,78 @@
"id": "app.page.update.store_error.app_error",
"translation": "An error occurred while updating the page."
},
{
"id": "app.shared.description_too_long.app_error",
"translation": "The description exceeds the maximum length of {{.MaxLength}} characters."
},
{
"id": "app.shared.icon_too_large.app_error",
"translation": "The icon exceeds the maximum size of {{.MaxBytes}} bytes."
},
{
"id": "app.shared.title_required.app_error",
"translation": "A title is required."
},
{
"id": "app.shared.title_too_long.app_error",
"translation": "The title exceeds the maximum length of {{.MaxLength}} characters."
},
{
"id": "app.space.access.channel_lookup_failed.app_error",
"translation": "Failed to verify space membership."
},
{
"id": "app.space.access.forbidden.app_error",
"translation": "You do not have permission to access this space."
},
{
"id": "app.space.access.invalid_user_id.app_error",
"translation": "Invalid user ID."
},
{
"id": "app.space.access.team_lookup_failed.app_error",
"translation": "Failed to verify team membership."
},
{
"id": "app.space.add_member.failed.app_error",
"translation": "Unable to add the member to the space."
},
{
"id": "app.space.client_not_wired.app_error",
"translation": "Unable to complete the request due to a server configuration error."
},
{
"id": "app.space.create.add_member_failed.app_error",
"translation": "Unable to add the creator to the backing channel for the space."
},
{
"id": "app.space.create.backing_channel_failed.app_error",
"translation": "Unable to create the backing channel for the space."
},
{
"id": "app.space.create.channel_id_not_allowed.app_error",
"translation": "A channel ID must not be supplied when creating a space."
},
{
"id": "app.space.create.invalid_team_id.app_error",
"translation": "Invalid team ID."
},
{
"id": "app.space.create.invalid_user_id.app_error",
"translation": "Invalid user ID."
},
{
"id": "app.space.create.nil_input.app_error",
"translation": "A space must be supplied."
},
{
"id": "app.space.create.not_team_member.app_error",
"translation": "You must be a member of the team to create a space in it."
},
{
"id": "app.space.create.team_lookup_failed.app_error",
"translation": "Unable to verify team membership."
},
{
"id": "app.space.delete.invalid_id.app_error",
"translation": "Invalid space ID."
Expand All @@ -99,29 +279,73 @@
"id": "app.space.get.invalid_id.app_error",
"translation": "Invalid space ID."
},
{
"id": "app.space.get_for_channel.invalid_channel_id.app_error",
"translation": "Invalid channel ID."
},
{
"id": "app.space.get_for_team.invalid_team_id.app_error",
"translation": "Invalid team ID."
},
{
"id": "app.space.get_for_team.invalid_user_id.app_error",
"translation": "Invalid user ID."
},
{
"id": "app.space.get_for_team.not_team_member.app_error",
"translation": "You must be a member of the team to list its spaces."
},
{
"id": "app.space.get_for_team.team_lookup_failed.app_error",
"translation": "Unable to verify team membership."
},
{
"id": "app.space.get_pages.invalid_space_id.app_error",
"translation": "Invalid space ID."
},
{
"id": "app.space.list_members.failed.app_error",
"translation": "Unable to list the members of the space."
},
{
"id": "app.space.member.invalid_user_id.app_error",
"translation": "The supplied user ID is not valid."
},
{
"id": "app.space.member.not_team_member.app_error",
"translation": "The specified user is not a member of the space's team."
},
{
"id": "app.space.member.team_lookup_failed.app_error",
"translation": "Unable to verify the user's team membership."
},
{
"id": "app.space.member.user_not_found.app_error",
"translation": "The specified user could not be found."
},
{
"id": "app.space.remove_member.failed.app_error",
"translation": "Unable to remove the member from the space."
},
{
"id": "app.space.remove_member.last_member.app_error",
"translation": "The last member with access to a space cannot be removed."
},
{
"id": "app.space.restore.channel_restore_failed.app_error",
"translation": "The space was restored, but its backing channel could not be un-archived."
},
{
"id": "app.space.restore.invalid_id.app_error",
"translation": "Invalid space ID."
},
{
"id": "app.space.update.invalid_id.app_error",
"translation": "Invalid space ID."
"id": "app.space.restore.not_deleted.app_error",
"translation": "The space is not deleted and cannot be restored."
},
{
"id": "app.space.update.nil_input.app_error",
"translation": "No space data was provided."
"id": "app.space.restore.read_back_failed.app_error",
"translation": "The space was restored, but it could not be loaded. Refresh to see it."
},
{
"id": "app.space.update.invalid_id.app_error",
"translation": "Invalid space ID."
},
{
"id": "app.store.conflict.app_error",
Expand Down Expand Up @@ -195,6 +419,10 @@
"id": "model.page.is_valid.create_at.app_error",
"translation": "Invalid page creation time."
},
{
"id": "model.page.is_valid.edit_at.app_error",
"translation": "Invalid page edit time."
},
{
"id": "model.page.is_valid.id.app_error",
"translation": "Invalid page ID."
Expand Down
Loading
Loading