From 5b937388e55ba5916a43baa5108a7082816bc243 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E4=B8=AB=E8=AE=B2=E6=A2=B5?= Date: Sun, 20 Sep 2026 01:18:26 +0800 Subject: [PATCH] feat(meilisearch): add index creation --- .../meilisearch/MeilisearchOverviewPage.vue | 78 ++++++++++++++++++- .../meilisearch/MeilisearchSettingsPage.vue | 7 ++ .../__tests__/MeilisearchSettingsPage.spec.ts | 2 + .../sidebar/SidebarTreeItemDialogs.vue | 34 ++++++++ .../sidebar/SidebarTreeRuntimeHost.vue | 23 ++++++ .../sidebar/sidebarTreeDialogState.ts | 10 +++ ...eSidebarDatabaseSpecificMutationRuntime.ts | 51 ++++++++++++ .../src/i18n/locales/meilisearchManagement.ts | 16 ++++ apps/desktop/src/lib/backend/api.ts | 1 + apps/desktop/src/lib/backend/http.ts | 6 +- apps/desktop/src/lib/backend/tauri.ts | 6 +- .../src/types/meilisearchManagement.ts | 5 ++ crates/dbx-core/src/query/document_ops.rs | 9 +++ .../dbx-drivers/src/db/meilisearch_driver.rs | 30 +++++++ crates/dbx-web/src/main.rs | 1 + crates/dbx-web/src/routes/document_store.rs | 18 +++++ src-tauri/src/commands/document_cmd.rs | 10 +++ src-tauri/src/lib.rs | 1 + 18 files changed, 304 insertions(+), 4 deletions(-) diff --git a/apps/desktop/src/components/meilisearch/MeilisearchOverviewPage.vue b/apps/desktop/src/components/meilisearch/MeilisearchOverviewPage.vue index 6bc11de807..ca50a4c885 100644 --- a/apps/desktop/src/components/meilisearch/MeilisearchOverviewPage.vue +++ b/apps/desktop/src/components/meilisearch/MeilisearchOverviewPage.vue @@ -1,19 +1,32 @@ @@ -52,7 +101,10 @@ onMounted(() => void load());

{{ t("meilisearch.overview") }}

{{ t("meilisearch.overviewDescription") }}

- +
+ + +
@@ -120,5 +172,27 @@ onMounted(() => void load());

{{ sectionLabel(overview.topIndexes) }}

