Explorar el Código

feat: auto-fill field labels and add server-side field name validation

Frontend (Views.tsx):
- Added camelToTitleCase utility to convert camelCase to Title Case
- Auto-fill label from field name when label is empty
  - kisCica -> Kis Cica
  - thisIsAnExample -> This Is An Example

Backend (http_server.cpp):
- Add server-side validation in HandleCreateView and HandleUpdateView
- Reject field names starting with '_' (reserved for system fields)
- Exception: fields with group "_system" are allowed to use _ prefix
fszontagh hace 6 meses
padre
commit
c9c853a127
Se han modificado 2 ficheros con 47 adiciones y 3 borrados
  1. 28 2
      webserver/src/http_server.cpp
  2. 19 1
      webui/src/pages/Views.tsx

+ 28 - 2
webserver/src/http_server.cpp

@@ -549,6 +549,16 @@ auto HttpServer::InitializeServices() -> bool {
     wsHandler_ = std::make_unique<WsHandler>(*authService_);
     spdlog::info("WsHandler initialized successfully");
 
+    // Initialize LLM Client (connection to LLM service - optional, may not be available)
+    LlmClientConfig llm_config;
+    llm_config.address = config_.llm_address;
+    llmClient_ = std::make_unique<LlmClient>(std::move(llm_config));
+    if (llmClient_->Connect()) {
+        spdlog::info("LLM client connected to {}", config_.llm_address);
+    } else {
+        spdlog::warn("LLM service not available at {} - LLM features will be disabled", config_.llm_address);
+    }
+
     // Note: WebSocket message handler is set up in Start() after wsServer_ is created
 
     return true;
@@ -4097,13 +4107,21 @@ void HttpServer::HandleCreateView(const httplib::Request& req, httplib::Response
                 for (const auto& field_json : schema_json["fields"]) {
                     SchemaField field;
                     field.name = field_json.value("name", "");
+                    field.group = field_json.value("group", "");
+
+                    // Validate field name - names starting with "_" are reserved for system fields
+                    if (!field.name.empty() && field.name[0] == '_' && field.group != "_system") {
+                        res.status = 400;
+                        res.set_content(R"({"error":"Field names starting with '_' are reserved for system fields"})", "application/json");
+                        return;
+                    }
+
                     field.type = StringToFieldType(field_json.value("type", "text"));
                     field.label = field_json.value("label", "");
                     field.description = field_json.value("description", "");
                     field.required = field_json.value("required", false);
                     field.display_order = field_json.value("display_order", 0);
                     field.widget = field_json.value("widget", "");
-                    field.group = field_json.value("group", "");
                     field.default_value = field_json.value("default_value", nlohmann::json());
                     field.options = field_json.value("options", nlohmann::json());
                     field.reference_collection = field_json.value("reference_collection", "");
@@ -4526,13 +4544,21 @@ void HttpServer::HandleUpdateView(const httplib::Request& req, httplib::Response
                 for (const auto& field_json : schema_json["fields"]) {
                     SchemaField field;
                     field.name = field_json.value("name", "");
+                    field.group = field_json.value("group", "");
+
+                    // Validate field name - names starting with "_" are reserved for system fields
+                    if (!field.name.empty() && field.name[0] == '_' && field.group != "_system") {
+                        res.status = 400;
+                        res.set_content(R"({"error":"Field names starting with '_' are reserved for system fields"})", "application/json");
+                        return;
+                    }
+
                     field.type = StringToFieldType(field_json.value("type", "text"));
                     field.label = field_json.value("label", "");
                     field.description = field_json.value("description", "");
                     field.required = field_json.value("required", false);
                     field.display_order = field_json.value("display_order", 0);
                     field.widget = field_json.value("widget", "");
-                    field.group = field_json.value("group", "");
                     field.default_value = field_json.value("default_value", nlohmann::json());
                     field.options = field_json.value("options", nlohmann::json());
                     field.reference_collection = field_json.value("reference_collection", "");

+ 19 - 1
webui/src/pages/Views.tsx

@@ -67,6 +67,17 @@ const getWidgetsForType = (fieldType: string) => {
   return WIDGET_TYPES_BY_FIELD[fieldType] || [{ value: '', label: 'Auto' }]
 }
 
+// Convert camelCase or PascalCase to Title Case
+// e.g., kisCica -> Kis Cica, thisIsAnExample -> This Is An Example
+const camelToTitleCase = (str: string): string => {
+  if (!str) return ''
+  // Insert space before uppercase letters, then capitalize first letter of each word
+  return str
+    .replace(/([A-Z])/g, ' $1') // Add space before uppercase
+    .replace(/^./, (c) => c.toUpperCase()) // Capitalize first letter
+    .trim()
+}
+
 // Built-in system fields that can be displayed in views
 const SYSTEM_FIELDS = [
   { name: '_id', label: 'Document ID', type: 'text', description: 'Unique identifier' },
@@ -520,7 +531,14 @@ function ViewModal({
       }
       if (sanitized && !fields.some((f) => f._id !== editingFieldId && f.name === sanitized)) {
         setFields(
-          fields.map((f) => (f._id === editingFieldId ? { ...f, name: sanitized } : f))
+          fields.map((f) => {
+            if (f._id === editingFieldId) {
+              // Auto-fill label from field name if label is empty
+              const newLabel = f.label || camelToTitleCase(sanitized)
+              return { ...f, name: sanitized, label: newLabel }
+            }
+            return f
+          })
         )
       }
     }