mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
feat: PATCH /application/:id
This commit is contained in:
parent
334cc5903a
commit
6b6210feee
8 changed files with 106 additions and 12 deletions
|
@ -32,19 +32,19 @@ type InsertIntoConfig = {
|
|||
};
|
||||
|
||||
interface BuildInsertInto {
|
||||
<Schema extends SchemaLike<string>>(
|
||||
<Schema extends SchemaLike>(
|
||||
pool: DatabasePoolType,
|
||||
{ fieldKeys, ...rest }: GeneratedSchema<Schema>,
|
||||
config: InsertIntoConfigReturning
|
||||
): (data: OmitAutoSetFields<Schema>) => Promise<Schema>;
|
||||
<Schema extends SchemaLike<string>>(
|
||||
<Schema extends SchemaLike>(
|
||||
pool: DatabasePoolType,
|
||||
{ fieldKeys, ...rest }: GeneratedSchema<Schema>,
|
||||
config?: InsertIntoConfig
|
||||
): (data: OmitAutoSetFields<Schema>) => Promise<void>;
|
||||
}
|
||||
|
||||
export const buildInsertInto: BuildInsertInto = <Schema extends SchemaLike<string>>(
|
||||
export const buildInsertInto: BuildInsertInto = <Schema extends SchemaLike>(
|
||||
pool: DatabasePoolType,
|
||||
{ fieldKeys, ...rest }: GeneratedSchema<Schema>,
|
||||
config?: InsertIntoConfig | InsertIntoConfigReturning
|
||||
|
@ -55,7 +55,9 @@ export const buildInsertInto: BuildInsertInto = <Schema extends SchemaLike<strin
|
|||
const onConflict = config?.onConflict;
|
||||
|
||||
return async (data: OmitAutoSetFields<Schema>): Promise<Schema | void> => {
|
||||
const result = await pool.query<Schema>(sql`
|
||||
const {
|
||||
rows: [entry],
|
||||
} = await pool.query<Schema>(sql`
|
||||
insert into ${table} (${sql.join(
|
||||
keys.map((key) => fields[key]),
|
||||
sql`, `
|
||||
|
@ -74,10 +76,6 @@ export const buildInsertInto: BuildInsertInto = <Schema extends SchemaLike<strin
|
|||
)}
|
||||
`);
|
||||
|
||||
const {
|
||||
rows: [entry],
|
||||
} = result;
|
||||
|
||||
assert(
|
||||
!returning || entry,
|
||||
new RequestError({ code: 'entity.create_failed', name: rest.tableSingular })
|
||||
|
|
58
packages/core/src/database/update-where.ts
Normal file
58
packages/core/src/database/update-where.ts
Normal file
|
@ -0,0 +1,58 @@
|
|||
import assert from 'assert';
|
||||
import RequestError from '@/errors/RequestError';
|
||||
import { SchemaLike, GeneratedSchema } from '@logto/schemas';
|
||||
import { DatabasePoolType, sql } from 'slonik';
|
||||
import { isKeyOf } from '@/utils/schema';
|
||||
import { notFalsy, Truthy } from '@logto/essentials';
|
||||
import { conditionalSql, convertToIdentifiers, convertToPrimitiveOrSql } from './utils';
|
||||
|
||||
export type UpdateWhereData<Schema extends SchemaLike> = {
|
||||
set: Partial<Schema>;
|
||||
where: Partial<Schema>;
|
||||
};
|
||||
|
||||
interface BuildUpdateWhere {
|
||||
<Schema extends SchemaLike>(
|
||||
pool: DatabasePoolType,
|
||||
schema: GeneratedSchema<Schema>,
|
||||
returning: true
|
||||
): (data: UpdateWhereData<Schema>) => Promise<Schema>;
|
||||
<Schema extends SchemaLike>(
|
||||
pool: DatabasePoolType,
|
||||
schema: GeneratedSchema<Schema>,
|
||||
returning?: false
|
||||
): (data: UpdateWhereData<Schema>) => Promise<void>;
|
||||
}
|
||||
|
||||
export const buildUpdateWhere: BuildUpdateWhere = <Schema extends SchemaLike>(
|
||||
pool: DatabasePoolType,
|
||||
schema: GeneratedSchema<Schema>,
|
||||
returning = false
|
||||
) => {
|
||||
const { table, fields } = convertToIdentifiers(schema);
|
||||
const isKeyOfSchema = isKeyOf(schema);
|
||||
const connectKeyValueWithEqualSign = (data: Partial<Schema>) =>
|
||||
Object.entries(data)
|
||||
.map(
|
||||
([key, value]) =>
|
||||
isKeyOfSchema(key) && sql`${fields[key]}=${convertToPrimitiveOrSql(key, value)}`
|
||||
)
|
||||
.filter((value): value is Truthy<typeof value> => notFalsy(value));
|
||||
|
||||
return async ({ set, where }: UpdateWhereData<Schema>) => {
|
||||
const {
|
||||
rows: [entry],
|
||||
} = await pool.query<Schema>(sql`
|
||||
update ${table}
|
||||
set ${sql.join(connectKeyValueWithEqualSign(set), sql`, `)}
|
||||
where ${sql.join(connectKeyValueWithEqualSign(where), sql`, `)}
|
||||
${conditionalSql(returning, () => sql`returning *`)}
|
||||
`);
|
||||
|
||||
assert(
|
||||
!returning || entry,
|
||||
new RequestError({ code: 'entity.update_failed', name: schema.tableSingular })
|
||||
);
|
||||
return entry;
|
||||
};
|
||||
};
|
|
@ -1,6 +1,7 @@
|
|||
import { buildInsertInto } from '@/database/insert-into';
|
||||
import pool from '@/database/pool';
|
||||
import { convertToIdentifiers } from '@/database/utils';
|
||||
import { buildUpdateWhere } from '@/database/update-where';
|
||||
import { convertToIdentifiers, OmitAutoSetFields } from '@/database/utils';
|
||||
import RequestError from '@/errors/RequestError';
|
||||
import { ApplicationDBEntry, Applications } from '@logto/schemas';
|
||||
import { sql } from 'slonik';
|
||||
|
@ -18,6 +19,13 @@ export const insertApplication = buildInsertInto<ApplicationDBEntry>(pool, Appli
|
|||
returning: true,
|
||||
});
|
||||
|
||||
const updateApplication = buildUpdateWhere<ApplicationDBEntry>(pool, Applications, true);
|
||||
|
||||
export const updateApplicationById = async (
|
||||
id: string,
|
||||
set: Partial<OmitAutoSetFields<ApplicationDBEntry>>
|
||||
) => updateApplication({ set, where: { id } });
|
||||
|
||||
export const deleteApplicationById = async (id: string) => {
|
||||
const { rowCount } = await pool.query(sql`
|
||||
delete from ${table}
|
||||
|
|
|
@ -2,7 +2,11 @@ import Router from 'koa-router';
|
|||
import { object, string } from 'zod';
|
||||
import { Applications } from '@logto/schemas';
|
||||
import koaGuard from '@/middleware/koa-guard';
|
||||
import { deleteApplicationById, insertApplication } from '@/queries/application';
|
||||
import {
|
||||
deleteApplicationById,
|
||||
insertApplication,
|
||||
updateApplicationById,
|
||||
} from '@/queries/application';
|
||||
import { buildIdGenerator } from '@/utils/id';
|
||||
import { generateOidcClientMetadata } from '@/oidc/utils';
|
||||
|
||||
|
@ -31,6 +35,24 @@ export default function applicationRoutes<StateT, ContextT>(router: Router<State
|
|||
}
|
||||
);
|
||||
|
||||
router.patch(
|
||||
'/application/:id',
|
||||
koaGuard({
|
||||
params: object({ id: string().min(1) }),
|
||||
// Consider `.deepPartial()` if OIDC client metadata bloats
|
||||
body: Applications.guard.omit({ id: true, createdAt: true }).partial(),
|
||||
}),
|
||||
async (ctx, next) => {
|
||||
const {
|
||||
params: { id },
|
||||
body,
|
||||
} = ctx.guard;
|
||||
|
||||
ctx.body = await updateApplicationById(id, body);
|
||||
return next();
|
||||
}
|
||||
);
|
||||
|
||||
router.delete(
|
||||
'/application/:id',
|
||||
koaGuard({ params: object({ id: string().min(1) }) }),
|
||||
|
|
6
packages/core/src/utils/schema.ts
Normal file
6
packages/core/src/utils/schema.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
import { GeneratedSchema, SchemaLike } from '@logto/schemas';
|
||||
|
||||
export const isKeyOf =
|
||||
<Schema extends SchemaLike>({ fieldKeys }: GeneratedSchema<Schema>) =>
|
||||
(key: string): key is keyof Schema extends string ? keyof Schema : never =>
|
||||
fieldKeys.includes(key);
|
|
@ -39,6 +39,7 @@ const errors = {
|
|||
},
|
||||
entity: {
|
||||
create_failed: 'Failed to create {{name}}.',
|
||||
update_failed: 'Failed to update {{name}}.',
|
||||
not_exists: 'The {{name}} with ID `{{id}}` does not exist.',
|
||||
},
|
||||
};
|
||||
|
|
|
@ -41,6 +41,7 @@ const errors = {
|
|||
},
|
||||
entity: {
|
||||
create_failed: '创建 {{name}} 失败。',
|
||||
update_failed: '更新 {{name}} 失败。',
|
||||
not_exists: 'ID 为 `{{id}}` 的 {{name}} 不存在。',
|
||||
},
|
||||
};
|
||||
|
|
|
@ -8,11 +8,11 @@ export type Guard<T extends Record<string, unknown>> = ZodObject<
|
|||
|
||||
export type SchemaValuePrimitive = string | number | boolean | undefined;
|
||||
export type SchemaValue = SchemaValuePrimitive | Record<string, unknown>;
|
||||
export type SchemaLike<Key extends string> = {
|
||||
export type SchemaLike<Key extends string = string> = {
|
||||
[key in Key]: SchemaValue;
|
||||
};
|
||||
|
||||
export type GeneratedSchema<Schema extends SchemaLike<string>> = keyof Schema extends string
|
||||
export type GeneratedSchema<Schema extends SchemaLike> = keyof Schema extends string
|
||||
? Readonly<{
|
||||
table: string;
|
||||
tableSingular: string;
|
||||
|
|
Loading…
Reference in a new issue