mirror of
https://github.com/logto-io/logto.git
synced 2025-03-31 22:51:25 -05:00
feat(core,phrases,schemas): upload user assets with azure storage (#3289)
This commit is contained in:
parent
d98086ff9a
commit
f25a9d343c
24 changed files with 503 additions and 33 deletions
5
.changeset-staged/happy-paws-nail.md
Normal file
5
.changeset-staged/happy-paws-nail.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@logto/core": minor
|
||||
---
|
||||
|
||||
Add API for uploading user images to storage providers: Azure Storage.
|
|
@ -25,6 +25,7 @@
|
|||
"test:report": "codecov -F core"
|
||||
},
|
||||
"dependencies": {
|
||||
"@azure/storage-blob": "^12.13.0",
|
||||
"@koa/cors": "^4.0.0",
|
||||
"@logto/cli": "workspace:*",
|
||||
"@logto/connector-kit": "workspace:*",
|
||||
|
|
|
@ -4,6 +4,7 @@ import { findUp } from 'find-up';
|
|||
import Koa from 'koa';
|
||||
|
||||
import { checkAlterationState } from './env-set/check-alteration-state.js';
|
||||
import SystemContext from './tenants/SystemContext.js';
|
||||
|
||||
dotenv.config({ path: await findUp('.env', {}) });
|
||||
|
||||
|
@ -23,6 +24,7 @@ try {
|
|||
checkRowLevelSecurity(EnvSet.queryClient),
|
||||
checkAlterationState(await EnvSet.pool),
|
||||
]);
|
||||
await SystemContext.shared.loadStorageProviderConfig(await EnvSet.pool);
|
||||
|
||||
// Import last until init completed
|
||||
const { default: initApp } = await import('./app/init.js');
|
||||
|
|
|
@ -44,7 +44,7 @@ describe('koaGuardMiddleware', () => {
|
|||
});
|
||||
|
||||
// Use to bypass the context type assert
|
||||
const defaultGuard = { body: undefined, query: undefined, params: undefined };
|
||||
const defaultGuard = { body: undefined, query: undefined, params: undefined, files: undefined };
|
||||
|
||||
it('invalid body type should throw', async () => {
|
||||
const ctx = {
|
||||
|
|
|
@ -10,27 +10,30 @@ import RequestError from '#src/errors/RequestError/index.js';
|
|||
import ServerError from '#src/errors/ServerError/index.js';
|
||||
import assertThat from '#src/utils/assert-that.js';
|
||||
|
||||
export type GuardConfig<QueryT, BodyT, ParametersT, ResponseT> = {
|
||||
export type GuardConfig<QueryT, BodyT, ParametersT, ResponseT, FilesT> = {
|
||||
query?: ZodType<QueryT>;
|
||||
body?: ZodType<BodyT>;
|
||||
params?: ZodType<ParametersT>;
|
||||
response?: ZodType<ResponseT>;
|
||||
status?: number | number[];
|
||||
files?: ZodType<FilesT>;
|
||||
};
|
||||
|
||||
export type GuardedRequest<QueryT, BodyT, ParametersT> = {
|
||||
export type GuardedRequest<QueryT, BodyT, ParametersT, FilesT> = {
|
||||
query: QueryT;
|
||||
body: BodyT;
|
||||
params: ParametersT;
|
||||
files: FilesT;
|
||||
};
|
||||
|
||||
export type WithGuardedRequestContext<
|
||||
ContextT extends IRouterParamContext,
|
||||
GuardQueryT,
|
||||
GuardBodyT,
|
||||
GuardParametersT
|
||||
GuardParametersT,
|
||||
GuardFilesT
|
||||
> = ContextT & {
|
||||
guard: GuardedRequest<GuardQueryT, GuardBodyT, GuardParametersT>;
|
||||
guard: GuardedRequest<GuardQueryT, GuardBodyT, GuardParametersT, GuardFilesT>;
|
||||
};
|
||||
|
||||
export type WithGuardConfig<
|
||||
|
@ -38,9 +41,10 @@ export type WithGuardConfig<
|
|||
GuardQueryT = unknown,
|
||||
GuardBodyT = unknown,
|
||||
GuardParametersT = unknown,
|
||||
GuardResponseT = unknown
|
||||
GuardResponseT = unknown,
|
||||
GuardFilesT = undefined
|
||||
> = Type & {
|
||||
config: GuardConfig<GuardQueryT, GuardBodyT, GuardParametersT, GuardResponseT>;
|
||||
config: GuardConfig<GuardQueryT, GuardBodyT, GuardParametersT, GuardResponseT, GuardFilesT>;
|
||||
};
|
||||
|
||||
export const isGuardMiddleware = <Type extends IMiddleware>(
|
||||
|
@ -49,7 +53,7 @@ export const isGuardMiddleware = <Type extends IMiddleware>(
|
|||
function_.name === 'guardMiddleware' && has(function_, 'config');
|
||||
|
||||
const tryParse = <Output, Definition extends ZodTypeDef, Input>(
|
||||
type: 'query' | 'body' | 'params',
|
||||
type: 'query' | 'body' | 'params' | 'files',
|
||||
guard: Optional<ZodType<Output, Definition, Input>>,
|
||||
data: unknown
|
||||
) => {
|
||||
|
@ -66,21 +70,29 @@ export default function koaGuard<
|
|||
GuardQueryT = undefined,
|
||||
GuardBodyT = undefined,
|
||||
GuardParametersT = undefined,
|
||||
GuardResponseT = unknown
|
||||
GuardResponseT = unknown,
|
||||
GuardFilesT = undefined
|
||||
>({
|
||||
query,
|
||||
body,
|
||||
params,
|
||||
response,
|
||||
status,
|
||||
}: GuardConfig<GuardQueryT, GuardBodyT, GuardParametersT, GuardResponseT>): MiddlewareType<
|
||||
files,
|
||||
}: GuardConfig<
|
||||
GuardQueryT,
|
||||
GuardBodyT,
|
||||
GuardParametersT,
|
||||
GuardResponseT,
|
||||
GuardFilesT
|
||||
>): MiddlewareType<
|
||||
StateT,
|
||||
WithGuardedRequestContext<ContextT, GuardQueryT, GuardBodyT, GuardParametersT>,
|
||||
WithGuardedRequestContext<ContextT, GuardQueryT, GuardBodyT, GuardParametersT, GuardFilesT>,
|
||||
GuardResponseT
|
||||
> {
|
||||
const guard: MiddlewareType<
|
||||
StateT,
|
||||
WithGuardedRequestContext<ContextT, GuardQueryT, GuardBodyT, GuardParametersT>,
|
||||
WithGuardedRequestContext<ContextT, GuardQueryT, GuardBodyT, GuardParametersT, GuardFilesT>,
|
||||
GuardResponseT
|
||||
> = async (ctx, next) => {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions, no-restricted-syntax
|
||||
|
@ -88,7 +100,8 @@ export default function koaGuard<
|
|||
query: tryParse('query', query, ctx.request.query),
|
||||
body: tryParse('body', body, ctx.request.body),
|
||||
params: tryParse('params', params, ctx.params),
|
||||
} as GuardedRequest<GuardQueryT, GuardBodyT, GuardParametersT>; // Have to do this since it's too complicated for TS
|
||||
files: tryParse('files', files, ctx.request.files),
|
||||
} as GuardedRequest<GuardQueryT, GuardBodyT, GuardParametersT, GuardFilesT>; // Have to do this since it's too complicated for TS
|
||||
|
||||
return next();
|
||||
};
|
||||
|
@ -96,12 +109,14 @@ export default function koaGuard<
|
|||
const guardMiddleware: WithGuardConfig<
|
||||
MiddlewareType<
|
||||
StateT,
|
||||
WithGuardedRequestContext<ContextT, GuardQueryT, GuardBodyT, GuardParametersT>,
|
||||
WithGuardedRequestContext<ContextT, GuardQueryT, GuardBodyT, GuardParametersT, GuardFilesT>,
|
||||
GuardResponseT
|
||||
>
|
||||
> = async function (ctx, next) {
|
||||
if (body) {
|
||||
return koaBody<StateT, ContextT>()(ctx, async () => guard(ctx, next));
|
||||
if (body ?? files) {
|
||||
return koaBody<StateT, ContextT>({ multipart: Boolean(files) })(ctx, async () =>
|
||||
guard(ctx, next)
|
||||
);
|
||||
}
|
||||
|
||||
await guard(ctx, next);
|
||||
|
|
|
@ -26,6 +26,7 @@ import signInExperiencesRoutes from './sign-in-experience/index.js';
|
|||
import statusRoutes from './status.js';
|
||||
import swaggerRoutes from './swagger.js';
|
||||
import type { AnonymousRouter, AuthedRouter } from './types.js';
|
||||
import userAssetsRoutes from './user-assets.js';
|
||||
import verificationCodeRoutes from './verification-code.js';
|
||||
import wellKnownRoutes from './well-known.js';
|
||||
|
||||
|
@ -49,6 +50,7 @@ const createRouters = (tenant: TenantContext) => {
|
|||
customPhraseRoutes(managementRouter, tenant);
|
||||
hookRoutes(managementRouter, tenant);
|
||||
verificationCodeRoutes(managementRouter, tenant);
|
||||
userAssetsRoutes(managementRouter, tenant);
|
||||
|
||||
const anonymousRouter: AnonymousRouter = new Router();
|
||||
phraseRoutes(anonymousRouter, tenant);
|
||||
|
|
80
packages/core/src/routes/user-assets.ts
Normal file
80
packages/core/src/routes/user-assets.ts
Normal file
|
@ -0,0 +1,80 @@
|
|||
import { readFile } from 'fs/promises';
|
||||
|
||||
import { generateStandardId } from '@logto/core-kit';
|
||||
import { format } from 'date-fns';
|
||||
import { object } from 'zod';
|
||||
|
||||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
import koaGuard from '#src/middleware/koa-guard.js';
|
||||
import SystemContext from '#src/tenants/SystemContext.js';
|
||||
import assertThat from '#src/utils/assert-that.js';
|
||||
import {
|
||||
allowUploadMimeTypes,
|
||||
maxUploadFileSize,
|
||||
uploadFileGuard,
|
||||
} from '#src/utils/storage/consts.js';
|
||||
import { buildUploadFile } from '#src/utils/storage/index.js';
|
||||
import { getTenantId } from '#src/utils/tenant.js';
|
||||
|
||||
import type { AuthedRouter, RouterInitArgs } from './types.js';
|
||||
|
||||
export default function userAssetsRoutes<T extends AuthedRouter>(...[router]: RouterInitArgs<T>) {
|
||||
router.get('/user-assets/service-status', async (ctx, next) => {
|
||||
const { storageProviderConfig } = SystemContext.shared;
|
||||
|
||||
ctx.body = storageProviderConfig
|
||||
? {
|
||||
status: 'ready',
|
||||
allowUploadMimeTypes,
|
||||
maxUploadFileSize,
|
||||
}
|
||||
: {
|
||||
status: 'not_configured',
|
||||
};
|
||||
|
||||
return next();
|
||||
});
|
||||
|
||||
router.post(
|
||||
'/user-assets',
|
||||
koaGuard({
|
||||
files: object({
|
||||
file: uploadFileGuard,
|
||||
}),
|
||||
}),
|
||||
async (ctx, next) => {
|
||||
const { file } = ctx.guard.files;
|
||||
|
||||
assertThat(file.size <= maxUploadFileSize, 'guard.file_size_exceeded');
|
||||
assertThat(allowUploadMimeTypes.includes(file.mimetype), 'guard.mime_type_not_allowed');
|
||||
|
||||
const tenantId = getTenantId(ctx.URL);
|
||||
assertThat(tenantId, 'guard.can_not_get_tenant_id');
|
||||
|
||||
const { storageProviderConfig } = SystemContext.shared;
|
||||
assertThat(storageProviderConfig, 'storage.not_configured');
|
||||
|
||||
const userId = ctx.auth.id;
|
||||
const uploadFile = buildUploadFile(storageProviderConfig);
|
||||
const objectKey = `${tenantId}/${userId}/${format(
|
||||
new Date(),
|
||||
'yyyy/MM/dd'
|
||||
)}/${generateStandardId(8)}/${file.originalFilename}`;
|
||||
|
||||
try {
|
||||
const { url } = await uploadFile(await readFile(file.filepath), objectKey, {
|
||||
contentType: file.mimetype,
|
||||
publicUrl: storageProviderConfig.publicUrl,
|
||||
});
|
||||
|
||||
ctx.body = {
|
||||
url,
|
||||
};
|
||||
} catch {
|
||||
throw new RequestError('storage.upload_error');
|
||||
}
|
||||
|
||||
return next();
|
||||
}
|
||||
);
|
||||
}
|
33
packages/core/src/tenants/SystemContext.ts
Normal file
33
packages/core/src/tenants/SystemContext.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
import type { StorageProviderData } from '@logto/schemas';
|
||||
import { storageProviderDataGuard, StorageProviderKey, Systems } from '@logto/schemas';
|
||||
import { convertToIdentifiers } from '@logto/shared';
|
||||
import type { CommonQueryMethods } from 'slonik';
|
||||
import { sql } from 'slonik';
|
||||
|
||||
const { table, fields } = convertToIdentifiers(Systems);
|
||||
|
||||
export default class SystemContext {
|
||||
static shared = new SystemContext();
|
||||
public storageProviderConfig: StorageProviderData | undefined;
|
||||
|
||||
async loadStorageProviderConfig(pool: CommonQueryMethods) {
|
||||
const record = await pool.maybeOne<Record<string, unknown>>(sql`
|
||||
select ${fields.value} from ${table}
|
||||
where ${fields.key} = ${StorageProviderKey.StorageProvider}
|
||||
`);
|
||||
|
||||
if (!record) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = storageProviderDataGuard.safeParse(record.value);
|
||||
|
||||
if (!result.success) {
|
||||
console.error('Failed to parse storage provider config:', result.error);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.storageProviderConfig = result.data;
|
||||
}
|
||||
}
|
|
@ -83,7 +83,14 @@ export default class Tenant implements TenantContext {
|
|||
const provider = initOidc(envSet, queries, libraries);
|
||||
app.use(mount('/oidc', provider.app));
|
||||
|
||||
const tenantContext: TenantContext = { id, provider, queries, libraries, modelRouters, envSet };
|
||||
const tenantContext: TenantContext = {
|
||||
id,
|
||||
provider,
|
||||
queries,
|
||||
libraries,
|
||||
modelRouters,
|
||||
envSet,
|
||||
};
|
||||
// Mount APIs
|
||||
app.use(mount('/api', initApis(tenantContext)));
|
||||
|
||||
|
|
32
packages/core/src/utils/storage/azure-storage.ts
Normal file
32
packages/core/src/utils/storage/azure-storage.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { BlobServiceClient } from '@azure/storage-blob';
|
||||
|
||||
import type { UploadFile } from './types.js';
|
||||
|
||||
const defaultPublicDomain = 'blob.core.windows.net';
|
||||
|
||||
export const buildAzureStorage = (connectionString: string, container: string) => {
|
||||
const blobServiceClient = BlobServiceClient.fromConnectionString(connectionString);
|
||||
const containerClient = blobServiceClient.getContainerClient(container);
|
||||
|
||||
const uploadFile: UploadFile = async (
|
||||
data: Buffer,
|
||||
objectKey: string,
|
||||
{ contentType, publicUrl } = {}
|
||||
) => {
|
||||
const blockBlobClient = containerClient.getBlockBlobClient(objectKey);
|
||||
|
||||
await blockBlobClient.uploadData(data, {
|
||||
blobHTTPHeaders: contentType ? { blobContentType: contentType } : undefined,
|
||||
});
|
||||
|
||||
if (publicUrl) {
|
||||
return { url: `${publicUrl}/${objectKey}` };
|
||||
}
|
||||
|
||||
return {
|
||||
url: `https://${blobServiceClient.accountName}.${defaultPublicDomain}/${container}/${objectKey}`,
|
||||
};
|
||||
};
|
||||
|
||||
return { uploadFile };
|
||||
};
|
21
packages/core/src/utils/storage/consts.ts
Normal file
21
packages/core/src/utils/storage/consts.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { number, object, string } from 'zod';
|
||||
|
||||
export const maxUploadFileSize = 8 * 1024 * 1024; // 8MB
|
||||
// Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
|
||||
export const allowUploadMimeTypes = [
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/gif',
|
||||
'image/vnd.microsoft.icon',
|
||||
'image/svg+xml',
|
||||
'image/tiff',
|
||||
'image/webp',
|
||||
'image/bmp',
|
||||
];
|
||||
|
||||
export const uploadFileGuard = object({
|
||||
filepath: string(),
|
||||
mimetype: string(),
|
||||
originalFilename: string(),
|
||||
size: number(),
|
||||
});
|
14
packages/core/src/utils/storage/index.ts
Normal file
14
packages/core/src/utils/storage/index.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import type { StorageProviderData } from '@logto/schemas';
|
||||
|
||||
import { buildAzureStorage } from './azure-storage.js';
|
||||
import type { UploadFile } from './types.js';
|
||||
|
||||
export const buildUploadFile = (config: StorageProviderData): UploadFile => {
|
||||
if (config.provider === 'AzureStorage') {
|
||||
const storage = buildAzureStorage(config.connectionString, config.container);
|
||||
|
||||
return storage.uploadFile;
|
||||
}
|
||||
|
||||
throw new Error('provider not supported');
|
||||
};
|
10
packages/core/src/utils/storage/types.ts
Normal file
10
packages/core/src/utils/storage/types.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
export type UploadFileOptions = {
|
||||
contentType?: string;
|
||||
publicUrl?: string;
|
||||
};
|
||||
|
||||
export type UploadFile = (
|
||||
data: Buffer,
|
||||
objectKey: string,
|
||||
options?: UploadFileOptions
|
||||
) => Promise<{ url: string }>;
|
|
@ -16,6 +16,9 @@ const errors = {
|
|||
guard: {
|
||||
invalid_input: 'Die Anfrage {{type}} ist ungültig.',
|
||||
invalid_pagination: 'Die Paginierung der Anfrage ist ungültig.',
|
||||
can_not_get_tenant_id: 'Unable to get tenant id from request.', // UNTRANSLATED
|
||||
file_size_exceeded: 'File size exceeded.', // UNTRANSLATED
|
||||
mime_type_not_allowed: 'Mime type is not allowed.', // UNTRANSLATED
|
||||
},
|
||||
oidc: {
|
||||
aborted: 'Der Endnutzer hat die Interaktion abgebrochen.',
|
||||
|
@ -189,6 +192,11 @@ const errors = {
|
|||
name_exists: 'The scope name {{name}} is already in use', // UNTRANSLATED
|
||||
name_with_space: 'The name of the scope cannot contain any spaces.', // UNTRANSLATED
|
||||
},
|
||||
storage: {
|
||||
not_configured: 'Storage provider is not configured.', // UNTRANSLATED
|
||||
missing_parameter: 'Missing parameter {{parameter}} for storage provider.', // UNTRANSLATED
|
||||
upload_error: 'Failed to upload file to the storage provider.', // UNTRANSLATED
|
||||
},
|
||||
};
|
||||
|
||||
export default errors;
|
||||
|
|
|
@ -16,6 +16,9 @@ const errors = {
|
|||
guard: {
|
||||
invalid_input: 'The request {{type}} is invalid.',
|
||||
invalid_pagination: 'The request pagination value is invalid.',
|
||||
can_not_get_tenant_id: 'Unable to get tenant id from request.',
|
||||
file_size_exceeded: 'File size exceeded.',
|
||||
mime_type_not_allowed: 'Mime type is not allowed.',
|
||||
},
|
||||
oidc: {
|
||||
aborted: 'The end-user aborted interaction.',
|
||||
|
@ -188,6 +191,11 @@ const errors = {
|
|||
name_exists: 'The scope name {{name}} is already in use',
|
||||
name_with_space: 'The name of the scope cannot contain any spaces.',
|
||||
},
|
||||
storage: {
|
||||
not_configured: 'Storage provider is not configured.',
|
||||
missing_parameter: 'Missing parameter {{parameter}} for storage provider.',
|
||||
upload_error: 'Failed to upload file to the storage provider.',
|
||||
},
|
||||
};
|
||||
|
||||
export default errors;
|
||||
|
|
|
@ -17,6 +17,9 @@ const errors = {
|
|||
guard: {
|
||||
invalid_input: "La requête {{type}} n'est pas valide.",
|
||||
invalid_pagination: "La valeur de la pagination de la requête n'est pas valide.",
|
||||
can_not_get_tenant_id: 'Unable to get tenant id from request.', // UNTRANSLATED
|
||||
file_size_exceeded: 'File size exceeded.', // UNTRANSLATED
|
||||
mime_type_not_allowed: 'Mime type is not allowed.', // UNTRANSLATED
|
||||
},
|
||||
oidc: {
|
||||
aborted: "L'utilisateur a abandonné l'interaction.",
|
||||
|
@ -195,6 +198,11 @@ const errors = {
|
|||
name_exists: 'The scope name {{name}} is already in use', // UNTRANSLATED
|
||||
name_with_space: 'The name of the scope cannot contain any spaces.', // UNTRANSLATED
|
||||
},
|
||||
storage: {
|
||||
not_configured: 'Storage provider is not configured.', // UNTRANSLATED
|
||||
missing_parameter: 'Missing parameter {{parameter}} for storage provider.', // UNTRANSLATED
|
||||
upload_error: 'Failed to upload file to the storage provider.', // UNTRANSLATED
|
||||
},
|
||||
};
|
||||
|
||||
export default errors;
|
||||
|
|
|
@ -16,6 +16,9 @@ const errors = {
|
|||
guard: {
|
||||
invalid_input: '{{type}} 요청 타입은 유효하지 않아요.',
|
||||
invalid_pagination: '요청의 Pagination 값이 유효하지 않아요.',
|
||||
can_not_get_tenant_id: 'Unable to get tenant id from request.', // UNTRANSLATED
|
||||
file_size_exceeded: 'File size exceeded.', // UNTRANSLATED
|
||||
mime_type_not_allowed: 'Mime type is not allowed.', // UNTRANSLATED
|
||||
},
|
||||
oidc: {
|
||||
aborted: 'End 사용자가 상호 작용을 중단했어요.',
|
||||
|
@ -181,6 +184,11 @@ const errors = {
|
|||
name_exists: '범위 이름 {{name}}이/가 이미 사용 중이에요.',
|
||||
name_with_space: '범위 이름에 공백을 포함할 수 없어요.',
|
||||
},
|
||||
storage: {
|
||||
not_configured: 'Storage provider is not configured.', // UNTRANSLATED
|
||||
missing_parameter: 'Missing parameter {{parameter}} for storage provider.', // UNTRANSLATED
|
||||
upload_error: 'Failed to upload file to the storage provider.', // UNTRANSLATED
|
||||
},
|
||||
};
|
||||
|
||||
export default errors;
|
||||
|
|
|
@ -16,6 +16,9 @@ const errors = {
|
|||
guard: {
|
||||
invalid_input: 'A solicitação {{type}} é inválida.',
|
||||
invalid_pagination: 'O valor de paginação da solicitação é inválido.',
|
||||
can_not_get_tenant_id: 'Unable to get tenant id from request.', // UNTRANSLATED
|
||||
file_size_exceeded: 'File size exceeded.', // UNTRANSLATED
|
||||
mime_type_not_allowed: 'Mime type is not allowed.', // UNTRANSLATED
|
||||
},
|
||||
oidc: {
|
||||
aborted: 'A interação abortada pelo end-user',
|
||||
|
@ -196,6 +199,11 @@ const errors = {
|
|||
name_exists: 'The scope name {{name}} is already in use', // UNTRANSLATED
|
||||
name_with_space: 'The name of the scope cannot contain any spaces.', // UNTRANSLATED
|
||||
},
|
||||
storage: {
|
||||
not_configured: 'Storage provider is not configured.', // UNTRANSLATED
|
||||
missing_parameter: 'Missing parameter {{parameter}} for storage provider.', // UNTRANSLATED
|
||||
upload_error: 'Failed to upload file to the storage provider.', // UNTRANSLATED
|
||||
},
|
||||
};
|
||||
|
||||
export default errors;
|
||||
|
|
|
@ -15,6 +15,9 @@ const errors = {
|
|||
guard: {
|
||||
invalid_input: 'O pedido {{type}} é inválido.',
|
||||
invalid_pagination: 'O valor de paginação enviado é inválido.',
|
||||
can_not_get_tenant_id: 'Unable to get tenant id from request.', // UNTRANSLATED
|
||||
file_size_exceeded: 'File size exceeded.', // UNTRANSLATED
|
||||
mime_type_not_allowed: 'Mime type is not allowed.', // UNTRANSLATED
|
||||
},
|
||||
oidc: {
|
||||
aborted: 'O utilizador final abortou a interação.',
|
||||
|
@ -190,6 +193,11 @@ const errors = {
|
|||
name_exists: 'The scope name {{name}} is already in use', // UNTRANSLATED
|
||||
name_with_space: 'The name of the scope cannot contain any spaces.', // UNTRANSLATED
|
||||
},
|
||||
storage: {
|
||||
not_configured: 'Storage provider is not configured.', // UNTRANSLATED
|
||||
missing_parameter: 'Missing parameter {{parameter}} for storage provider.', // UNTRANSLATED
|
||||
upload_error: 'Failed to upload file to the storage provider.', // UNTRANSLATED
|
||||
},
|
||||
};
|
||||
|
||||
export default errors;
|
||||
|
|
|
@ -16,6 +16,9 @@ const errors = {
|
|||
guard: {
|
||||
invalid_input: 'İstek {{type}} geçersiz.',
|
||||
invalid_pagination: 'İstenen sayfalandırma değeri geçersiz.',
|
||||
can_not_get_tenant_id: 'Unable to get tenant id from request.', // UNTRANSLATED
|
||||
file_size_exceeded: 'File size exceeded.', // UNTRANSLATED
|
||||
mime_type_not_allowed: 'Mime type is not allowed.', // UNTRANSLATED
|
||||
},
|
||||
oidc: {
|
||||
aborted: 'Son kullanıcı etkileşimi iptal etti.',
|
||||
|
@ -190,6 +193,11 @@ const errors = {
|
|||
name_exists: 'The scope name {{name}} is already in use', // UNTRANSLATED
|
||||
name_with_space: 'The name of the scope cannot contain any spaces.', // UNTRANSLATED
|
||||
},
|
||||
storage: {
|
||||
not_configured: 'Storage provider is not configured.', // UNTRANSLATED
|
||||
missing_parameter: 'Missing parameter {{parameter}} for storage provider.', // UNTRANSLATED
|
||||
upload_error: 'Failed to upload file to the storage provider.', // UNTRANSLATED
|
||||
},
|
||||
};
|
||||
|
||||
export default errors;
|
||||
|
|
|
@ -15,6 +15,9 @@ const errors = {
|
|||
guard: {
|
||||
invalid_input: '请求中 {{type}} 无效',
|
||||
invalid_pagination: '分页参数无效',
|
||||
can_not_get_tenant_id: 'Unable to get tenant id from request.', // UNTRANSLATED
|
||||
file_size_exceeded: 'File size exceeded.', // UNTRANSLATED
|
||||
mime_type_not_allowed: 'Mime type is not allowed.', // UNTRANSLATED
|
||||
},
|
||||
oidc: {
|
||||
aborted: '用户终止了交互。',
|
||||
|
@ -170,6 +173,11 @@ const errors = {
|
|||
name_exists: 'The scope name {{name}} is already in use', // UNTRANSLATED
|
||||
name_with_space: 'The name of the scope cannot contain any spaces.', // UNTRANSLATED
|
||||
},
|
||||
storage: {
|
||||
not_configured: 'Storage provider is not configured.', // UNTRANSLATED
|
||||
missing_parameter: 'Missing parameter {{parameter}} for storage provider.', // UNTRANSLATED
|
||||
upload_error: 'Failed to upload file to the storage provider.', // UNTRANSLATED
|
||||
},
|
||||
};
|
||||
|
||||
export default errors;
|
||||
|
|
|
@ -36,7 +36,6 @@ export type AdminConsoleData = z.infer<typeof adminConsoleDataGuard>;
|
|||
export enum AdminConsoleConfigKey {
|
||||
AdminConsole = 'adminConsole',
|
||||
}
|
||||
|
||||
export type AdminConsoleConfigType = {
|
||||
[AdminConsoleConfigKey.AdminConsole]: AdminConsoleData;
|
||||
};
|
||||
|
|
|
@ -21,13 +21,59 @@ export const alterationStateGuard: Readonly<{
|
|||
}),
|
||||
});
|
||||
|
||||
// Summary
|
||||
export type SystemKey = AlterationStateKey;
|
||||
export type SystemType = AlterationStateType;
|
||||
export type SystemGuard = typeof alterationStateGuard;
|
||||
// Storage provider
|
||||
export enum StorageProvider {
|
||||
AzureStorage = 'AzureStorage',
|
||||
S3Storage = 'S3Storage',
|
||||
}
|
||||
|
||||
export const systemKeys: readonly SystemKey[] = Object.freeze(Object.values(AlterationStateKey));
|
||||
const basicConfig = {
|
||||
publicUrl: z.string().optional(),
|
||||
};
|
||||
|
||||
export const storageProviderDataGuard = z.discriminatedUnion('provider', [
|
||||
z.object({
|
||||
provider: z.literal(StorageProvider.AzureStorage),
|
||||
connectionString: z.string(),
|
||||
container: z.string(),
|
||||
...basicConfig,
|
||||
}),
|
||||
z.object({
|
||||
provider: z.literal(StorageProvider.S3Storage),
|
||||
endpoint: z.string(),
|
||||
accessKeyId: z.string(),
|
||||
accessSecretKey: z.string(),
|
||||
...basicConfig,
|
||||
}),
|
||||
]);
|
||||
|
||||
export type StorageProviderData = z.infer<typeof storageProviderDataGuard>;
|
||||
|
||||
export enum StorageProviderKey {
|
||||
StorageProvider = 'storageProvider',
|
||||
}
|
||||
|
||||
export type StorageProviderType = {
|
||||
[StorageProviderKey.StorageProvider]: StorageProviderData;
|
||||
};
|
||||
|
||||
export const storageProviderGuard: Readonly<{
|
||||
[key in StorageProviderKey]: ZodType<StorageProviderType[key]>;
|
||||
}> = Object.freeze({
|
||||
[StorageProviderKey.StorageProvider]: storageProviderDataGuard,
|
||||
});
|
||||
|
||||
// Summary
|
||||
export type SystemKey = AlterationStateKey | StorageProviderKey;
|
||||
export type SystemType = AlterationStateType | StorageProviderType;
|
||||
export type SystemGuard = typeof alterationStateGuard & typeof storageProviderGuard;
|
||||
|
||||
export const systemKeys: readonly SystemKey[] = Object.freeze([
|
||||
...Object.values(AlterationStateKey),
|
||||
...Object.values(StorageProviderKey),
|
||||
]);
|
||||
|
||||
export const systemGuards: SystemGuard = Object.freeze({
|
||||
...alterationStateGuard,
|
||||
...storageProviderGuard,
|
||||
});
|
||||
|
|
159
pnpm-lock.yaml
generated
159
pnpm-lock.yaml
generated
|
@ -313,6 +313,7 @@ importers:
|
|||
|
||||
packages/core:
|
||||
specifiers:
|
||||
'@azure/storage-blob': ^12.13.0
|
||||
'@koa/cors': ^4.0.0
|
||||
'@logto/cli': workspace:*
|
||||
'@logto/connector-kit': workspace:*
|
||||
|
@ -391,6 +392,7 @@ importers:
|
|||
typescript: ^4.9.4
|
||||
zod: ^3.20.2
|
||||
dependencies:
|
||||
'@azure/storage-blob': 12.13.0
|
||||
'@koa/cors': 4.0.0
|
||||
'@logto/cli': link:../cli
|
||||
'@logto/connector-kit': link:../toolkit/connector-kit
|
||||
|
@ -972,6 +974,98 @@ packages:
|
|||
'@jridgewell/trace-mapping': 0.3.17
|
||||
dev: true
|
||||
|
||||
/@azure/abort-controller/1.1.0:
|
||||
resolution: {integrity: sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
dependencies:
|
||||
tslib: 2.4.1
|
||||
dev: false
|
||||
|
||||
/@azure/core-auth/1.4.0:
|
||||
resolution: {integrity: sha512-HFrcTgmuSuukRf/EdPmqBrc5l6Q5Uu+2TbuhaKbgaCpP2TfAeiNaQPAadxO+CYBRHGUzIDteMAjFspFLDLnKVQ==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
dependencies:
|
||||
'@azure/abort-controller': 1.1.0
|
||||
tslib: 2.4.1
|
||||
dev: false
|
||||
|
||||
/@azure/core-http/3.0.0:
|
||||
resolution: {integrity: sha512-BxI2SlGFPPz6J1XyZNIVUf0QZLBKFX+ViFjKOkzqD18J1zOINIQ8JSBKKr+i+v8+MB6LacL6Nn/sP/TE13+s2Q==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
dependencies:
|
||||
'@azure/abort-controller': 1.1.0
|
||||
'@azure/core-auth': 1.4.0
|
||||
'@azure/core-tracing': 1.0.0-preview.13
|
||||
'@azure/core-util': 1.2.0
|
||||
'@azure/logger': 1.0.4
|
||||
'@types/node-fetch': 2.6.2
|
||||
'@types/tunnel': 0.0.3
|
||||
form-data: 4.0.0
|
||||
node-fetch: 2.6.7
|
||||
process: 0.11.10
|
||||
tslib: 2.4.1
|
||||
tunnel: 0.0.6
|
||||
uuid: 8.3.2
|
||||
xml2js: 0.4.23
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
dev: false
|
||||
|
||||
/@azure/core-lro/2.5.1:
|
||||
resolution: {integrity: sha512-JHQy/bA3NOz2WuzOi5zEk6n/TJdAropupxUT521JIJvW7EXV2YN2SFYZrf/2RHeD28QAClGdynYadZsbmP+nyQ==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
dependencies:
|
||||
'@azure/abort-controller': 1.1.0
|
||||
'@azure/logger': 1.0.4
|
||||
tslib: 2.4.1
|
||||
dev: false
|
||||
|
||||
/@azure/core-paging/1.5.0:
|
||||
resolution: {integrity: sha512-zqWdVIt+2Z+3wqxEOGzR5hXFZ8MGKK52x4vFLw8n58pR6ZfKRx3EXYTxTaYxYHc/PexPUTyimcTWFJbji9Z6Iw==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
dependencies:
|
||||
tslib: 2.4.1
|
||||
dev: false
|
||||
|
||||
/@azure/core-tracing/1.0.0-preview.13:
|
||||
resolution: {integrity: sha512-KxDlhXyMlh2Jhj2ykX6vNEU0Vou4nHr025KoSEiz7cS3BNiHNaZcdECk/DmLkEB0as5T7b/TpRcehJ5yV6NeXQ==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.4.0
|
||||
tslib: 2.4.1
|
||||
dev: false
|
||||
|
||||
/@azure/core-util/1.2.0:
|
||||
resolution: {integrity: sha512-ffGIw+Qs8bNKNLxz5UPkz4/VBM/EZY07mPve1ZYFqYUdPwFqRj0RPk0U7LZMOfT7GCck9YjuT1Rfp1PApNl1ng==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
dependencies:
|
||||
'@azure/abort-controller': 1.1.0
|
||||
tslib: 2.4.1
|
||||
dev: false
|
||||
|
||||
/@azure/logger/1.0.4:
|
||||
resolution: {integrity: sha512-ustrPY8MryhloQj7OWGe+HrYx+aoiOxzbXTtgblbV3xwCqpzUK36phH3XNHQKj3EPonyFUuDTfR3qFhTEAuZEg==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
dependencies:
|
||||
tslib: 2.4.1
|
||||
dev: false
|
||||
|
||||
/@azure/storage-blob/12.13.0:
|
||||
resolution: {integrity: sha512-t3Q2lvBMJucgTjQcP5+hvEJMAsJSk0qmAnjDLie2td017IiduZbbC9BOcFfmwzR6y6cJdZOuewLCNFmEx9IrXA==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
dependencies:
|
||||
'@azure/abort-controller': 1.1.0
|
||||
'@azure/core-http': 3.0.0
|
||||
'@azure/core-lro': 2.5.1
|
||||
'@azure/core-paging': 1.5.0
|
||||
'@azure/core-tracing': 1.0.0-preview.13
|
||||
'@azure/logger': 1.0.4
|
||||
events: 3.3.0
|
||||
tslib: 2.4.1
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
dev: false
|
||||
|
||||
/@babel/code-frame/7.18.6:
|
||||
resolution: {integrity: sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
@ -2624,6 +2718,11 @@ packages:
|
|||
fastq: 1.13.0
|
||||
dev: true
|
||||
|
||||
/@opentelemetry/api/1.4.0:
|
||||
resolution: {integrity: sha512-IgMK9i3sFGNUqPMbjABm0G26g0QCKCUBfglhQ7rQq6WcxbKfEHRcmwsoER4hZcuYqJgkYn2OeuoJIv7Jsftp7g==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
dev: false
|
||||
|
||||
/@parcel/bundler-default/2.8.3_@parcel+core@2.8.3:
|
||||
resolution: {integrity: sha512-yJvRsNWWu5fVydsWk3O2L4yIy3UZiKWO2cPDukGOIWMgp/Vbpp+2Ct5IygVRtE22bnseW/E/oe0PV3d2IkEJGg==}
|
||||
engines: {node: '>= 12.0.0', parcel: ^2.8.3}
|
||||
|
@ -4241,6 +4340,13 @@ packages:
|
|||
resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==}
|
||||
dev: true
|
||||
|
||||
/@types/node-fetch/2.6.2:
|
||||
resolution: {integrity: sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==}
|
||||
dependencies:
|
||||
'@types/node': 18.11.18
|
||||
form-data: 3.0.1
|
||||
dev: false
|
||||
|
||||
/@types/node/12.20.55:
|
||||
resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==}
|
||||
dev: true
|
||||
|
@ -4414,6 +4520,12 @@ packages:
|
|||
resolution: {integrity: sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==}
|
||||
dev: true
|
||||
|
||||
/@types/tunnel/0.0.3:
|
||||
resolution: {integrity: sha512-sOUTGn6h1SfQ+gbgqC364jLFBw2lnFqkgF3q0WovEHRLMrVD1sd5aufqi/aJObLekJO+Aq5z646U4Oxy6shXMA==}
|
||||
dependencies:
|
||||
'@types/node': 18.11.18
|
||||
dev: false
|
||||
|
||||
/@types/unist/2.0.6:
|
||||
resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==}
|
||||
dev: true
|
||||
|
@ -4881,7 +4993,6 @@ packages:
|
|||
|
||||
/asynckit/0.4.0:
|
||||
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||
dev: true
|
||||
|
||||
/axe-core/4.4.3:
|
||||
resolution: {integrity: sha512-32+ub6kkdhhWick/UjvEwRchgoetXqTK14INLqbGm5U2TzBkBNF3nQtLYm8ovxSkQWArjEQvftCKryjZaATu3w==}
|
||||
|
@ -5534,7 +5645,6 @@ packages:
|
|||
engines: {node: '>= 0.8'}
|
||||
dependencies:
|
||||
delayed-stream: 1.0.0
|
||||
dev: true
|
||||
|
||||
/comma-separated-tokens/1.0.8:
|
||||
resolution: {integrity: sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==}
|
||||
|
@ -6106,7 +6216,6 @@ packages:
|
|||
/delayed-stream/1.0.0:
|
||||
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
dev: true
|
||||
|
||||
/delegates/1.0.0:
|
||||
resolution: {integrity: sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=}
|
||||
|
@ -6946,6 +7055,11 @@ packages:
|
|||
/eventemitter3/4.0.7:
|
||||
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
|
||||
|
||||
/events/3.3.0:
|
||||
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
|
||||
engines: {node: '>=0.8.x'}
|
||||
dev: false
|
||||
|
||||
/execa/5.1.1:
|
||||
resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
|
||||
engines: {node: '>=10'}
|
||||
|
@ -7264,6 +7378,15 @@ packages:
|
|||
resolution: {integrity: sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==}
|
||||
engines: {node: '>= 14.17'}
|
||||
|
||||
/form-data/3.0.1:
|
||||
resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==}
|
||||
engines: {node: '>= 6'}
|
||||
dependencies:
|
||||
asynckit: 0.4.0
|
||||
combined-stream: 1.0.8
|
||||
mime-types: 2.1.35
|
||||
dev: false
|
||||
|
||||
/form-data/4.0.0:
|
||||
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
|
||||
engines: {node: '>= 6'}
|
||||
|
@ -7271,7 +7394,6 @@ packages:
|
|||
asynckit: 0.4.0
|
||||
combined-stream: 1.0.8
|
||||
mime-types: 2.1.35
|
||||
dev: true
|
||||
|
||||
/format/0.2.2:
|
||||
resolution: {integrity: sha1-1hcBB+nv3E7TDJ3DkBbflCtctYs=}
|
||||
|
@ -11052,7 +11174,6 @@ packages:
|
|||
optional: true
|
||||
dependencies:
|
||||
whatwg-url: 5.0.0
|
||||
dev: true
|
||||
|
||||
/node-fetch/3.3.0:
|
||||
resolution: {integrity: sha512-BKwRP/O0UvoMKp7GNdwPlObhYGB5DQqwhEDQlNKuoqwVYSxkSZCSbHjnFFmUEtwSKRPU4kNK8PbDYYitwaE3QA==}
|
||||
|
@ -12167,9 +12288,8 @@ packages:
|
|||
dev: true
|
||||
|
||||
/process/0.11.10:
|
||||
resolution: {integrity: sha1-czIwDoQBYb2j5podHZGn1LwW8YI=}
|
||||
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
|
||||
engines: {node: '>= 0.6.0'}
|
||||
dev: true
|
||||
|
||||
/progress/2.0.3:
|
||||
resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==}
|
||||
|
@ -13087,6 +13207,10 @@ packages:
|
|||
source-map-js: 1.0.2
|
||||
dev: true
|
||||
|
||||
/sax/1.2.4:
|
||||
resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==}
|
||||
dev: false
|
||||
|
||||
/saxes/6.0.0:
|
||||
resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
|
||||
engines: {node: '>=v12.22.7'}
|
||||
|
@ -14137,7 +14261,6 @@ packages:
|
|||
|
||||
/tr46/0.0.3:
|
||||
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
|
||||
dev: true
|
||||
|
||||
/tr46/1.0.1:
|
||||
resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==}
|
||||
|
@ -14295,6 +14418,11 @@ packages:
|
|||
yargs: 17.6.0
|
||||
dev: true
|
||||
|
||||
/tunnel/0.0.6:
|
||||
resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==}
|
||||
engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'}
|
||||
dev: false
|
||||
|
||||
/type-check/0.3.2:
|
||||
resolution: {integrity: sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
@ -14757,7 +14885,6 @@ packages:
|
|||
|
||||
/webidl-conversions/3.0.1:
|
||||
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
|
||||
dev: true
|
||||
|
||||
/webidl-conversions/4.0.2:
|
||||
resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==}
|
||||
|
@ -14792,7 +14919,6 @@ packages:
|
|||
dependencies:
|
||||
tr46: 0.0.3
|
||||
webidl-conversions: 3.0.1
|
||||
dev: true
|
||||
|
||||
/whatwg-url/7.1.0:
|
||||
resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==}
|
||||
|
@ -14905,6 +15031,19 @@ packages:
|
|||
engines: {node: '>=12'}
|
||||
dev: true
|
||||
|
||||
/xml2js/0.4.23:
|
||||
resolution: {integrity: sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==}
|
||||
engines: {node: '>=4.0.0'}
|
||||
dependencies:
|
||||
sax: 1.2.4
|
||||
xmlbuilder: 11.0.1
|
||||
dev: false
|
||||
|
||||
/xmlbuilder/11.0.1:
|
||||
resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==}
|
||||
engines: {node: '>=4.0'}
|
||||
dev: false
|
||||
|
||||
/xmlchars/2.2.0:
|
||||
resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
|
||||
dev: true
|
||||
|
|
Loading…
Add table
Reference in a new issue