diff --git a/packages/core/src/database/update-where.test.ts b/packages/core/src/database/update-where.test.ts index 806b39172..b75577f3d 100644 --- a/packages/core/src/database/update-where.test.ts +++ b/packages/core/src/database/update-where.test.ts @@ -1,4 +1,4 @@ -import { CreateUser, Users } from '@logto/schemas'; +import { CreateUser, Users, Applications } from '@logto/schemas'; import RequestError from '@/errors/RequestError'; import { createTestPool } from '@/utils/test-utils'; @@ -35,6 +35,24 @@ describe('buildUpdateWhere()', () => { ).resolves.toStrictEqual(user); }); + it('return query with jsonb partial update if input data type is jsonb', async () => { + const pool = createTestPool( + 'update "applications"\nset\n"custom_client_metadata"=\ncoalesce("custom_client_metadata",\'{}\'::jsonb)|| $1\nwhere "id"=$2\nreturning *', + (_, [costumClientMetadata, id]) => ({ + id: String(id), + costumClientMetadata: String(costumClientMetadata), + }) + ); + const updateWhere = buildUpdateWhere(pool, Applications, true); + + await expect( + updateWhere({ + set: { customClientMetadata: { idTokenTtl: 3600 } }, + where: { id: 'foo' }, + }) + ).resolves.toStrictEqual({ id: 'foo', costumClientMetadata: '{"idTokenTtl":3600}' }); + }); + it('throws an error when `undefined` found in values', async () => { const pool = createTestPool( 'update "users"\nset "username"=$1\nwhere "id"=$2 and "username"=$3' diff --git a/packages/core/src/database/update-where.ts b/packages/core/src/database/update-where.ts index e082caf4b..13f6a5866 100644 --- a/packages/core/src/database/update-where.ts +++ b/packages/core/src/database/update-where.ts @@ -38,10 +38,25 @@ export const buildUpdateWhere: BuildUpdateWhere = < const isKeyOfSchema = isKeyOf(schema); const connectKeyValueWithEqualSign = (data: Partial) => Object.entries(data) - .map( - ([key, value]) => - isKeyOfSchema(key) && sql`${fields[key]}=${convertToPrimitiveOrSql(key, value)}` - ) + .map(([key, value]) => { + if (!isKeyOfSchema(key)) { + return; + } + + if (value && typeof value === 'object' && !Array.isArray(value)) { + /** + * Jsonb || operator is used to shallow merge two jsonb types of data + * all jsonb data field must be non-nullable + * https://www.postgresql.org/docs/current/functions-json.html + */ + return sql` + ${fields[key]}= + coalesce(${fields[key]},'{}'::jsonb)|| ${convertToPrimitiveOrSql(key, value)} + `; + } + + return sql`${fields[key]}=${convertToPrimitiveOrSql(key, value)}`; + }) .filter((value): value is Truthy => notFalsy(value)); return async ({ set, where }: UpdateWhereData) => { diff --git a/packages/core/src/routes/application.ts b/packages/core/src/routes/application.ts index 351823e49..15053e352 100644 --- a/packages/core/src/routes/application.ts +++ b/packages/core/src/routes/application.ts @@ -84,14 +84,9 @@ export default function applicationRoutes(router: T) { params: { id }, body, } = ctx.guard; - const application = await findApplicationById(id); ctx.body = await updateApplicationById(id, { ...body, - oidcClientMetadata: buildOidcClientMetadata({ - ...application.oidcClientMetadata, - ...body.oidcClientMetadata, - }), }); return next();