Skip to content

Commit 8b7ed79

Browse files
authored
feat: create item (#196)
feat: create item Refs: #142 --------- Signed-off-by: seven <[email protected]>
1 parent c7b5703 commit 8b7ed79

File tree

14 files changed

+1047
-401
lines changed

14 files changed

+1047
-401
lines changed

components.d.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ declare module 'vue' {
5252
NSwitch: typeof import('naive-ui')['NSwitch']
5353
NTabPane: typeof import('naive-ui')['NTabPane']
5454
NTabs: typeof import('naive-ui')['NTabs']
55-
NTag: typeof import('naive-ui')['NTag']
5655
NText: typeof import('naive-ui')['NText']
5756
NTooltip: typeof import('naive-ui')['NTooltip']
5857
PathBreadcrumb: typeof import('./src/components/path-breadcrumb.vue')['default']

src-tauri/Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ log = "0.4.22"
3030
futures = "0.3.30"
3131
aws-config = "1.6.1"
3232
aws-sdk-dynamodb = "1.71.2"
33+
base64 = "0.22.1"
3334
[features]
3435
# This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!!
3536
custom-protocol = ["tauri/custom-protocol"]

src-tauri/src/dynamo_client.rs

Lines changed: 90 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use aws_sdk_dynamodb::{Client, config::Credentials, types::AttributeValue};
33
use aws_config::Region;
44
use serde::{Deserialize, Serialize};
55
use serde_json::json;
6+
use base64;
67

78
#[derive(Debug, Deserialize)]
89
pub struct DynamoCredentials {
@@ -25,6 +26,23 @@ struct ApiResponse {
2526
data: Option<serde_json::Value>,
2627
}
2728

29+
fn convert_json_to_attr_value(value: &serde_json::Value) -> Option<AttributeValue> {
30+
match value {
31+
serde_json::Value::String(s) => Some(AttributeValue::S(s.clone())),
32+
serde_json::Value::Number(n) => Some(AttributeValue::N(n.to_string())),
33+
serde_json::Value::Bool(b) => Some(AttributeValue::Bool(*b)),
34+
serde_json::Value::Null => Some(AttributeValue::Null(true)),
35+
serde_json::Value::Array(arr) => Some(AttributeValue::L(
36+
arr.iter().filter_map(|v| convert_json_to_attr_value(v)).collect()
37+
)),
38+
serde_json::Value::Object(map) => Some(AttributeValue::M(
39+
map.iter().filter_map(|(k, v)| {
40+
convert_json_to_attr_value(v).map(|av| (k.clone(), av))
41+
}).collect()
42+
)),
43+
}
44+
}
45+
2846
#[tauri::command]
2947
pub async fn dynamo_api(
3048
window: tauri::Window,
@@ -65,7 +83,8 @@ pub async fn dynamo_api(
6583
let table_info = json!({
6684
"id": response.table().and_then(|t| t.table_id()),
6785
"name": response.table().map(|t| t.table_name()),
68-
"status": response.table().and_then(|t| t.table_status().map(|s| s.as_str().to_string())), "itemCount": response.table().and_then(|t| t.item_count()),
86+
"status": response.table().and_then(|t| t.table_status().map(|s| s.as_str().to_string())),
87+
"itemCount": response.table().and_then(|t| t.item_count()),
6988
"sizeBytes": response.table().and_then(|t| t.table_size_bytes()),
7089
"keySchema": response.table().and_then(|t| {
7190
Some(t.key_schema().iter().map(|k| {
@@ -146,18 +165,79 @@ pub async fn dynamo_api(
146165
})
147166
}
148167
},
149-
"put_item" => {
150-
if let Some(_payload) = &options.payload {
151-
// Implementation for put_item would go here
152-
Ok(ApiResponse {
153-
status: 200,
154-
message: "Item put successfully".to_string(),
155-
data: None,
156-
})
168+
"CREATE_ITEM" => {
169+
if let Some(payload) = &options.payload {
170+
// Expecting payload to have an "attributes" array
171+
if let Some(attributes) = payload.get("attributes").and_then(|v| v.as_array()) {
172+
let mut put_item = client.put_item().table_name(&options.table_name);
173+
174+
for attr in attributes {
175+
if let (Some(key), Some(value), Some(attr_type)) = (
176+
attr.get("key").and_then(|v| v.as_str()),
177+
attr.get("value"),
178+
attr.get("type").and_then(|v| v.as_str()),
179+
) {
180+
let attr_value = match attr_type {
181+
"S" => value.as_str().map(|s| AttributeValue::S(s.to_string())),
182+
"N" => value.as_f64().map(|n| AttributeValue::N(n.to_string())),
183+
"B" => value.as_str().map(|s| AttributeValue::B(
184+
aws_sdk_dynamodb::primitives::Blob::new(base64::decode(s).unwrap_or_default())
185+
)),
186+
"BOOL" => value.as_bool().map(AttributeValue::Bool),
187+
"NULL" => Some(AttributeValue::Null(true)),
188+
"SS" => value.as_array().map(|arr| {
189+
AttributeValue::Ss(arr.iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect())
190+
}),
191+
"NS" => value.as_array().map(|arr| {
192+
AttributeValue::Ns(arr.iter().filter_map(|v| v.as_f64().map(|n| n.to_string())).collect())
193+
}),
194+
"BS" => value.as_array().map(|arr| {
195+
AttributeValue::Bs(arr.iter().filter_map(|v| v.as_str().map(|s| {
196+
aws_sdk_dynamodb::primitives::Blob::new(base64::decode(s).unwrap_or_default())
197+
})).collect())
198+
}),
199+
"L" => value.as_array().map(|arr| {
200+
AttributeValue::L(arr.iter().filter_map(|v| {
201+
// Recursively convert each element
202+
convert_json_to_attr_value(v)
203+
}).collect())
204+
}),
205+
"M" => value.as_object().map(|map| {
206+
AttributeValue::M(map.iter().filter_map(|(k, v)| {
207+
convert_json_to_attr_value(v).map(|av| (k.clone(), av))
208+
}).collect())
209+
}),
210+
_ => None,
211+
};
212+
if let Some(av) = attr_value {
213+
put_item = put_item.item(key, av);
214+
}
215+
}
216+
}
217+
218+
match put_item.send().await {
219+
Ok(_) => Ok(ApiResponse {
220+
status: 200,
221+
message: "Item created successfully".to_string(),
222+
data: None,
223+
}),
224+
Err(e) => Ok(ApiResponse {
225+
status: 500,
226+
message: format!("Failed to create item: {}", e),
227+
data: None,
228+
}),
229+
}
230+
} else {
231+
Ok(ApiResponse {
232+
status: 400,
233+
message: "Attributes array is required".to_string(),
234+
data: None,
235+
})
236+
}
157237
} else {
158238
Ok(ApiResponse {
159239
status: 400,
160-
message: "Item is required for put_item operation".to_string(),
240+
message: "Item payload is required".to_string(),
161241
data: None,
162242
})
163243
}

src/components/tool-bar.vue

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -61,22 +61,30 @@
6161
{{ $t('editor.loadDefault') }}
6262
</n-tooltip>
6363
<n-button-group v-if="props.type === 'DYNAMO_EDITOR'">
64-
<n-button quaternary>
64+
<n-button quaternary @click="handleEditorSwitch('DYNAMO_EDITOR_UI')">
6565
<template #icon>
6666
<n-icon>
6767
<Template />
6868
</n-icon>
6969
</template>
7070
{{ $t('editor.dynamo.uiQuery') }}
7171
</n-button>
72-
<n-button quaternary>
72+
<n-button quaternary @click="handleEditorSwitch('DYNAMO_EDITOR_SQL')">
7373
<template #icon>
7474
<n-icon>
7575
<Code />
7676
</n-icon>
7777
</template>
7878
{{ $t('editor.dynamo.sqlEditor') }}
7979
</n-button>
80+
<n-button quaternary @click="handleEditorSwitch('DYNAMO_EDITOR_CREATE_ITEM')">
81+
<template #icon>
82+
<n-icon>
83+
<Add />
84+
</n-icon>
85+
</template>
86+
{{ $t('editor.dynamo.createItem') }}
87+
</n-button>
8088
</n-button-group>
8189
<n-tabs
8290
v-if="props.type === 'MANAGE'"
@@ -95,24 +103,22 @@
95103
</template>
96104

97105
<script setup lang="ts">
98-
import { AiStatus, Search, Code, Template } from '@vicons/carbon';
106+
import { Add, AiStatus, Search, Code, Template } from '@vicons/carbon';
99107
import { storeToRefs } from 'pinia';
100108
import { useClusterManageStore, useConnectionStore, useTabStore } from '../store';
101109
import { useLang } from '../lang';
102110
import { CustomError, inputProps } from '../common';
103111
112+
const props = defineProps({ type: String });
113+
const emits = defineEmits(['switch-manage-tab']);
114+
104115
const message = useMessage();
105116
const lang = useLang();
106117
107118
const connectionStore = useConnectionStore();
108119
const { fetchConnections, fetchIndices, selectIndex } = connectionStore;
109120
const { connections } = storeToRefs(connectionStore);
110121
111-
const props = defineProps({
112-
type: String,
113-
});
114-
const emits = defineEmits(['switch-manage-tab']);
115-
116122
const tabStore = useTabStore();
117123
const { loadDefaultSnippet, selectConnection } = tabStore;
118124
const { activePanel, activeElasticsearchIndexOption } = storeToRefs(tabStore);
@@ -255,6 +261,12 @@ const handleHiddenChange = async (value: boolean) => {
255261
await refreshStates(value);
256262
}
257263
};
264+
265+
const handleEditorSwitch = async (
266+
value: 'DYNAMO_EDITOR_UI' | 'DYNAMO_EDITOR_SQL' | 'DYNAMO_EDITOR_CREATE_ITEM',
267+
) => {
268+
activePanel.value.editorType = value;
269+
};
258270
</script>
259271

260272
<style lang="scss" scoped>

src/datasources/dynamoApi.ts

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,36 @@ export type DynamoIndex = {
2929
};
3030

3131
// Main table info type
32-
export type DynamoDBTableInfo = {
32+
export type RawDynamoDBTableInfo = {
3333
id: string;
3434
name: string;
3535
status: string;
3636
itemCount: number;
3737
sizeBytes: number;
3838
keySchema: KeySchema[]; // Based on connection store usage
39-
attributeDefinitions?: AttributeDefinition[];
39+
attributeDefinitions: AttributeDefinition[];
40+
indices: DynamoIndex[];
41+
creationDateTime: string;
42+
};
43+
44+
export type DynamoDBTableInfo = {
45+
id: string;
46+
name: string;
47+
status: string;
48+
itemCount: number;
49+
sizeBytes: number;
50+
partitionKey: {
51+
name: string;
52+
type: string;
53+
valueType: string;
54+
};
55+
sortKey?: {
56+
name: string;
57+
type: string;
58+
valueType: string;
59+
};
60+
keySchema: KeySchema[];
61+
attributeDefinitions: AttributeDefinition[];
4062
indices?: DynamoIndex[];
4163
creationDateTime?: string;
4264
};
@@ -84,8 +106,27 @@ const dynamoApi = {
84106
if (status !== 200) {
85107
throw new CustomError(status, message);
86108
}
87-
return data as DynamoDBTableInfo;
109+
const { keySchema, attributeDefinitions } = data as RawDynamoDBTableInfo;
110+
111+
const pkName = keySchema.find(({ keyType }) => keyType.toUpperCase() === 'HASH')?.attributeName;
112+
const pkValueType = attributeDefinitions.find(
113+
({ attributeName }) => attributeName === pkName,
114+
)?.attributeType;
115+
116+
const skName = keySchema.find(
117+
({ keyType }) => keyType.toUpperCase() === 'RANGE',
118+
)?.attributeName;
119+
const skValueType = attributeDefinitions.find(
120+
({ attributeName }) => attributeName === skName,
121+
)?.attributeType;
122+
123+
const partitionKey = { name: pkName, valueType: pkValueType, type: 'HASH' };
124+
125+
const sortKey = { name: skName, valueType: skValueType, type: 'RANGE' };
126+
127+
return { ...data, partitionKey, sortKey } as DynamoDBTableInfo;
88128
},
129+
89130
queryTable: async (con: DynamoDBConnection, queryParams: QueryParams): Promise<QueryResult> => {
90131
const credentials = {
91132
region: con.region,
@@ -136,6 +177,32 @@ const dynamoApi = {
136177

137178
return data as QueryResult;
138179
},
180+
createItem: async (
181+
con: DynamoDBConnection,
182+
attributes: Array<{
183+
key: string;
184+
value: string | number | boolean | null;
185+
type: string;
186+
}>,
187+
) => {
188+
const credentials = {
189+
region: con.region,
190+
access_key_id: con.accessKeyId,
191+
secret_access_key: con.secretAccessKey,
192+
};
193+
const options = {
194+
table_name: con.tableName,
195+
operation: 'CREATE_ITEM',
196+
payload: { attributes },
197+
};
198+
199+
const { status, message, data } = await tauriClient.invokeDynamoApi(credentials, options);
200+
201+
if (status !== 200) {
202+
throw new CustomError(status, message);
203+
}
204+
return data as QueryResult;
205+
},
139206
};
140207

141208
export { dynamoApi };

src/lang/enUS.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -193,9 +193,6 @@ export const enUS = {
193193
unsupportedFile: 'DocKit only supports file end with .search file',
194194
loadDefault: 'Load default code snippet',
195195
dynamo: {
196-
filterKeyRequired: 'Filter key name is required',
197-
filterOperatorRequired: 'Filter operator is required',
198-
filterValueRequired: 'Filter key value is required',
199196
uiQuery: 'Query UI',
200197
sqlEditor: 'PartiQL Editor',
201198
tableOrIndex: 'Table/Index',
@@ -205,11 +202,20 @@ export const enUS = {
205202
filterTitle: 'Filters - Optional',
206203
inputAttrName: 'Enter attribute name',
207204
inputOperator: 'Select Operator',
205+
type: 'Select attribute type',
208206
inputAttrValue: 'Enter attribute value',
209207
resultTitle: 'Query Result',
210208
indexIsRequired: 'Table/Index is required',
209+
partitionKeyRequired: 'Table/Index Partition key is required',
211210
atLeastRequired: 'Partition key or at least 1 filter is required',
212211
scanWarning: 'Request without partitionKey will scan the table',
212+
createItem: 'Create Item',
213+
addAttributesTitle: 'Add Attributes',
214+
attributeNameRequired: 'Attribute name is required',
215+
attributeTypeRequired: 'Attribute type is required',
216+
attributeValueRequired: 'Attribute value is required',
217+
operatorRequired: 'Filter operator is required',
218+
createItemSuccess: 'Item created successfully!',
213219
},
214220
},
215221
file: {

src/lang/zhCN.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -193,9 +193,6 @@ export const zhCN = {
193193
unsupportedFile: 'DocKit仅支持以 .search 结尾的文件',
194194
loadDefault: '加载默认代码片段',
195195
dynamo: {
196-
filterKeyRequired: '请输入滤器键名',
197-
filterOperatorRequired: '请输入滤器操作符',
198-
filterValueRequired: '请输入滤器键的值',
199196
uiQuery: '查询 UI',
200197
sqlEditor: 'PartiQL 查询',
201198
tableOrIndex: '表/索引',
@@ -205,11 +202,20 @@ export const zhCN = {
205202
filterTitle: '条件过滤 - 可选',
206203
inputAttrName: '请输入属性名称',
207204
inputOperator: '选择操作符',
205+
type: '选择属性类型',
208206
inputAttrValue: '请输入属性的值',
209207
resultTitle: '查询结果',
208+
partitionKeyRequired: '表/索引的 Partition Key 是必需的',
210209
indexIsRequired: '表/索引 是必需的',
211210
atLeastRequired: 'Partition Key 或至少 1 个过滤器是必需的',
212211
scanWarning: '未提供 Partition Key,执行 Scan 操作可能会导致性能问题',
212+
createItem: '添加数据',
213+
addAttributesTitle: '添加属性',
214+
attributeNameRequired: '请输入属性名称',
215+
attributeTypeRequired: '请输入属性类型',
216+
attributeValueRequired: '请输入属性值',
217+
operatorRequired: '请输入滤器操作符',
218+
createItemSuccess: '数据添加成功!',
213219
},
214220
},
215221
file: {

0 commit comments

Comments
 (0)