From 53f808a2c11391b1659c42d57f566690d7341491 Mon Sep 17 00:00:00 2001 From: Preeti Dewani Date: Sun, 21 Sep 2025 17:56:15 +0530 Subject: [PATCH] add migraiton file to convert snake case to camel case (#522) ## Motivation and Context - Make all entries in current database matching the new schema ## How Has This Been Tested? - locally ## Breaking Changes - Yes ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [X] Breaking change (fix or feature that would cause existing functionality to change) - [X] Documentation update ## Checklist - [ ] I have read the [MCP Documentation](https://modelcontextprotocol.io) - [X] My code follows the repository's style guidelines - [X] New and existing tests pass locally - [ ] I have added appropriate error handling - [X] I have added or updated documentation as needed Before migration: ``` { "name": "io.github.kenjihikmatullah/productboard-mcp", "description": "Integrate the Productboard API into agentic workflows via MCP", "status": "active", "repository": { "url": "https://github.com/kenjihikmatullah/productboard-mcp", "source": "github", "id": "939104710" }, "version": "0.0.1-seed", "packages": [ { "registry_type": "npm", "identifier": "productboard-mcp", "version": "1.0.1", "transport": { "type": "stdio" } } ], "_meta": { "io.modelcontextprotocol.registry/official": { "id": "00613acb-73e2-4f93-8b96-296df17316c8", "published_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z", "is_latest": true } } } ``` After migration: ``` { "name": "io.github.kenjihikmatullah/productboard-mcp", "description": "Integrate the Productboard API into agentic workflows via MCP", "status": "active", "repository": { "url": "https://github.com/kenjihikmatullah/productboard-mcp", "source": "github", "id": "939104710" }, "version": "0.0.1-seed", "packages": [ { "registryType": "npm", "identifier": "productboard-mcp", "version": "1.0.1", "transport": { "type": "stdio" }, } ], "_meta": { "io.modelcontextprotocol.registry/official": { "serverId": "83cfe55c-55c5-4e7e-b9c0-6d7d369f52ed", "versionId": "00613acb-73e2-4f93-8b96-296df17316c8", "publishedAt": "0001-01-01T00:00:00Z", "updatedAt": "0001-01-01T00:00:00Z", "isLatest": true } } } --- .../006_migrate_server_json_camelcase.sql | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 internal/database/migrations/006_migrate_server_json_camelcase.sql diff --git a/internal/database/migrations/006_migrate_server_json_camelcase.sql b/internal/database/migrations/006_migrate_server_json_camelcase.sql new file mode 100644 index 0000000..7abd506 --- /dev/null +++ b/internal/database/migrations/006_migrate_server_json_camelcase.sql @@ -0,0 +1,82 @@ +-- Migrate server.json fields from snake_case to camelCase +-- This migration converts all fields including those in _meta objects + +-- Helper function to recursively convert snake_case keys to camelCase in JSON objects +-- This handles nested structures like arguments, environment variables, etc. +CREATE OR REPLACE FUNCTION convert_object_keys_to_camelcase(input_json jsonb) +RETURNS jsonb +LANGUAGE plpgsql +AS $$ +DECLARE + result jsonb := '{}'; + key text; + value jsonb; + new_key text; +BEGIN + -- Handle null input + IF input_json IS NULL THEN + RETURN NULL; + END IF; + + -- Iterate through all keys in the object + FOR key, value IN SELECT * FROM jsonb_each(input_json) + LOOP + -- Convert snake_case keys to camelCase + new_key := CASE + WHEN key = 'registry_type' THEN 'registryType' + WHEN key = 'registry_base_url' THEN 'registryBaseUrl' + WHEN key = 'file_sha256' THEN 'fileSha256' + WHEN key = 'runtime_hint' THEN 'runtimeHint' + WHEN key = 'runtime_arguments' THEN 'runtimeArguments' + WHEN key = 'package_arguments' THEN 'packageArguments' + WHEN key = 'environment_variables' THEN 'environmentVariables' + WHEN key = 'is_required' THEN 'isRequired' + WHEN key = 'is_secret' THEN 'isSecret' + WHEN key = 'value_hint' THEN 'valueHint' + WHEN key = 'is_repeated' THEN 'isRepeated' + WHEN key = 'website_url' THEN 'websiteUrl' + -- _meta specific fields + WHEN key = 'is_latest' THEN 'isLatest' + WHEN key = 'published_at' THEN 'publishedAt' + WHEN key = 'updated_at' THEN 'updatedAt' + ELSE key -- Keep other keys unchanged + END; + + -- Process values based on their type + IF jsonb_typeof(value) = 'array' THEN + -- Process array elements + result := jsonb_set(result, ARRAY[new_key], + (SELECT jsonb_agg( + CASE + WHEN jsonb_typeof(elem) = 'object' THEN convert_object_keys_to_camelcase(elem) + ELSE elem + END + ) FROM jsonb_array_elements(value) AS elem) + ); + ELSIF jsonb_typeof(value) = 'object' THEN + -- Process nested objects + result := jsonb_set(result, ARRAY[new_key], convert_object_keys_to_camelcase(value)); + ELSE + -- Keep all primitive values (strings, numbers, booleans, null) unchanged + result := jsonb_set(result, ARRAY[new_key], value); + END IF; + END LOOP; + + RETURN result; +END; +$$; + +-- Update all server records to use camelCase field names +-- This converts snake_case keys in all fields including _meta sections +UPDATE servers +SET value = convert_object_keys_to_camelcase(value) +WHERE value IS NOT NULL; + +-- Update $schema field to latest version if it exists +UPDATE servers +SET value = jsonb_set(value, '{$schema}', '"https://static.modelcontextprotocol.io/schemas/2025-09-16/server.schema.json"') +WHERE value ? '$schema' AND value IS NOT NULL; + +-- Clean up the helper function +DROP FUNCTION IF EXISTS convert_object_keys_to_camelcase(jsonb); +