0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-01-27 21:39:16 -05:00

feat(schema): add resource and scope table (#145)

* feat(schema): add resource and scope table

implement resource-servers and resource-scopes table with some basic queries

* fix(schema): remove scope update querie

not allowed to update scope once created

* feat(schema): add resourceId foreign key to the scope table

add resourceId foreign key to the scope table

* fix(schema): remove trailling comma

remove trailling comma

* fix(schema): cr fix replace resource-servers table name using resources

replace resource-servers table name using resources
This commit is contained in:
simeng-li 2021-12-01 11:41:16 +08:00 committed by GitHub
parent 838ae3fad9
commit 18a142ab65
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 221 additions and 3 deletions

View file

@ -0,0 +1,70 @@
import { ResourceDBEntry, Resources } from '@logto/schemas';
import { sql } from 'slonik';
import { buildInsertInto } from '@/database/insert-into';
import pool from '@/database/pool';
import { buildUpdateWhere } from '@/database/update-where';
import { convertToIdentifiers, OmitAutoSetFields } from '@/database/utils';
import RequestError from '@/errors/RequestError';
const { table, fields } = convertToIdentifiers(Resources);
export const findAllResources = async () =>
pool.many<ResourceDBEntry>(sql`
select ${sql.join(Object.values(fields), sql`,`)}
from ${table}
`);
export const hasResource = async (indentifier: string) =>
pool.exists(sql`
select ${fields.id}
from ${table}
where ${fields.identifier}=${indentifier}
`);
export const hasResourceWithId = async (id: string) =>
pool.exists(sql`
select ${fields.id}
from ${table}
where ${fields.id}=${id}
`);
export const findResourceByIdentifier = async (indentifier: string) =>
pool.one<ResourceDBEntry>(sql`
select ${sql.join(Object.values(fields), sql`,`)}
from ${table}
where ${fields.identifier}=${indentifier}
`);
export const findResourceById = async (id: string) =>
pool.one<ResourceDBEntry>(sql`
select ${sql.join(Object.values(fields), sql`,`)}
from ${table}
where ${fields.id}=${id}
`);
export const insertResource = buildInsertInto<ResourceDBEntry>(pool, Resources, {
returning: true,
});
const updateResource = buildUpdateWhere<ResourceDBEntry>(pool, Resources, true);
export const updateResourceById = async (
id: string,
set: Partial<OmitAutoSetFields<ResourceDBEntry>>
) => updateResource({ set, where: { id } });
export const deleteResourceById = async (id: string) => {
const { rowCount } = await pool.query(sql`
delete from ${table}
where id=${id}
`);
if (rowCount < 1) {
throw new RequestError({
code: 'entity.not_exists_with_id',
name: Resources.tableSingular,
id,
status: 404,
});
}
};

View file

@ -0,0 +1,35 @@
import { ResourceScopeDBEntry, ResourceScopes } from '@logto/schemas';
import { sql } from 'slonik';
import { buildInsertInto } from '@/database/insert-into';
import pool from '@/database/pool';
import { convertToIdentifiers } from '@/database/utils';
import RequestError from '@/errors/RequestError';
const { table, fields } = convertToIdentifiers(ResourceScopes);
export const findAllScopesWithResourceId = async (resourceId: string) =>
pool.many<ResourceScopeDBEntry>(sql`
select ${sql.join(Object.values(fields), sql`,`)}
from ${table}
where ${fields.resourceId}=${resourceId}
`);
export const insertScope = buildInsertInto<ResourceScopeDBEntry>(pool, ResourceScopes, {
returning: true,
});
export const deleteScopeById = async (id: string) => {
const { rowCount } = await pool.query(sql`
delete from ${table}
where id=${id}
`);
if (rowCount < 1) {
throw new RequestError({
code: 'entity.not_exists_with_id',
name: ResourceScopes.tableSingular,
id,
status: 404,
});
}
};

View file

@ -5,6 +5,12 @@ export enum ApplicationType {
SPA = 'SPA',
Traditional = 'Traditional',
}
export enum SignAlgorithmType {
RS256 = 'RS256',
}
export enum AccessTokenFormatType {
jwt = 'jwt',
}
export enum UserLogType {
SignInUsernameAndPassword = 'SignInUsernameAndPassword',
ExchangeAccessToken = 'ExchangeAccessToken',

View file

@ -3,5 +3,7 @@
export * from './custom-types';
export * from './application';
export * from './oidc-model-instance';
export * from './resource-scope';
export * from './resource';
export * from './user-log';
export * from './user';

View file

@ -0,0 +1,32 @@
// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
import { z } from 'zod';
import { GeneratedSchema, Guard } from '../foundations';
export type ResourceScopeDBEntry = {
id: string;
name: string;
description: string;
resourceId: string;
};
const guard: Guard<ResourceScopeDBEntry> = z.object({
id: z.string(),
name: z.string(),
description: z.string(),
resourceId: z.string(),
});
export const ResourceScopes: GeneratedSchema<ResourceScopeDBEntry> = Object.freeze({
table: 'resource_scopes',
tableSingular: 'resource_scope',
fields: {
id: 'id',
name: 'name',
description: 'description',
resourceId: 'resource_id',
},
fieldKeys: ['id', 'name', 'description', 'resourceId'],
guard,
});

View file

@ -0,0 +1,39 @@
// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
import { z } from 'zod';
import { GeneratedSchema, Guard } from '../foundations';
import { AccessTokenFormatType, SignAlgorithmType } from './custom-types';
export type ResourceDBEntry = {
id: string;
name: string;
identifier: string;
accessTokenTtl: number;
accessTokenFormat: AccessTokenFormatType;
signAlgorithm: SignAlgorithmType;
};
const guard: Guard<ResourceDBEntry> = z.object({
id: z.string(),
name: z.string(),
identifier: z.string(),
accessTokenTtl: z.number(),
accessTokenFormat: z.nativeEnum(AccessTokenFormatType),
signAlgorithm: z.nativeEnum(SignAlgorithmType),
});
export const Resources: GeneratedSchema<ResourceDBEntry> = Object.freeze({
table: 'resources',
tableSingular: 'resource',
fields: {
id: 'id',
name: 'name',
identifier: 'identifier',
accessTokenTtl: 'access_token_ttl',
accessTokenFormat: 'access_token_format',
signAlgorithm: 'sign_algorithm',
},
fieldKeys: ['id', 'name', 'identifier', 'accessTokenTtl', 'accessTokenFormat', 'signAlgorithm'],
guard,
});

View file

@ -45,9 +45,15 @@ const generate = async () => {
.split(',')
.map((value) => normalizeWhitespaces(value))
.filter((value) =>
['primary', 'foreign', 'unique', 'exclude', 'check'].every(
(constraint) => !value.toLowerCase().startsWith(constraint + ' ')
)
[
'primary',
'foreign',
'unique',
'exclude',
'check',
'constraint',
'references',
].every((constraint) => !value.toLowerCase().startsWith(constraint + ' '))
)
.map<Field>((value) => {
const [nameRaw, typeRaw, ...rest] = value.split(' ');

View file

@ -0,0 +1,16 @@
create table resource_scopes (
id varchar(24) not null,
name varchar(64) not null,
description text not null,
resource_id varchar(24) not null,
primary key (id),
constraint fk_resource
foreign key (resource_id)
references resources(id)
);
create unique index resource_scopes__resource_id_name
on resource_scopes (
resource_id,
name,
);

View file

@ -0,0 +1,12 @@
create type sign_algorithm_type as enum ('RS256');
create type access_token_format_type as enum ('jwt');
create table resources (
id varchar(24) not null,
name text not null,
identifier text not null unique, /* resource indicator also used as audience */
access_token_ttl bigint not null default(86400), /* expiration value in seconds, default is 24h */
access_token_format access_token_format_type not null default('jwt'),
sign_algorithm sign_algorithm_type not null default('RS256'),
primary key (id)
);