From ce9b6d596ec40b889cc2767f6d811720b816d0bb Mon Sep 17 00:00:00 2001 From: vitaligi <54726763+vitaligi@users.noreply.github.com> Date: Thu, 16 Oct 2025 15:22:06 +0300 Subject: [PATCH 01/11] feat: raster layer metadata schema --- src/schemas/core/metadata.schema.ts | 110 ++++++++++++++++++++++++++++ src/types/core/metadata.type.ts | 32 +------- 2 files changed, 112 insertions(+), 30 deletions(-) create mode 100644 src/schemas/core/metadata.schema.ts diff --git a/src/schemas/core/metadata.schema.ts b/src/schemas/core/metadata.schema.ts new file mode 100644 index 0000000..0b032b0 --- /dev/null +++ b/src/schemas/core/metadata.schema.ts @@ -0,0 +1,110 @@ +import { RecordStatus, RecordType } from '@map-colonies/types'; +import z from 'zod'; +import { CORE_VALIDATIONS, INGESTION_VALIDATIONS, TileOutputFormat, Transparency } from '../../constants'; +import { multiPolygonSchema, polygonSchema } from './geo.schema'; +import { rasterProductTypeSchema, resourceIdSchema, versionSchema } from './job.schema'; +import { tilesMimeFormatSchema } from './mime.schema'; + +export const rasterLayerMetadataSchema = z.object({ + id: z.string().uuid(), + metadata: z.object({ + id: z.string().uuid(), + srs: z.literal('4326'), + productVersion: versionSchema, + maxResolutionDeg: z + .number({ message: 'Max resolution degree should be a number' }) + .min(CORE_VALIDATIONS.resolutionDeg.min, { + message: `Max resolution degree should not be less than ${CORE_VALIDATIONS.resolutionDeg.min}`, + }) + .max(CORE_VALIDATIONS.resolutionDeg.max, { + message: `Max resolution degree should not be larger than ${CORE_VALIDATIONS.resolutionDeg.max}`, + }), + minResolutionDeg: z + .number({ message: 'Min resolution degree should be a number' }) + .min(CORE_VALIDATIONS.resolutionDeg.min, { + message: `Min resolution degree should not be less than ${CORE_VALIDATIONS.resolutionDeg.min}`, + }) + .max(CORE_VALIDATIONS.resolutionDeg.max, { + message: `Min resolution degree should not be larger than ${CORE_VALIDATIONS.resolutionDeg.max}`, + }), + scale: z.number().min(INGESTION_VALIDATIONS.scale.min).max(INGESTION_VALIDATIONS.scale.max).optional(), + creationDateUTC: z.coerce.date().optional(), + ingestionDate: z.coerce.date().optional(), + minHorizontalAccuracyCE90: z + .number({ message: 'Min horizontal accuracy CE90 should be a number' }) + .min(INGESTION_VALIDATIONS.horizontalAccuracyCE90.min, { + message: `Min horizontal accuracy CE90 should not be less than ${INGESTION_VALIDATIONS.horizontalAccuracyCE90.min}`, + }) + .max(INGESTION_VALIDATIONS.horizontalAccuracyCE90.max, { + message: `Min horizontal accuracy CE90 should not be larger than ${INGESTION_VALIDATIONS.horizontalAccuracyCE90.max}`, + }), + maxHorizontalAccuracyCE90: z + .number({ message: 'Max horizontal accuracy CE90 should be a number' }) + .min(INGESTION_VALIDATIONS.horizontalAccuracyCE90.min, { + message: `Max horizontal accuracy CE90 should not be less than ${INGESTION_VALIDATIONS.horizontalAccuracyCE90.min}`, + }) + .max(INGESTION_VALIDATIONS.horizontalAccuracyCE90.max, { + message: `Max horizontal accuracy CE90 should not be larger than ${INGESTION_VALIDATIONS.horizontalAccuracyCE90.max}`, + }), + region: z.array(z.string().min(1)).min(1), + sensors: z + .array( + z.string({ message: 'Sensors should be an array of strings' }).regex(new RegExp(INGESTION_VALIDATIONS.sensor.pattern), { + message: 'Sensors should be an array with items not starting or ending with whitespace characters', + }), + { message: 'Sensors should be an array' } + ) + .min(1, { message: 'Sensors should have an array length of at least 1' }), + imagingTimeBeginUTC: z.coerce.date({ message: 'Imaging time begin UTC should be a datetime' }), + imagingTimeEndUTC: z.coerce.date({ message: 'Imaging time end UTC should be a datetime' }), + updateDateUTC: z.coerce.date().optional(), + maxResolutionMeter: z + .number({ message: 'Max resolution meter should be a number' }) + .min(INGESTION_VALIDATIONS.resolutionMeter.min, { + message: `Max resolution meter should not be less than ${INGESTION_VALIDATIONS.resolutionMeter.min}`, + }) + .max(INGESTION_VALIDATIONS.resolutionMeter.max, { + message: `Max resolution meter should not be larger than ${INGESTION_VALIDATIONS.resolutionMeter.max}`, + }), + minResolutionMeter: z + .number({ message: 'Min horizontal accuracy CE90 should be a number' }) + .min(INGESTION_VALIDATIONS.horizontalAccuracyCE90.min, { + message: `Min horizontal accuracy CE90 should not be less than ${INGESTION_VALIDATIONS.horizontalAccuracyCE90.min}`, + }) + .max(INGESTION_VALIDATIONS.horizontalAccuracyCE90.max, { + message: `Min horizontal accuracy CE90 should not be larger than ${INGESTION_VALIDATIONS.horizontalAccuracyCE90.max}`, + }), + productSubType: z.string().optional(), + productBoundingBox: z + .string({ message: 'Product bounding box should be a string' }) + .regex(new RegExp(INGESTION_VALIDATIONS.boundingBox.pattern), { + message: 'Product bounding box must be of the shape min_x,min_y,max_x,max_y', + }), + displayPath: z.string().uuid(), + transparency: z.nativeEnum(Transparency), + tileMimeFormat: tilesMimeFormatSchema, + tileOutputFormat: z.nativeEnum(TileOutputFormat), + productStatus: z.nativeEnum(RecordStatus).optional(), + type: z.literal(RecordType.RECORD_RASTER), + classification: z + .string() + .regex(new RegExp(INGESTION_VALIDATIONS.classification.pattern), { message: 'Classification value must be between 0 and 100' }), + productName: z.string(), + description: z.string(), + srsName: z.literal('WGS84GEO'), + producerName: z.string(), + footprint: polygonSchema.or(multiPolygonSchema), + productId: resourceIdSchema, + productType: rasterProductTypeSchema, + }), + links: z + .array( + z.object({ + name: z.string().optional(), + description: z.string().optional(), + protocol: z.string().optional(), + url: z.string().optional(), + }) + ) + .optional(), +}); diff --git a/src/types/core/metadata.type.ts b/src/types/core/metadata.type.ts index b7b8e64..b193951 100644 --- a/src/types/core/metadata.type.ts +++ b/src/types/core/metadata.type.ts @@ -1,34 +1,6 @@ import z from 'zod'; -import { IMetadataCommonModel, RecordStatus, TilesMimeFormat } from '@map-colonies/types'; -import { TileOutputFormat, Transparency } from '../../constants/core'; import { aggregationFeatureSchema } from '../../schemas'; +import type { rasterLayerMetadataSchema } from '../../schemas/core/metadata.schema'; -// eslint-disable-next-line @typescript-eslint/consistent-type-definitions -export type RasterLayerMetadata = { - id: string; - srs: string; - productVersion: string; - maxResolutionDeg: number; - minResolutionDeg: number; - scale?: number; - creationDateUTC?: Date; - ingestionDate?: Date; - minHorizontalAccuracyCE90: number; - maxHorizontalAccuracyCE90: number; - region: string[]; - sensors: string[]; - imagingTimeBeginUTC: Date; - imagingTimeEndUTC: Date; - updateDateUTC?: Date; - maxResolutionMeter: number; - minResolutionMeter: number; - productSubType?: string; - productBoundingBox?: string; - displayPath: string; - transparency: Transparency; - tileMimeFormat: TilesMimeFormat; - tileOutputFormat: TileOutputFormat; - productStatus?: RecordStatus; -} & IMetadataCommonModel; - +export type RasterLayerMetadata = z.infer; export type AggregationFeature = z.infer; From 93a4413a5caec684f26dcf1fc52174757fec1bb0 Mon Sep 17 00:00:00 2001 From: vitaligi <54726763+vitaligi@users.noreply.github.com> Date: Thu, 16 Oct 2025 15:25:13 +0300 Subject: [PATCH 02/11] refactor: import type --- src/types/core/metadata.type.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/core/metadata.type.ts b/src/types/core/metadata.type.ts index b193951..0237322 100644 --- a/src/types/core/metadata.type.ts +++ b/src/types/core/metadata.type.ts @@ -1,5 +1,5 @@ import z from 'zod'; -import { aggregationFeatureSchema } from '../../schemas'; +import type { aggregationFeatureSchema } from '../../schemas'; import type { rasterLayerMetadataSchema } from '../../schemas/core/metadata.schema'; export type RasterLayerMetadata = z.infer; From c291edc3c73724e9e58ec308a5a58693ae71ad43 Mon Sep 17 00:00:00 2001 From: vitaligi <54726763+vitaligi@users.noreply.github.com> Date: Thu, 16 Oct 2025 15:43:26 +0300 Subject: [PATCH 03/11] refactor: stricter validation for raster layer metadata --- src/schemas/core/metadata.schema.ts | 215 +++++++++++++++------------- 1 file changed, 119 insertions(+), 96 deletions(-) diff --git a/src/schemas/core/metadata.schema.ts b/src/schemas/core/metadata.schema.ts index 0b032b0..71bb44d 100644 --- a/src/schemas/core/metadata.schema.ts +++ b/src/schemas/core/metadata.schema.ts @@ -5,106 +5,129 @@ import { multiPolygonSchema, polygonSchema } from './geo.schema'; import { rasterProductTypeSchema, resourceIdSchema, versionSchema } from './job.schema'; import { tilesMimeFormatSchema } from './mime.schema'; -export const rasterLayerMetadataSchema = z.object({ - id: z.string().uuid(), - metadata: z.object({ +export const rasterLayerMetadataSchema = z + .object({ id: z.string().uuid(), - srs: z.literal('4326'), - productVersion: versionSchema, - maxResolutionDeg: z - .number({ message: 'Max resolution degree should be a number' }) - .min(CORE_VALIDATIONS.resolutionDeg.min, { - message: `Max resolution degree should not be less than ${CORE_VALIDATIONS.resolutionDeg.min}`, + metadata: z + .object({ + id: z.string().uuid(), + srs: z.literal('4326'), + productVersion: versionSchema, + maxResolutionDeg: z + .number({ message: 'Max resolution degree should be a number' }) + .min(CORE_VALIDATIONS.resolutionDeg.min, { + message: `Max resolution degree should not be less than ${CORE_VALIDATIONS.resolutionDeg.min}`, + }) + .max(CORE_VALIDATIONS.resolutionDeg.max, { + message: `Max resolution degree should not be larger than ${CORE_VALIDATIONS.resolutionDeg.max}`, + }), + minResolutionDeg: z + .number({ message: 'Min resolution degree should be a number' }) + .min(CORE_VALIDATIONS.resolutionDeg.min, { + message: `Min resolution degree should not be less than ${CORE_VALIDATIONS.resolutionDeg.min}`, + }) + .max(CORE_VALIDATIONS.resolutionDeg.max, { + message: `Min resolution degree should not be larger than ${CORE_VALIDATIONS.resolutionDeg.max}`, + }), + scale: z.number().min(INGESTION_VALIDATIONS.scale.min).max(INGESTION_VALIDATIONS.scale.max).optional(), + creationDateUTC: z.coerce.date().optional(), + ingestionDate: z.coerce.date().optional(), + minHorizontalAccuracyCE90: z + .number({ message: 'Min horizontal accuracy CE90 should be a number' }) + .min(INGESTION_VALIDATIONS.horizontalAccuracyCE90.min, { + message: `Min horizontal accuracy CE90 should not be less than ${INGESTION_VALIDATIONS.horizontalAccuracyCE90.min}`, + }) + .max(INGESTION_VALIDATIONS.horizontalAccuracyCE90.max, { + message: `Min horizontal accuracy CE90 should not be larger than ${INGESTION_VALIDATIONS.horizontalAccuracyCE90.max}`, + }), + maxHorizontalAccuracyCE90: z + .number({ message: 'Max horizontal accuracy CE90 should be a number' }) + .min(INGESTION_VALIDATIONS.horizontalAccuracyCE90.min, { + message: `Max horizontal accuracy CE90 should not be less than ${INGESTION_VALIDATIONS.horizontalAccuracyCE90.min}`, + }) + .max(INGESTION_VALIDATIONS.horizontalAccuracyCE90.max, { + message: `Max horizontal accuracy CE90 should not be larger than ${INGESTION_VALIDATIONS.horizontalAccuracyCE90.max}`, + }), + region: z.array(z.string().min(1)).min(1), + sensors: z + .array( + z.string({ message: 'Sensors should be an array of strings' }).regex(new RegExp(INGESTION_VALIDATIONS.sensor.pattern), { + message: 'Sensors should be an array with items not starting or ending with whitespace characters', + }), + { message: 'Sensors should be an array' } + ) + .min(1, { message: 'Sensors should have an array length of at least 1' }), + imagingTimeBeginUTC: z.coerce.date({ message: 'Imaging time begin UTC should be a datetime' }), + imagingTimeEndUTC: z.coerce.date({ message: 'Imaging time end UTC should be a datetime' }), + updateDateUTC: z.coerce.date().optional(), + maxResolutionMeter: z + .number({ message: 'Max resolution meter should be a number' }) + .min(INGESTION_VALIDATIONS.resolutionMeter.min, { + message: `Max resolution meter should not be less than ${INGESTION_VALIDATIONS.resolutionMeter.min}`, + }) + .max(INGESTION_VALIDATIONS.resolutionMeter.max, { + message: `Max resolution meter should not be larger than ${INGESTION_VALIDATIONS.resolutionMeter.max}`, + }), + minResolutionMeter: z + .number({ message: 'Min horizontal accuracy CE90 should be a number' }) + .min(INGESTION_VALIDATIONS.horizontalAccuracyCE90.min, { + message: `Min horizontal accuracy CE90 should not be less than ${INGESTION_VALIDATIONS.horizontalAccuracyCE90.min}`, + }) + .max(INGESTION_VALIDATIONS.horizontalAccuracyCE90.max, { + message: `Min horizontal accuracy CE90 should not be larger than ${INGESTION_VALIDATIONS.horizontalAccuracyCE90.max}`, + }), + productSubType: z.string().optional(), + productBoundingBox: z + .string({ message: 'Product bounding box should be a string' }) + .regex(new RegExp(INGESTION_VALIDATIONS.boundingBox.pattern), { + message: 'Product bounding box must be of the shape min_x,min_y,max_x,max_y', + }), + displayPath: z.string().uuid(), + transparency: z.nativeEnum(Transparency), + tileMimeFormat: tilesMimeFormatSchema, + tileOutputFormat: z.nativeEnum(TileOutputFormat), + productStatus: z.nativeEnum(RecordStatus).optional(), + type: z.literal(RecordType.RECORD_RASTER), + classification: z + .string() + .regex(new RegExp(INGESTION_VALIDATIONS.classification.pattern), { message: 'Classification value must be between 0 and 100' }), + productName: z.string(), + description: z.string(), + srsName: z.literal('WGS84GEO'), + producerName: z.string(), + footprint: polygonSchema.or(multiPolygonSchema), + productId: resourceIdSchema, + productType: rasterProductTypeSchema, }) - .max(CORE_VALIDATIONS.resolutionDeg.max, { - message: `Max resolution degree should not be larger than ${CORE_VALIDATIONS.resolutionDeg.max}`, - }), - minResolutionDeg: z - .number({ message: 'Min resolution degree should be a number' }) - .min(CORE_VALIDATIONS.resolutionDeg.min, { - message: `Min resolution degree should not be less than ${CORE_VALIDATIONS.resolutionDeg.min}`, - }) - .max(CORE_VALIDATIONS.resolutionDeg.max, { - message: `Min resolution degree should not be larger than ${CORE_VALIDATIONS.resolutionDeg.max}`, - }), - scale: z.number().min(INGESTION_VALIDATIONS.scale.min).max(INGESTION_VALIDATIONS.scale.max).optional(), - creationDateUTC: z.coerce.date().optional(), - ingestionDate: z.coerce.date().optional(), - minHorizontalAccuracyCE90: z - .number({ message: 'Min horizontal accuracy CE90 should be a number' }) - .min(INGESTION_VALIDATIONS.horizontalAccuracyCE90.min, { - message: `Min horizontal accuracy CE90 should not be less than ${INGESTION_VALIDATIONS.horizontalAccuracyCE90.min}`, + .strict() + .refine( + (rasterLayerMetadata) => + rasterLayerMetadata.imagingTimeBeginUTC <= rasterLayerMetadata.imagingTimeEndUTC && rasterLayerMetadata.imagingTimeEndUTC <= new Date(), + { + message: 'Imaging time begin UTC should be less than or equal to imaging time end UTC and both less than or equal to current timestamp', + } + ) + .refine((rasterLayerMetadata) => rasterLayerMetadata.maxHorizontalAccuracyCE90 <= rasterLayerMetadata.minHorizontalAccuracyCE90, { + message: 'Max horizontal accuracy CE90 should be less than or equal to min horizontal accuracy CE90', }) - .max(INGESTION_VALIDATIONS.horizontalAccuracyCE90.max, { - message: `Min horizontal accuracy CE90 should not be larger than ${INGESTION_VALIDATIONS.horizontalAccuracyCE90.max}`, - }), - maxHorizontalAccuracyCE90: z - .number({ message: 'Max horizontal accuracy CE90 should be a number' }) - .min(INGESTION_VALIDATIONS.horizontalAccuracyCE90.min, { - message: `Max horizontal accuracy CE90 should not be less than ${INGESTION_VALIDATIONS.horizontalAccuracyCE90.min}`, + .refine((rasterLayerMetadata) => rasterLayerMetadata.maxResolutionDeg <= rasterLayerMetadata.minResolutionDeg, { + message: 'Max resolution degree should be less than or equal to min resolution degree', }) - .max(INGESTION_VALIDATIONS.horizontalAccuracyCE90.max, { - message: `Max horizontal accuracy CE90 should not be larger than ${INGESTION_VALIDATIONS.horizontalAccuracyCE90.max}`, + .refine((rasterLayerMetadata) => rasterLayerMetadata.maxResolutionMeter <= rasterLayerMetadata.minResolutionMeter, { + message: 'Max resolution meter should be less than or equal to min resolution meter', }), - region: z.array(z.string().min(1)).min(1), - sensors: z + links: z .array( - z.string({ message: 'Sensors should be an array of strings' }).regex(new RegExp(INGESTION_VALIDATIONS.sensor.pattern), { - message: 'Sensors should be an array with items not starting or ending with whitespace characters', - }), - { message: 'Sensors should be an array' } + z + .object({ + name: z.string().optional(), + description: z.string().optional(), + protocol: z.string().optional(), + url: z.string().optional(), + }) + .strict() ) - .min(1, { message: 'Sensors should have an array length of at least 1' }), - imagingTimeBeginUTC: z.coerce.date({ message: 'Imaging time begin UTC should be a datetime' }), - imagingTimeEndUTC: z.coerce.date({ message: 'Imaging time end UTC should be a datetime' }), - updateDateUTC: z.coerce.date().optional(), - maxResolutionMeter: z - .number({ message: 'Max resolution meter should be a number' }) - .min(INGESTION_VALIDATIONS.resolutionMeter.min, { - message: `Max resolution meter should not be less than ${INGESTION_VALIDATIONS.resolutionMeter.min}`, - }) - .max(INGESTION_VALIDATIONS.resolutionMeter.max, { - message: `Max resolution meter should not be larger than ${INGESTION_VALIDATIONS.resolutionMeter.max}`, - }), - minResolutionMeter: z - .number({ message: 'Min horizontal accuracy CE90 should be a number' }) - .min(INGESTION_VALIDATIONS.horizontalAccuracyCE90.min, { - message: `Min horizontal accuracy CE90 should not be less than ${INGESTION_VALIDATIONS.horizontalAccuracyCE90.min}`, - }) - .max(INGESTION_VALIDATIONS.horizontalAccuracyCE90.max, { - message: `Min horizontal accuracy CE90 should not be larger than ${INGESTION_VALIDATIONS.horizontalAccuracyCE90.max}`, - }), - productSubType: z.string().optional(), - productBoundingBox: z - .string({ message: 'Product bounding box should be a string' }) - .regex(new RegExp(INGESTION_VALIDATIONS.boundingBox.pattern), { - message: 'Product bounding box must be of the shape min_x,min_y,max_x,max_y', - }), - displayPath: z.string().uuid(), - transparency: z.nativeEnum(Transparency), - tileMimeFormat: tilesMimeFormatSchema, - tileOutputFormat: z.nativeEnum(TileOutputFormat), - productStatus: z.nativeEnum(RecordStatus).optional(), - type: z.literal(RecordType.RECORD_RASTER), - classification: z - .string() - .regex(new RegExp(INGESTION_VALIDATIONS.classification.pattern), { message: 'Classification value must be between 0 and 100' }), - productName: z.string(), - description: z.string(), - srsName: z.literal('WGS84GEO'), - producerName: z.string(), - footprint: polygonSchema.or(multiPolygonSchema), - productId: resourceIdSchema, - productType: rasterProductTypeSchema, - }), - links: z - .array( - z.object({ - name: z.string().optional(), - description: z.string().optional(), - protocol: z.string().optional(), - url: z.string().optional(), - }) - ) - .optional(), -}); + .optional(), + }) + .strict() + .describe('rasterLayerMetadataSchema'); From b100ce96d37e381df8a8b01a2a7ecb3e17fb7c08 Mon Sep 17 00:00:00 2001 From: vitaligi <54726763+vitaligi@users.noreply.github.com> Date: Thu, 16 Oct 2025 17:50:12 +0300 Subject: [PATCH 04/11] fix: duplicate id property --- src/schemas/core/metadata.schema.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/schemas/core/metadata.schema.ts b/src/schemas/core/metadata.schema.ts index 71bb44d..9b222e5 100644 --- a/src/schemas/core/metadata.schema.ts +++ b/src/schemas/core/metadata.schema.ts @@ -7,7 +7,6 @@ import { tilesMimeFormatSchema } from './mime.schema'; export const rasterLayerMetadataSchema = z .object({ - id: z.string().uuid(), metadata: z .object({ id: z.string().uuid(), From 9d2240c2b8a25d04c024572e98edf80abc13146b Mon Sep 17 00:00:00 2001 From: vitaligi <54726763+vitaligi@users.noreply.github.com> Date: Thu, 16 Oct 2025 17:52:37 +0300 Subject: [PATCH 05/11] refactor: rename file schema and type --- src/schemas/core/{metadata.schema.ts => layer.schema.ts} | 4 ++-- src/types/core/metadata.type.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename src/schemas/core/{metadata.schema.ts => layer.schema.ts} (98%) diff --git a/src/schemas/core/metadata.schema.ts b/src/schemas/core/layer.schema.ts similarity index 98% rename from src/schemas/core/metadata.schema.ts rename to src/schemas/core/layer.schema.ts index 9b222e5..1113b1b 100644 --- a/src/schemas/core/metadata.schema.ts +++ b/src/schemas/core/layer.schema.ts @@ -5,7 +5,7 @@ import { multiPolygonSchema, polygonSchema } from './geo.schema'; import { rasterProductTypeSchema, resourceIdSchema, versionSchema } from './job.schema'; import { tilesMimeFormatSchema } from './mime.schema'; -export const rasterLayerMetadataSchema = z +export const rasterLayerCatalogSchema = z .object({ metadata: z .object({ @@ -129,4 +129,4 @@ export const rasterLayerMetadataSchema = z .optional(), }) .strict() - .describe('rasterLayerMetadataSchema'); + .describe('rasterLayerCatalogSchema'); diff --git a/src/types/core/metadata.type.ts b/src/types/core/metadata.type.ts index 0237322..02d0fdc 100644 --- a/src/types/core/metadata.type.ts +++ b/src/types/core/metadata.type.ts @@ -1,6 +1,6 @@ import z from 'zod'; import type { aggregationFeatureSchema } from '../../schemas'; -import type { rasterLayerMetadataSchema } from '../../schemas/core/metadata.schema'; +import type { rasterLayerCatalogSchema } from '../../schemas/core/layer.schema'; -export type RasterLayerMetadata = z.infer; +export type RasterLayerCatalog = z.infer; export type AggregationFeature = z.infer; From a7e0bbf5f3b79b4536c5490715f74cddb2e67ef1 Mon Sep 17 00:00:00 2001 From: vitaligi <54726763+vitaligi@users.noreply.github.com> Date: Thu, 16 Oct 2025 23:54:46 +0300 Subject: [PATCH 06/11] refactor: modify optional properties based on db schema --- src/schemas/core/layer.schema.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/schemas/core/layer.schema.ts b/src/schemas/core/layer.schema.ts index 1113b1b..795ecbb 100644 --- a/src/schemas/core/layer.schema.ts +++ b/src/schemas/core/layer.schema.ts @@ -29,8 +29,8 @@ export const rasterLayerCatalogSchema = z message: `Min resolution degree should not be larger than ${CORE_VALIDATIONS.resolutionDeg.max}`, }), scale: z.number().min(INGESTION_VALIDATIONS.scale.min).max(INGESTION_VALIDATIONS.scale.max).optional(), - creationDateUTC: z.coerce.date().optional(), - ingestionDate: z.coerce.date().optional(), + creationDateUTC: z.coerce.date(), + ingestionDate: z.coerce.date(), minHorizontalAccuracyCE90: z .number({ message: 'Min horizontal accuracy CE90 should be a number' }) .min(INGESTION_VALIDATIONS.horizontalAccuracyCE90.min, { @@ -38,7 +38,8 @@ export const rasterLayerCatalogSchema = z }) .max(INGESTION_VALIDATIONS.horizontalAccuracyCE90.max, { message: `Min horizontal accuracy CE90 should not be larger than ${INGESTION_VALIDATIONS.horizontalAccuracyCE90.max}`, - }), + }) + .optional(), maxHorizontalAccuracyCE90: z .number({ message: 'Max horizontal accuracy CE90 should be a number' }) .min(INGESTION_VALIDATIONS.horizontalAccuracyCE90.min, { @@ -58,7 +59,7 @@ export const rasterLayerCatalogSchema = z .min(1, { message: 'Sensors should have an array length of at least 1' }), imagingTimeBeginUTC: z.coerce.date({ message: 'Imaging time begin UTC should be a datetime' }), imagingTimeEndUTC: z.coerce.date({ message: 'Imaging time end UTC should be a datetime' }), - updateDateUTC: z.coerce.date().optional(), + updateDateUTC: z.coerce.date(), maxResolutionMeter: z .number({ message: 'Max resolution meter should be a number' }) .min(INGESTION_VALIDATIONS.resolutionMeter.min, { @@ -80,7 +81,8 @@ export const rasterLayerCatalogSchema = z .string({ message: 'Product bounding box should be a string' }) .regex(new RegExp(INGESTION_VALIDATIONS.boundingBox.pattern), { message: 'Product bounding box must be of the shape min_x,min_y,max_x,max_y', - }), + }) + .optional(), displayPath: z.string().uuid(), transparency: z.nativeEnum(Transparency), tileMimeFormat: tilesMimeFormatSchema, @@ -90,10 +92,10 @@ export const rasterLayerCatalogSchema = z classification: z .string() .regex(new RegExp(INGESTION_VALIDATIONS.classification.pattern), { message: 'Classification value must be between 0 and 100' }), - productName: z.string(), - description: z.string(), + productName: z.string().optional(), + description: z.string().optional(), srsName: z.literal('WGS84GEO'), - producerName: z.string(), + producerName: z.string().optional(), footprint: polygonSchema.or(multiPolygonSchema), productId: resourceIdSchema, productType: rasterProductTypeSchema, From 27bd4a1afdccf005d866b9205b4b201c6d50fa5e Mon Sep 17 00:00:00 2001 From: vitaligi <54726763+vitaligi@users.noreply.github.com> Date: Thu, 16 Oct 2025 23:57:21 +0300 Subject: [PATCH 07/11] refactor: move type to other file --- src/types/core/layer.type.ts | 4 ++++ src/types/core/metadata.type.ts | 2 -- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/types/core/layer.type.ts b/src/types/core/layer.type.ts index 731b5e4..8da605a 100644 --- a/src/types/core/layer.type.ts +++ b/src/types/core/layer.type.ts @@ -1,4 +1,6 @@ +import type z from 'zod'; import { RasterProductTypes } from '../../constants'; +import type { rasterLayerCatalogSchema } from '../../schemas/core/layer.schema'; /** * Represents a layer name composed of a resourceId (equivalent to productId) and RasterProductType, joined by a hyphen. @@ -73,3 +75,5 @@ export interface LayerNameFormats { polygonPartsEntityName: PolygonPartsEntityName; layerName: LayerName; } + +export type RasterLayerCatalog = z.infer; diff --git a/src/types/core/metadata.type.ts b/src/types/core/metadata.type.ts index 02d0fdc..a106fe1 100644 --- a/src/types/core/metadata.type.ts +++ b/src/types/core/metadata.type.ts @@ -1,6 +1,4 @@ import z from 'zod'; import type { aggregationFeatureSchema } from '../../schemas'; -import type { rasterLayerCatalogSchema } from '../../schemas/core/layer.schema'; -export type RasterLayerCatalog = z.infer; export type AggregationFeature = z.infer; From 74c2ac5ff42f5f765e077265fdec405ee1abc5c5 Mon Sep 17 00:00:00 2001 From: vitaligi <54726763+vitaligi@users.noreply.github.com> Date: Thu, 16 Oct 2025 23:59:56 +0300 Subject: [PATCH 08/11] fix: handle case where minHorizontalAccuracyCE90 is undefined --- src/schemas/core/layer.schema.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/schemas/core/layer.schema.ts b/src/schemas/core/layer.schema.ts index 795ecbb..d3cf606 100644 --- a/src/schemas/core/layer.schema.ts +++ b/src/schemas/core/layer.schema.ts @@ -108,9 +108,15 @@ export const rasterLayerCatalogSchema = z message: 'Imaging time begin UTC should be less than or equal to imaging time end UTC and both less than or equal to current timestamp', } ) - .refine((rasterLayerMetadata) => rasterLayerMetadata.maxHorizontalAccuracyCE90 <= rasterLayerMetadata.minHorizontalAccuracyCE90, { - message: 'Max horizontal accuracy CE90 should be less than or equal to min horizontal accuracy CE90', - }) + .refine( + (rasterLayerMetadata) => + rasterLayerMetadata.minHorizontalAccuracyCE90 !== undefined + ? rasterLayerMetadata.maxHorizontalAccuracyCE90 <= rasterLayerMetadata.minHorizontalAccuracyCE90 + : true, + { + message: 'Max horizontal accuracy CE90 should be less than or equal to min horizontal accuracy CE90', + } + ) .refine((rasterLayerMetadata) => rasterLayerMetadata.maxResolutionDeg <= rasterLayerMetadata.minResolutionDeg, { message: 'Max resolution degree should be less than or equal to min resolution degree', }) From 182fcefa1bf502744d906798a346bd43c35c08cb Mon Sep 17 00:00:00 2001 From: vitaligi <54726763+vitaligi@users.noreply.github.com> Date: Fri, 17 Oct 2025 00:06:41 +0300 Subject: [PATCH 09/11] fix: productStatus is not optional --- src/schemas/core/layer.schema.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/schemas/core/layer.schema.ts b/src/schemas/core/layer.schema.ts index d3cf606..ace2ebd 100644 --- a/src/schemas/core/layer.schema.ts +++ b/src/schemas/core/layer.schema.ts @@ -87,7 +87,7 @@ export const rasterLayerCatalogSchema = z transparency: z.nativeEnum(Transparency), tileMimeFormat: tilesMimeFormatSchema, tileOutputFormat: z.nativeEnum(TileOutputFormat), - productStatus: z.nativeEnum(RecordStatus).optional(), + productStatus: z.nativeEnum(RecordStatus), type: z.literal(RecordType.RECORD_RASTER), classification: z .string() From 98ffe37990a90ce7ceb98c2e3dd80d282067b15b Mon Sep 17 00:00:00 2001 From: vitaligi <54726763+vitaligi@users.noreply.github.com> Date: Fri, 17 Oct 2025 00:19:47 +0300 Subject: [PATCH 10/11] fix: link optional properties --- src/schemas/core/layer.schema.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/schemas/core/layer.schema.ts b/src/schemas/core/layer.schema.ts index ace2ebd..2a19d5d 100644 --- a/src/schemas/core/layer.schema.ts +++ b/src/schemas/core/layer.schema.ts @@ -127,10 +127,10 @@ export const rasterLayerCatalogSchema = z .array( z .object({ + protocol: z.string(), + url: z.string().url(), name: z.string().optional(), description: z.string().optional(), - protocol: z.string().optional(), - url: z.string().optional(), }) .strict() ) From c5b13993cc6eb08e34e353ccbf7d4f7d73b345ee Mon Sep 17 00:00:00 2001 From: vitaligi <54726763+vitaligi@users.noreply.github.com> Date: Tue, 21 Oct 2025 10:51:51 +0300 Subject: [PATCH 11/11] fix: incorrect minResolutionMeter schema --- src/schemas/core/layer.schema.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/schemas/core/layer.schema.ts b/src/schemas/core/layer.schema.ts index 2a19d5d..5e3e6f0 100644 --- a/src/schemas/core/layer.schema.ts +++ b/src/schemas/core/layer.schema.ts @@ -69,12 +69,12 @@ export const rasterLayerCatalogSchema = z message: `Max resolution meter should not be larger than ${INGESTION_VALIDATIONS.resolutionMeter.max}`, }), minResolutionMeter: z - .number({ message: 'Min horizontal accuracy CE90 should be a number' }) - .min(INGESTION_VALIDATIONS.horizontalAccuracyCE90.min, { - message: `Min horizontal accuracy CE90 should not be less than ${INGESTION_VALIDATIONS.horizontalAccuracyCE90.min}`, + .number({ message: 'Min resolution meter should be a number' }) + .min(INGESTION_VALIDATIONS.resolutionMeter.min, { + message: `Min resolution meter should not be less than ${INGESTION_VALIDATIONS.resolutionMeter.min}`, }) - .max(INGESTION_VALIDATIONS.horizontalAccuracyCE90.max, { - message: `Min horizontal accuracy CE90 should not be larger than ${INGESTION_VALIDATIONS.horizontalAccuracyCE90.max}`, + .max(INGESTION_VALIDATIONS.resolutionMeter.max, { + message: `Min resolution meter should not be larger than ${INGESTION_VALIDATIONS.resolutionMeter.max}`, }), productSubType: z.string().optional(), productBoundingBox: z