| @@ -1,13 +1,17 @@ | ||
| 1 | 1 | <?php |
| 2 | 2 | //if accessed directly exit |
| 3 | 3 | if (!defined('ABSPATH')) exit; |
| 4 | 4 | |
| 5 | +use FluentBoards\App\Http\Controllers\AiController; | |
| 5 | 6 | use FluentBoards\App\Http\Controllers\BoardController; |
| 6 | 7 | use FluentBoards\App\Http\Controllers\CommentController; |
| 8 | +use FluentBoards\App\Http\Controllers\FolderController; | |
| 7 | 9 | use FluentBoards\App\Http\Controllers\LabelController; |
| 10 | +use FluentBoards\App\Http\Controllers\MCPSettingsController; | |
| 8 | 11 | use FluentBoards\App\Http\Controllers\NotificationController; |
| 9 | 12 | use FluentBoards\App\Http\Controllers\OptionsController; |
| 13 | +use FluentBoards\App\Http\Controllers\PublicBoardController; | |
| 10 | 14 | use FluentBoards\App\Http\Controllers\ReportController; |
| 11 | 15 | use FluentBoards\App\Http\Controllers\StageController; |
| 12 | 16 | use FluentBoards\App\Http\Controllers\TaskController; |
| 13 | 17 | use FluentBoards\App\Http\Controllers\UserController; |
| @@ -23,9 +27,8 @@ | ||
| 23 | 27 | $router->get('/crm-associated-tasks/{associated_id}', [TaskController::class, 'getAssociatedTasks'])->int('associated_id'); |
| 24 | 28 | $router->get('/stage/{task_id}', [TaskController::class, 'getStageByTask']); //FUTURE: this api need to be relocated |
| 25 | 29 | |
| 26 | 30 | $router->get('/boards-by-type/{type}', [BoardController::class, 'getBoardsByType']); |
| 27 | - $router->get('/{task_id}/labels', [TaskController::class, 'getLabelsByTask']); | |
| 28 | 31 | |
| 29 | 32 | // Task tabs configuration |
| 30 | 33 | $router->get('/task-tabs/config', [TaskController::class, 'getTaskTabsConfig']); |
| 31 | 34 | $router->post('/task-tabs/config', [TaskController::class, 'saveTaskTabsConfig']); |
| @@ -32,9 +35,9 @@ | ||
| 32 | 35 | }); |
| 33 | 36 | |
| 34 | 37 | $router->withPolicy('BoardUserPolicy')->group(function ($router) { |
| 35 | 38 | |
| 36 | - $router->get('/quick-search', [OptionsController::class, 'quickSearch']); | |
| 39 | + $router->get('/global-search', [OptionsController::class, 'globalSearch']); | |
| 37 | 40 | |
| 38 | 41 | $router->get('/member-associated-users/{id}', [UserController::class, 'memberAssociatedTaskUsers']); |
| 39 | 42 | $router->get('ajax-options', [OptionsController::class, 'selectorOptions']); |
| 40 | 43 | $router->post('update-global-notification-settings', [OptionsController::class, 'updateGlobalNotificationSettings']); |
| @@ -40,10 +43,13 @@ | ||
| 40 | 43 | $router->post('update-global-notification-settings', [OptionsController::class, 'updateGlobalNotificationSettings']); |
| 41 | 44 | $router->get('get-global-notification-settings', [OptionsController::class, 'getGlobalNotificationSettings']); |
| 42 | 45 | $router->put('update-dashboard-view-settings', [OptionsController::class, 'updateDashboardViewSettings']); |
| 43 | 46 | $router->get('get-dashboard-view-settings', [OptionsController::class, 'getDashboardViewSettings']); |
| 44 | - $router->get('projects/reports', [ReportController::class, 'getBoardReports']); | |
| 45 | 47 | $router->get('reports/timesheet', [ReportController::class, 'getTimeSheetReport']); |
| 48 | + $router->get('reports/overview', [ReportController::class, 'getOverviewReport']); | |
| 49 | + $router->get('reports/tasks', [ReportController::class, 'getTasksReport']); | |
| 50 | + $router->get('reports/activity', [ReportController::class, 'getActivityReport']); | |
| 51 | + $router->get('reports/roadmap', [ReportController::class, 'getRoadmapReport']); | |
| 46 | 52 | |
| 47 | 53 | }); |
| 48 | 54 | |
| 49 | 55 | $router->prefix('projects')->withPolicy('AuthPolicy')->group(function ($router) { |
| @@ -49,11 +55,12 @@ | ||
| 49 | 55 | $router->prefix('projects')->withPolicy('AuthPolicy')->group(function ($router) { |
| 50 | 56 | $router->get('/', [BoardController::class, 'getBoards']); |
| 51 | 57 | $router->post('/', [BoardController::class, 'create']); |
| 52 | 58 | $router->get('/get-default-board-colors', [BoardController::class, 'getBoardDefaultBackgroundColors']); |
| 59 | + // NOTE: The global "Add" (board/stage/task) drawer was removed from the v2 UI. This endpoint is NOT dedicated to that feature — it remains in active use by other components, so it must stay. | |
| 53 | 60 | $router->get('/list-of-boards', [BoardController::class, 'getBoardsList']); // it is using for to get all boards by user |
| 54 | 61 | $router->get('/user-accessible-boards', [BoardController::class, 'getOnlyBoardsByUser']); |
| 55 | - $router->get('/crm-associated-boards/{id}', [BoardController::class, 'getAssociatedBoards'])->int('associated_id'); | |
| 62 | + $router->get('/crm-associated-boards/{associated_id}', [BoardController::class, 'getAssociatedBoards'])->int('associated_id'); | |
| 56 | 63 | $router->get('/currencies', [BoardController::class, 'getCurrencies']); |
| 57 | 64 | $router->get('/user-admin-in-boards', [BoardController::class, 'getUsersOfBoards']); |
| 58 | 65 | $router->get('/recent-boards', [BoardController::class, 'getRecentBoards']); |
| 59 | 66 | $router->get('/pinned-boards', [BoardController::class, 'getPinnedBoards']); |
| @@ -88,8 +95,9 @@ | ||
| 88 | 95 | $router->get('/assignees', [BoardController::class, 'getAssigneesByBoard'])->int('board_id'); |
| 89 | 96 | $router->get('/activities', [BoardController::class, 'getActivities'])->int('board_id'); |
| 90 | 97 | |
| 91 | 98 | $router->put('/stage-move-all-task', [BoardController::class, 'moveAllTasks'])->int('board_id'); |
| 99 | + // NOTE: The global "Add" drawer was removed from the v2 UI. This stage-create endpoint is shared (in-board stage creation still uses it), so it must stay. | |
| 92 | 100 | $router->post('/stage-create', [BoardController::class, 'createStage'])->int('board_id'); |
| 93 | 101 | $router->put('/stage/{stage_id}/sort-task', [StageController::class, 'sortStageTasks'])->int('board_id')->int('stage_id'); |
| 94 | 102 | $router->put('/stage/{stage_id}/archive-all-task', [BoardController::class, 'archiveAllTasksInStage'])->int('board_id')->int('stage_id'); |
| 95 | 103 | $router->put('/re-position-stages', [BoardController::class, 'repositionStages'])->int('board_id'); |
| @@ -106,9 +114,8 @@ | ||
| 106 | 114 | $router->get('/stage-task-available-positions/{stage_id}', [BoardController::class, 'getStageTaskAvailablePositions'])->int('board_id')->int('stage_id'); |
| 107 | 115 | |
| 108 | 116 | $router->post('/crm-contact', [BoardController::class, 'updateAssociateCrmContact'])->int('board_id'); |
| 109 | 117 | $router->get('/crm-contacts', [BoardController::class, 'getAssociateCrmContacts'])->int('board_id'); |
| 110 | - $router->delete('/crm-contact/{contact_id}', [BoardController::class, 'deleteAssociateCrmContact'])->int('board_id')->int('contact_id'); | |
| 111 | 118 | |
| 112 | 119 | $router->get('/notification-settings', [NotificationController::class, 'getBoardNotificationSettings'])->int('board_id'); |
| 113 | 120 | $router->put('/update-notification-settings', [NotificationController::class, 'updateBoardNotificationSettings'])->int('board_id'); |
| 114 | 121 | |
| @@ -119,9 +126,10 @@ | ||
| 119 | 126 | $router->post('/upload/background-image', [BoardController::class, 'uploadBoardBackground'])->int('board_id'); |
| 120 | 127 | $router->put('/archive-board', [BoardController::class, 'archiveBoard'])->int('board_id'); |
| 121 | 128 | $router->put('/restore-board', [BoardController::class, 'restoreBoard'])->int('board_id'); |
| 122 | 129 | $router->get('/board-menu-items', [BoardController::class, 'getBoardMenuItems'])->int('board_id'); |
| 123 | - $router->get('/stage-wise-reports', [ReportController::class, 'getStageWiseBoardReports'])->int('board_id'); | |
| 130 | + $router->get('/public-access-settings', [BoardController::class, 'getPublicAccessSettings'])->int('board_id'); | |
| 131 | + $router->put('/toggle-public-access', [BoardController::class, 'togglePublicAccess'])->int('board_id'); | |
| 124 | 132 | |
| 125 | 133 | |
| 126 | 134 | //# Tasks under a single board routes |
| 127 | 135 | //# Route prefix: /projects/{id}/tasks |
| @@ -126,14 +134,21 @@ | ||
| 126 | 134 | //# Tasks under a single board routes |
| 127 | 135 | //# Route prefix: /projects/{id}/tasks |
| 128 | 136 | $router->prefix('/tasks')->group(function ($router) { |
| 129 | 137 | $router->get('/', [TaskController::class, 'getTasksByBoard'])->int('board_id'); |
| 138 | + $router->get('/filtered', [TaskController::class, 'getFilteredBoardTasks'])->int('board_id'); | |
| 139 | + $router->get('/table', [TaskController::class, 'getTableTasks'])->int('board_id'); | |
| 130 | 140 | $router->get('/by-stage', [TaskController::class, 'getTasksByBoardStage'])->int('board_id'); |
| 141 | + $router->get('/stage-page', [TaskController::class, 'getStageTasksPage'])->int('board_id'); | |
| 142 | + // NOTE: The global "Add" drawer was removed from the v2 UI. This is the primary task-create endpoint used throughout the app, so it must stay. | |
| 131 | 143 | $router->post('/', [TaskController::class, 'create']); |
| 132 | 144 | $router->post('/create-task-from-image', [TaskController::class, 'createTaskFromImage'])->int('board_id'); |
| 133 | 145 | $router->get('/archived', [TaskController::class, 'getArchivedTasks'])->int('board_id'); |
| 134 | 146 | $router->get('/{task_id}', [TaskController::class, 'find'])->int('board_id')->int('task_id')->int('task_id'); |
| 135 | 147 | $router->put('/{task_id}', [TaskController::class, 'updateTaskProperties'])->int('board_id')->int('task_id'); |
| 148 | + $router->delete('/{task_id}/support-ticket', [TaskController::class, 'removeSupportTicketLink']) | |
| 149 | + ->int('board_id') | |
| 150 | + ->int('task_id'); | |
| 136 | 151 | $router->post('/{task_id}/dates', [TaskController::class, 'updateTaskDates'])->int('board_id')->int('task_id'); |
| 137 | 152 | $router->put('/{task_id}/move-task', [TaskController::class, 'moveTask'])->int('board_id')->int('task_id'); |
| 138 | 153 | $router->post('/bulk-actions', [TaskController::class, 'bulkActions'])->int('board_id'); |
| 139 | 154 | $router->post('/update-cover-photo/{task_id}', [TaskController::class, 'updateTaskCoverPhoto'])->int('task_id'); |
| @@ -139,9 +154,16 @@ | ||
| 139 | 154 | $router->post('/update-cover-photo/{task_id}', [TaskController::class, 'updateTaskCoverPhoto'])->int('task_id'); |
| 140 | 155 | $router->post('/status-update/{task_id}', [TaskController::class, 'taskStatusUpdate'])->int('task_id'); |
| 141 | 156 | $router->delete('/{task_id}', [TaskController::class, 'deleteTask'])->int('board_id')->int('task_id'); |
| 142 | 157 | $router->put('/{task_id}/move-to-next-stage', [TaskController::class, 'moveTaskToNextStage'])->int('board_id')->int('task_id'); |
| 158 | + $router->put('/{task_id}/pin', [TaskController::class, 'toggleTaskPinned'])->int('board_id')->int('task_id'); | |
| 143 | 159 | |
| 160 | + // AI task intelligence (summarize / subtasks / suggestions) — board-scoped. | |
| 161 | + $router->post('/{task_id}/ai-assist', [AiController::class, 'taskAssist'])->int('board_id')->int('task_id'); | |
| 162 | + // Transactional batch apply for AI label/priority suggestions (all-or-nothing). | |
| 163 | + // Bulk subtask creation lives with the other subtask APIs in Fluent Boards Pro. | |
| 164 | + $router->post('/{task_id}/ai-apply-suggestions', [AiController::class, 'applySuggestions'])->int('board_id')->int('task_id'); | |
| 165 | + | |
| 144 | 166 | // Comments Routes Area |
| 145 | 167 | $router->get('/{task_id}/comments', [CommentController::class, 'getComments'])->int('board_id')->int('task_id'); |
| 146 | 168 | $router->post('/{task_id}/comments', [CommentController::class, 'create'])->int('board_id')->int('task_id'); |
| 147 | 169 | $router->put('/comments/{comment_id}', [CommentController::class, 'update'])->int('board_id')->int('comment_id'); |
| @@ -173,12 +195,39 @@ | ||
| 173 | 195 | |
| 174 | 196 | $router->get('/general-settings', [OptionsController::class, 'getGeneralSettings']); |
| 175 | 197 | $router->post('/general-settings', [OptionsController::class, 'saveGeneralSettings']); |
| 176 | 198 | |
| 199 | + $router->get('/mcp/status', [MCPSettingsController::class, 'status']); | |
| 200 | + $router->post('/mcp/toggle', [MCPSettingsController::class, 'toggle']); | |
| 201 | + $router->post('/mcp/install-adapter', [MCPSettingsController::class, 'installAdapter']); | |
| 202 | + $router->get('/mcp/config-snippet', [MCPSettingsController::class, 'getConfigSnippet']); | |
| 203 | + | |
| 177 | 204 | $router->get('pages', [OptionsController::class, 'getPages']); |
| 178 | 205 | |
| 206 | + $router->prefix('folders')->group(function ($router) { | |
| 207 | + $router->get('/', [FolderController::class, 'getFolders']); | |
| 208 | + $router->post('/', [FolderController::class, 'createFolder']); | |
| 209 | + $router->prefix('/{folder_id}')->group(function ($router) { | |
| 210 | + $router->get('/', [FolderController::class, 'getFolderById'])->int('folder_id'); | |
| 211 | + $router->post('/add-board', [FolderController::class, 'addBoardToFolder'])->int('folder_id'); | |
| 212 | + $router->post('/remove-board', [FolderController::class, 'removeBoardFromFolder'])->int('folder_id'); | |
| 213 | + $router->put('/', [FolderController::class, 'updateFolder'])->int('folder_id'); | |
| 214 | + $router->delete('/', [FolderController::class, 'deleteFolder'])->int('folder_id'); | |
| 215 | + }); | |
| 216 | + }); | |
| 217 | + | |
| 179 | 218 | }); |
| 180 | 219 | |
| 220 | +// AI writing assistant. Settings routes are admin-only; `generate` is open to | |
| 221 | +// any authenticated user (enforced per-method inside AiPolicy). | |
| 222 | +$router->prefix('ai')->withPolicy('AiPolicy')->group(function ($router) { | |
| 223 | + $router->get('/settings', [AiController::class, 'getSettings']); | |
| 224 | + $router->post('/settings', [AiController::class, 'saveSettings']); | |
| 225 | + $router->post('/models', [AiController::class, 'getModels']); | |
| 226 | + $router->post('/test', [AiController::class, 'testConnection']); | |
| 227 | + $router->post('/generate', [AiController::class, 'generate']); | |
| 228 | +}); | |
| 229 | + | |
| 181 | 230 | $router->prefix('webhooks')->withPolicy('WebhookPolicy')->group(function ($router) { |
| 182 | 231 | $router->get('/', [WebhookController::class, 'index']); |
| 183 | 232 | $router->post('/', [WebhookController::class, 'create']); |
| 184 | 233 | $router->put('/{id}', [WebhookController::class, 'update'])->int('id'); |
| @@ -197,9 +246,11 @@ | ||
| 197 | 246 | $router->prefix('member/{id}')->withPolicy('UserPolicy')->group(function ($router) { |
| 198 | 247 | $router->get('/', [UserController::class, 'getMemberInfo']); |
| 199 | 248 | $router->get('/projects', [UserController::class, 'getMemberBoards']); |
| 200 | 249 | $router->get('/tasks', [UserController::class, 'getMemberAssociatedTasks']); |
| 250 | + $router->get('/task-counts', [UserController::class, 'getMemberTaskCounts']); | |
| 201 | 251 | $router->get('/activities', [UserController::class, 'getMemberRelatedAcitivies']); |
| 252 | + $router->get('/stats', [UserController::class, 'getMemberStats']); | |
| 202 | 253 | }); |
| 203 | 254 | |
| 204 | 255 | /* |
| 205 | 256 | * TODO: I guess we can minimize the number of routes. and Backend code needs to be refactored |
| @@ -216,11 +267,18 @@ | ||
| 216 | 267 | $router->prefix('contacts/{board_id}')->withPolicy('SingleBoardPolicy')->group(function ($router) { |
| 217 | 268 | $router->get('/', [TaskController::class, 'getAssociatedCrmContacts'])->int('board_id'); |
| 218 | 269 | }); |
| 219 | 270 | |
| 271 | +$router->prefix('public/boards/{board_id}')->withPolicy('PublicBoardPolicy')->group(function ($router) { | |
| 272 | + $router->get('/', [PublicBoardController::class, 'find'])->int('board_id'); | |
| 273 | + $router->get('/tasks', [PublicBoardController::class, 'getTasksByBoard'])->int('board_id'); | |
| 274 | + $router->get('/tasks/by-stage', [PublicBoardController::class, 'getTasksByBoardStage'])->int('board_id'); | |
| 275 | + $router->get('/tasks/stage-page', [PublicBoardController::class, 'getStageTasksPage'])->int('board_id'); | |
| 276 | +}); | |
| 277 | + | |
| 220 | 278 | // User utility routes |
| 221 | 279 | $router->withPolicy('UserPolicy')->group(function ($router) { |
| 222 | - $router->get('/quick-search', [OptionsController::class, 'quickSearch']); | |
| 280 | + $router->get('/global-search', [OptionsController::class, 'globalSearch']); | |
| 223 | 281 | }); |
| 224 | 282 | |
| 225 | 283 | |
| 226 | 284 | $router->prefix('options')->withPolicy('AuthPolicy')->group(function ($router) { |