Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kzlprd 166 upsert assets #323

Merged
merged 3 commits into from
Jan 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions doc/2/controllers/assets/upsert/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
---
code: true
type: page
title: upsert
description: Update or Create an asset
---

# upsert

Update or Create an asset.
The Upsert operation allows you to create a new asset or update an existing one if it already exists. This operation is useful when you want to ensure that an asset is either created or updated in a single request.

## Query Syntax

### HTTP

```http
URL: http://kuzzle:7512/_/device-manager/:engineId/assets/:_id
Method: POST
```

## Other protocols

```js
{
"controller": "device-manager/assets",
"action": "upsert",
"engineId": "<engineId>",
"_id": "<assetId>",
"body": {
"metadata": {
"<metadata name>": "<metadata value>"
}
}
}
```

---

## Arguments

- `engineId`: Engine ID
- `_id`: Asset ID

## Body properties

- `metadata`: Object containing metadata

---

## Response

```js
{
"status": 200,
"error": null,
"controller": "device-manager/assets",
"action": "update",
"requestId": "<unique request identifier>",
"result": {
"_id": "<assetId>",
"_source": {
// Asset content
},
}
}
```
72 changes: 71 additions & 1 deletion lib/modules/asset/AssetService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export class AssetService extends BaseService {
}

/**
* Updates an asset metadata
* Update an asset metadata
*/
public async update(
engineId: string,
Expand Down Expand Up @@ -135,6 +135,73 @@ export class AssetService extends BaseService {
});
}

/**
* Update or Create an asset metadata
*/
public async upsert(
engineId: string,
assetId: string,
model: string,
reference: string,
metadata: Metadata,
request: KuzzleRequest
): Promise<KDocument<AssetContent>> {
return lock(`asset:${engineId}:${assetId}`, async () => {
const asset = await this.get(engineId, assetId, request).catch(
() => null
);

if (!asset) {
return this.create(engineId, model, reference, metadata, request);
}

const updatedPayload = await this.app.trigger<EventAssetUpdateBefore>(
"device-manager:asset:update:before",
Leagian marked this conversation as resolved.
Show resolved Hide resolved
{ asset, metadata }
);

const updatedAsset = await this.updateDocument<AssetContent>(
request,
{
_id: assetId,
_source: { metadata: updatedPayload.metadata },
},
{
collection: InternalCollection.ASSETS,
engineId,
},
{ source: true }
);

await this.assetHistoryService.add<AssetHistoryEventMetadata>(engineId, [
{
asset: updatedAsset._source,
event: {
metadata: {
names: Object.keys(flattenObject(updatedPayload.metadata)),
},
name: "metadata",
},
id: updatedAsset._id,
timestamp: Date.now(),
},
]);

await this.app.trigger<EventAssetUpdateAfter>(
"device-manager:asset:update:after",
Leagian marked this conversation as resolved.
Show resolved Hide resolved
{
asset: updatedAsset,
metadata: updatedPayload.metadata,
}
);

return updatedAsset;
});
}

/**
* Create an asset metadata
*/
public async create(
engineId: string,
model: string,
Expand Down Expand Up @@ -207,6 +274,9 @@ export class AssetService extends BaseService {
});
}

