From 5361cc76f193c0d4ee79910c0249c7ea7ece73f6 Mon Sep 17 00:00:00 2001 From: Guillaume Masclet Date: Tue, 18 Nov 2025 16:10:11 +0100 Subject: [PATCH 1/3] LKE-14245: Add the CSV import API --- index.ts | 1 + src/api/GraphEdge/index.ts | 19 +++++++++++++- src/api/GraphEdge/types.ts | 5 ++++ src/api/GraphNode/index.ts | 19 ++++++++++++++ src/api/GraphNode/types.ts | 5 ++++ src/api/import/index.ts | 51 ++++++++++++++++++++++++++++++++++++++ src/api/import/types.ts | 33 ++++++++++++++++++++++++ src/index.ts | 3 +++ 8 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 src/api/import/index.ts create mode 100644 src/api/import/types.ts diff --git a/index.ts b/index.ts index ae3002a51..5d69b1f92 100644 --- a/index.ts +++ b/index.ts @@ -18,6 +18,7 @@ export * from './src/api/GraphEdge'; export * from './src/api/GraphNode'; export * from './src/api/GraphQuery'; export * from './src/api/GraphSchema'; +export * from './src/api/import'; export * from './src/api/License'; export * from './src/api/Linkurious'; export * from './src/api/Plugin'; diff --git a/src/api/GraphEdge/index.ts b/src/api/GraphEdge/index.ts index a823569a6..a9c0625bb 100644 --- a/src/api/GraphEdge/index.ts +++ b/src/api/GraphEdge/index.ts @@ -9,7 +9,13 @@ import {LkErrorKey} from '../../http/response'; import {LkEdge, LkSubGraph} from '../graphItemTypes'; import {IDataSourceParams} from '../commonTypes'; -import {ICreateEdgeParams, IDeleteEdgeParams, IGetEdgeParams, IUpdateEdgeParams} from './types'; +import { + BulkCreateEdgesParams, + ICreateEdgeParams, + IDeleteEdgeParams, + IGetEdgeParams, + IUpdateEdgeParams +} from './types'; export * from './types'; @@ -56,6 +62,17 @@ export class GraphEdgeAPI extends Request { }); } + /** + * Add a chunk of edges to the graph. + */ + public bulkCreateEdges(params: BulkCreateEdgesParams) { + return this.request({ + errors: [UNAUTHORIZED, DATA_SOURCE_UNAVAILABLE, FORBIDDEN], + url: '/:sourceKey/graph/edges/bulk', + method: 'POST', + params: params + }); + } /** * Delete an edge from the graph. */ diff --git a/src/api/GraphEdge/types.ts b/src/api/GraphEdge/types.ts index 725b3b633..755f5d8ec 100644 --- a/src/api/GraphEdge/types.ts +++ b/src/api/GraphEdge/types.ts @@ -27,3 +27,8 @@ export interface IUpdateEdgeParams extends IDataSourceParams { export interface IDeleteEdgeParams extends IDataSourceParams { id: string; } + +export interface BulkCreateEdgesParams extends IDataSourceParams { + nodes: Omit; + importId?: number; +} diff --git a/src/api/GraphNode/index.ts b/src/api/GraphNode/index.ts index 48b6778e3..0fb9ca497 100644 --- a/src/api/GraphNode/index.ts +++ b/src/api/GraphNode/index.ts @@ -10,6 +10,7 @@ import {LkNode, LkNodeStatistics, LkSubGraph} from '../graphItemTypes'; import {IDataSourceParams} from '../commonTypes'; import { + BulkCreateNodesParams, ICreateNodeParams, IDeleteNodeParams, IGetAdjacentNodesParams, @@ -98,6 +99,24 @@ export class GraphNodeAPI extends Request { }); } + /** + * Add a chunk of nodes to the graph. + */ + public bulkCreateNodes(params: BulkCreateNodesParams) { + return this.request({ + errors: [ + UNAUTHORIZED, + DATA_SOURCE_UNAVAILABLE, + FORBIDDEN, + INVALID_PARAMETER, + CONSTRAINT_VIOLATION + ], + url: '/:sourceKey/graph/nodes/bulk', + method: 'POST', + params: params + }); + } + /** * Get the number of nodes in the graph. */ diff --git a/src/api/GraphNode/types.ts b/src/api/GraphNode/types.ts index 7b75e0fb2..8cdafd2f0 100644 --- a/src/api/GraphNode/types.ts +++ b/src/api/GraphNode/types.ts @@ -29,6 +29,11 @@ export interface IDeleteNodeParams extends IDataSourceParams { id: string; } +export interface BulkCreateNodesParams extends IDataSourceParams { + nodes: Omit; + importId?: number; +} + export interface IGetStatisticsParams extends IDataSourceParams { ids: string[]; withDigest?: boolean; diff --git a/src/api/import/index.ts b/src/api/import/index.ts new file mode 100644 index 000000000..865eb96cf --- /dev/null +++ b/src/api/import/index.ts @@ -0,0 +1,51 @@ +/** + * LINKURIOUS CONFIDENTIAL + * Copyright Linkurious SAS 2012 - 2025 + * + * - Created on 2025-11-18. + */ +import {Request} from '../../http/request'; +import {LkErrorKey} from '../../http/response'; + +import {CreateImportParams, DeleteImportDataParams, Import} from './types'; + +export * from './types'; + +const {UNAUTHORIZED, DATA_SOURCE_UNAVAILABLE, FORBIDDEN} = LkErrorKey; + +export class ImportAPI extends Request { + /** + * Create a new import. + */ + createImport(this: Request, params: CreateImportParams) { + return this.request({ + errors: [UNAUTHORIZED, FORBIDDEN, DATA_SOURCE_UNAVAILABLE], + url: '/:sourceKey/imports', + method: 'POST', + params: params + }); + } + + /** + * List all the existing imports. + */ + getImports(this: Request<{items: Import[]}>) { + return this.request({ + errors: [UNAUTHORIZED, FORBIDDEN, DATA_SOURCE_UNAVAILABLE], + url: '/:sourceKey/imports', + method: 'GET' + }); + } + + /** + * Delete all the nodes/edges uploaded as part of an existing import. + */ + deleteImportData(params: DeleteImportDataParams) { + return this.request({ + errors: [UNAUTHORIZED, FORBIDDEN, DATA_SOURCE_UNAVAILABLE], + url: '/:sourceKey/imports/:id/data', + method: 'DELETE', + params: params + }); + } +} diff --git a/src/api/import/types.ts b/src/api/import/types.ts new file mode 100644 index 000000000..d7c32b7ee --- /dev/null +++ b/src/api/import/types.ts @@ -0,0 +1,33 @@ +/** + * LINKURIOUS CONFIDENTIAL + * Copyright Linkurious SAS 2012 - 2025 + * + * - Created on 2025-11-18. + */ +import {DeletableUser, IDataSourceParams} from '../commonTypes'; + +export interface CreateImportParams extends IDataSourceParams { + /** + * Filename of the uploaded file (including its extension). + */ + filename: string; +} + +export interface Import extends CreateImportParams { + id: number; + sourceKey: string; + /** + * Who created this import. + */ + createdBy: DeletableUser; + /** + * When was this import handle created (so before actually uploading nodes/edges). + * + * It's a date-time formatted as a ISO 8601 string, for instance "2025-01-31T09:32:07.508Z". + */ + createdAt: string; +} + +export interface DeleteImportDataParams extends IDataSourceParams { + id: number; +} diff --git a/src/index.ts b/src/index.ts index 2dcd7edcd..5185908b1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -21,6 +21,7 @@ import {GraphEdgeAPI} from './api/GraphEdge'; import {GraphNodeAPI} from './api/GraphNode'; import {GraphQueryAPI} from './api/GraphQuery'; import {GraphSchemaAPI} from './api/GraphSchema'; +import {ImportAPI} from './api/import'; import {LicenseAPI} from './api/License'; import {LinkuriousAPI} from './api/Linkurious'; import {PluginAPI} from './api/Plugin'; @@ -50,6 +51,7 @@ export class RestClient extends ErrorListener { public readonly graphNode: GraphNodeAPI; public readonly graphQuery: GraphQueryAPI; public readonly graphSchema: GraphSchemaAPI; + public readonly import: ImportAPI; public readonly license: LicenseAPI; public readonly linkurious: LinkuriousAPI; public readonly plugin: PluginAPI; @@ -97,6 +99,7 @@ export class RestClient extends ErrorListener { this.graphNode = new GraphNodeAPI(moduleProps); this.graphQuery = new GraphQueryAPI(moduleProps); this.graphSchema = new GraphSchemaAPI(moduleProps); + this.import = new ImportAPI(moduleProps); this.license = new LicenseAPI(moduleProps); this.linkurious = new LinkuriousAPI(moduleProps); this.plugin = new PluginAPI(moduleProps); From c22e9bde10ae30d93b9dccfe657e412fb7b6179e Mon Sep 17 00:00:00 2001 From: Guillaume Masclet Date: Thu, 20 Nov 2025 11:14:44 +0100 Subject: [PATCH 2/3] LKE-14245: Post-review --- src/api/GraphEdge/index.ts | 4 +++- src/api/GraphEdge/types.ts | 2 +- src/api/GraphNode/index.ts | 4 +++- src/api/import/index.ts | 3 ++- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/api/GraphEdge/index.ts b/src/api/GraphEdge/index.ts index a9c0625bb..a1f76e482 100644 --- a/src/api/GraphEdge/index.ts +++ b/src/api/GraphEdge/index.ts @@ -63,7 +63,9 @@ export class GraphEdgeAPI extends Request { } /** - * Add a chunk of edges to the graph. + * Add a chunk of edges to the graph. Optionally accepts an import ID parameter. If it is + * provided, the edges are linked to this import (via a hidden property). Otherwise, the + * edges are created without any link. */ public bulkCreateEdges(params: BulkCreateEdgesParams) { return this.request({ diff --git a/src/api/GraphEdge/types.ts b/src/api/GraphEdge/types.ts index 755f5d8ec..1b39f52c4 100644 --- a/src/api/GraphEdge/types.ts +++ b/src/api/GraphEdge/types.ts @@ -29,6 +29,6 @@ export interface IDeleteEdgeParams extends IDataSourceParams { } export interface BulkCreateEdgesParams extends IDataSourceParams { - nodes: Omit; + edges: Omit; importId?: number; } diff --git a/src/api/GraphNode/index.ts b/src/api/GraphNode/index.ts index 0fb9ca497..c31e62e3c 100644 --- a/src/api/GraphNode/index.ts +++ b/src/api/GraphNode/index.ts @@ -100,7 +100,9 @@ export class GraphNodeAPI extends Request { } /** - * Add a chunk of nodes to the graph. + * Add a chunk of nodes to the graph. Optionally accepts an import ID parameter. If it is + * provided, the nodes are linked to this import (via a hidden property). Otherwise, the + * nodes are created without any link. */ public bulkCreateNodes(params: BulkCreateNodesParams) { return this.request({ diff --git a/src/api/import/index.ts b/src/api/import/index.ts index 865eb96cf..1eb19badd 100644 --- a/src/api/import/index.ts +++ b/src/api/import/index.ts @@ -27,7 +27,8 @@ export class ImportAPI extends Request { } /** - * List all the existing imports. + * List all the existing imports (for the current user if they are not admin, or for all users + * if the current user is an admin). */ getImports(this: Request<{items: Import[]}>) { return this.request({ From 2067c2a03cc78d06d1657626a1772b38dfdde317 Mon Sep 17 00:00:00 2001 From: Guillaume Masclet Date: Wed, 26 Nov 2025 16:03:23 +0100 Subject: [PATCH 3/3] LKE-14245: Add import templates API --- src/api/import/index.ts | 61 +++++++++++++++++++++++++++++++-- src/api/import/types.ts | 75 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 133 insertions(+), 3 deletions(-) diff --git a/src/api/import/index.ts b/src/api/import/index.ts index 1eb19badd..487e9581c 100644 --- a/src/api/import/index.ts +++ b/src/api/import/index.ts @@ -7,13 +7,70 @@ import {Request} from '../../http/request'; import {LkErrorKey} from '../../http/response'; -import {CreateImportParams, DeleteImportDataParams, Import} from './types'; +import { + CreateImportParams, + CreateImportTemplateParams, + DeleteImportDataParams, + DeleteImportTemplateParams, + GetImportTemplatesParams, + Import, + ImportTemplate, + UpdateImportTemplateParams +} from './types'; export * from './types'; -const {UNAUTHORIZED, DATA_SOURCE_UNAVAILABLE, FORBIDDEN} = LkErrorKey; +const {UNAUTHORIZED, DATA_SOURCE_UNAVAILABLE, FORBIDDEN, NOT_FOUND} = LkErrorKey; export class ImportAPI extends Request { + /** + * Create a new import template. + */ + createImportTemplate(this: Request, params: CreateImportTemplateParams) { + return this.request({ + errors: [UNAUTHORIZED, FORBIDDEN, DATA_SOURCE_UNAVAILABLE], + url: '/:sourceKey/imports/templates', + method: 'POST', + params: params + }); + } + + /** + * Update an existing import template. + */ + updateImportTemplate(this: Request, params: UpdateImportTemplateParams) { + return this.request({ + errors: [UNAUTHORIZED, FORBIDDEN, DATA_SOURCE_UNAVAILABLE, NOT_FOUND], + url: '/:sourceKey/imports/templates/:id', + method: 'PATCH', + params: params + }); + } + + /** + * Delete an existing import template. + */ + deleteImportTemplate(params: DeleteImportTemplateParams) { + return this.request({ + errors: [UNAUTHORIZED, FORBIDDEN, DATA_SOURCE_UNAVAILABLE, NOT_FOUND], + url: '/:sourceKey/imports/templates/:id', + method: 'DELETE', + params: params + }); + } + + /** + * List all the import templates (the publicly shared ones and the private ones owned by the user). + */ + getImportTemplates(this: Request<{items: ImportTemplate[]}>, params: GetImportTemplatesParams) { + return this.request({ + errors: [UNAUTHORIZED, FORBIDDEN, DATA_SOURCE_UNAVAILABLE], + url: '/:sourceKey/imports/templates', + method: 'GET', + params: params + }); + } + /** * Create a new import. */ diff --git a/src/api/import/types.ts b/src/api/import/types.ts index d7c32b7ee..0da7c68a1 100644 --- a/src/api/import/types.ts +++ b/src/api/import/types.ts @@ -4,7 +4,80 @@ * * - Created on 2025-11-18. */ -import {DeletableUser, IDataSourceParams} from '../commonTypes'; +import {DeletableUser, IDataSourceParams, SharingMode} from '../commonTypes'; +import {EntityType} from '../GraphSchema'; + +export type CreateImportTemplateParams = + | CreateNodeImportTemplateParams + | CreateEdgeImportTemplateParams; + +export interface CreateNodeImportTemplateParams extends CreateBaseImportTemplateParams { + entityType: EntityType.NODE; +} + +export interface CreateEdgeImportTemplateParams extends CreateBaseImportTemplateParams { + entityType: EntityType.EDGE; + sourceNode: NodeReference; + targetNode: NodeReference; +} + +interface CreateBaseImportTemplateParams extends IDataSourceParams { + name: string; + description?: string; + sharing?: SharingMode.PRIVATE | SharingMode.SOURCE; + /** + * The target node category / edge type. + */ + itemType: string; + /** + * How to map imported fields to node/edge properties. + */ + properties: PropertyMapping[]; +} + +interface PropertyMapping { + /** + * The field in the imported file. + */ + sourceField: string; + /** + * The destination property key on the node/edge. + */ + targetProperty: string; +} + +interface NodeReference { + /** + * The field in the imported file. + */ + sourceField: string; + /** + * The destination node category. + */ + targetCategory: string; + /** + * The destination property key on the node. If it is undefined, the destination is the native + * ID of the node. + */ + targetProperty?: string; +} + +export type UpdateImportTemplateParams = CreateImportTemplateParams & { + id: number; +}; + +export interface DeleteImportTemplateParams extends IDataSourceParams { + id: number; +} + +export interface GetImportTemplatesParams extends IDataSourceParams { + entityType?: EntityType; +} + +export type ImportTemplate = CreateImportTemplateParams & { + id: number; + sourceKey: string; +}; export interface CreateImportParams extends IDataSourceParams { /**