mirror of
https://github.com/logto-io/logto.git
synced 2025-03-31 22:51:25 -05:00
refactor(core,cli): application query factory (#2853)
This commit is contained in:
parent
bc9f8906a7
commit
8f809da308
17 changed files with 282 additions and 259 deletions
|
@ -2,13 +2,13 @@ import type { AlterationState, LogtoConfig, LogtoConfigKey } from '@logto/schema
|
|||
import { logtoConfigGuards, LogtoConfigs, AlterationStateKey } from '@logto/schemas';
|
||||
import { convertToIdentifiers } from '@logto/shared';
|
||||
import type { Nullable } from '@silverhand/essentials';
|
||||
import type { DatabasePool, DatabaseTransactionConnection } from 'slonik';
|
||||
import type { CommonQueryMethods, DatabaseTransactionConnection } from 'slonik';
|
||||
import { sql } from 'slonik';
|
||||
import { z } from 'zod';
|
||||
|
||||
const { table, fields } = convertToIdentifiers(LogtoConfigs);
|
||||
|
||||
export const doesConfigsTableExist = async (pool: DatabasePool) => {
|
||||
export const doesConfigsTableExist = async (pool: CommonQueryMethods) => {
|
||||
const { rows } = await pool.query<{ regclass: Nullable<string> }>(
|
||||
sql`select to_regclass(${LogtoConfigs.table}) as regclass`
|
||||
);
|
||||
|
@ -16,17 +16,14 @@ export const doesConfigsTableExist = async (pool: DatabasePool) => {
|
|||
return Boolean(rows[0]?.regclass);
|
||||
};
|
||||
|
||||
export const getRowsByKeys = async (
|
||||
pool: DatabasePool | DatabaseTransactionConnection,
|
||||
keys: LogtoConfigKey[]
|
||||
) =>
|
||||
export const getRowsByKeys = async (pool: CommonQueryMethods, keys: LogtoConfigKey[]) =>
|
||||
pool.query<LogtoConfig>(sql`
|
||||
select ${sql.join([fields.key, fields.value], sql`,`)} from ${table}
|
||||
where ${fields.key} in (${sql.join(keys, sql`,`)})
|
||||
`);
|
||||
|
||||
export const updateValueByKey = async <T extends LogtoConfigKey>(
|
||||
pool: DatabasePool | DatabaseTransactionConnection,
|
||||
pool: CommonQueryMethods,
|
||||
key: T,
|
||||
value: z.infer<typeof logtoConfigGuards[T]>
|
||||
) =>
|
||||
|
@ -38,7 +35,7 @@ export const updateValueByKey = async <T extends LogtoConfigKey>(
|
|||
`
|
||||
);
|
||||
|
||||
export const getCurrentDatabaseAlterationTimestamp = async (pool: DatabasePool) => {
|
||||
export const getCurrentDatabaseAlterationTimestamp = async (pool: CommonQueryMethods) => {
|
||||
try {
|
||||
const result = await pool.maybeOne<LogtoConfig>(
|
||||
sql`select * from ${table} where ${fields.key}=${AlterationStateKey.AlterationState}`
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import type { SchemaLike, GeneratedSchema } from '@logto/schemas';
|
||||
import { convertToIdentifiers } from '@logto/shared';
|
||||
import type { CommonQueryMethods } from 'slonik';
|
||||
import { sql, NotFoundError } from 'slonik';
|
||||
|
||||
import envSet from '#src/env-set/index.js';
|
||||
|
@ -7,32 +8,37 @@ import RequestError from '#src/errors/RequestError/index.js';
|
|||
import assertThat from '#src/utils/assert-that.js';
|
||||
import { isKeyOf } from '#src/utils/schema.js';
|
||||
|
||||
export const buildFindEntityById = <Schema extends SchemaLike, ReturnType extends SchemaLike>(
|
||||
schema: GeneratedSchema<Schema & { id: string }>
|
||||
) => {
|
||||
const { table, fields } = convertToIdentifiers(schema);
|
||||
const isKeyOfSchema = isKeyOf(schema);
|
||||
export const buildFindEntityByIdWithPool =
|
||||
(pool: CommonQueryMethods) =>
|
||||
<Schema extends SchemaLike, ReturnType extends SchemaLike>(
|
||||
schema: GeneratedSchema<Schema & { id: string }>
|
||||
) => {
|
||||
const { table, fields } = convertToIdentifiers(schema);
|
||||
const isKeyOfSchema = isKeyOf(schema);
|
||||
|
||||
// Make sure id is key of the schema
|
||||
assertThat(isKeyOfSchema('id'), 'entity.not_exists');
|
||||
// Make sure id is key of the schema
|
||||
assertThat(isKeyOfSchema('id'), 'entity.not_exists');
|
||||
|
||||
return async (id: string) => {
|
||||
try {
|
||||
return await envSet.pool.one<ReturnType>(sql`
|
||||
select ${sql.join(Object.values(fields), sql`, `)}
|
||||
from ${table}
|
||||
where ${fields.id}=${id}
|
||||
`);
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof NotFoundError) {
|
||||
throw new RequestError({
|
||||
code: 'entity.not_exists_with_id',
|
||||
name: schema.table,
|
||||
id,
|
||||
status: 404,
|
||||
});
|
||||
return async (id: string) => {
|
||||
try {
|
||||
return await pool.one<ReturnType>(sql`
|
||||
select ${sql.join(Object.values(fields), sql`, `)}
|
||||
from ${table}
|
||||
where ${fields.id}=${id}
|
||||
`);
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof NotFoundError) {
|
||||
throw new RequestError({
|
||||
code: 'entity.not_exists_with_id',
|
||||
name: schema.table,
|
||||
id,
|
||||
status: 404,
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/** @deprecated Will be removed soon. Use buildFindEntityByIdWithPool() factory instead. */
|
||||
export const buildFindEntityById = buildFindEntityByIdWithPool(envSet.pool);
|
||||
|
|
|
@ -3,15 +3,10 @@ import { Users } from '@logto/schemas';
|
|||
import { convertToIdentifiers } from '@logto/shared';
|
||||
import decamelize from 'decamelize';
|
||||
|
||||
import envSet from '#src/env-set/index.js';
|
||||
import { InsertionError } from '#src/errors/SlonikError/index.js';
|
||||
import { createTestPool } from '#src/utils/test-utils.js';
|
||||
|
||||
import { buildInsertInto } from './insert-into.js';
|
||||
|
||||
const { jest } = import.meta;
|
||||
|
||||
const poolSpy = jest.spyOn(envSet, 'pool', 'get');
|
||||
const { buildInsertIntoWithPool } = await import('./insert-into.js');
|
||||
|
||||
const buildExpectedInsertIntoSql = (keys: string[]) => [
|
||||
// eslint-disable-next-line sql/no-unsafe-query
|
||||
|
@ -24,9 +19,8 @@ describe('buildInsertInto()', () => {
|
|||
const user: CreateUser = { id: 'foo', username: '456', applicationId: 'bar' };
|
||||
const expectInsertIntoSql = buildExpectedInsertIntoSql(Object.keys(user));
|
||||
const pool = createTestPool(expectInsertIntoSql.join('\n'));
|
||||
poolSpy.mockReturnValue(pool);
|
||||
|
||||
const insertInto = buildInsertInto(Users);
|
||||
const insertInto = buildInsertIntoWithPool(pool)(Users);
|
||||
await expect(insertInto(user)).resolves.toBe(undefined);
|
||||
});
|
||||
|
||||
|
@ -45,10 +39,9 @@ describe('buildInsertInto()', () => {
|
|||
'set "primary_email"=excluded."primary_email"',
|
||||
].join('\n')
|
||||
);
|
||||
poolSpy.mockReturnValue(pool);
|
||||
|
||||
const { fields } = convertToIdentifiers(Users);
|
||||
const insertInto = buildInsertInto(Users, {
|
||||
const insertInto = buildInsertIntoWithPool(pool)(Users, {
|
||||
onConflict: {
|
||||
fields: [fields.id, fields.username],
|
||||
setExcludedFields: [fields.primaryEmail],
|
||||
|
@ -74,9 +67,8 @@ describe('buildInsertInto()', () => {
|
|||
applicationId: String(applicationId),
|
||||
})
|
||||
);
|
||||
poolSpy.mockReturnValue(pool);
|
||||
|
||||
const insertInto = buildInsertInto(Users, { returning: true });
|
||||
const insertInto = buildInsertIntoWithPool(pool)(Users, { returning: true });
|
||||
await expect(
|
||||
insertInto({ id: 'foo', username: '123', primaryEmail: 'foo@bar.com', applicationId: 'bar' })
|
||||
).resolves.toStrictEqual(user);
|
||||
|
@ -91,9 +83,8 @@ describe('buildInsertInto()', () => {
|
|||
};
|
||||
const expectInsertIntoSql = buildExpectedInsertIntoSql(Object.keys(user));
|
||||
const pool = createTestPool([...expectInsertIntoSql, 'returning *'].join('\n'));
|
||||
poolSpy.mockReturnValue(pool);
|
||||
|
||||
const insertInto = buildInsertInto(Users, { returning: true });
|
||||
const insertInto = buildInsertIntoWithPool(pool)(Users, { returning: true });
|
||||
const dataToInsert = {
|
||||
id: 'foo',
|
||||
username: '123',
|
||||
|
|
|
@ -7,7 +7,7 @@ import {
|
|||
conditionalSql,
|
||||
} from '@logto/shared';
|
||||
import { has } from '@silverhand/essentials';
|
||||
import type { IdentifierSqlToken } from 'slonik';
|
||||
import type { CommonQueryMethods, IdentifierSqlToken } from 'slonik';
|
||||
import { sql } from 'slonik';
|
||||
|
||||
import envSet from '#src/env-set/index.js';
|
||||
|
@ -46,44 +46,46 @@ type BuildInsertInto = {
|
|||
): (data: OmitAutoSetFields<Schema>) => Promise<void>;
|
||||
};
|
||||
|
||||
export const buildInsertInto: BuildInsertInto = <
|
||||
Schema extends SchemaLike,
|
||||
ReturnType extends SchemaLike
|
||||
>(
|
||||
schema: GeneratedSchema<Schema>,
|
||||
config?: InsertIntoConfig | InsertIntoConfigReturning
|
||||
) => {
|
||||
const { fieldKeys, ...rest } = schema;
|
||||
const { table, fields } = convertToIdentifiers(rest);
|
||||
const keys = excludeAutoSetFields(fieldKeys);
|
||||
const returning = Boolean(config?.returning);
|
||||
const onConflict = config?.onConflict;
|
||||
export const buildInsertIntoWithPool =
|
||||
(pool: CommonQueryMethods): BuildInsertInto =>
|
||||
<Schema extends SchemaLike, ReturnType extends SchemaLike>(
|
||||
schema: GeneratedSchema<Schema>,
|
||||
config?: InsertIntoConfig | InsertIntoConfigReturning
|
||||
) => {
|
||||
const { fieldKeys, ...rest } = schema;
|
||||
const { table, fields } = convertToIdentifiers(rest);
|
||||
const keys = excludeAutoSetFields(fieldKeys);
|
||||
const returning = Boolean(config?.returning);
|
||||
const onConflict = config?.onConflict;
|
||||
|
||||
return async (data: OmitAutoSetFields<Schema>): Promise<ReturnType | void> => {
|
||||
const insertingKeys = keys.filter((key) => has(data, key));
|
||||
const {
|
||||
rows: [entry],
|
||||
} = await envSet.pool.query<ReturnType>(sql`
|
||||
insert into ${table} (${sql.join(
|
||||
insertingKeys.map((key) => fields[key]),
|
||||
sql`, `
|
||||
)})
|
||||
values (${sql.join(
|
||||
insertingKeys.map((key) => convertToPrimitiveOrSql(key, data[key] ?? null)),
|
||||
return async (data: OmitAutoSetFields<Schema>): Promise<ReturnType | void> => {
|
||||
const insertingKeys = keys.filter((key) => has(data, key));
|
||||
const {
|
||||
rows: [entry],
|
||||
} = await pool.query<ReturnType>(sql`
|
||||
insert into ${table} (${sql.join(
|
||||
insertingKeys.map((key) => fields[key]),
|
||||
sql`, `
|
||||
)})
|
||||
${conditionalSql(
|
||||
onConflict,
|
||||
({ fields, setExcludedFields }) => sql`
|
||||
on conflict (${sql.join(fields, sql`, `)}) do update
|
||||
set ${setExcluded(...setExcludedFields)}
|
||||
`
|
||||
)}
|
||||
${conditionalSql(returning, () => sql`returning *`)}
|
||||
`);
|
||||
values (${sql.join(
|
||||
insertingKeys.map((key) => convertToPrimitiveOrSql(key, data[key] ?? null)),
|
||||
sql`, `
|
||||
)})
|
||||
${conditionalSql(
|
||||
onConflict,
|
||||
({ fields, setExcludedFields }) => sql`
|
||||
on conflict (${sql.join(fields, sql`, `)}) do update
|
||||
set ${setExcluded(...setExcludedFields)}
|
||||
`
|
||||
)}
|
||||
${conditionalSql(returning, () => sql`returning *`)}
|
||||
`);
|
||||
|
||||
assertThat(!returning || entry, new InsertionError(schema, data));
|
||||
assertThat(!returning || entry, new InsertionError(schema, data));
|
||||
|
||||
return entry;
|
||||
return entry;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/** @deprecated Will be removed soon. Use buildInsertIntoWithPool() factory instead. */
|
||||
export const buildInsertInto = buildInsertIntoWithPool(envSet.pool);
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
import type { IdentifierSqlToken } from 'slonik';
|
||||
import type { CommonQueryMethods, IdentifierSqlToken } from 'slonik';
|
||||
import { sql } from 'slonik';
|
||||
|
||||
import envSet from '#src/env-set/index.js';
|
||||
|
||||
export const getTotalRowCount = async (table: IdentifierSqlToken) =>
|
||||
envSet.pool.one<{ count: number }>(sql`
|
||||
select count(*)
|
||||
from ${table}
|
||||
`);
|
||||
export const getTotalRowCountWithPool =
|
||||
(pool: CommonQueryMethods) => async (table: IdentifierSqlToken) =>
|
||||
pool.one<{ count: number }>(sql`
|
||||
select count(*)
|
||||
from ${table}
|
||||
`);
|
||||
|
||||
/** @deprecated Will be removed soon. Use getTotalRowCountWithPool() factory instead. */
|
||||
export const getTotalRowCount = getTotalRowCountWithPool(envSet.pool);
|
||||
|
|
|
@ -2,24 +2,18 @@ import type { CreateUser, User } from '@logto/schemas';
|
|||
import { Users, Applications } from '@logto/schemas';
|
||||
import type { UpdateWhereData } from '@logto/shared';
|
||||
|
||||
import envSet from '#src/env-set/index.js';
|
||||
import { UpdateError } from '#src/errors/SlonikError/index.js';
|
||||
import { createTestPool } from '#src/utils/test-utils.js';
|
||||
|
||||
import { buildUpdateWhere } from './update-where.js';
|
||||
|
||||
const { jest } = import.meta;
|
||||
|
||||
const poolSpy = jest.spyOn(envSet, 'pool', 'get');
|
||||
const { buildUpdateWhereWithPool } = await import('./update-where.js');
|
||||
|
||||
describe('buildUpdateWhere()', () => {
|
||||
it('resolves a promise with `undefined` when `returning` is false', async () => {
|
||||
const pool = createTestPool(
|
||||
'update "users"\nset "username"=$1\nwhere "id"=$2 and "username"=$3'
|
||||
);
|
||||
poolSpy.mockReturnValue(pool);
|
||||
|
||||
const updateWhere = buildUpdateWhere(Users);
|
||||
const updateWhere = buildUpdateWhereWithPool(pool)(Users);
|
||||
await expect(
|
||||
updateWhere({
|
||||
set: { username: '123' },
|
||||
|
@ -45,9 +39,8 @@ describe('buildUpdateWhere()', () => {
|
|||
applicationId: String(applicationId),
|
||||
})
|
||||
);
|
||||
poolSpy.mockReturnValue(pool);
|
||||
|
||||
const updateWhere = buildUpdateWhere(Users, true);
|
||||
const updateWhere = buildUpdateWhereWithPool(pool)(Users, true);
|
||||
await expect(
|
||||
updateWhere({
|
||||
set: { username: '123', primaryEmail: 'foo@bar.com', applicationId: 'bar' },
|
||||
|
@ -65,9 +58,8 @@ describe('buildUpdateWhere()', () => {
|
|||
customClientMetadata: String(customClientMetadata),
|
||||
})
|
||||
);
|
||||
poolSpy.mockReturnValue(pool);
|
||||
|
||||
const updateWhere = buildUpdateWhere(Applications, true);
|
||||
const updateWhere = buildUpdateWhereWithPool(pool)(Applications, true);
|
||||
await expect(
|
||||
updateWhere({
|
||||
set: { customClientMetadata: { idTokenTtl: 3600 } },
|
||||
|
@ -81,9 +73,8 @@ describe('buildUpdateWhere()', () => {
|
|||
const pool = createTestPool(
|
||||
'update "users"\nset "username"=$1\nwhere "id"=$2 and "username"=$3'
|
||||
);
|
||||
poolSpy.mockReturnValue(pool);
|
||||
|
||||
const updateWhere = buildUpdateWhere(Users);
|
||||
const updateWhere = buildUpdateWhereWithPool(pool)(Users);
|
||||
|
||||
await expect(
|
||||
updateWhere({
|
||||
|
@ -96,9 +87,8 @@ describe('buildUpdateWhere()', () => {
|
|||
|
||||
it('throws `entity.not_exists_with_id` error with `undefined` when `returning` is true', async () => {
|
||||
const pool = createTestPool('update "users"\nset "username"=$1\nwhere "id"=$2\nreturning *');
|
||||
poolSpy.mockReturnValue(pool);
|
||||
|
||||
const updateWhere = buildUpdateWhere(Users, true);
|
||||
const updateWhere = buildUpdateWhereWithPool(pool)(Users, true);
|
||||
const updateWhereData: UpdateWhereData<User> = {
|
||||
set: { username: '123' },
|
||||
where: { id: 'foo' },
|
||||
|
@ -114,9 +104,8 @@ describe('buildUpdateWhere()', () => {
|
|||
const pool = createTestPool(
|
||||
'update "users"\nset "username"=$1\nwhere "username"=$2\nreturning *'
|
||||
);
|
||||
poolSpy.mockReturnValue(pool);
|
||||
|
||||
const updateWhere = buildUpdateWhere(Users, true);
|
||||
const updateWhere = buildUpdateWhereWithPool(pool)(Users, true);
|
||||
const updateWhereData: UpdateWhereData<User> = {
|
||||
set: { username: '123' },
|
||||
where: { username: 'foo' },
|
||||
|
|
|
@ -3,6 +3,7 @@ import { convertToIdentifiers, convertToPrimitiveOrSql, conditionalSql } from '@
|
|||
import type { UpdateWhereData } from '@logto/shared';
|
||||
import type { Truthy } from '@silverhand/essentials';
|
||||
import { notFalsy } from '@silverhand/essentials';
|
||||
import type { CommonQueryMethods } from 'slonik';
|
||||
import { sql } from 'slonik';
|
||||
|
||||
import envSet from '#src/env-set/index.js';
|
||||
|
@ -20,50 +21,57 @@ type BuildUpdateWhere = {
|
|||
) => Promise<void>;
|
||||
};
|
||||
|
||||
export const buildUpdateWhere: BuildUpdateWhere = <
|
||||
Schema extends SchemaLike,
|
||||
ReturnType extends SchemaLike
|
||||
>(
|
||||
schema: GeneratedSchema<Schema>,
|
||||
returning = false
|
||||
) => {
|
||||
const { table, fields } = convertToIdentifiers(schema);
|
||||
const isKeyOfSchema = isKeyOf(schema);
|
||||
const connectKeyValueWithEqualSign = (data: Partial<Schema>, jsonbMode: 'replace' | 'merge') =>
|
||||
Object.entries(data)
|
||||
.map(([key, value]) => {
|
||||
if (!isKeyOfSchema(key)) {
|
||||
return;
|
||||
}
|
||||
export const buildUpdateWhereWithPool =
|
||||
(pool: CommonQueryMethods): BuildUpdateWhere =>
|
||||
<Schema extends SchemaLike, ReturnType extends SchemaLike>(
|
||||
schema: GeneratedSchema<Schema>,
|
||||
returning = false
|
||||
) => {
|
||||
const { table, fields } = convertToIdentifiers(schema);
|
||||
const isKeyOfSchema = isKeyOf(schema);
|
||||
const connectKeyValueWithEqualSign = (data: Partial<Schema>, jsonbMode: 'replace' | 'merge') =>
|
||||
Object.entries(data)
|
||||
.map(([key, value]) => {
|
||||
if (!isKeyOfSchema(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (jsonbMode === 'merge' && 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)}
|
||||
`;
|
||||
}
|
||||
if (
|
||||
jsonbMode === 'merge' &&
|
||||
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<typeof value> => notFalsy(value));
|
||||
return sql`${fields[key]}=${convertToPrimitiveOrSql(key, value)}`;
|
||||
})
|
||||
.filter((value): value is Truthy<typeof value> => notFalsy(value));
|
||||
|
||||
return async ({ set, where, jsonbMode }: UpdateWhereData<Schema>) => {
|
||||
const {
|
||||
rows: [data],
|
||||
} = await envSet.pool.query<ReturnType>(sql`
|
||||
update ${table}
|
||||
set ${sql.join(connectKeyValueWithEqualSign(set, jsonbMode), sql`, `)}
|
||||
where ${sql.join(connectKeyValueWithEqualSign(where, jsonbMode), sql` and `)}
|
||||
${conditionalSql(returning, () => sql`returning *`)}
|
||||
`);
|
||||
return async ({ set, where, jsonbMode }: UpdateWhereData<Schema>) => {
|
||||
const {
|
||||
rows: [data],
|
||||
} = await pool.query<ReturnType>(sql`
|
||||
update ${table}
|
||||
set ${sql.join(connectKeyValueWithEqualSign(set, jsonbMode), sql`, `)}
|
||||
where ${sql.join(connectKeyValueWithEqualSign(where, jsonbMode), sql` and `)}
|
||||
${conditionalSql(returning, () => sql`returning *`)}
|
||||
`);
|
||||
|
||||
assertThat(!returning || data, new UpdateError(schema, { set, where, jsonbMode }));
|
||||
assertThat(!returning || data, new UpdateError(schema, { set, where, jsonbMode }));
|
||||
|
||||
return data;
|
||||
return data;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/** @deprecated Will be removed soon. Use buildUpdateWhereWithPool() factory instead. */
|
||||
export const buildUpdateWhere = buildUpdateWhereWithPool(envSet.pool);
|
||||
|
|
|
@ -2,12 +2,10 @@ import { getRowsByKeys } from '@logto/cli/lib/queries/logto-config.js';
|
|||
import type { LogtoOidcConfigType } from '@logto/schemas';
|
||||
import { logtoOidcConfigGuard, LogtoOidcConfigKey } from '@logto/schemas';
|
||||
import chalk from 'chalk';
|
||||
import type { DatabasePool, DatabaseTransactionConnection } from 'slonik';
|
||||
import type { CommonQueryMethods } from 'slonik';
|
||||
import { z, ZodError } from 'zod';
|
||||
|
||||
export const getOidcConfigs = async (
|
||||
pool: DatabasePool | DatabaseTransactionConnection
|
||||
): Promise<LogtoOidcConfigType> => {
|
||||
export const getOidcConfigs = async (pool: CommonQueryMethods): Promise<LogtoOidcConfigType> => {
|
||||
try {
|
||||
const { rows } = await getRowsByKeys(pool, Object.values(LogtoOidcConfigKey));
|
||||
|
||||
|
|
|
@ -9,15 +9,6 @@ import { DeletionError } from '#src/errors/SlonikError/index.js';
|
|||
import type { QueryType } from '#src/utils/test-utils.js';
|
||||
import { expectSqlAssert } from '#src/utils/test-utils.js';
|
||||
|
||||
import {
|
||||
findTotalNumberOfApplications,
|
||||
findAllApplications,
|
||||
findApplicationById,
|
||||
insertApplication,
|
||||
updateApplicationById,
|
||||
deleteApplicationById,
|
||||
} from './application.js';
|
||||
|
||||
const { jest } = import.meta;
|
||||
|
||||
const mockQuery: jest.MockedFunction<QueryType> = jest.fn();
|
||||
|
@ -30,6 +21,15 @@ jest.spyOn(envSet, 'pool', 'get').mockReturnValue(
|
|||
})
|
||||
);
|
||||
|
||||
const {
|
||||
findTotalNumberOfApplications,
|
||||
findAllApplications,
|
||||
findApplicationById,
|
||||
insertApplication,
|
||||
updateApplicationById,
|
||||
deleteApplicationById,
|
||||
} = await import('./application.js');
|
||||
|
||||
describe('application query', () => {
|
||||
const { table, fields } = convertToIdentifiers(Applications);
|
||||
|
||||
|
|
|
@ -2,52 +2,77 @@ import type { Application, CreateApplication } from '@logto/schemas';
|
|||
import { Applications } from '@logto/schemas';
|
||||
import type { OmitAutoSetFields } from '@logto/shared';
|
||||
import { convertToIdentifiers, conditionalSql, manyRows } from '@logto/shared';
|
||||
import type { CommonQueryMethods } from 'slonik';
|
||||
import { sql } from 'slonik';
|
||||
|
||||
import { buildFindEntityById } from '#src/database/find-entity-by-id.js';
|
||||
import { buildInsertInto } from '#src/database/insert-into.js';
|
||||
import { getTotalRowCount } from '#src/database/row-count.js';
|
||||
import { buildUpdateWhere } from '#src/database/update-where.js';
|
||||
import { buildFindEntityByIdWithPool } from '#src/database/find-entity-by-id.js';
|
||||
import { buildInsertIntoWithPool } from '#src/database/insert-into.js';
|
||||
import { getTotalRowCountWithPool } from '#src/database/row-count.js';
|
||||
import { buildUpdateWhereWithPool } from '#src/database/update-where.js';
|
||||
import envSet from '#src/env-set/index.js';
|
||||
import { DeletionError } from '#src/errors/SlonikError/index.js';
|
||||
|
||||
const { table, fields } = convertToIdentifiers(Applications);
|
||||
|
||||
export const findTotalNumberOfApplications = async () => getTotalRowCount(table);
|
||||
|
||||
export const findAllApplications = async (limit: number, offset: number) =>
|
||||
manyRows(
|
||||
envSet.pool.query<Application>(sql`
|
||||
select ${sql.join(Object.values(fields), sql`, `)}
|
||||
from ${table}
|
||||
order by ${fields.createdAt} desc
|
||||
${conditionalSql(limit, (limit) => sql`limit ${limit}`)}
|
||||
${conditionalSql(offset, (offset) => sql`offset ${offset}`)}
|
||||
`)
|
||||
export const createApplicationQueries = (pool: CommonQueryMethods) => {
|
||||
const findTotalNumberOfApplications = async () => getTotalRowCountWithPool(pool)(table);
|
||||
const findAllApplications = async (limit: number, offset: number) =>
|
||||
manyRows(
|
||||
pool.query<Application>(sql`
|
||||
select ${sql.join(Object.values(fields), sql`, `)}
|
||||
from ${table}
|
||||
order by ${fields.createdAt} desc
|
||||
${conditionalSql(limit, (limit) => sql`limit ${limit}`)}
|
||||
${conditionalSql(offset, (offset) => sql`offset ${offset}`)}
|
||||
`)
|
||||
);
|
||||
const findApplicationById = buildFindEntityByIdWithPool(pool)<CreateApplication, Application>(
|
||||
Applications
|
||||
);
|
||||
const insertApplication = buildInsertIntoWithPool(pool)<CreateApplication, Application>(
|
||||
Applications,
|
||||
{
|
||||
returning: true,
|
||||
}
|
||||
);
|
||||
const updateApplication = buildUpdateWhereWithPool(pool)<CreateApplication, Application>(
|
||||
Applications,
|
||||
true
|
||||
);
|
||||
const updateApplicationById = async (
|
||||
id: string,
|
||||
set: Partial<OmitAutoSetFields<CreateApplication>>
|
||||
) => updateApplication({ set, where: { id }, jsonbMode: 'merge' });
|
||||
|
||||
export const findApplicationById = buildFindEntityById<CreateApplication, Application>(
|
||||
Applications
|
||||
);
|
||||
const deleteApplicationById = async (id: string) => {
|
||||
const { rowCount } = await envSet.pool.query(sql`
|
||||
delete from ${table}
|
||||
where ${fields.id}=${id}
|
||||
`);
|
||||
|
||||
export const insertApplication = buildInsertInto<CreateApplication, Application>(Applications, {
|
||||
returning: true,
|
||||
});
|
||||
if (rowCount < 1) {
|
||||
throw new DeletionError(Applications.table, id);
|
||||
}
|
||||
};
|
||||
|
||||
const updateApplication = buildUpdateWhere<CreateApplication, Application>(Applications, true);
|
||||
|
||||
export const updateApplicationById = async (
|
||||
id: string,
|
||||
set: Partial<OmitAutoSetFields<CreateApplication>>
|
||||
) => updateApplication({ set, where: { id }, jsonbMode: 'merge' });
|
||||
|
||||
export const deleteApplicationById = async (id: string) => {
|
||||
const { rowCount } = await envSet.pool.query(sql`
|
||||
delete from ${table}
|
||||
where ${fields.id}=${id}
|
||||
`);
|
||||
|
||||
if (rowCount < 1) {
|
||||
throw new DeletionError(Applications.table, id);
|
||||
}
|
||||
return {
|
||||
findTotalNumberOfApplications,
|
||||
findAllApplications,
|
||||
findApplicationById,
|
||||
insertApplication,
|
||||
updateApplication,
|
||||
updateApplicationById,
|
||||
deleteApplicationById,
|
||||
};
|
||||
};
|
||||
|
||||
/** @deprecated Will be removed soon. Use createApplicationQueries() factory instead. */
|
||||
export const {
|
||||
findTotalNumberOfApplications,
|
||||
findAllApplications,
|
||||
findApplicationById,
|
||||
insertApplication,
|
||||
updateApplication,
|
||||
updateApplicationById,
|
||||
deleteApplicationById,
|
||||
} = createApplicationQueries(envSet.pool);
|
||||
|
|
|
@ -8,16 +8,6 @@ import { DeletionError } from '#src/errors/SlonikError/index.js';
|
|||
import type { QueryType } from '#src/utils/test-utils.js';
|
||||
import { expectSqlAssert } from '#src/utils/test-utils.js';
|
||||
|
||||
import {
|
||||
findAllConnectors,
|
||||
findConnectorById,
|
||||
countConnectorByConnectorId,
|
||||
deleteConnectorById,
|
||||
deleteConnectorByIds,
|
||||
insertConnector,
|
||||
updateConnector,
|
||||
} from './connector.js';
|
||||
|
||||
const { jest } = import.meta;
|
||||
|
||||
const mockQuery: jest.MockedFunction<QueryType> = jest.fn();
|
||||
|
@ -30,6 +20,16 @@ jest.spyOn(envSet, 'pool', 'get').mockReturnValue(
|
|||
})
|
||||
);
|
||||
|
||||
const {
|
||||
findAllConnectors,
|
||||
findConnectorById,
|
||||
countConnectorByConnectorId,
|
||||
deleteConnectorById,
|
||||
deleteConnectorByIds,
|
||||
insertConnector,
|
||||
updateConnector,
|
||||
} = await import('./connector.js');
|
||||
|
||||
describe('connector queries', () => {
|
||||
const { table, fields } = convertToIdentifiers(Connectors);
|
||||
|
||||
|
|
|
@ -10,14 +10,6 @@ import { DeletionError } from '#src/errors/SlonikError/index.js';
|
|||
import type { QueryType } from '#src/utils/test-utils.js';
|
||||
import { expectSqlAssert } from '#src/utils/test-utils.js';
|
||||
|
||||
import {
|
||||
findUnconsumedPasscodeByJtiAndType,
|
||||
findUnconsumedPasscodesByJtiAndType,
|
||||
insertPasscode,
|
||||
deletePasscodeById,
|
||||
deletePasscodesByIds,
|
||||
} from './passcode.js';
|
||||
|
||||
const { jest } = import.meta;
|
||||
|
||||
const mockQuery: jest.MockedFunction<QueryType> = jest.fn();
|
||||
|
@ -30,6 +22,14 @@ jest.spyOn(envSet, 'pool', 'get').mockReturnValue(
|
|||
})
|
||||
);
|
||||
|
||||
const {
|
||||
findUnconsumedPasscodeByJtiAndType,
|
||||
findUnconsumedPasscodesByJtiAndType,
|
||||
insertPasscode,
|
||||
deletePasscodeById,
|
||||
deletePasscodesByIds,
|
||||
} = await import('./passcode.js');
|
||||
|
||||
describe('passcode query', () => {
|
||||
const { table, fields } = convertToIdentifiers(Passcodes);
|
||||
|
||||
|
|
|
@ -8,16 +8,6 @@ import { DeletionError } from '#src/errors/SlonikError/index.js';
|
|||
import type { QueryType } from '#src/utils/test-utils.js';
|
||||
import { expectSqlAssert } from '#src/utils/test-utils.js';
|
||||
|
||||
import {
|
||||
findTotalNumberOfResources,
|
||||
findAllResources,
|
||||
findResourceById,
|
||||
findResourceByIndicator,
|
||||
insertResource,
|
||||
updateResourceById,
|
||||
deleteResourceById,
|
||||
} from './resource.js';
|
||||
|
||||
const { jest } = import.meta;
|
||||
|
||||
const mockQuery: jest.MockedFunction<QueryType> = jest.fn();
|
||||
|
@ -30,9 +20,23 @@ jest.spyOn(envSet, 'pool', 'get').mockReturnValue(
|
|||
})
|
||||
);
|
||||
|
||||
const {
|
||||
findTotalNumberOfResources,
|
||||
findAllResources,
|
||||
findResourceById,
|
||||
findResourceByIndicator,
|
||||
insertResource,
|
||||
updateResourceById,
|
||||
deleteResourceById,
|
||||
} = await import('./resource.js');
|
||||
|
||||
describe('resource query', () => {
|
||||
const { table, fields } = convertToIdentifiers(Resources);
|
||||
|
||||
afterEach(() => {
|
||||
mockQuery.mockClear();
|
||||
});
|
||||
|
||||
it('findTotalNumberOfResources', async () => {
|
||||
const expectSql = sql`
|
||||
select count(*)
|
||||
|
|
|
@ -8,17 +8,6 @@ import { DeletionError } from '#src/errors/SlonikError/index.js';
|
|||
import type { QueryType } from '#src/utils/test-utils.js';
|
||||
import { expectSqlAssert } from '#src/utils/test-utils.js';
|
||||
|
||||
import {
|
||||
deleteRoleById,
|
||||
findRoleById,
|
||||
findRoleByRoleName,
|
||||
findRolesByRoleIds,
|
||||
findRolesByRoleNames,
|
||||
insertRole,
|
||||
insertRoles,
|
||||
updateRoleById,
|
||||
} from './roles.js';
|
||||
|
||||
const { jest } = import.meta;
|
||||
|
||||
const mockQuery: jest.MockedFunction<QueryType> = jest.fn();
|
||||
|
@ -31,6 +20,17 @@ jest.spyOn(envSet, 'pool', 'get').mockReturnValue(
|
|||
})
|
||||
);
|
||||
|
||||
const {
|
||||
deleteRoleById,
|
||||
findRoleById,
|
||||
findRoleByRoleName,
|
||||
findRolesByRoleIds,
|
||||
findRolesByRoleNames,
|
||||
insertRole,
|
||||
insertRoles,
|
||||
updateRoleById,
|
||||
} = await import('./roles.js');
|
||||
|
||||
describe('roles query', () => {
|
||||
const { table, fields } = convertToIdentifiers(Roles);
|
||||
|
||||
|
|
|
@ -7,8 +7,6 @@ import envSet from '#src/env-set/index.js';
|
|||
import type { QueryType } from '#src/utils/test-utils.js';
|
||||
import { expectSqlAssert } from '#src/utils/test-utils.js';
|
||||
|
||||
import { defaultSettingId, getSetting, updateSetting } from './setting.js';
|
||||
|
||||
const { jest } = import.meta;
|
||||
|
||||
const mockQuery: jest.MockedFunction<QueryType> = jest.fn();
|
||||
|
@ -21,6 +19,8 @@ jest.spyOn(envSet, 'pool', 'get').mockReturnValue(
|
|||
})
|
||||
);
|
||||
|
||||
const { defaultSettingId, getSetting, updateSetting } = await import('./setting.js');
|
||||
|
||||
describe('setting query', () => {
|
||||
const { table, fields } = convertToIdentifiers(Settings);
|
||||
const dbvalue = { ...mockSetting, adminConsole: JSON.stringify(mockSetting.adminConsole) };
|
||||
|
|
|
@ -5,11 +5,6 @@ import envSet from '#src/env-set/index.js';
|
|||
import type { QueryType } from '#src/utils/test-utils.js';
|
||||
import { expectSqlAssert } from '#src/utils/test-utils.js';
|
||||
|
||||
import {
|
||||
findDefaultSignInExperience,
|
||||
updateDefaultSignInExperience,
|
||||
} from './sign-in-experience.js';
|
||||
|
||||
const { jest } = import.meta;
|
||||
|
||||
const mockQuery: jest.MockedFunction<QueryType> = jest.fn();
|
||||
|
@ -22,6 +17,10 @@ jest.spyOn(envSet, 'pool', 'get').mockReturnValue(
|
|||
})
|
||||
);
|
||||
|
||||
const { findDefaultSignInExperience, updateDefaultSignInExperience } = await import(
|
||||
'./sign-in-experience.js'
|
||||
);
|
||||
|
||||
describe('sign-in-experience query', () => {
|
||||
const id = 'default';
|
||||
|
||||
|
|
|
@ -8,21 +8,6 @@ import { DeletionError } from '#src/errors/SlonikError/index.js';
|
|||
import type { QueryType } from '#src/utils/test-utils.js';
|
||||
import { expectSqlAssert } from '#src/utils/test-utils.js';
|
||||
|
||||
import {
|
||||
findUserByUsername,
|
||||
findUserByEmail,
|
||||
findUserByPhone,
|
||||
findUserByIdentity,
|
||||
hasUser,
|
||||
hasUserWithId,
|
||||
hasUserWithEmail,
|
||||
hasUserWithIdentity,
|
||||
hasUserWithPhone,
|
||||
updateUserById,
|
||||
deleteUserById,
|
||||
deleteUserIdentity,
|
||||
} from './user.js';
|
||||
|
||||
const { jest } = import.meta;
|
||||
|
||||
const mockQuery: jest.MockedFunction<QueryType> = jest.fn();
|
||||
|
@ -35,6 +20,21 @@ jest.spyOn(envSet, 'pool', 'get').mockReturnValue(
|
|||
})
|
||||
);
|
||||
|
||||
const {
|
||||
findUserByUsername,
|
||||
findUserByEmail,
|
||||
findUserByPhone,
|
||||
findUserByIdentity,
|
||||
hasUser,
|
||||
hasUserWithId,
|
||||
hasUserWithEmail,
|
||||
hasUserWithIdentity,
|
||||
hasUserWithPhone,
|
||||
updateUserById,
|
||||
deleteUserById,
|
||||
deleteUserIdentity,
|
||||
} = await import('./user.js');
|
||||
|
||||
describe('user query', () => {
|
||||
const { table, fields } = convertToIdentifiers(Users);
|
||||
const { fields: rolesFields, table: rolesTable } = convertToIdentifiers(Roles);
|
||||
|
|
Loading…
Add table
Reference in a new issue