+ + + {{ t("meilisearch.createIndex") }} +
+ + + +
+ +
+
diff --git a/apps/desktop/src/components/meilisearch/MeilisearchSettingsPage.vue b/apps/desktop/src/components/meilisearch/MeilisearchSettingsPage.vue index 9f74b99732..35ff72d654 100644 --- a/apps/desktop/src/components/meilisearch/MeilisearchSettingsPage.vue +++ b/apps/desktop/src/components/meilisearch/MeilisearchSettingsPage.vue @@ -10,6 +10,7 @@ import JsonTree from "@/components/common/JsonTree.vue"; import RedisJsonEditor from "@/components/redis/RedisJsonEditor.vue"; import * as api from "@/lib/backend/api"; import { useToast } from "@/composables/useToast"; +import { useConnectionStore } from "@/stores/connectionStore"; import { useQueryStore } from "@/stores/queryStore"; const props = defineProps<{ @@ -23,6 +24,7 @@ const emit = defineEmits<{ const { t } = useI18n(); const { toast } = useToast(); +const connectionStore = useConnectionStore(); const queryStore = useQueryStore(); const settingsText = ref(""); @@ -116,6 +118,11 @@ async function confirmDeleteIndex() { await api.meilisearchDeleteIndex(props.connectionId, props.index); toast(t("meilisearch.indexDeleted")); deleteIndexConfirmOpen.value = false; + try { + await connectionStore.loadElasticsearchIndices(props.connectionId); + } catch (refreshError) { + console.warn("[DBX][meilisearch-index-refresh:error]", refreshError); + } // The tab may have been closed while the delete was in flight. if (ownerTabId && queryStore.tabs.some((tab) => tab.id === ownerTabId)) queryStore.closeTab(ownerTabId); } catch (e: any) { diff --git a/apps/desktop/src/components/meilisearch/__tests__/MeilisearchSettingsPage.spec.ts b/apps/desktop/src/components/meilisearch/__tests__/MeilisearchSettingsPage.spec.ts index efc07d3a1e..1cba6523fc 100644 --- a/apps/desktop/src/components/meilisearch/__tests__/MeilisearchSettingsPage.spec.ts +++ b/apps/desktop/src/components/meilisearch/__tests__/MeilisearchSettingsPage.spec.ts @@ -7,6 +7,7 @@ const mocks = vi.hoisted(() => ({ deleteIndex: vi.fn(), getIndexSettings: vi.fn(), closeTab: vi.fn(), + refreshTree: vi.fn(), toast: vi.fn(), })); @@ -51,6 +52,7 @@ vi.mock("@/components/common/QueryLoadingState.vue", () => ({ default: passthrou vi.mock("@/components/common/JsonTree.vue", () => ({ default: passthrough("div") })); vi.mock("@/components/redis/RedisJsonEditor.vue", () => ({ default: passthrough("div") })); vi.mock("@/composables/useToast", () => ({ useToast: () => ({ toast: mocks.toast }) })); +vi.mock("@/stores/connectionStore", () => ({ useConnectionStore: () => ({ loadElasticsearchIndices: mocks.refreshTree }) })); vi.mock("@/lib/backend/api", () => ({ meilisearchGetIndexSettings: mocks.getIndexSettings, meilisearchDeleteIndex: mocks.deleteIndex, diff --git a/apps/desktop/src/components/sidebar/SidebarTreeItemDialogs.vue b/apps/desktop/src/components/sidebar/SidebarTreeItemDialogs.vue index 9837f64694..b72f27a467 100644 --- a/apps/desktop/src/components/sidebar/SidebarTreeItemDialogs.vue +++ b/apps/desktop/src/components/sidebar/SidebarTreeItemDialogs.vue @@ -113,6 +113,12 @@ const { cloneMongoCollectionLoading, confirmCloneMongoCollection, showCreateMongoIndexDialog, + showCreateMeilisearchIndexDialog, + meilisearchCreateIndexUid, + meilisearchCreateIndexPrimaryKey, + meilisearchCreateIndexError, + meilisearchCreateIndexLoading, + confirmCreateMeilisearchIndex, mongoCreateIndexForm, mongoCreateIndexFieldOptions, mongoCreateIndexError, @@ -208,6 +214,7 @@ watch( showRenameMongoCollectionDialog, showCloneMongoCollectionDialog, showCreateMongoIndexDialog, + showCreateMeilisearchIndexDialog, showMongoIndexManagerDialog, showRedisDatabaseAliasDialog, showCreateSchemaDialog, @@ -401,6 +408,33 @@ watch( + + + + {{ t("meilisearch.createIndex") }} + +
+ + +

{{ meilisearchCreateIndexError }}

+
+ + + + +
+
+ diff --git a/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue b/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue index 032785eef2..baa68e14f0 100644 --- a/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue +++ b/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue @@ -316,6 +316,11 @@ import { showDropAllMongoIndexesConfirm, dropAllMongoIndexesLoading, showCreateMongoIndexDialog, + showCreateMeilisearchIndexDialog, + meilisearchCreateIndexUid, + meilisearchCreateIndexPrimaryKey, + meilisearchCreateIndexError, + meilisearchCreateIndexLoading, mongoCreateIndexForm, mongoCreateIndexFieldOptions, mongoCreateIndexError, @@ -566,6 +571,9 @@ const { mongoCreateIndexCanSubmit, mongoCreateIndexCanAddField, prepareCreateMongoIndexDialog, + canCreateMeilisearchIndex, + prepareCreateMeilisearchIndexDialog, + confirmCreateMeilisearchIndex, addMongoCreateIndexField, removeMongoCreateIndexField, confirmCreateMongoIndex, @@ -1446,6 +1454,12 @@ function openCreateMongoIndexDialog() { prepareCreateMongoIndexDialog(); } +function openCreateMeilisearchIndexDialog() { + claimTreeItemDialogOwnership(); + routeTreeItemDialogController(); + prepareCreateMeilisearchIndexDialog(); +} + function openMongoIndexManagerDialog() { claimTreeItemDialogOwnership(); routeTreeItemDialogController(); @@ -5311,6 +5325,12 @@ function databaseSpecificDialogCapabilities() { cloneMongoCollectionLoading, confirmCloneMongoCollection, showCreateMongoIndexDialog, + showCreateMeilisearchIndexDialog, + meilisearchCreateIndexUid, + meilisearchCreateIndexPrimaryKey, + meilisearchCreateIndexError, + meilisearchCreateIndexLoading, + confirmCreateMeilisearchIndex, mongoCreateIndexForm, mongoCreateIndexFieldOptions, mongoCreateIndexError, @@ -5549,6 +5569,9 @@ function buildConnectionSidebarMenu(context: SidebarMenuFactoryContext): boolean items.push(addToAiMenuItem(node)); } } + if (canCreateMeilisearchIndex.value) { + items.push({ label: t("meilisearch.createIndex"), action: openCreateMeilisearchIndexDialog, icon: Plus }); + } const connectionWorkspace = node.connectionId ? driverProfileDatabaseWorkspace(connectionStore.getConfig(node.connectionId)?.driver_profile) : undefined; if (connectionWorkspace?.entryScopes.includes("connection")) { items.push({ label: t(connectionWorkspace.menuLabelKey), action: openProfileConnectionWorkspace, icon: GitBranch }); diff --git a/apps/desktop/src/components/sidebar/sidebarTreeDialogState.ts b/apps/desktop/src/components/sidebar/sidebarTreeDialogState.ts index b6b35881e7..67cc7b416f 100644 --- a/apps/desktop/src/components/sidebar/sidebarTreeDialogState.ts +++ b/apps/desktop/src/components/sidebar/sidebarTreeDialogState.ts @@ -133,6 +133,11 @@ export const dropMongoIndexLoading = ref(false); export const showDropAllMongoIndexesConfirm = ref(false); export const dropAllMongoIndexesLoading = ref(false); export const showCreateMongoIndexDialog = ref(false); +export const showCreateMeilisearchIndexDialog = ref(false); +export const meilisearchCreateIndexUid = ref(""); +export const meilisearchCreateIndexPrimaryKey = ref(""); +export const meilisearchCreateIndexError = ref(""); +export const meilisearchCreateIndexLoading = ref(false); function emptyMongoCreateIndexForm(): MongoCreateIndexForm { return { @@ -234,6 +239,7 @@ const openFlags = [ showDropMongoIndexConfirm, showDropAllMongoIndexesConfirm, showCreateMongoIndexDialog, + showCreateMeilisearchIndexDialog, showMongoIndexManagerDialog, showClearElasticsearchIndexConfirm, showFlushRedisDbConfirm, @@ -268,6 +274,10 @@ export function resetSidebarTreeDialogState() { cloneMongoCollectionError.value = ""; cloneMongoCollectionLoading.value = false; resetMongoCreateIndexForm(); + meilisearchCreateIndexUid.value = ""; + meilisearchCreateIndexPrimaryKey.value = ""; + meilisearchCreateIndexError.value = ""; + meilisearchCreateIndexLoading.value = false; resetMongoIndexManager(); vacuumTableExecuting.value = false; sidebarTreeDialogOwner.value = null; diff --git a/apps/desktop/src/composables/useSidebarDatabaseSpecificMutationRuntime.ts b/apps/desktop/src/composables/useSidebarDatabaseSpecificMutationRuntime.ts index 50d0ccb293..694ac510cf 100644 --- a/apps/desktop/src/composables/useSidebarDatabaseSpecificMutationRuntime.ts +++ b/apps/desktop/src/composables/useSidebarDatabaseSpecificMutationRuntime.ts @@ -36,6 +36,7 @@ import { elasticsearchClearIndexPreview, isElasticsearchProtocolIndex, isPartial import { supportsMongoAllDriverMutations, supportsMongoIndexMutations, supportsNativeMongoDriverMutations } from "@/lib/mongo/mongoCapabilities"; import { runMongoSidebarMutation } from "@/lib/sidebar/runMongoSidebarMutation"; import { executeWithProductionContextGuard } from "@/lib/database/productionExecutionGuard"; +import { connectionIsEffectivelyReadOnly } from "@/lib/database/readOnlyWriteAccess"; import { uuid } from "@/lib/common/utils"; import { refreshLoadedMongoIndexes } from "@/lib/mongo/mongoIndexMetadata"; import type { NacosAdminConfig } from "@/types/nacos"; @@ -77,6 +78,11 @@ import { cloneMongoCollectionError, cloneMongoCollectionLoading, showCreateMongoIndexDialog, + showCreateMeilisearchIndexDialog, + meilisearchCreateIndexUid, + meilisearchCreateIndexPrimaryKey, + meilisearchCreateIndexError, + meilisearchCreateIndexLoading, mongoCreateIndexForm, mongoCreateIndexFieldOptions, mongoCreateIndexError, @@ -368,6 +374,48 @@ export function useSidebarDatabaseSpecificMutationRuntime(options: SidebarDataba }); } + const canCreateMeilisearchIndex = computed(() => { + const node = activeNode.value; + const config = node.connectionId ? connectionStore.getConfig(node.connectionId) : undefined; + return node.type === "connection" && config?.db_type === "meilisearch" && !connectionIsEffectivelyReadOnly(config); + }); + + function prepareCreateMeilisearchIndexDialog() { + if (!canCreateMeilisearchIndex.value) return; + meilisearchCreateIndexUid.value = ""; + meilisearchCreateIndexPrimaryKey.value = ""; + meilisearchCreateIndexError.value = ""; + meilisearchCreateIndexLoading.value = false; + showCreateMeilisearchIndexDialog.value = true; + } + + async function confirmCreateMeilisearchIndex() { + const node = sidebarFormTarget.value ?? activeNode.value; + const connectionId = node.connectionId; + const uid = meilisearchCreateIndexUid.value.trim(); + if (!canCreateMeilisearchIndex.value || node.type !== "connection" || !connectionId) return; + if (!uid) { + meilisearchCreateIndexError.value = t("meilisearch.createIndexUidRequired"); + return; + } + meilisearchCreateIndexLoading.value = true; + meilisearchCreateIndexError.value = ""; + try { + await connectionStore.ensureConnected(connectionId); + await api.meilisearchCreateIndex(connectionId, { + uid, + primaryKey: meilisearchCreateIndexPrimaryKey.value.trim() || undefined, + }); + showCreateMeilisearchIndexDialog.value = false; + toast(t("meilisearch.indexCreated"), 3000); + await connectionStore.loadElasticsearchIndices(connectionId); + } catch (error) { + meilisearchCreateIndexError.value = translateBackendError(t, errorMessage(error)); + } finally { + meilisearchCreateIndexLoading.value = false; + } + } + async function loadMongoIndexManagerRows() { const node = sidebarFormTarget.value ?? activeNode.value; const connectionId = node.connectionId; @@ -1219,6 +1267,9 @@ export function useSidebarDatabaseSpecificMutationRuntime(options: SidebarDataba mongoCreateIndexCanSubmit, mongoCreateIndexCanAddField, prepareCreateMongoIndexDialog, + canCreateMeilisearchIndex, + prepareCreateMeilisearchIndexDialog, + confirmCreateMeilisearchIndex, addMongoCreateIndexField, removeMongoCreateIndexField, confirmCreateMongoIndex, diff --git a/apps/desktop/src/i18n/locales/meilisearchManagement.ts b/apps/desktop/src/i18n/locales/meilisearchManagement.ts index 9b9f1ecb16..cb4ad38d66 100644 --- a/apps/desktop/src/i18n/locales/meilisearchManagement.ts +++ b/apps/desktop/src/i18n/locales/meilisearchManagement.ts @@ -106,6 +106,14 @@ const en = { taskOperationFinished: "Task operation finished with status {status}", taskPollingTimeout: "Task #{uid} is still pending after 60 seconds. Refresh manually to check it.", taskOutsideIndex: "This task does not belong to the current index.", + createIndex: "Create index", + createIndexUid: "Index UID", + createIndexPrimaryKey: "Primary key", + indexCreated: "Index created", + createIndexUidRequired: "Index UID is required.", + createIndexUidPlaceholder: "Enter index UID", + createIndexPrimaryKeyPlaceholder: "Optional primary key", + createIndexPrimaryKeyHelp: "Leave empty to set the primary key when adding the first document.", }; const zhCN = { @@ -217,6 +225,14 @@ const zhCN = { taskOperationFinished: "任务操作结束,状态为 {status}", taskPollingTimeout: "任务 #{uid} 在 60 秒后仍未完成,请手动刷新查看。", taskOutsideIndex: "该任务不属于当前索引。", + createIndex: "创建索引", + createIndexUid: "索引 UID", + createIndexPrimaryKey: "主键", + indexCreated: "索引已创建", + createIndexUidRequired: "索引 UID 不能为空。", + createIndexUidPlaceholder: "输入索引 UID", + createIndexPrimaryKeyPlaceholder: "可选,输入主键字段", + createIndexPrimaryKeyHelp: "留空时可在添加第一条文档时设置主键。", }; export const meilisearchManagementMessages = { diff --git a/apps/desktop/src/lib/backend/api.ts b/apps/desktop/src/lib/backend/api.ts index b89c7571c5..ef44f0c2de 100644 --- a/apps/desktop/src/lib/backend/api.ts +++ b/apps/desktop/src/lib/backend/api.ts @@ -838,6 +838,7 @@ export const meilisearchGetIndexSettings = forward("meilisearchGetIndexSettings" export const meilisearchUpdateIndexSettings = forward("meilisearchUpdateIndexSettings"); export const meilisearchGetIndexStats = forward("meilisearchGetIndexStats"); export const meilisearchGetIndexOverview = forward("meilisearchGetIndexOverview"); +export const meilisearchCreateIndex = forward("meilisearchCreateIndex"); export const meilisearchDeleteIndex = forward("meilisearchDeleteIndex"); export const meilisearchDeleteAllDocuments = forward("meilisearchDeleteAllDocuments"); export const meilisearchGetSystemOverview = forward("meilisearchGetSystemOverview"); diff --git a/apps/desktop/src/lib/backend/http.ts b/apps/desktop/src/lib/backend/http.ts index 760653eec9..66b1229e53 100644 --- a/apps/desktop/src/lib/backend/http.ts +++ b/apps/desktop/src/lib/backend/http.ts @@ -57,7 +57,7 @@ import { normalizeRustMongoCommand, type MongoCommand } from "@/lib/mongo/mongoS import type { MongoBulkWriteResult } from "@/lib/mongo/mongoShellCommand"; import { BackendErrorException, type BackendError } from "@/lib/backend/errorUtils"; import { decodeMeilisearchDocumentPage, decodeMeilisearchSearchResult, type MeilisearchDocumentPage, type MeilisearchDocumentPageWire, type MeilisearchSearchResult, type MeilisearchSearchWireResult } from "@/lib/backend/meilisearchTransport"; -import type { CreatedKey, EnqueuedTaskSummary, KeyCreateInput, KeyListItem, KeyPage, KeyUpdateInput, MeilisearchSystemOverview, MeilisearchTask, TaskListInput, TaskPage, TaskSelector } from "@/types/meilisearchManagement"; +import type { CreatedKey, EnqueuedTaskSummary, KeyCreateInput, KeyListItem, KeyPage, KeyUpdateInput, MeilisearchCreateIndexInput, MeilisearchSystemOverview, MeilisearchTask, TaskListInput, TaskPage, TaskSelector } from "@/types/meilisearchManagement"; import type { CollectionInfo } from "@/types/database"; import type { SchemaDiffPreparation, SchemaDiffPreparationOptions, SchemaSyncSqlPlan, SelectedSchemaDiffInput, GenerateSchemaSyncPlanOptions, TableDiff, FunctionDiff, SequenceDiff, RuleDiff, OwnerDiff } from "@/lib/schema/schemaDiff"; import type { SidebarObjectKind } from "@/lib/database/databaseObjectCapabilities"; @@ -4862,6 +4862,10 @@ export async function meilisearchGetIndexOverview(connectionId: string, index: s }); } +export async function meilisearchCreateIndex(connectionId: string, input: MeilisearchCreateIndexInput): Promise { + return post("/api/document-store/meilisearch/index/create", { connectionId, input }); +} + export async function meilisearchDeleteIndex(connectionId: string, index: string): Promise { return post("/api/document-store/meilisearch/index/delete", { connectionId, diff --git a/apps/desktop/src/lib/backend/tauri.ts b/apps/desktop/src/lib/backend/tauri.ts index db997d5b13..437f2dcfe7 100644 --- a/apps/desktop/src/lib/backend/tauri.ts +++ b/apps/desktop/src/lib/backend/tauri.ts @@ -17,7 +17,7 @@ import { ExternalSqlFileTooLargeError } from "@/lib/sql/sqlFileOpen"; import { appendDebugLog, isDebugLoggingEnabled } from "@/lib/backend/debugLog"; import { decodeMeilisearchDocumentPage, decodeMeilisearchSearchResult, type MeilisearchDocumentPage, type MeilisearchDocumentPageWire, type MeilisearchSearchResult, type MeilisearchSearchWireResult } from "@/lib/backend/meilisearchTransport"; import type { XuguTablespaceInfo } from "@/types/database"; -import type { CreatedKey, EnqueuedTaskSummary, KeyCreateInput, KeyListItem, KeyPage, KeyUpdateInput, MeilisearchSystemOverview, MeilisearchTask, TaskListInput, TaskPage, TaskSelector } from "@/types/meilisearchManagement"; +import type { CreatedKey, EnqueuedTaskSummary, KeyCreateInput, KeyListItem, KeyPage, KeyUpdateInput, MeilisearchCreateIndexInput, MeilisearchSystemOverview, MeilisearchTask, TaskListInput, TaskPage, TaskSelector } from "@/types/meilisearchManagement"; import type { CsvQuoteMode } from "@/lib/export/csvQuoteMode"; import type { SqlInsertMode } from "@/lib/export/sqlInsertMode"; @@ -4665,6 +4665,10 @@ export async function meilisearchGetIndexOverview(connectionId: string, index: s }); } +export async function meilisearchCreateIndex(connectionId: string, input: MeilisearchCreateIndexInput): Promise { + return invoke("meilisearch_create_index", { connectionId, input }); +} + export async function meilisearchDeleteIndex(connectionId: string, index: string): Promise { return invoke("meilisearch_delete_index", { connectionId, diff --git a/apps/desktop/src/types/meilisearchManagement.ts b/apps/desktop/src/types/meilisearchManagement.ts index f80f4a56c8..00753c0a6e 100644 --- a/apps/desktop/src/types/meilisearchManagement.ts +++ b/apps/desktop/src/types/meilisearchManagement.ts @@ -125,6 +125,11 @@ export interface EnqueuedTaskSummary { enqueuedAt?: string | null; } +export interface MeilisearchCreateIndexInput { + uid: string; + primaryKey?: string; +} + export interface TaskListInput { selector: TaskSelector; from?: number | null; diff --git a/crates/dbx-core/src/query/document_ops.rs b/crates/dbx-core/src/query/document_ops.rs index 68fcdd8a90..f5db665ceb 100644 --- a/crates/dbx-core/src/query/document_ops.rs +++ b/crates/dbx-core/src/query/document_ops.rs @@ -942,6 +942,15 @@ pub async fn meilisearch_get_index_overview_core( } } +pub async fn meilisearch_create_index_core( + state: &AppState, + connection_id: &str, + input: &crate::db::meilisearch_driver::MeilisearchCreateIndexInput, +) -> Result<(), String> { + let client = meilisearch_client_core(state, connection_id).await?; + crate::db::meilisearch_driver::create_index(&client, input).await +} + pub async fn meilisearch_delete_index_core(state: &AppState, connection_id: &str, index: &str) -> Result<(), String> { ensure_document_pool(state, connection_id).await?; let pool = state.pool_handle(connection_id).await.ok_or("Not found")?; diff --git a/crates/dbx-drivers/src/db/meilisearch_driver.rs b/crates/dbx-drivers/src/db/meilisearch_driver.rs index b7d620d780..7680c141ce 100644 --- a/crates/dbx-drivers/src/db/meilisearch_driver.rs +++ b/crates/dbx-drivers/src/db/meilisearch_driver.rs @@ -250,6 +250,36 @@ pub async fn list_indexes(client: &MeilisearchClient) -> Result, Str Ok(names) } +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct MeilisearchCreateIndexInput { + pub uid: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub primary_key: Option, +} + +pub async fn create_index(client: &MeilisearchClient, input: &MeilisearchCreateIndexInput) -> Result<(), String> { + let uid = input.uid.trim(); + if uid.is_empty() { + return Err("Meilisearch index UID cannot be empty".to_string()); + } + + let mut body = serde_json::Map::new(); + body.insert("uid".to_string(), Value::String(uid.to_string())); + if let Some(primary_key) = input.primary_key.as_deref().map(str::trim).filter(|value| !value.is_empty()) { + body.insert("primaryKey".to_string(), Value::String(primary_key.to_string())); + } + + let response = client + .post("/indexes") + .json(&body) + .send() + .await + .map_err(|error| format!("Meilisearch request failed: {error}"))?; + let task = task_from_response(response, "index creation").await?; + wait_for_task(client, task.task_uid).await +} + #[derive(Debug, Deserialize)] struct DocumentsResponse { results: Vec, diff --git a/crates/dbx-web/src/main.rs b/crates/dbx-web/src/main.rs index 7c744a0b2a..63f70c0af0 100644 --- a/crates/dbx-web/src/main.rs +++ b/crates/dbx-web/src/main.rs @@ -932,6 +932,7 @@ async fn main() { .route("/document-store/meilisearch/settings/update", post(routes::document_store::meilisearch_update_settings)) .route("/document-store/meilisearch/stats", post(routes::document_store::meilisearch_get_stats)) .route("/document-store/meilisearch/overview", post(routes::document_store::meilisearch_get_overview)) + .route("/document-store/meilisearch/index/create", post(routes::document_store::meilisearch_create_index)) .route("/document-store/meilisearch/index/delete", post(routes::document_store::meilisearch_delete_index)) .route( "/document-store/meilisearch/system/overview", diff --git a/crates/dbx-web/src/routes/document_store.rs b/crates/dbx-web/src/routes/document_store.rs index 22cd2650f0..b4ae28abed 100644 --- a/crates/dbx-web/src/routes/document_store.rs +++ b/crates/dbx-web/src/routes/document_store.rs @@ -190,6 +190,13 @@ pub struct MeilisearchIndexRequest { pub index: String, } +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MeilisearchCreateIndexRequest { + pub connection_id: String, + pub input: dbx_core::db::meilisearch_driver::MeilisearchCreateIndexInput, +} + #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct MeilisearchDocumentGetRequest { @@ -613,6 +620,17 @@ pub async fn meilisearch_get_overview( Ok(Json(result)) } +pub async fn meilisearch_create_index( + State(state): State>, + Json(req): Json, +) -> Result, AppError> { + ensure_writable(&state.app, &req.connection_id, "Create index").await?; + dbx_core::document_ops::meilisearch_create_index_core(&state.app, &req.connection_id, &req.input) + .await + .map_err(AppError::from)?; + Ok(Json(())) +} + pub async fn meilisearch_delete_index( State(state): State>, Json(req): Json, diff --git a/src-tauri/src/commands/document_cmd.rs b/src-tauri/src/commands/document_cmd.rs index 8eb4881e71..bff5f6c437 100644 --- a/src-tauri/src/commands/document_cmd.rs +++ b/src-tauri/src/commands/document_cmd.rs @@ -357,6 +357,16 @@ pub async fn meilisearch_get_index_overview( dbx_core::document_ops::meilisearch_get_index_overview_core(&state, &connection_id, &index).await } +#[tauri::command] +pub async fn meilisearch_create_index( + state: State<'_, Arc>, + connection_id: String, + input: dbx_core::db::meilisearch_driver::MeilisearchCreateIndexInput, +) -> Result<(), String> { + ensure_connection_writable(&state, &connection_id, "Create index").await?; + dbx_core::document_ops::meilisearch_create_index_core(&state, &connection_id, &input).await +} + #[tauri::command] pub async fn meilisearch_delete_index( state: State<'_, Arc>, diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index d777e59d33..bbe1eda750 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -2372,6 +2372,7 @@ pub fn run() { commands::document_cmd::meilisearch_update_index_settings, commands::document_cmd::meilisearch_get_index_stats, commands::document_cmd::meilisearch_get_index_overview, + commands::document_cmd::meilisearch_create_index, commands::document_cmd::meilisearch_delete_index, commands::document_cmd::meilisearch_delete_all_documents, commands::document_cmd::meilisearch_get_system_overview,