|
|
@@ -529,6 +529,14 @@ auto HttpServer::InitializeServices() -> bool {
|
|
|
}
|
|
|
spdlog::info("ViewService initialized successfully");
|
|
|
|
|
|
+ // Initialize PageService
|
|
|
+ pageService_ = std::make_unique<PageService>(*dbClient_);
|
|
|
+ if (!pageService_->Initialize()) {
|
|
|
+ spdlog::error("Failed to initialize PageService");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ spdlog::info("PageService initialized successfully");
|
|
|
+
|
|
|
// Initialize AuthorizationService
|
|
|
authorizationService_ = std::make_unique<AuthorizationService>(*groupService_, *collectionService_);
|
|
|
if (!authorizationService_->Initialize()) {
|
|
|
@@ -626,6 +634,9 @@ void HttpServer::SetupRoutes() {
|
|
|
// Setup view routes (schema definitions)
|
|
|
SetupViewRoutes();
|
|
|
|
|
|
+ // Setup page routes (page builder)
|
|
|
+ SetupPageRoutes();
|
|
|
+
|
|
|
// Setup collection routes (before workspace routes since they're more specific)
|
|
|
SetupCollectionRoutes();
|
|
|
|
|
|
@@ -4585,4 +4596,828 @@ void HttpServer::HandleDeleteView(const httplib::Request& req, httplib::Response
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// ============================================================================
|
|
|
+// Page Routes
|
|
|
+// ============================================================================
|
|
|
+
|
|
|
+void HttpServer::SetupPageRoutes() {
|
|
|
+ // POST /api/workspaces/:workspace_id/pages - Create a new page
|
|
|
+ httpServer_->Post(R"(/api/workspaces/([^/]+)/pages$)",
|
|
|
+ [this](const httplib::Request& req, httplib::Response& res) {
|
|
|
+ HandleCreatePage(req, res);
|
|
|
+ });
|
|
|
+
|
|
|
+ // GET /api/workspaces/:workspace_id/pages - List all pages
|
|
|
+ httpServer_->Get(R"(/api/workspaces/([^/]+)/pages$)",
|
|
|
+ [this](const httplib::Request& req, httplib::Response& res) {
|
|
|
+ HandleListPages(req, res);
|
|
|
+ });
|
|
|
+
|
|
|
+ // GET /api/workspaces/:workspace_id/pages/sidebar - List sidebar pages
|
|
|
+ httpServer_->Get(R"(/api/workspaces/([^/]+)/pages/sidebar$)",
|
|
|
+ [this](const httplib::Request& req, httplib::Response& res) {
|
|
|
+ HandleListSidebarPages(req, res);
|
|
|
+ });
|
|
|
+
|
|
|
+ // GET /api/workspaces/:workspace_id/pages/slug/:slug - Get page by slug
|
|
|
+ httpServer_->Get(R"(/api/workspaces/([^/]+)/pages/slug/([^/]+)$)",
|
|
|
+ [this](const httplib::Request& req, httplib::Response& res) {
|
|
|
+ HandleGetPageBySlug(req, res);
|
|
|
+ });
|
|
|
+
|
|
|
+ // GET /api/workspaces/:workspace_id/pages/:page_id - Get a specific page
|
|
|
+ httpServer_->Get(R"(/api/workspaces/([^/]+)/pages/([^/]+)$)",
|
|
|
+ [this](const httplib::Request& req, httplib::Response& res) {
|
|
|
+ HandleGetPage(req, res);
|
|
|
+ });
|
|
|
+
|
|
|
+ // PATCH /api/workspaces/:workspace_id/pages/:page_id - Update a page
|
|
|
+ httpServer_->Patch(R"(/api/workspaces/([^/]+)/pages/([^/]+)$)",
|
|
|
+ [this](const httplib::Request& req, httplib::Response& res) {
|
|
|
+ HandleUpdatePage(req, res);
|
|
|
+ });
|
|
|
+
|
|
|
+ // PATCH /api/workspaces/:workspace_id/pages/:page_id/share - Update page sharing
|
|
|
+ httpServer_->Patch(R"(/api/workspaces/([^/]+)/pages/([^/]+)/share$)",
|
|
|
+ [this](const httplib::Request& req, httplib::Response& res) {
|
|
|
+ HandleUpdatePageSharing(req, res);
|
|
|
+ });
|
|
|
+
|
|
|
+ // DELETE /api/workspaces/:workspace_id/pages/:page_id - Delete a page
|
|
|
+ httpServer_->Delete(R"(/api/workspaces/([^/]+)/pages/([^/]+)$)",
|
|
|
+ [this](const httplib::Request& req, httplib::Response& res) {
|
|
|
+ HandleDeletePage(req, res);
|
|
|
+ });
|
|
|
+
|
|
|
+ spdlog::info("Page routes configured");
|
|
|
+}
|
|
|
+
|
|
|
+void HttpServer::HandleCreatePage(const httplib::Request& req, httplib::Response& res) {
|
|
|
+ // Authenticate the request
|
|
|
+ auto auth_user = AuthenticateRequest(req);
|
|
|
+ if (!auth_user) {
|
|
|
+ res.status = 401;
|
|
|
+ res.set_content(R"({"error":"Unauthorized"})", "application/json");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Extract workspace ID from path
|
|
|
+ std::string workspace_id = req.matches[1].str();
|
|
|
+
|
|
|
+ // Verify workspace exists
|
|
|
+ auto ws_result = workspaceService_->GetWorkspace(workspace_id);
|
|
|
+ if (!ws_result.success) {
|
|
|
+ res.status = 404;
|
|
|
+ res.set_content(R"({"error":"Workspace not found"})", "application/json");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check permission to create pages
|
|
|
+ if (!authorizationService_->CanCreatePage(*auth_user, workspace_id)) {
|
|
|
+ res.status = 403;
|
|
|
+ res.set_content(R"({"error":"Forbidden - insufficient permissions to create pages"})", "application/json");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check if PageService is available
|
|
|
+ if (!pageService_) {
|
|
|
+ res.status = 503;
|
|
|
+ res.set_content(R"({"error":"Page service not available"})", "application/json");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Parse request body
|
|
|
+ nlohmann::json body;
|
|
|
+ try {
|
|
|
+ body = nlohmann::json::parse(req.body);
|
|
|
+ } catch (const nlohmann::json::parse_error& /*e*/) {
|
|
|
+ res.status = 400;
|
|
|
+ res.set_content(R"({"error":"Invalid JSON body"})", "application/json");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Validate required fields
|
|
|
+ if (!body.contains("name") || !body["name"].is_string()) {
|
|
|
+ res.status = 400;
|
|
|
+ res.set_content(R"({"error":"name is required"})", "application/json");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ CreatePageRequest request;
|
|
|
+ request.workspace_id = workspace_id;
|
|
|
+ request.name = body["name"].get<std::string>();
|
|
|
+ request.slug = body.value("slug", "");
|
|
|
+ request.created_by = auth_user->user_id; // Set owner to current user
|
|
|
+
|
|
|
+ // Parse layout if provided
|
|
|
+ if (body.contains("layout") && body["layout"].is_object()) {
|
|
|
+ const auto& layout_json = body["layout"];
|
|
|
+ request.layout.version = layout_json.value("version", 1);
|
|
|
+ request.layout.grid_columns = layout_json.value("grid_columns", 12);
|
|
|
+
|
|
|
+ if (layout_json.contains("components") && layout_json["components"].is_array()) {
|
|
|
+ for (const auto& comp_json : layout_json["components"]) {
|
|
|
+ LayoutComponent comp;
|
|
|
+ comp.id = comp_json.value("id", "");
|
|
|
+ comp.type = comp_json.value("type", "");
|
|
|
+ comp.config = comp_json.value("config", nlohmann::json::object());
|
|
|
+
|
|
|
+ if (comp_json.contains("position") && comp_json["position"].is_object()) {
|
|
|
+ const auto& pos = comp_json["position"];
|
|
|
+ comp.position.x = pos.value("x", 0);
|
|
|
+ comp.position.y = pos.value("y", 0);
|
|
|
+ comp.position.width = pos.value("width", 12);
|
|
|
+ comp.position.height = pos.value("height", 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ request.layout.components.push_back(comp);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Parse settings if provided
|
|
|
+ if (body.contains("settings") && body["settings"].is_object()) {
|
|
|
+ const auto& settings_json = body["settings"];
|
|
|
+ request.settings.show_in_sidebar = settings_json.value("show_in_sidebar", true);
|
|
|
+ request.settings.icon = settings_json.value("icon", "");
|
|
|
+ request.settings.menu_order = settings_json.value("menu_order", 0);
|
|
|
+ }
|
|
|
+
|
|
|
+ auto result = pageService_->CreatePage(request);
|
|
|
+
|
|
|
+ if (!result.success) {
|
|
|
+ res.status = 400;
|
|
|
+ nlohmann::json error_response = {{"error", result.error}};
|
|
|
+ res.set_content(error_response.dump(), "application/json");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Build response with page info
|
|
|
+ nlohmann::json page_json = {
|
|
|
+ {"id", result.page->id},
|
|
|
+ {"workspace_id", result.page->workspace_id},
|
|
|
+ {"name", result.page->name},
|
|
|
+ {"slug", result.page->slug},
|
|
|
+ {"created_at", result.page->created_at},
|
|
|
+ {"updated_at", result.page->updated_at},
|
|
|
+ {"created_by", result.page->created_by},
|
|
|
+ {"shared_with_groups", result.page->shared_with_groups}
|
|
|
+ };
|
|
|
+
|
|
|
+ // Add layout
|
|
|
+ nlohmann::json layout_json = {
|
|
|
+ {"version", result.page->layout.version},
|
|
|
+ {"grid_columns", result.page->layout.grid_columns}
|
|
|
+ };
|
|
|
+ nlohmann::json components_json = nlohmann::json::array();
|
|
|
+ for (const auto& comp : result.page->layout.components) {
|
|
|
+ nlohmann::json comp_json = {
|
|
|
+ {"id", comp.id},
|
|
|
+ {"type", comp.type},
|
|
|
+ {"position", {
|
|
|
+ {"x", comp.position.x},
|
|
|
+ {"y", comp.position.y},
|
|
|
+ {"width", comp.position.width},
|
|
|
+ {"height", comp.position.height}
|
|
|
+ }},
|
|
|
+ {"config", comp.config}
|
|
|
+ };
|
|
|
+ components_json.push_back(comp_json);
|
|
|
+ }
|
|
|
+ layout_json["components"] = components_json;
|
|
|
+ page_json["layout"] = layout_json;
|
|
|
+
|
|
|
+ // Add settings
|
|
|
+ page_json["settings"] = {
|
|
|
+ {"show_in_sidebar", result.page->settings.show_in_sidebar},
|
|
|
+ {"icon", result.page->settings.icon},
|
|
|
+ {"menu_order", result.page->settings.menu_order}
|
|
|
+ };
|
|
|
+
|
|
|
+ res.status = 201;
|
|
|
+ res.set_content(page_json.dump(), "application/json");
|
|
|
+
|
|
|
+ } catch (const std::exception& e) {
|
|
|
+ spdlog::error("Create page error: {}", e.what());
|
|
|
+ res.status = 500;
|
|
|
+ res.set_content(R"({"error":"Internal server error"})", "application/json");
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void HttpServer::HandleListPages(const httplib::Request& req, httplib::Response& res) {
|
|
|
+ // Authenticate the request
|
|
|
+ auto auth_user = AuthenticateRequest(req);
|
|
|
+ if (!auth_user) {
|
|
|
+ res.status = 401;
|
|
|
+ res.set_content(R"({"error":"Unauthorized"})", "application/json");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Extract workspace ID from path
|
|
|
+ std::string workspace_id = req.matches[1].str();
|
|
|
+
|
|
|
+ // Verify workspace exists
|
|
|
+ auto ws_result = workspaceService_->GetWorkspace(workspace_id);
|
|
|
+ if (!ws_result.success) {
|
|
|
+ res.status = 404;
|
|
|
+ res.set_content(R"({"error":"Workspace not found"})", "application/json");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check if PageService is available
|
|
|
+ if (!pageService_) {
|
|
|
+ res.status = 503;
|
|
|
+ res.set_content(R"({"error":"Page service not available"})", "application/json");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ auto result = pageService_->ListPages(workspace_id);
|
|
|
+
|
|
|
+ if (!result.success) {
|
|
|
+ res.status = 500;
|
|
|
+ nlohmann::json error_response = {{"error", result.error}};
|
|
|
+ res.set_content(error_response.dump(), "application/json");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Filter pages based on user permissions
|
|
|
+ nlohmann::json pages_json = nlohmann::json::array();
|
|
|
+ for (const auto& page : result.pages) {
|
|
|
+ // Check if user can view this page
|
|
|
+ if (!authorizationService_->CanViewPage(*auth_user, workspace_id,
|
|
|
+ page.created_by, page.shared_with_groups)) {
|
|
|
+ continue; // Skip pages user cannot view
|
|
|
+ }
|
|
|
+
|
|
|
+ nlohmann::json page_json = {
|
|
|
+ {"id", page.id},
|
|
|
+ {"workspace_id", page.workspace_id},
|
|
|
+ {"name", page.name},
|
|
|
+ {"slug", page.slug},
|
|
|
+ {"created_at", page.created_at},
|
|
|
+ {"updated_at", page.updated_at},
|
|
|
+ {"created_by", page.created_by},
|
|
|
+ {"shared_with_groups", page.shared_with_groups},
|
|
|
+ {"settings", {
|
|
|
+ {"show_in_sidebar", page.settings.show_in_sidebar},
|
|
|
+ {"icon", page.settings.icon},
|
|
|
+ {"menu_order", page.settings.menu_order}
|
|
|
+ }}
|
|
|
+ };
|
|
|
+ pages_json.push_back(page_json);
|
|
|
+ }
|
|
|
+
|
|
|
+ res.set_content(pages_json.dump(), "application/json");
|
|
|
+
|
|
|
+ } catch (const std::exception& e) {
|
|
|
+ spdlog::error("List pages error: {}", e.what());
|
|
|
+ res.status = 500;
|
|
|
+ res.set_content(R"({"error":"Internal server error"})", "application/json");
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void HttpServer::HandleListSidebarPages(const httplib::Request& req, httplib::Response& res) {
|
|
|
+ // Authenticate the request
|
|
|
+ auto auth_user = AuthenticateRequest(req);
|
|
|
+ if (!auth_user) {
|
|
|
+ res.status = 401;
|
|
|
+ res.set_content(R"({"error":"Unauthorized"})", "application/json");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Extract workspace ID from path
|
|
|
+ std::string workspace_id = req.matches[1].str();
|
|
|
+
|
|
|
+ // Verify workspace exists
|
|
|
+ auto ws_result = workspaceService_->GetWorkspace(workspace_id);
|
|
|
+ if (!ws_result.success) {
|
|
|
+ res.status = 404;
|
|
|
+ res.set_content(R"({"error":"Workspace not found"})", "application/json");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check if PageService is available
|
|
|
+ if (!pageService_) {
|
|
|
+ res.status = 503;
|
|
|
+ res.set_content(R"({"error":"Page service not available"})", "application/json");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ auto result = pageService_->ListSidebarPages(workspace_id);
|
|
|
+
|
|
|
+ if (!result.success) {
|
|
|
+ res.status = 500;
|
|
|
+ nlohmann::json error_response = {{"error", result.error}};
|
|
|
+ res.set_content(error_response.dump(), "application/json");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Filter pages based on user permissions
|
|
|
+ nlohmann::json pages_json = nlohmann::json::array();
|
|
|
+ for (const auto& page : result.pages) {
|
|
|
+ // Check if user can view this page
|
|
|
+ if (!authorizationService_->CanViewPage(*auth_user, workspace_id,
|
|
|
+ page.created_by, page.shared_with_groups)) {
|
|
|
+ continue; // Skip pages user cannot view
|
|
|
+ }
|
|
|
+
|
|
|
+ nlohmann::json page_json = {
|
|
|
+ {"id", page.id},
|
|
|
+ {"workspace_id", page.workspace_id},
|
|
|
+ {"name", page.name},
|
|
|
+ {"slug", page.slug},
|
|
|
+ {"created_by", page.created_by},
|
|
|
+ {"settings", {
|
|
|
+ {"show_in_sidebar", page.settings.show_in_sidebar},
|
|
|
+ {"icon", page.settings.icon},
|
|
|
+ {"menu_order", page.settings.menu_order}
|
|
|
+ }}
|
|
|
+ };
|
|
|
+ pages_json.push_back(page_json);
|
|
|
+ }
|
|
|
+
|
|
|
+ res.set_content(pages_json.dump(), "application/json");
|
|
|
+
|
|
|
+ } catch (const std::exception& e) {
|
|
|
+ spdlog::error("List sidebar pages error: {}", e.what());
|
|
|
+ res.status = 500;
|
|
|
+ res.set_content(R"({"error":"Internal server error"})", "application/json");
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void HttpServer::HandleGetPageBySlug(const httplib::Request& req, httplib::Response& res) {
|
|
|
+ // Authenticate the request
|
|
|
+ auto auth_user = AuthenticateRequest(req);
|
|
|
+ if (!auth_user) {
|
|
|
+ res.status = 401;
|
|
|
+ res.set_content(R"({"error":"Unauthorized"})", "application/json");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Extract workspace ID and slug from path
|
|
|
+ std::string workspace_id = req.matches[1].str();
|
|
|
+ std::string slug = req.matches[2].str();
|
|
|
+
|
|
|
+ // Check if PageService is available
|
|
|
+ if (!pageService_) {
|
|
|
+ res.status = 503;
|
|
|
+ res.set_content(R"({"error":"Page service not available"})", "application/json");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ auto result = pageService_->GetPageBySlug(workspace_id, slug);
|
|
|
+
|
|
|
+ if (!result.success) {
|
|
|
+ res.status = 404;
|
|
|
+ nlohmann::json error_response = {{"error", result.error}};
|
|
|
+ res.set_content(error_response.dump(), "application/json");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check if user can view this page
|
|
|
+ if (!authorizationService_->CanViewPage(*auth_user, workspace_id,
|
|
|
+ result.page->created_by, result.page->shared_with_groups)) {
|
|
|
+ res.status = 403;
|
|
|
+ res.set_content(R"({"error":"Forbidden - no access to this page"})", "application/json");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Build full response with layout
|
|
|
+ nlohmann::json page_json = {
|
|
|
+ {"id", result.page->id},
|
|
|
+ {"workspace_id", result.page->workspace_id},
|
|
|
+ {"name", result.page->name},
|
|
|
+ {"slug", result.page->slug},
|
|
|
+ {"created_at", result.page->created_at},
|
|
|
+ {"updated_at", result.page->updated_at},
|
|
|
+ {"created_by", result.page->created_by},
|
|
|
+ {"shared_with_groups", result.page->shared_with_groups}
|
|
|
+ };
|
|
|
+
|
|
|
+ // Add layout
|
|
|
+ nlohmann::json layout_json = {
|
|
|
+ {"version", result.page->layout.version},
|
|
|
+ {"grid_columns", result.page->layout.grid_columns}
|
|
|
+ };
|
|
|
+ nlohmann::json components_json = nlohmann::json::array();
|
|
|
+ for (const auto& comp : result.page->layout.components) {
|
|
|
+ nlohmann::json comp_json = {
|
|
|
+ {"id", comp.id},
|
|
|
+ {"type", comp.type},
|
|
|
+ {"position", {
|
|
|
+ {"x", comp.position.x},
|
|
|
+ {"y", comp.position.y},
|
|
|
+ {"width", comp.position.width},
|
|
|
+ {"height", comp.position.height}
|
|
|
+ }},
|
|
|
+ {"config", comp.config}
|
|
|
+ };
|
|
|
+ components_json.push_back(comp_json);
|
|
|
+ }
|
|
|
+ layout_json["components"] = components_json;
|
|
|
+ page_json["layout"] = layout_json;
|
|
|
+
|
|
|
+ // Add settings
|
|
|
+ page_json["settings"] = {
|
|
|
+ {"show_in_sidebar", result.page->settings.show_in_sidebar},
|
|
|
+ {"icon", result.page->settings.icon},
|
|
|
+ {"menu_order", result.page->settings.menu_order}
|
|
|
+ };
|
|
|
+
|
|
|
+ res.set_content(page_json.dump(), "application/json");
|
|
|
+
|
|
|
+ } catch (const std::exception& e) {
|
|
|
+ spdlog::error("Get page by slug error: {}", e.what());
|
|
|
+ res.status = 500;
|
|
|
+ res.set_content(R"({"error":"Internal server error"})", "application/json");
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void HttpServer::HandleGetPage(const httplib::Request& req, httplib::Response& res) {
|
|
|
+ // Authenticate the request
|
|
|
+ auto auth_user = AuthenticateRequest(req);
|
|
|
+ if (!auth_user) {
|
|
|
+ res.status = 401;
|
|
|
+ res.set_content(R"({"error":"Unauthorized"})", "application/json");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Extract workspace ID and page ID from path
|
|
|
+ std::string workspace_id = req.matches[1].str();
|
|
|
+ std::string page_id = req.matches[2].str();
|
|
|
+
|
|
|
+ // Check if PageService is available
|
|
|
+ if (!pageService_) {
|
|
|
+ res.status = 503;
|
|
|
+ res.set_content(R"({"error":"Page service not available"})", "application/json");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ auto result = pageService_->GetPage(workspace_id, page_id);
|
|
|
+
|
|
|
+ if (!result.success) {
|
|
|
+ res.status = 404;
|
|
|
+ nlohmann::json error_response = {{"error", result.error}};
|
|
|
+ res.set_content(error_response.dump(), "application/json");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check if user can view this page
|
|
|
+ if (!authorizationService_->CanViewPage(*auth_user, workspace_id,
|
|
|
+ result.page->created_by, result.page->shared_with_groups)) {
|
|
|
+ res.status = 403;
|
|
|
+ res.set_content(R"({"error":"Forbidden - no access to this page"})", "application/json");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Build full response with layout
|
|
|
+ nlohmann::json page_json = {
|
|
|
+ {"id", result.page->id},
|
|
|
+ {"workspace_id", result.page->workspace_id},
|
|
|
+ {"name", result.page->name},
|
|
|
+ {"slug", result.page->slug},
|
|
|
+ {"created_at", result.page->created_at},
|
|
|
+ {"updated_at", result.page->updated_at},
|
|
|
+ {"created_by", result.page->created_by},
|
|
|
+ {"shared_with_groups", result.page->shared_with_groups}
|
|
|
+ };
|
|
|
+
|
|
|
+ // Add layout
|
|
|
+ nlohmann::json layout_json = {
|
|
|
+ {"version", result.page->layout.version},
|
|
|
+ {"grid_columns", result.page->layout.grid_columns}
|
|
|
+ };
|
|
|
+ nlohmann::json components_json = nlohmann::json::array();
|
|
|
+ for (const auto& comp : result.page->layout.components) {
|
|
|
+ nlohmann::json comp_json = {
|
|
|
+ {"id", comp.id},
|
|
|
+ {"type", comp.type},
|
|
|
+ {"position", {
|
|
|
+ {"x", comp.position.x},
|
|
|
+ {"y", comp.position.y},
|
|
|
+ {"width", comp.position.width},
|
|
|
+ {"height", comp.position.height}
|
|
|
+ }},
|
|
|
+ {"config", comp.config}
|
|
|
+ };
|
|
|
+ components_json.push_back(comp_json);
|
|
|
+ }
|
|
|
+ layout_json["components"] = components_json;
|
|
|
+ page_json["layout"] = layout_json;
|
|
|
+
|
|
|
+ // Add settings
|
|
|
+ page_json["settings"] = {
|
|
|
+ {"show_in_sidebar", result.page->settings.show_in_sidebar},
|
|
|
+ {"icon", result.page->settings.icon},
|
|
|
+ {"menu_order", result.page->settings.menu_order}
|
|
|
+ };
|
|
|
+
|
|
|
+ res.set_content(page_json.dump(), "application/json");
|
|
|
+
|
|
|
+ } catch (const std::exception& e) {
|
|
|
+ spdlog::error("Get page error: {}", e.what());
|
|
|
+ res.status = 500;
|
|
|
+ res.set_content(R"({"error":"Internal server error"})", "application/json");
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void HttpServer::HandleUpdatePage(const httplib::Request& req, httplib::Response& res) {
|
|
|
+ // Authenticate the request
|
|
|
+ auto auth_user = AuthenticateRequest(req);
|
|
|
+ if (!auth_user) {
|
|
|
+ res.status = 401;
|
|
|
+ res.set_content(R"({"error":"Unauthorized"})", "application/json");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Extract workspace ID and page ID from path
|
|
|
+ std::string workspace_id = req.matches[1].str();
|
|
|
+ std::string page_id = req.matches[2].str();
|
|
|
+
|
|
|
+ // Check if PageService is available
|
|
|
+ if (!pageService_) {
|
|
|
+ res.status = 503;
|
|
|
+ res.set_content(R"({"error":"Page service not available"})", "application/json");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Get existing page to check ownership
|
|
|
+ auto existing = pageService_->GetPage(workspace_id, page_id);
|
|
|
+ if (!existing.success || !existing.page) {
|
|
|
+ res.status = 404;
|
|
|
+ res.set_content(R"({"error":"Page not found"})", "application/json");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check permission to edit
|
|
|
+ if (!authorizationService_->CanEditPage(*auth_user, workspace_id, existing.page->created_by)) {
|
|
|
+ res.status = 403;
|
|
|
+ res.set_content(R"({"error":"Forbidden - insufficient permissions to edit this page"})", "application/json");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Parse request body
|
|
|
+ nlohmann::json body;
|
|
|
+ try {
|
|
|
+ body = nlohmann::json::parse(req.body);
|
|
|
+ } catch (const nlohmann::json::parse_error& /*e*/) {
|
|
|
+ res.status = 400;
|
|
|
+ res.set_content(R"({"error":"Invalid JSON body"})", "application/json");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ UpdatePageRequest request;
|
|
|
+ request.workspace_id = workspace_id;
|
|
|
+ request.id = page_id;
|
|
|
+
|
|
|
+ if (body.contains("name") && body["name"].is_string()) {
|
|
|
+ request.name = body["name"].get<std::string>();
|
|
|
+ }
|
|
|
+ if (body.contains("slug") && body["slug"].is_string()) {
|
|
|
+ request.slug = body["slug"].get<std::string>();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Parse layout if provided
|
|
|
+ if (body.contains("layout") && body["layout"].is_object()) {
|
|
|
+ PageLayout layout;
|
|
|
+ const auto& layout_json = body["layout"];
|
|
|
+ layout.version = layout_json.value("version", 1);
|
|
|
+ layout.grid_columns = layout_json.value("grid_columns", 12);
|
|
|
+
|
|
|
+ if (layout_json.contains("components") && layout_json["components"].is_array()) {
|
|
|
+ for (const auto& comp_json : layout_json["components"]) {
|
|
|
+ LayoutComponent comp;
|
|
|
+ comp.id = comp_json.value("id", "");
|
|
|
+ comp.type = comp_json.value("type", "");
|
|
|
+ comp.config = comp_json.value("config", nlohmann::json::object());
|
|
|
+
|
|
|
+ if (comp_json.contains("position") && comp_json["position"].is_object()) {
|
|
|
+ const auto& pos = comp_json["position"];
|
|
|
+ comp.position.x = pos.value("x", 0);
|
|
|
+ comp.position.y = pos.value("y", 0);
|
|
|
+ comp.position.width = pos.value("width", 12);
|
|
|
+ comp.position.height = pos.value("height", 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ layout.components.push_back(comp);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ request.layout = layout;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Parse settings if provided
|
|
|
+ if (body.contains("settings") && body["settings"].is_object()) {
|
|
|
+ PageSettings settings;
|
|
|
+ const auto& settings_json = body["settings"];
|
|
|
+ settings.show_in_sidebar = settings_json.value("show_in_sidebar", true);
|
|
|
+ settings.icon = settings_json.value("icon", "");
|
|
|
+ settings.menu_order = settings_json.value("menu_order", 0);
|
|
|
+ request.settings = settings;
|
|
|
+ }
|
|
|
+
|
|
|
+ auto result = pageService_->UpdatePage(request);
|
|
|
+
|
|
|
+ if (!result.success) {
|
|
|
+ res.status = 400;
|
|
|
+ nlohmann::json error_response = {{"error", result.error}};
|
|
|
+ res.set_content(error_response.dump(), "application/json");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Build response with page info
|
|
|
+ nlohmann::json page_json = {
|
|
|
+ {"id", result.page->id},
|
|
|
+ {"workspace_id", result.page->workspace_id},
|
|
|
+ {"name", result.page->name},
|
|
|
+ {"slug", result.page->slug},
|
|
|
+ {"created_at", result.page->created_at},
|
|
|
+ {"updated_at", result.page->updated_at},
|
|
|
+ {"created_by", result.page->created_by},
|
|
|
+ {"shared_with_groups", result.page->shared_with_groups}
|
|
|
+ };
|
|
|
+
|
|
|
+ // Add layout
|
|
|
+ nlohmann::json layout_json = {
|
|
|
+ {"version", result.page->layout.version},
|
|
|
+ {"grid_columns", result.page->layout.grid_columns}
|
|
|
+ };
|
|
|
+ nlohmann::json components_json = nlohmann::json::array();
|
|
|
+ for (const auto& comp : result.page->layout.components) {
|
|
|
+ nlohmann::json comp_json = {
|
|
|
+ {"id", comp.id},
|
|
|
+ {"type", comp.type},
|
|
|
+ {"position", {
|
|
|
+ {"x", comp.position.x},
|
|
|
+ {"y", comp.position.y},
|
|
|
+ {"width", comp.position.width},
|
|
|
+ {"height", comp.position.height}
|
|
|
+ }},
|
|
|
+ {"config", comp.config}
|
|
|
+ };
|
|
|
+ components_json.push_back(comp_json);
|
|
|
+ }
|
|
|
+ layout_json["components"] = components_json;
|
|
|
+ page_json["layout"] = layout_json;
|
|
|
+
|
|
|
+ // Add settings
|
|
|
+ page_json["settings"] = {
|
|
|
+ {"show_in_sidebar", result.page->settings.show_in_sidebar},
|
|
|
+ {"icon", result.page->settings.icon},
|
|
|
+ {"menu_order", result.page->settings.menu_order}
|
|
|
+ };
|
|
|
+
|
|
|
+ res.set_content(page_json.dump(), "application/json");
|
|
|
+
|
|
|
+ } catch (const std::exception& e) {
|
|
|
+ spdlog::error("Update page error: {}", e.what());
|
|
|
+ res.status = 500;
|
|
|
+ res.set_content(R"({"error":"Internal server error"})", "application/json");
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void HttpServer::HandleUpdatePageSharing(const httplib::Request& req, httplib::Response& res) {
|
|
|
+ // Authenticate the request
|
|
|
+ auto auth_user = AuthenticateRequest(req);
|
|
|
+ if (!auth_user) {
|
|
|
+ res.status = 401;
|
|
|
+ res.set_content(R"({"error":"Unauthorized"})", "application/json");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Extract workspace ID and page ID from path
|
|
|
+ std::string workspace_id = req.matches[1].str();
|
|
|
+ std::string page_id = req.matches[2].str();
|
|
|
+
|
|
|
+ // Check if PageService is available
|
|
|
+ if (!pageService_) {
|
|
|
+ res.status = 503;
|
|
|
+ res.set_content(R"({"error":"Page service not available"})", "application/json");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Get existing page to check ownership
|
|
|
+ auto existing = pageService_->GetPage(workspace_id, page_id);
|
|
|
+ if (!existing.success || !existing.page) {
|
|
|
+ res.status = 404;
|
|
|
+ res.set_content(R"({"error":"Page not found"})", "application/json");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check permission to share
|
|
|
+ if (!authorizationService_->CanSharePage(*auth_user, workspace_id, existing.page->created_by)) {
|
|
|
+ res.status = 403;
|
|
|
+ res.set_content(R"({"error":"Forbidden - insufficient permissions to share this page"})", "application/json");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Parse request body
|
|
|
+ nlohmann::json body;
|
|
|
+ try {
|
|
|
+ body = nlohmann::json::parse(req.body);
|
|
|
+ } catch (const nlohmann::json::parse_error& /*e*/) {
|
|
|
+ res.status = 400;
|
|
|
+ res.set_content(R"({"error":"Invalid JSON body"})", "application/json");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Validate shared_with_groups field
|
|
|
+ if (!body.contains("shared_with_groups") || !body["shared_with_groups"].is_array()) {
|
|
|
+ res.status = 400;
|
|
|
+ res.set_content(R"({"error":"shared_with_groups array is required"})", "application/json");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ std::vector<std::string> shared_with_groups;
|
|
|
+ for (const auto& group : body["shared_with_groups"]) {
|
|
|
+ if (group.is_string()) {
|
|
|
+ shared_with_groups.push_back(group.get<std::string>());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ auto result = pageService_->UpdatePageSharing(workspace_id, page_id, shared_with_groups);
|
|
|
+
|
|
|
+ if (!result.success) {
|
|
|
+ res.status = 400;
|
|
|
+ nlohmann::json error_response = {{"error", result.error}};
|
|
|
+ res.set_content(error_response.dump(), "application/json");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Build response
|
|
|
+ nlohmann::json response = {
|
|
|
+ {"id", result.page->id},
|
|
|
+ {"shared_with_groups", result.page->shared_with_groups},
|
|
|
+ {"message", "Page sharing updated successfully"}
|
|
|
+ };
|
|
|
+
|
|
|
+ res.set_content(response.dump(), "application/json");
|
|
|
+
|
|
|
+ } catch (const std::exception& e) {
|
|
|
+ spdlog::error("Update page sharing error: {}", e.what());
|
|
|
+ res.status = 500;
|
|
|
+ res.set_content(R"({"error":"Internal server error"})", "application/json");
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void HttpServer::HandleDeletePage(const httplib::Request& req, httplib::Response& res) {
|
|
|
+ // Authenticate the request
|
|
|
+ auto auth_user = AuthenticateRequest(req);
|
|
|
+ if (!auth_user) {
|
|
|
+ res.status = 401;
|
|
|
+ res.set_content(R"({"error":"Unauthorized"})", "application/json");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Extract workspace ID and page ID from path
|
|
|
+ std::string workspace_id = req.matches[1].str();
|
|
|
+ std::string page_id = req.matches[2].str();
|
|
|
+
|
|
|
+ // Check if PageService is available
|
|
|
+ if (!pageService_) {
|
|
|
+ res.status = 503;
|
|
|
+ res.set_content(R"({"error":"Page service not available"})", "application/json");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Get existing page to check ownership
|
|
|
+ auto existing = pageService_->GetPage(workspace_id, page_id);
|
|
|
+ if (!existing.success || !existing.page) {
|
|
|
+ res.status = 404;
|
|
|
+ res.set_content(R"({"error":"Page not found"})", "application/json");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check permission to delete
|
|
|
+ if (!authorizationService_->CanDeletePage(*auth_user, workspace_id, existing.page->created_by)) {
|
|
|
+ res.status = 403;
|
|
|
+ res.set_content(R"({"error":"Forbidden - insufficient permissions to delete this page"})", "application/json");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ auto result = pageService_->DeletePage(workspace_id, page_id);
|
|
|
+
|
|
|
+ if (!result.success) {
|
|
|
+ res.status = 404;
|
|
|
+ nlohmann::json error_response = {{"error", result.error}};
|
|
|
+ res.set_content(error_response.dump(), "application/json");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ res.set_content(R"({"message":"Page deleted successfully"})", "application/json");
|
|
|
+
|
|
|
+ } catch (const std::exception& e) {
|
|
|
+ spdlog::error("Delete page error: {}", e.what());
|
|
|
+ res.status = 500;
|
|
|
+ res.set_content(R"({"error":"Internal server error"})", "application/json");
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
} // namespace smartbotic::webserver
|