/**
* Delete an asset metadata
*/
public async delete(
engineId: string,
assetId: string,
Expand Down
26 changes: 26 additions & 0 deletions lib/modules/asset/AssetsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { AssetService } from "./AssetService";
import { AssetSerializer } from "./model/AssetSerializer";
import {
ApiAssetCreateResult,
ApiAssetUpsertResult,
ApiAssetDeleteResult,
ApiAssetGetMeasuresResult,
ApiAssetGetResult,
Expand All @@ -37,6 +38,12 @@ export class AssetsController {
handler: this.create.bind(this),
http: [{ path: "device-manager/:engineId/assets", verb: "post" }],
},
upsert: {
handler: this.upsert.bind(this),
http: [
{ path: "device-manager/:engineId/assets/:_id", verb: "post" },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This path does not reflect the behaviour of this action

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used POST by following this : https://docs.kuzzle.io/core/2/api/controllers/document/upsert/
Should I change it to PUT?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nah, you are right I though the path was the same as the update action, but nvm it's okay :)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no problem !

],
},
delete: {
handler: this.delete.bind(this),
http: [
Expand Down Expand Up @@ -129,6 +136,25 @@ export class AssetsController {
return AssetSerializer.serialize(asset);
}

async upsert(request: KuzzleRequest): Promise<ApiAssetUpsertResult> {
const engineId = request.getString("engineId");
const assetId = request.getId();
const model = request.getBodyString("model");
const reference = request.getBodyString("reference");
const metadata = request.getBodyObject("metadata");

const upsertAsset = await this.assetService.upsert(
engineId,
assetId,
model,
reference,
metadata,
request
);

return AssetSerializer.serialize(upsertAsset);
}

async update(request: KuzzleRequest): Promise<ApiAssetUpdateResult> {
const assetId = request.getId();
const engineId = request.getString("engineId");
Expand Down
17 changes: 17 additions & 0 deletions lib/modules/asset/types/AssetApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,23 @@ export interface ApiAssetUpdateRequest extends AssetsControllerRequest {
}
export type ApiAssetUpdateResult = KDocument<AssetContent>;

export interface ApiAssetUpsertRequest extends AssetsControllerRequest {
action: "upsert";

_id: string;

refresh?: string;

body: {
model: string;

reference: string;

metadata: Metadata;
};
}
export type ApiAssetUpsertResult = KDocument<AssetContent>;

export interface ApiAssetCreateRequest extends AssetsControllerRequest {
action: "create";

Expand Down
1 change: 1 addition & 0 deletions lib/modules/shared/services/BaseService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
ArgsDocumentControllerCreate,
ArgsDocumentControllerDelete,
ArgsDocumentControllerUpdate,
ArgsDocumentControllerUpsert,

Check warning on line 5 in lib/modules/shared/services/BaseService.ts

View workflow job for this annotation

GitHub Actions / Lint

'ArgsDocumentControllerUpsert' is defined but never used
Backend,
BaseRequest,
DocumentSearchResult,
Expand Down
64 changes: 64 additions & 0 deletions tests/scenario/migrated/asset-controller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,4 +172,68 @@ describe("features/Asset/Controller", () => {
sdk.document.exists("engine-kuzzle", "devices", "DummyTemp-foobar")
).resolves.toBe(true);
});

it("Upsert asset", async () => {
const response = await sdk.query({
controller: "device-manager/assets",
action: "upsert",
engineId: "engine-kuzzle",
_id: "Container-linked2",
body: {
model: "Container",
reference: "linked2",
metadata: { height: 21, weight: 42 },
},
});

expect(response).toBeDefined();
expect(response.result).toBeDefined();
expect(response.result._id).toEqual("Container-linked2");
expect(response.result._source.model).toEqual("Container");
expect(response.result._source.reference).toEqual("linked2");
expect(response.result._source.metadata).toEqual({
height: 21,
trailer: null,
weight: 42,
});
});

it("Upsert asset - update existing asset", async () => {
// create asset
await sdk.query({
controller: "device-manager/assets",
action: "upsert",
engineId: "engine-kuzzle",
_id: "Container-linked2",
body: {
model: "Container",
reference: "linked2",
metadata: { height: 21, trailer: null, weight: 42 },
},
});

// update asset
const response = await sdk.query({
controller: "device-manager/assets",
action: "upsert",
engineId: "engine-kuzzle",
_id: "Container-linked2",
body: {
model: "Container",
reference: "linked2",
metadata: { height: 22, trailer: null, weight: 43 },
},
});

expect(response).toBeDefined();
expect(response.result).toBeDefined();
expect(response.result._id).toEqual("Container-linked2");
expect(response.result._source.model).toEqual("Container");
expect(response.result._source.reference).toEqual("linked2");
expect(response.result._source.metadata).toEqual({
height: 22,
trailer: null,
weight: 43,
});
});
});
Loading