From 32d43d6147c9e419367c73f6370cf2add1903540 Mon Sep 17 00:00:00 2001 From: roicohen326 <213414225+roicohen326@users.noreply.github.com> Date: Tue, 23 Jun 2026 15:57:17 +0300 Subject: [PATCH 1/4] feat: add keywords field to API response and OpenAPI spec --- openapi3.yaml | 9 +++++-- src/DAL/convertors/recordModelConverter.ts | 3 +++ tests/unit/DAL/recordModelConverter.spec.ts | 26 +++++++++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/openapi3.yaml b/openapi3.yaml index e23a310..43454bf 100644 --- a/openapi3.yaml +++ b/openapi3.yaml @@ -293,8 +293,13 @@ components: description: raster catalog find model properties: metadata: - $ref: >- - ./Schema/fullLayerMetadata.yaml#/components/schemas/fullLayerMetadata + allOf: + - $ref: >- + ./Schema/fullLayerMetadata.yaml#/components/schemas/fullLayerMetadata + - properties: + keywords: + type: string + description: The keywords of the product links: type: array items: diff --git a/src/DAL/convertors/recordModelConverter.ts b/src/DAL/convertors/recordModelConverter.ts index 1df5365..c5cc4bf 100644 --- a/src/DAL/convertors/recordModelConverter.ts +++ b/src/DAL/convertors/recordModelConverter.ts @@ -115,6 +115,9 @@ export class RecordModelConvertor { }); metadata.sensors = record.sensors !== '' ? record.sensors.split(',') : []; metadata.region = record.region ? record.region.split(',') : []; + if (record.keywords != null) { + (metadata as unknown as Record)['keywords'] = record.keywords; + } if (typeof metadata.footprint === 'string') { metadata.footprint = JSON.parse(metadata.footprint) as GeoJSON; } diff --git a/tests/unit/DAL/recordModelConverter.spec.ts b/tests/unit/DAL/recordModelConverter.spec.ts index 9ea45e3..fd246ac 100644 --- a/tests/unit/DAL/recordModelConverter.spec.ts +++ b/tests/unit/DAL/recordModelConverter.spec.ts @@ -315,6 +315,32 @@ describe('RecordModelConverter', () => { }); }); + describe('entityToModel - keywords', () => { + it('converted model includes keywords when entity has keywords value', () => { + const entity = { + sensors: '', + region: null, + keywords: 'satellite,aerial', + } as unknown as RecordEntity; + + const model = convertor.entityToModel(entity); + + expect((model.metadata as unknown as Record)['keywords']).toBe('satellite,aerial'); + }); + + it('converted model omits keywords when entity keywords is null', () => { + const entity = { + sensors: '', + region: null, + keywords: null, + } as unknown as RecordEntity; + + const model = convertor.entityToModel(entity); + + expect((model.metadata as unknown as Record)['keywords']).toBeUndefined(); + }); + }); + describe('findModelToEntity', () => { it('find should return the given attribute', () => { const testUpdateRecordRequest = { From 3711ea6d5e4a61a5ae6221b35a571a946b514fc1 Mon Sep 17 00:00:00 2001 From: Roi Cohen <213414225+roicohen326@users.noreply.github.com> Date: Wed, 24 Jun 2026 09:13:53 +0300 Subject: [PATCH 2/4] Update description for keywords property --- openapi3.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openapi3.yaml b/openapi3.yaml index 43454bf..3ab257f 100644 --- a/openapi3.yaml +++ b/openapi3.yaml @@ -299,7 +299,7 @@ components: - properties: keywords: type: string - description: The keywords of the product + description: Product's keywords links: type: array items: From 7da025b78e60cc4aef0d8c05045c26946fb2c81a Mon Sep 17 00:00:00 2001 From: roicohen326 <213414225+roicohen326@users.noreply.github.com> Date: Wed, 24 Jun 2026 17:31:22 +0300 Subject: [PATCH 3/4] refactor: drop keywords cast now that LayerMetadata exposes it --- src/DAL/convertors/recordModelConverter.ts | 3 --- tests/unit/DAL/recordModelConverter.spec.ts | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/DAL/convertors/recordModelConverter.ts b/src/DAL/convertors/recordModelConverter.ts index c5cc4bf..1df5365 100644 --- a/src/DAL/convertors/recordModelConverter.ts +++ b/src/DAL/convertors/recordModelConverter.ts @@ -115,9 +115,6 @@ export class RecordModelConvertor { }); metadata.sensors = record.sensors !== '' ? record.sensors.split(',') : []; metadata.region = record.region ? record.region.split(',') : []; - if (record.keywords != null) { - (metadata as unknown as Record)['keywords'] = record.keywords; - } if (typeof metadata.footprint === 'string') { metadata.footprint = JSON.parse(metadata.footprint) as GeoJSON; } diff --git a/tests/unit/DAL/recordModelConverter.spec.ts b/tests/unit/DAL/recordModelConverter.spec.ts index fd246ac..f499d48 100644 --- a/tests/unit/DAL/recordModelConverter.spec.ts +++ b/tests/unit/DAL/recordModelConverter.spec.ts @@ -325,7 +325,7 @@ describe('RecordModelConverter', () => { const model = convertor.entityToModel(entity); - expect((model.metadata as unknown as Record)['keywords']).toBe('satellite,aerial'); + expect(model.metadata?.keywords).toBe('satellite,aerial'); }); it('converted model omits keywords when entity keywords is null', () => { @@ -337,7 +337,7 @@ describe('RecordModelConverter', () => { const model = convertor.entityToModel(entity); - expect((model.metadata as unknown as Record)['keywords']).toBeUndefined(); + expect(model.metadata?.keywords).toBeUndefined(); }); }); From fea896512cbb1ed7582a8e8da162c9cb8d2f19ea Mon Sep 17 00:00:00 2001 From: roicohen326 <213414225+roicohen326@users.noreply.github.com> Date: Mon, 29 Jun 2026 11:06:54 +0300 Subject: [PATCH 4/4] refactor: expose keywords via PycswLayerCatalogRecord Pick and simplify openapi ref --- openapi3.yaml | 9 ++------- src/DAL/convertors/recordModelConverter.ts | 11 +++++++---- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/openapi3.yaml b/openapi3.yaml index 3ab257f..e23a310 100644 --- a/openapi3.yaml +++ b/openapi3.yaml @@ -293,13 +293,8 @@ components: description: raster catalog find model properties: metadata: - allOf: - - $ref: >- - ./Schema/fullLayerMetadata.yaml#/components/schemas/fullLayerMetadata - - properties: - keywords: - type: string - description: Product's keywords + $ref: >- + ./Schema/fullLayerMetadata.yaml#/components/schemas/fullLayerMetadata links: type: array items: diff --git a/src/DAL/convertors/recordModelConverter.ts b/src/DAL/convertors/recordModelConverter.ts index 1df5365..3782b9b 100644 --- a/src/DAL/convertors/recordModelConverter.ts +++ b/src/DAL/convertors/recordModelConverter.ts @@ -1,7 +1,7 @@ import { singleton } from 'tsyringe'; import { GeoJSON } from 'geojson'; import { GeoJSONGeometry, stringify as geoJsonToWkt } from 'wellknown'; -import { LayerMetadata, Link, IRasterCatalogUpsertRequestBody, RecordStatus } from '@map-colonies/mc-model-types'; +import { LayerMetadata, Link, IRasterCatalogUpsertRequestBody, RecordStatus, PycswLayerCatalogRecord } from '@map-colonies/mc-model-types'; import { IEditRecordRequest, IFindRecordRequest, IFindRecordResponse, IUpdateRecordRequest } from '../../common/dataModels/records'; import { RecordEntity } from '../entity/generated'; @@ -106,15 +106,18 @@ export class RecordModelConvertor { return links; } - private recordToMetadata(record: RecordEntity): LayerMetadata { - const metadata = new LayerMetadata(); - Object.keys(metadata).forEach((key) => { + private recordToMetadata(record: RecordEntity): LayerMetadata & Pick { + const metadata = new LayerMetadata() as LayerMetadata & Pick; + Object.keys(new LayerMetadata()).forEach((key) => { if (record[key as keyof RecordEntity] !== null) { (metadata[key as keyof LayerMetadata] as unknown) = record[key as keyof RecordEntity]; } }); metadata.sensors = record.sensors !== '' ? record.sensors.split(',') : []; metadata.region = record.region ? record.region.split(',') : []; + if (record.keywords != null) { + metadata.keywords = record.keywords; + } if (typeof metadata.footprint === 'string') { metadata.footprint = JSON.parse(metadata.footprint) as GeoJSON; }