From 9a33146bd894f1c37e7c75bc56ce6da90cd07b04 Mon Sep 17 00:00:00 2001 From: Chen Asraf Date: Mon, 9 Mar 2026 23:14:46 +0200 Subject: [PATCH] feat: allow editing can post/reply permissions --- Makefile | 6 + lib/Controller/CategoryController.php | 7 +- lib/Controller/RoleController.php | 8 +- lib/Controller/TeamController.php | 6 +- lib/Migration/Version17Date20260118000000.php | 11 +- lib/Migration/Version21Date20260301000001.php | 18 + openapi-full.json | 24 ++ openapi.json | 24 ++ .../AppNavigation/AppNavigation.vue | 21 +- .../CategoryPermissionsTable.test.ts | 73 ++-- .../CategoryPermissionsTable.vue | 327 ++++++++++++------ src/views/admin/AdminCategoryEdit.vue | 99 +++++- src/views/admin/AdminRoleEdit.vue | 14 + src/views/admin/AdminTeamEdit.vue | 8 + .../Controller/CategoryControllerTest.php | 28 +- tests/unit/Controller/RoleControllerTest.php | 41 ++- tests/unit/Controller/TeamControllerTest.php | 4 +- 17 files changed, 535 insertions(+), 184 deletions(-) diff --git a/Makefile b/Makefile index d0607bf..930f200 100644 --- a/Makefile +++ b/Makefile @@ -303,6 +303,12 @@ test-docker: echo "\x1b[33mRunning tests in container $$CONTAINER_ID for app $$APP_DIR\x1b[0m"; \ docker exec $$CONTAINER_ID phpunit -c apps-shared/$$APP_DIR/tests/phpunit.docker.xml +# test-frontend: +# - Run frontend (Vitest) tests +.PHONY: test-frontend +test-frontend: + $(pnpm_cmd) vitest run + # lint: # - Lint JS via pnpm and PHP via composer script "lint" .PHONY: lint diff --git a/lib/Controller/CategoryController.php b/lib/Controller/CategoryController.php index 1306001..f467e29 100644 --- a/lib/Controller/CategoryController.php +++ b/lib/Controller/CategoryController.php @@ -402,7 +402,7 @@ class CategoryController extends OCSController { * Update permissions for a category * * @param int $id Category ID - * @param list $permissions Role permissions array + * @param list $permissions Role permissions array * @return DataResponse * * 200: Permissions updated @@ -439,9 +439,8 @@ class CategoryController extends OCSController { $categoryPerm->setTargetType(CategoryPerm::TARGET_TYPE_ROLE); $categoryPerm->setTargetId((string)$perm['roleId']); $categoryPerm->setCanView($perm['canView'] ?? false); - // canPost and canReply default to canView value - $categoryPerm->setCanPost($perm['canView'] ?? false); - $categoryPerm->setCanReply($perm['canView'] ?? false); + $categoryPerm->setCanPost($perm['canPost'] ?? $perm['canView'] ?? false); + $categoryPerm->setCanReply($perm['canReply'] ?? $perm['canPost'] ?? $perm['canView'] ?? false); // Guest and Default roles never have moderate permission try { diff --git a/lib/Controller/RoleController.php b/lib/Controller/RoleController.php index 1b63243..ddf3171 100644 --- a/lib/Controller/RoleController.php +++ b/lib/Controller/RoleController.php @@ -257,7 +257,7 @@ class RoleController extends OCSController { * Update permissions for a role * * @param int $id Role ID - * @param list $permissions Permissions array + * @param list $permissions Permissions array * @return DataResponse * * 200: Permissions updated @@ -280,10 +280,8 @@ class RoleController extends OCSController { $categoryPerm->setTargetType(CategoryPerm::TARGET_TYPE_ROLE); $categoryPerm->setTargetId((string)$id); $categoryPerm->setCanView($perm['canView'] ?? false); - // canPost and canReply default to canView value - // This ensures that if a role can view a category, they can also post/reply unless explicitly restricted - $categoryPerm->setCanPost($perm['canView'] ?? false); - $categoryPerm->setCanReply($perm['canView'] ?? false); + $categoryPerm->setCanPost($perm['canPost'] ?? $perm['canView'] ?? false); + $categoryPerm->setCanReply($perm['canReply'] ?? $perm['canPost'] ?? $perm['canView'] ?? false); // Guest and Default roles never have moderate permission $categoryPerm->setCanModerate($role->isModeratorRestricted() ? false : ($perm['canModerate'] ?? false)); diff --git a/lib/Controller/TeamController.php b/lib/Controller/TeamController.php index 4bb8b36..858cd75 100644 --- a/lib/Controller/TeamController.php +++ b/lib/Controller/TeamController.php @@ -128,7 +128,7 @@ class TeamController extends OCSController { * Update category permissions for a team (circle) * * @param string $id Team/circle single ID - * @param list $permissions Permissions array + * @param list $permissions Permissions array * @return DataResponse * * 200: Permissions updated @@ -163,8 +163,8 @@ class TeamController extends OCSController { $categoryPerm->setTargetType(CategoryPerm::TARGET_TYPE_TEAM); $categoryPerm->setTargetId($id); $categoryPerm->setCanView($perm['canView'] ?? false); - $categoryPerm->setCanPost($perm['canView'] ?? false); - $categoryPerm->setCanReply($perm['canView'] ?? false); + $categoryPerm->setCanPost($perm['canPost'] ?? $perm['canView'] ?? false); + $categoryPerm->setCanReply($perm['canReply'] ?? $perm['canPost'] ?? $perm['canView'] ?? false); $categoryPerm->setCanModerate($perm['canModerate'] ?? false); $this->categoryPermMapper->insert($categoryPerm); diff --git a/lib/Migration/Version17Date20260118000000.php b/lib/Migration/Version17Date20260118000000.php index 9d06ad6..7612267 100644 --- a/lib/Migration/Version17Date20260118000000.php +++ b/lib/Migration/Version17Date20260118000000.php @@ -32,15 +32,6 @@ class Version17Date20260118000000 extends SimpleMigrationStep { * @param array $options */ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void { - // Re-run seeding to ensure all required data exists - // Pass throwOnError=false to avoid PostgreSQL transaction abort issues - // If seeding fails, users can run "occ forum:repair-seeds" to retry - try { - SeedHelper::seedAll($output, false); - } catch (\Exception $e) { - // This should not happen with throwOnError=false, but handle it gracefully - $this->logger->error('Forum migration: Seeding failed unexpectedly', ['exception' => $e->getMessage()]); - $output->warning('Forum: Seeding failed. Run "occ forum:repair-seeds" after enabling the app to complete setup.'); - } + // Seeding moved to Version21 postSchemaChange, after target_type/target_id columns exist } } diff --git a/lib/Migration/Version21Date20260301000001.php b/lib/Migration/Version21Date20260301000001.php index 94686bf..076d708 100644 --- a/lib/Migration/Version21Date20260301000001.php +++ b/lib/Migration/Version21Date20260301000001.php @@ -11,6 +11,7 @@ use Closure; use OCP\DB\ISchemaWrapper; use OCP\Migration\IOutput; use OCP\Migration\SimpleMigrationStep; +use Psr\Log\LoggerInterface; /** * Version 21 Migration (runs after data migration in Version 20): @@ -19,6 +20,11 @@ use OCP\Migration\SimpleMigrationStep; * - Create new indexes for (category_id, target_type, target_id) */ class Version21Date20260301000001 extends SimpleMigrationStep { + public function __construct( + private LoggerInterface $logger, + ) { + } + /** * @param IOutput $output * @param Closure(): ISchemaWrapper $schemaClosure @@ -63,4 +69,16 @@ class Version21Date20260301000001 extends SimpleMigrationStep { return $schema; } + + /** + * Run seeding after schema is finalized (target_type/target_id columns now exist) + */ + public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void { + try { + SeedHelper::seedAll($output, false); + } catch (\Exception $e) { + $this->logger->error('Forum migration: Seeding failed unexpectedly', ['exception' => $e->getMessage()]); + $output->warning('Forum: Seeding failed. Run "occ forum:repair-seeds" after enabling the app to complete setup.'); + } + } } diff --git a/openapi-full.json b/openapi-full.json index 5daecdc..0cc7080 100644 --- a/openapi-full.json +++ b/openapi-full.json @@ -3473,6 +3473,8 @@ "required": [ "roleId", "canView", + "canPost", + "canReply", "canModerate" ], "properties": { @@ -3483,6 +3485,12 @@ "canView": { "type": "boolean" }, + "canPost": { + "type": "boolean" + }, + "canReply": { + "type": "boolean" + }, "canModerate": { "type": "boolean" } @@ -7444,6 +7452,8 @@ "required": [ "categoryId", "canView", + "canPost", + "canReply", "canModerate" ], "properties": { @@ -7454,6 +7464,12 @@ "canView": { "type": "boolean" }, + "canPost": { + "type": "boolean" + }, + "canReply": { + "type": "boolean" + }, "canModerate": { "type": "boolean" } @@ -8035,6 +8051,8 @@ "required": [ "categoryId", "canView", + "canPost", + "canReply", "canModerate" ], "properties": { @@ -8045,6 +8063,12 @@ "canView": { "type": "boolean" }, + "canPost": { + "type": "boolean" + }, + "canReply": { + "type": "boolean" + }, "canModerate": { "type": "boolean" } diff --git a/openapi.json b/openapi.json index 2b109ff..292fb8e 100644 --- a/openapi.json +++ b/openapi.json @@ -3473,6 +3473,8 @@ "required": [ "roleId", "canView", + "canPost", + "canReply", "canModerate" ], "properties": { @@ -3483,6 +3485,12 @@ "canView": { "type": "boolean" }, + "canPost": { + "type": "boolean" + }, + "canReply": { + "type": "boolean" + }, "canModerate": { "type": "boolean" } @@ -7444,6 +7452,8 @@ "required": [ "categoryId", "canView", + "canPost", + "canReply", "canModerate" ], "properties": { @@ -7454,6 +7464,12 @@ "canView": { "type": "boolean" }, + "canPost": { + "type": "boolean" + }, + "canReply": { + "type": "boolean" + }, "canModerate": { "type": "boolean" } @@ -8035,6 +8051,8 @@ "required": [ "categoryId", "canView", + "canPost", + "canReply", "canModerate" ], "properties": { @@ -8045,6 +8063,12 @@ "canView": { "type": "boolean" }, + "canPost": { + "type": "boolean" + }, + "canReply": { + "type": "boolean" + }, "canModerate": { "type": "boolean" } diff --git a/src/components/AppNavigation/AppNavigation.vue b/src/components/AppNavigation/AppNavigation.vue index e4ab5d5..910f779 100644 --- a/src/components/AppNavigation/AppNavigation.vue +++ b/src/components/AppNavigation/AppNavigation.vue @@ -146,7 +146,7 @@