@redocly/redoc-revel 0.129.0-next.5 → 0.129.0-next.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/dist/constants/common.d.ts +1 -0
- package/dist/constants/common.js +1 -1
- package/dist/server/plugins/catalog-entities/database/constants/relation-normalization.d.ts +3 -0
- package/dist/server/plugins/catalog-entities/database/constants/relation-normalization.js +1 -0
- package/dist/server/plugins/catalog-entities/database/mappers/create-entity-relation-db-record-from-dto.js +1 -1
- package/dist/server/plugins/catalog-entities/database/mappers/create-entity-relation-db-record-from-file-schema.js +1 -1
- package/dist/server/plugins/catalog-entities/database/repositories/local/catalog-entities-bff-repository.d.ts +14 -0
- package/dist/server/plugins/catalog-entities/database/repositories/local/catalog-entities-bff-repository.js +112 -0
- package/dist/server/plugins/catalog-entities/database/repositories/local/catalog-entities-local-read-repository.js +12 -130
- package/dist/server/plugins/catalog-entities/database/repositories/local/catalog-entities-local-write-repository.js +1 -1
- package/dist/server/plugins/catalog-entities/utils/normalize-relation.d.ts +77 -0
- package/dist/server/plugins/catalog-entities/utils/normalize-relation.js +1 -0
- package/dist/server/plugins/config-parser/format-error.js +13 -5
- package/dist/server/plugins/config-parser/loaders/utils/read-and-validate-config.js +1 -1
- package/dist/server/providers/database/pagination/filter.d.ts +1 -0
- package/dist/server/providers/database/pagination/filter.js +1 -1
- package/dist/server/web-server/auth.js +2 -2
- package/dist/server/web-server/routes/auth.d.ts +1 -0
- package/dist/server/web-server/routes/auth.js +1 -1
- package/dist/server/web-server/routes/index.js +1 -1
- package/dist/server/web-server/routes/mcp-oauth.d.ts +10 -0
- package/dist/server/web-server/routes/mcp-oauth.js +1 -1
- package/dist/server/web-server/utils/get-request-origin.d.ts +3 -0
- package/dist/server/web-server/utils/get-request-origin.js +1 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @redocly/redoc-revel
|
|
2
2
|
|
|
3
|
+
## 0.129.0-next.6
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 13cdd871a0: Added `access` configuration object to group authentication settings (`requiresLogin`, `logoutReturnUrl`, `residency`, `sso`, `rbac`) and `logoutReturnUrl` option to customize post-logout redirect URL.
|
|
8
|
+
|
|
9
|
+
Fixed config validation error formatting to handle cases where location source may be undefined or not a proper Source object, preventing errors when formatting validation problems.
|
|
10
|
+
|
|
3
11
|
## 0.129.0-next.5
|
|
4
12
|
|
|
5
13
|
### Patch Changes
|
|
@@ -54,6 +54,7 @@ export declare const ServerRoutes: {
|
|
|
54
54
|
readonly SEARCH: "/_search";
|
|
55
55
|
readonly SEARCH_FACETS: "/_search-facets";
|
|
56
56
|
readonly LOGOUT: "/logout";
|
|
57
|
+
readonly POST_LOGOUT: "/post-logout";
|
|
57
58
|
readonly LOGIN: "/login";
|
|
58
59
|
readonly IDP_LOGIN: "/_auth/idp-login";
|
|
59
60
|
readonly INVITE: "/invite/:code";
|
package/dist/constants/common.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const E="openapi_docs",n="asyncapi_docs",A="graphql_docs",T="/_spec-gql",O="markdoc",p="tags",i="*",R={"*":"read"},L=["openid","email"],I=1440*60,C="anonymous",l="authenticated",S="**",d="Reunite",N="translations.yaml",P="redocly.yaml",u="@redocly/theme",D="@theme",h="/app-data.json",x="/eject-component",e=process.env.SERVER_EDITOR_APP_URL||"http://127.0.0.1:3000",g=process.env.REDOCLY_CORS_ORIGINS?process.env.REDOCLY_CORS_ORIGINS.split(",").map(o=>o.trim()).filter(Boolean).concat(e):[e];var a;(function(o){o.BUILD="build",o.DEVELOP="develop",o.PREPARE="prepare"})(a||(a={}));var r;(function(o){o.RATING="rating",o.SENTIMENT="sentiment",o.COMMENT="comment",o.MOOD="mood",o.PROBLEM="problem",o.SCALE="scale"})(r||(r={}));const t="entityKey",c="entityRelationId",U={INFO:"/_info",EJECT_COMPONENT:"/eject-component/:componentName",SHARED_PAGE_DATA:"/page-data/shared/*",PAGE_DATA:"/page-data/*",APP_DATA:"/app-data.json",AUTHORIZATION:"/authorize",OIDC_CALLBACK:"/_auth/oidc",SAML_CALLBACK:"/_auth/saml2",REDOCLY_CALLBACK:"/_auth/redocly",REPLAY_OAUTH2_CALLBACK:"/_auth/replay-oauth2",REDOCLY_LOGIN_CALLBACK:"/_auth/redocly/redocly-login",REDOCLY_TOKEN_LOGIN:"/_auth/redocly-token-login",SEARCH:"/_search",SEARCH_FACETS:"/_search-facets",LOGOUT:"/logout",LOGIN:"/login",IDP_LOGIN:"/_auth/idp-login",INVITE:"/invite/:code",RESOLVE_ROUTE_BY_PATH:"/resolve-route-by-path",RESOLVE_ROUTES_BY_PATHS:"/resolve-routes-by-paths",RESOLVE_ROUTE_BY_SLUG:"/resolve-route-by-slug",GET_ROUTES_BY_LINE:"/get-routes-by-line",FEEDBACK:"/feedback",TELEMETRY:"/_events",OTEL_TRACES:"/_otel/v1/traces",HEALTH:"/_health",ASK_AI:"/_ask-ai",CATALOG_ENTITIES:`/catalog-entities/:${t}?`,CATALOG_ENTITIES_RELATIONS:`/catalog-entities-relations/:${c}?`,BFF_CATALOG_ENTITIES:`/bff/catalog-entities/:${t}?`,BFF_CATALOG_RELATED_ENTITIES:`/bff/catalog-related-entities/:${t}`,BFF_CATALOG_REVISIONS:`/bff/catalog-revisions/:${t}`,MCP_OAUTH_AUTHORIZATION_SERVER:"/.well-known/oauth-authorization-server",MCP_OAUTH_PROTECTED_RESOURCE:"/.well-known/oauth-protected-resource/mcp",MCP_DYNAMIC_CLIENT_REGISTRATION:"/_mcp/register",MCP_AUTHORIZATION:"/_mcp/oauth2/auth",MCP_TOKEN_PORTAL:"/_mcp/oauth2/token-portal",MCP_CALLBACK:"/_mcp/oauth/callback",SCORECARDS:"/scorecards"};var _;(function(o){o.AI_SEARCH="aiSearch"})(_||(_={}));const m="/login",M="/invite",G={AUTH0_PASS:"/auth/auth0-pass"},b={NotExist:"ENOENT"},H="default_locale",v="sidebar-",F="PUBLIC_",y=["BROKEN_LINK","MARKDOC","RESOLVE"],B=["logo","navbar","products","footer","sidebar","scripts","links","feedback","search","aiAssistant","colorMode","navigation","codeSnippet","markdown","openapi","graphql","analytics","userMenu","versionPicker","breadcrumbs","catalog","entitiesCatalog","scorecard","scorecards","scorecardClassic","mcp","banner"],s="redocly_category",k="redocly_product",f="redocly_version",K="redocly_teams",V=s,Y={asyncapi:"asyncapi",apiFunctions:"apiFunctions",reactPages:"reactPages",catalog:"catalog",catalogClassic:"catalogClassic",lint:"lint",scorecard:"scorecard",scorecards:"scorecards",l10n:"l10n",openapi:"openapi",graphql:"graphql",markdown:"markdown",devOnboarding:"devOnboarding",seo:"seo",redirects:"redirects",customPlugins:"customPlugins",themeEjecting:"themeEjecting",products:"products",breadcrumbs:"breadcrumbs",mockServer:"mockServer",sso:"sso",rbac:"rbac",analytics:"analytics",removeAttribution:"removeAttribution",advancedSearch:"advancedSearch",soap:"soap",ssoDirect:"ssoDirect",codeWalkthrough:"codeWalkthrough",aiSearchLimit:"aiSearchLimit",mcp:"mcp",banner:"banner"};export{g as ALLOWED_CORS_ORIGINS,n as ASYNC_API_DOCS_TEMPLATE_ID,t as CATALOG_ENTITY_KEY,c as CATALOG_ENTITY_RELATION_ID,P as CONFIG_FILE_NAME,O as CUSTOM_MARKDOC_OPTIONS_PATH,p as CUSTOM_MARKDOC_TAGS_PATH,C as DEFAULT_ANONYMOUS_VISITOR_TEAM,l as DEFAULT_AUTHENTICATED_TEAM,I as DEFAULT_COOKIE_EXPIRATION,H as DEFAULT_LOCALE_PLACEHOLDER,S as DEFAULT_RBAC_SCOPE,d as DEFAULT_SSO_IDP_TITLE,u as DEFAULT_THEME_NAME,m as DEV_LOGIN_SLUG,x as EJECT_COMPONENT_URL,G as ExternalRoutes,Y as FEATURE,r as FEEDBACK_TYPES,b as FsErrors,h as GLOBAL_DATA_URL,T as GRAPHQL_SPEC_SLUG,A as GRAPHQL_TEMPLATE_ID,M as INVITE_SLUG,y as MARKDOC_ERROR_TYPES,E as OPENAPI_DOCS_TEMPLATE_ID,F as PUBLIC_ENV_PREFIX,R as PUBLIC_RBAC_SCOPE_ITEM,i as RBAC_ALL_OTHER_TEAMS,L as REQUIRED_OIDC_SCOPES,_ as RbacFeatures,s as SEARCH_CATEGORY_FIELD,V as SEARCH_GROUP_FACET_FIELD,k as SEARCH_PRODUCT_FIELD,K as SEARCH_RBAC_FIELD,f as SEARCH_VERSION_FIELD,e as SERVER_EDITOR_APP_URL,v as SIDEBAR_PREFIX,U as ServerRoutes,N as TRANSLATIONS_FILE_NAME,B as UI_ACCESSIBLE_CONFIG_PROPS,D as USER_THEME_ALIAS,a as cliCommandNames};
|
|
1
|
+
const E="openapi_docs",n="asyncapi_docs",A="graphql_docs",T="/_spec-gql",O="markdoc",p="tags",i="*",R={"*":"read"},L=["openid","email"],I=1440*60,C="anonymous",l="authenticated",S="**",d="Reunite",N="translations.yaml",P="redocly.yaml",u="@redocly/theme",D="@theme",h="/app-data.json",x="/eject-component",e=process.env.SERVER_EDITOR_APP_URL||"http://127.0.0.1:3000",g=process.env.REDOCLY_CORS_ORIGINS?process.env.REDOCLY_CORS_ORIGINS.split(",").map(o=>o.trim()).filter(Boolean).concat(e):[e];var a;(function(o){o.BUILD="build",o.DEVELOP="develop",o.PREPARE="prepare"})(a||(a={}));var r;(function(o){o.RATING="rating",o.SENTIMENT="sentiment",o.COMMENT="comment",o.MOOD="mood",o.PROBLEM="problem",o.SCALE="scale"})(r||(r={}));const t="entityKey",c="entityRelationId",U={INFO:"/_info",EJECT_COMPONENT:"/eject-component/:componentName",SHARED_PAGE_DATA:"/page-data/shared/*",PAGE_DATA:"/page-data/*",APP_DATA:"/app-data.json",AUTHORIZATION:"/authorize",OIDC_CALLBACK:"/_auth/oidc",SAML_CALLBACK:"/_auth/saml2",REDOCLY_CALLBACK:"/_auth/redocly",REPLAY_OAUTH2_CALLBACK:"/_auth/replay-oauth2",REDOCLY_LOGIN_CALLBACK:"/_auth/redocly/redocly-login",REDOCLY_TOKEN_LOGIN:"/_auth/redocly-token-login",SEARCH:"/_search",SEARCH_FACETS:"/_search-facets",LOGOUT:"/logout",POST_LOGOUT:"/post-logout",LOGIN:"/login",IDP_LOGIN:"/_auth/idp-login",INVITE:"/invite/:code",RESOLVE_ROUTE_BY_PATH:"/resolve-route-by-path",RESOLVE_ROUTES_BY_PATHS:"/resolve-routes-by-paths",RESOLVE_ROUTE_BY_SLUG:"/resolve-route-by-slug",GET_ROUTES_BY_LINE:"/get-routes-by-line",FEEDBACK:"/feedback",TELEMETRY:"/_events",OTEL_TRACES:"/_otel/v1/traces",HEALTH:"/_health",ASK_AI:"/_ask-ai",CATALOG_ENTITIES:`/catalog-entities/:${t}?`,CATALOG_ENTITIES_RELATIONS:`/catalog-entities-relations/:${c}?`,BFF_CATALOG_ENTITIES:`/bff/catalog-entities/:${t}?`,BFF_CATALOG_RELATED_ENTITIES:`/bff/catalog-related-entities/:${t}`,BFF_CATALOG_REVISIONS:`/bff/catalog-revisions/:${t}`,MCP_OAUTH_AUTHORIZATION_SERVER:"/.well-known/oauth-authorization-server",MCP_OAUTH_PROTECTED_RESOURCE:"/.well-known/oauth-protected-resource/mcp",MCP_DYNAMIC_CLIENT_REGISTRATION:"/_mcp/register",MCP_AUTHORIZATION:"/_mcp/oauth2/auth",MCP_TOKEN_PORTAL:"/_mcp/oauth2/token-portal",MCP_CALLBACK:"/_mcp/oauth/callback",SCORECARDS:"/scorecards"};var _;(function(o){o.AI_SEARCH="aiSearch"})(_||(_={}));const m="/login",M="/invite",G={AUTH0_PASS:"/auth/auth0-pass"},b={NotExist:"ENOENT"},H="default_locale",v="sidebar-",F="PUBLIC_",y=["BROKEN_LINK","MARKDOC","RESOLVE"],B=["logo","navbar","products","footer","sidebar","scripts","links","feedback","search","aiAssistant","colorMode","navigation","codeSnippet","markdown","openapi","graphql","analytics","userMenu","versionPicker","breadcrumbs","catalog","entitiesCatalog","scorecard","scorecards","scorecardClassic","mcp","banner"],s="redocly_category",k="redocly_product",f="redocly_version",K="redocly_teams",V=s,Y={asyncapi:"asyncapi",apiFunctions:"apiFunctions",reactPages:"reactPages",catalog:"catalog",catalogClassic:"catalogClassic",lint:"lint",scorecard:"scorecard",scorecards:"scorecards",l10n:"l10n",openapi:"openapi",graphql:"graphql",markdown:"markdown",devOnboarding:"devOnboarding",seo:"seo",redirects:"redirects",customPlugins:"customPlugins",themeEjecting:"themeEjecting",products:"products",breadcrumbs:"breadcrumbs",mockServer:"mockServer",sso:"sso",rbac:"rbac",analytics:"analytics",removeAttribution:"removeAttribution",advancedSearch:"advancedSearch",soap:"soap",ssoDirect:"ssoDirect",codeWalkthrough:"codeWalkthrough",aiSearchLimit:"aiSearchLimit",mcp:"mcp",banner:"banner"};export{g as ALLOWED_CORS_ORIGINS,n as ASYNC_API_DOCS_TEMPLATE_ID,t as CATALOG_ENTITY_KEY,c as CATALOG_ENTITY_RELATION_ID,P as CONFIG_FILE_NAME,O as CUSTOM_MARKDOC_OPTIONS_PATH,p as CUSTOM_MARKDOC_TAGS_PATH,C as DEFAULT_ANONYMOUS_VISITOR_TEAM,l as DEFAULT_AUTHENTICATED_TEAM,I as DEFAULT_COOKIE_EXPIRATION,H as DEFAULT_LOCALE_PLACEHOLDER,S as DEFAULT_RBAC_SCOPE,d as DEFAULT_SSO_IDP_TITLE,u as DEFAULT_THEME_NAME,m as DEV_LOGIN_SLUG,x as EJECT_COMPONENT_URL,G as ExternalRoutes,Y as FEATURE,r as FEEDBACK_TYPES,b as FsErrors,h as GLOBAL_DATA_URL,T as GRAPHQL_SPEC_SLUG,A as GRAPHQL_TEMPLATE_ID,M as INVITE_SLUG,y as MARKDOC_ERROR_TYPES,E as OPENAPI_DOCS_TEMPLATE_ID,F as PUBLIC_ENV_PREFIX,R as PUBLIC_RBAC_SCOPE_ITEM,i as RBAC_ALL_OTHER_TEAMS,L as REQUIRED_OIDC_SCOPES,_ as RbacFeatures,s as SEARCH_CATEGORY_FIELD,V as SEARCH_GROUP_FACET_FIELD,k as SEARCH_PRODUCT_FIELD,K as SEARCH_RBAC_FIELD,f as SEARCH_VERSION_FIELD,e as SERVER_EDITOR_APP_URL,v as SIDEBAR_PREFIX,U as ServerRoutes,N as TRANSLATIONS_FILE_NAME,B as UI_ACCESSIBLE_CONFIG_PROPS,D as USER_THEME_ALIAS,a as cliCommandNames};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e={owns:"owns",ownedBy:"owns",hasParts:"hasParts",partOf:"hasParts",creates:"creates",createdBy:"creates",implements:"implements",implementedBy:"implements",dependsOn:"dependsOn",dependencyOf:"dependsOn",uses:"uses",usedBy:"uses",extends:"extends",extendedBy:"extends",supersedes:"supersedes",supersededBy:"supersedes",hasMember:"hasMember",memberOf:"hasMember",triggers:"triggers",triggeredBy:"triggers",returns:"returns",returnedBy:"returns",produces:"produces",consumes:"consumes",linksTo:"linksTo",compatibleWith:"compatibleWith",relatesTo:"relatesTo"};export{e as RELATION_NORMALIZATION_MAP};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{ulid as
|
|
1
|
+
import{ulid as i}from"ulid";import{normalizeRelation as c}from"../../utils/normalize-relation.js";function a(e,o,s){const r=new Date().toISOString(),t=c({type:e.type,sourceKey:e.sourceKey,targetKey:e.targetKey,sourceVersion:e.sourceVersion,targetVersion:e.targetVersion,sourceRevision:e.sourceRevision,targetRevision:e.targetRevision});return{id:e.id||`cer_${i()}`,organizationId:o,projectId:s,sourceKey:t.sourceKey,sourceVersion:t.sourceVersion??"",sourceRevision:t.sourceRevision??"",sourceToTargetRelation:t.type,targetKey:t.targetKey,targetVersion:t.targetVersion??"",targetRevision:t.targetRevision??"",targetToSourceRelation:`reverse:${t.type}`,sourceFile:e.sourceFile||null,fileHash:e.fileHash||null,isDeleted:e.isDeleted??!1,createdAt:e.createdAt||r,updatedAt:r}}export{a as createEntityRelationDbRecordFromDto};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{ulid as
|
|
1
|
+
import{ulid as g}from"ulid";import{normalizeRelation as l}from"../../utils/normalize-relation.js";function R({relation:e,sourceFile:n,fileHash:s,sourceKey:a,sourceVersion:r,sourceRevision:o,organizationId:c,projectId:u}){const i=new Date().toISOString(),t=l({type:e.type,sourceKey:a,targetKey:e.key,sourceVersion:r&&e.version?r:null,targetVersion:r&&e.version?e.version:null,sourceRevision:o&&e.revision?o:null,targetRevision:o&&e.revision?e.revision:null});return{id:`cer_${g()}`,organizationId:c,projectId:u,sourceKey:t.sourceKey,sourceVersion:t.sourceVersion??"",sourceRevision:t.sourceRevision??"",sourceToTargetRelation:t.type,targetKey:t.targetKey,targetVersion:t.targetVersion??"",targetRevision:t.targetRevision??"",targetToSourceRelation:`reverse:${t.type}`,sourceFile:n,fileHash:s,isDeleted:!1,createdAt:i,updatedAt:i}}export{R as createEntityRelationDbRecordFromFileSchema};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { BffCatalogEntity } from '@redocly/theme/core/types';
|
|
2
|
+
import type { PaginationParams } from '../../../../../providers/database/pagination/schemas.js';
|
|
3
|
+
import type { DatabaseClient } from '../../../../../providers/database/client.js';
|
|
4
|
+
import type { ListResult } from './catalog-entities-local-read-repository.js';
|
|
5
|
+
export declare class CatalogEntitiesBffRepository {
|
|
6
|
+
#private;
|
|
7
|
+
constructor(db: DatabaseClient, attachedDatabasePath: string);
|
|
8
|
+
getEntitiesWithRelations(paginationParams?: PaginationParams): Promise<ListResult<BffCatalogEntity>>;
|
|
9
|
+
getEntityWithRelationsByKey(entityKey: string, filter?: {
|
|
10
|
+
revision?: string | null;
|
|
11
|
+
version?: string | null;
|
|
12
|
+
}): Promise<BffCatalogEntity | null>;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=catalog-entities-bff-repository.d.ts.map
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import{and as c,desc as H,eq as s,notExists as v,sql as e}from"drizzle-orm";import{unionAll as C}from"drizzle-orm/sqlite-core";import{logger as M}from"../../../../../tools/notifiers/logger.js";import{entitiesTable as i}from"../../../../../providers/database/databases/catalog-sqlite/schemas/entities-table.js";import{entitiesRelationsTable as k}from"../../../../../providers/database/databases/catalog-sqlite/schemas/entities-relations-table.js";import{applyPagination as S}from"../../../../../providers/database/pagination/index.js";import{applyFilter as q,excludeFieldsFromFilter as j,getAllFilterFieldValues as O}from"../../../../../providers/database/pagination/filter.js";import{createBffEntity as A}from"../../mappers/create-bff-entity.js";import{FIELDS_TO_SELECT_FOR_ENTITY as n,FIELDS_TO_SELECT_FOR_ENTITY_RELATION as $,createEntityFieldsForSelect as W}from"../utils.js";class Z{#e;#t;constructor(t,o){this.#e=t,this.#t=o}async getEntitiesWithRelations(t={}){const o=O(t.filter,"owners");if(o.length>0)return this.#c(t,o);const m=O(t.filter,"domains");return m.length>0?this.#l(t,m):this.#a(t)}async#c(t,o){const m=this.#s(),l=this.#n(),d=this.#o(),y=l.as("combined_relations"),u=d.as("combined_target_entities"),h=o.map(r=>e`
|
|
2
|
+
EXISTS (
|
|
3
|
+
SELECT 1 FROM (${l}) cr
|
|
4
|
+
WHERE cr.source_key = ${r}
|
|
5
|
+
AND cr.source_to_target_relation = 'owns'
|
|
6
|
+
AND cr.target_key = base_entities.key
|
|
7
|
+
)
|
|
8
|
+
`),_=h.length===1?h[0]:e`(${e.join(h,e` OR `)})`,b=O(t.filter,"domains");let w=_;if(b.length>0){const r=b.map(a=>e`
|
|
9
|
+
EXISTS (
|
|
10
|
+
SELECT 1 FROM (${l}) cr
|
|
11
|
+
JOIN (${d}) d ON d.key = ${a}
|
|
12
|
+
WHERE d.type = 'domain'
|
|
13
|
+
AND d.is_current = 1
|
|
14
|
+
AND cr.source_key = ${a}
|
|
15
|
+
AND cr.target_key = base_entities.key
|
|
16
|
+
AND cr.source_to_target_relation = 'hasParts'
|
|
17
|
+
)
|
|
18
|
+
`),f=r.length===1?r[0]:e`(${e.join(r,e` OR `)})`;w=e`(${_} AND ${f})`}const R=j(t.filter,["owners","domains"]),g={...t,filter:R,baseWhereCondition:w},E=this.#e.client.select(n).from(m.as("base_entities")).$dynamic();try{const r=this.#e.client.select(n).from(m.as("base_entities")).$dynamic();S(r,{...g,limit:void 0,skip:void 0,after:void 0,before:void 0});const f=this.#e.client.select({count:e`count(*)`}).from(r.as("count_subquery")),a=t.limit||10;S(E,{...g,limit:a+1});const p=E.as("paged_entities"),N=this.#e.client.with(y,u,p).select({...W("paged_entities"),domains:this.#i("paged_entities").as("domains"),owners:this.#r("paged_entities").as("owners")}).from(p),[F,I]=await Promise.all([N.run(),f.run()]),D=F.rows,L=Number(I.rows[0]?.count||0),Q=D.length>a;return{items:D.slice(0,a).map(T=>A(T)).filter(T=>T!==null),hasMore:Q,total:L}}catch(r){return M.error("Error getting entities with relations (owner optimized path):",r),{items:[],hasMore:!1,total:0}}}async#l(t,o){const m=this.#s(),l=this.#n(),d=this.#o(),y=l.as("combined_relations"),u=d.as("combined_target_entities"),h=o.map(r=>e`
|
|
19
|
+
EXISTS (
|
|
20
|
+
SELECT 1 FROM (${l}) cr
|
|
21
|
+
JOIN (${d}) d ON d.key = ${r}
|
|
22
|
+
WHERE d.type = 'domain'
|
|
23
|
+
AND d.is_current = 1
|
|
24
|
+
AND cr.source_key = ${r}
|
|
25
|
+
AND cr.target_key = base_entities.key
|
|
26
|
+
AND cr.source_to_target_relation = 'hasParts'
|
|
27
|
+
)
|
|
28
|
+
`),_=h.length===1?h[0]:e`(${e.join(h,e` OR `)})`,b=O(t.filter,"owners");let w=_;if(b.length>0){const r=b.map(a=>e`
|
|
29
|
+
EXISTS (
|
|
30
|
+
SELECT 1 FROM (${l}) cr
|
|
31
|
+
WHERE cr.source_key = ${a}
|
|
32
|
+
AND cr.source_to_target_relation = 'owns'
|
|
33
|
+
AND cr.target_key = base_entities.key
|
|
34
|
+
)
|
|
35
|
+
`),f=r.length===1?r[0]:e`(${e.join(r,e` OR `)})`;w=e`(${_} AND ${f})`}const R=j(t.filter,["domains","owners"]),g={...t,filter:R,baseWhereCondition:w},E=this.#e.client.select(n).from(m.as("base_entities")).$dynamic();try{const r=this.#e.client.select(n).from(m.as("base_entities")).$dynamic();S(r,{...g,limit:void 0,skip:void 0,after:void 0,before:void 0});const f=this.#e.client.select({count:e`count(*)`}).from(r.as("count_subquery")),a=t.limit||10;S(E,{...g,limit:a+1});const p=E.as("paged_entities"),N=this.#e.client.with(y,u,p).select({...W("paged_entities"),domains:this.#i("paged_entities").as("domains"),owners:this.#r("paged_entities").as("owners")}).from(p),[F,I]=await Promise.all([N.run(),f.run()]),D=F.rows,L=Number(I.rows[0]?.count||0),Q=D.length>a;return{items:D.slice(0,a).map(T=>A(T)).filter(T=>T!==null),hasMore:Q,total:L}}catch(r){return M.error("Error getting entities with relations (domain optimized path):",r),{items:[],hasMore:!1,total:0}}}async#a(t){const o=this.#s(),m=this.#n(),l=this.#o(),d=m.as("combined_relations"),y=l.as("combined_target_entities"),u=this.#e.client.select(n).from(o.as("base_entities")).$dynamic(),{whereCondition:h}=q(u,t.filter);h&&u.where(h);try{const _=this.#e.client.select(n).from(o.as("base_entities_count")).$dynamic();h&&_.where(h);const b=this.#e.client.select({count:e`count(*)`}).from(_.as("count_subquery")),w=t.limit||10;S(u,{...t,limit:w+1});const R=u.as("paged_entities"),g=this.#e.client.with(d,y,R).select({...W("paged_entities"),domains:this.#i("paged_entities").as("domains"),owners:this.#r("paged_entities").as("owners")}).from(R),[E,r]=await Promise.all([g.run(),b.run()]),f=E.rows,a=Number(r.rows[0]?.count||0),p=f.length>w;return{items:f.slice(0,w).map(N=>A(N)).filter(N=>N!==null),hasMore:p,total:a}}catch(_){return M.error("Error getting entities with relations (optimized path):",_),{items:[],hasMore:!1,total:0}}}#s(){return this.#t?C(this.#e.client.select(n).from(e`remote.entities`).where(c(s(e.raw("is_current"),1),s(e.raw("is_deleted"),!1))),this.#e.client.select(n).from(i).where(c(s(i.isCurrent,!0),s(i.isDeleted,!1),v(this.#e.client.select({id:n.id}).from(e`remote.entities as remote`).where(c(e`remote.key = ${i.key}`,e`remote.is_current = 1`)))))):this.#e.client.select(n).from(i).where(c(s(i.isCurrent,!0),s(i.isDeleted,!1)))}#n(){return this.#t?C(this.#e.client.select($).from(e`remote.entities_relations`),this.#e.client.select($).from(k).where(v(this.#e.client.select({id:$.id}).from(e`remote.entities_relations as remote`).where(e`remote.source_key = ${k.sourceKey}`)))):this.#e.client.select($).from(k)}#o(){return this.#t?C(this.#e.client.select(n).from(e`remote.entities`).where(c(s(e.raw("is_current"),1),s(e.raw("is_deleted"),!1))),this.#e.client.select(n).from(i).where(c(s(i.isCurrent,!0),s(i.isDeleted,!1),v(this.#e.client.select({id:n.id}).from(e`remote.entities as remote`).where(c(e`remote.key = ${i.key}`,e`remote.is_current = 1`)))))):this.#e.client.select(n).from(i).where(c(s(i.isCurrent,!0),s(i.isDeleted,!1)))}async getEntityWithRelationsByKey(t,o){if(!t||t.trim()==="")return null;const m=o?.revision,l=o?.version,d=m?null:await this.#m(t,l||null),y=l??d?.version,u=m||d?.revision||null,_=(this.#t?C(this.#e.client.select(n).from(e`remote.entities`).where(s(i.key,t)),this.#e.client.select(n).from(i).where(c(s(i.key,t),v(this.#e.client.select({id:n.id}).from(e`remote.entities as remote`).where(e`remote.key = ${i.key}`))))):this.#e.client.select(n).from(i).where(s(i.key,t))).as("e"),w=(this.#t?C(this.#e.client.select($).from(e`remote.entities_relations`),this.#e.client.select($).from(k).where(v(this.#e.client.select({id:$.id}).from(e`remote.entities_relations as remote`).where(e`remote.source_key = ${k.sourceKey}`)))):this.#e.client.select($).from(k)).as("combined_relations"),g=(this.#t?C(this.#e.client.select(n).from(e`remote.entities`),this.#e.client.select(n).from(i).where(v(this.#e.client.select({id:n.id}).from(e`remote.entities as remote`).where(e`remote.key = ${i.key}`)))):this.#e.client.select(n).from(i)).as("combined_target_entities"),E=this.#e.client.with(_,w,g).select({...W("e"),domains:this.#i("e").as("domains"),owners:this.#r("e").as("owners")}).from(_).$dynamic();u?E.where(c(s(e.raw("revision"),u),y?s(e.raw("version"),y):void 0)):E.where(s(e.raw("is_current"),1)),E.limit(1);const r=await E.run();if(r.rows.length===0)return null;const f=r.rows[0];return A(f)}async#m(t,o){if(o){const y=await(this.#t?C(this.#e.client.select({version:e.raw("version"),revision:e.raw("revision")}).from(e`remote.entities`).where(c(e`key = ${t}`,e`version = ${o}`)).orderBy(e.raw("revision DESC")),this.#e.client.select({version:i.version,revision:i.revision}).from(i).where(c(s(i.key,t),s(i.version,o),v(this.#e.client.select({id:e.raw("id")}).from(e`remote.entities as remote`).where(c(e`remote.key = ${i.key}`,e`remote.version = ${o}`))))).orderBy(H(i.revision))):this.#e.client.select({version:i.version,revision:i.revision}).from(i).where(c(s(i.key,t),s(i.version,o))).orderBy(H(i.revision))).limit(1).run();if(y.rows.length>0){const u=y.rows[0];return{version:u.version||null,revision:u.revision||null}}return null}const l=await(this.#t?C(this.#e.client.select({version:e.raw("version"),revision:e.raw("revision")}).from(e`remote.entities`).where(c(e`key = ${t}`,e`is_current = 1`)),this.#e.client.select({version:i.version,revision:i.revision}).from(i).where(c(s(i.key,t),s(i.isCurrent,!0),v(this.#e.client.select({id:e.raw("id")}).from(e`remote.entities as remote`).where(c(e`remote.key = ${i.key}`,e`remote.is_current = 1`)))))):this.#e.client.select({version:i.version,revision:i.revision}).from(i).where(c(s(i.key,t),s(i.isCurrent,!0)))).limit(1).run();if(l.rows.length>0){const d=l.rows[0];return{version:d.version||null,revision:d.revision||null}}return null}#i(t){return e`
|
|
36
|
+
COALESCE(
|
|
37
|
+
(
|
|
38
|
+
SELECT json_group_array(
|
|
39
|
+
json_object(
|
|
40
|
+
'id', d.id,
|
|
41
|
+
'key', d.key,
|
|
42
|
+
'type', d.type,
|
|
43
|
+
'title', d.title,
|
|
44
|
+
'summary', d.summary,
|
|
45
|
+
'created_at', d.created_at,
|
|
46
|
+
'updated_at', d.updated_at,
|
|
47
|
+
'source', d.source,
|
|
48
|
+
'source_file', d.source_file
|
|
49
|
+
)
|
|
50
|
+
)
|
|
51
|
+
FROM (
|
|
52
|
+
SELECT DISTINCT d.*
|
|
53
|
+
FROM combined_relations er
|
|
54
|
+
JOIN combined_target_entities d ON d.key = er.source_key
|
|
55
|
+
WHERE er.source_to_target_relation = 'hasParts'
|
|
56
|
+
AND er.target_key = ${e.raw(`${t}.key`)}
|
|
57
|
+
AND d.type = 'domain'
|
|
58
|
+
AND d.is_current = 1
|
|
59
|
+
AND CASE
|
|
60
|
+
WHEN ${e.raw(`${t}.version`)} = ''
|
|
61
|
+
THEN er.target_version = ''
|
|
62
|
+
ELSE (er.target_version = ${e.raw(`${t}.version`)} OR er.target_version = '')
|
|
63
|
+
END
|
|
64
|
+
AND CASE
|
|
65
|
+
WHEN ${e.raw(`${t}.revision`)} = ''
|
|
66
|
+
THEN 1 = 1
|
|
67
|
+
ELSE (er.target_revision <= ${e.raw(`${t}.revision`)} OR er.target_revision = '')
|
|
68
|
+
END
|
|
69
|
+
LIMIT 10
|
|
70
|
+
) d
|
|
71
|
+
),
|
|
72
|
+
json_array()
|
|
73
|
+
)
|
|
74
|
+
`}#r(t){return e`
|
|
75
|
+
COALESCE(
|
|
76
|
+
(
|
|
77
|
+
SELECT json_group_array(
|
|
78
|
+
json_object(
|
|
79
|
+
'id', o.id,
|
|
80
|
+
'key', o.key,
|
|
81
|
+
'type', o.type,
|
|
82
|
+
'title', o.title,
|
|
83
|
+
'summary', o.summary,
|
|
84
|
+
'created_at', o.created_at,
|
|
85
|
+
'updated_at', o.updated_at,
|
|
86
|
+
'source', o.source,
|
|
87
|
+
'source_file', o.source_file
|
|
88
|
+
)
|
|
89
|
+
)
|
|
90
|
+
FROM (
|
|
91
|
+
SELECT DISTINCT o.*
|
|
92
|
+
FROM combined_relations er
|
|
93
|
+
JOIN combined_target_entities o ON o.key = er.source_key
|
|
94
|
+
WHERE er.source_to_target_relation = 'owns'
|
|
95
|
+
AND er.target_key = ${e.raw(`${t}.key`)}
|
|
96
|
+
AND o.is_current = 1
|
|
97
|
+
AND CASE
|
|
98
|
+
WHEN ${e.raw(`${t}.version`)} = ''
|
|
99
|
+
THEN er.target_version = ''
|
|
100
|
+
ELSE (er.target_version = ${e.raw(`${t}.version`)} OR er.target_version = '')
|
|
101
|
+
END
|
|
102
|
+
AND CASE
|
|
103
|
+
WHEN ${e.raw(`${t}.revision`)} = ''
|
|
104
|
+
THEN 1 = 1
|
|
105
|
+
ELSE (er.target_revision <= ${e.raw(`${t}.revision`)} OR er.target_revision = '')
|
|
106
|
+
END
|
|
107
|
+
LIMIT 10
|
|
108
|
+
) o
|
|
109
|
+
),
|
|
110
|
+
json_array()
|
|
111
|
+
)
|
|
112
|
+
`}}export{Z as CatalogEntitiesBffRepository};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{and as o,count as V,desc as I,eq as r,isNotNull as Q,notExists as E,or as X,sql as e}from"drizzle-orm";import{unionAll as y}from"drizzle-orm/sqlite-core";import{logger as L}from"../../../../../tools/notifiers/logger.js";import{VERSION_NOT_SPECIFIED as O}from"@redocly/theme/core/constants";import{applyPagination as T}from"../../../../../providers/database/pagination/index.js";import{applyFilter as b}from"../../../../../providers/database/pagination/filter.js";import{createBffEntity as F}from"../../mappers/create-bff-entity.js";import{FIELDS_TO_SELECT_FOR_ENTITY as a,FIELDS_TO_SELECT_FOR_ENTITY_RELATION as d,createEntityFieldsForSelect as B}from"../utils.js";import{createEntityReadModel as g}from"../../mappers/create-entity-read-model.js";import{createEntityRelation as H}from"../../mappers/create-entity-relation.js";import{entitiesTable as t}from"../../../../../providers/database/databases/catalog-sqlite/schemas/entities-table.js";import{entitiesRelationsTable as h}from"../../../../../providers/database/databases/catalog-sqlite/schemas/entities-relations-table.js";import{CatalogEntitiesRelationsRepository as W}from"./catalog-entities-relations-repository.js";class ne{#e;#t=void 0;#i;constructor(i){this.#e=i,this.#i=new W(this.#e,"")}async attachDatabase(i){this.#t!==i&&(this.#t=i,await this.#e.client.run(e`ATTACH DATABASE ${i} AS remote`),this.#i=new W(this.#e,i))}async getEntities(i={}){const s=this.#t?y(this.#e.client.select(a).from(e`remote.entities`),this.#e.client.select(a).from(t).where(E(this.#e.client.select({id:a.id}).from(e`remote.entities as remote`).where(e`remote.key = ${t.key}`)))):this.#e.client.select(a).from(t),n=this.#e.client.select(a).from(s.as("combined_entities")),c=this.#e.client.select(a).from(s.as("combined_entities")).$dynamic(),m=T(c,{...i,limit:void 0,skip:void 0,after:void 0,before:void 0}),l=this.#e.client.$count(m),u=n.$dynamic(),_=i.limit||10,f=T(u,{...i,limit:_+1}),[N,k]=await Promise.all([f.run(),l]),S=N.rows,v=S.length>_;return{items:S.slice(0,_).map(w=>g(w)).filter(w=>w!==null),hasMore:v,total:k}}async getEntityKeysAndVersionsBySourceFile(i){const s=this.#t?y(this.#e.client.select({keyVersion:e`key || ':' || COALESCE(version, ${O})`.as("keyVersion")}).from(e`remote.entities`).where(o(e`source_file = ${i}`,e`source = 'file'`,e`key IS NOT NULL`)),this.#e.client.select({keyVersion:e`${t.key} || ':' || COALESCE(${t.version}, ${O})`.as("keyVersion")}).from(t).where(o(r(t.sourceFile,i),r(t.source,"file"),e`${t.key} IS NOT NULL`,E(this.#e.client.select({key:e.raw("key")}).from(e`remote.entities as remote`).where(e`remote.key = ${t.key}`))))):this.#e.client.select({keyVersion:e`${t.key} || ':' || COALESCE(${t.version}, ${O})`.as("keyVersion")}).from(t).where(o(r(t.sourceFile,i),r(t.source,"file"),Q(t.key))),n=await this.#e.client.selectDistinct({keyVersion:e`combined_keys_versions.keyVersion`}).from(s.as("combined_keys_versions")).run();return new Set(n.rows.map(c=>c.keyVersion))}async listEntityRevisions(i,s){const n=[r(t.key,i),r(t.isDeleted,!1),...s?[r(t.version,s)]:[]],c=this.#t?y(this.#e.client.select({version:e.raw("version"),revision:e.raw("revision"),isCurrent:e.raw("is_current"),createdAt:e.raw("created_at"),updatedAt:e.raw("updated_at"),isDefaultVersion:e.raw("is_default_version")}).from(e`remote.entities`).where(o(e`key = ${i}`,s?e`version = ${s}`:void 0)),this.#e.client.select({version:t.version,revision:t.revision,isCurrent:t.isCurrent,createdAt:t.createdAt,updatedAt:t.updatedAt,isDefaultVersion:t.isDefaultVersion}).from(t).where(o(...n,E(this.#e.client.select({version:e.raw("version")}).from(e`remote.entities as remote`).where(o(e`remote.key = ${t.key}`,e`remote.version = ${t.version}`,e`remote.revision = ${t.revision}`)))))):this.#e.client.select({version:t.version,revision:t.revision,isCurrent:t.isCurrent,createdAt:t.createdAt,updatedAt:t.updatedAt,isDefaultVersion:t.isDefaultVersion}).from(t).where(o(...n));return(await this.#e.client.select({version:e.raw("version"),revision:e.raw("revision"),isCurrent:e.raw("is_current"),createdAt:e.raw("created_at"),updatedAt:e.raw("updated_at"),isDefaultVersion:e.raw("is_default_version")}).from(c.as("combined_revisions")).orderBy(e.raw("is_current DESC"),e.raw("updated_at DESC"),e.raw("created_at DESC")).run()).rows.map(l=>({version:l.version||null,revision:l.revision??"",isCurrent:l.is_current!==null?!!l.is_current:!1,createdAt:l.created_at||null,updatedAt:l.updated_at||null,isDefaultVersion:l.is_default_version!==null?!!l.is_default_version:!1}))}async getEntitiesCountByTypes(){const i=this.#t?y(this.#e.client.select({type:e`type`}).from(e`remote.entities`).where(o(r(e.raw("is_current"),1),r(e.raw("is_deleted"),!1))),this.#e.client.select({type:t.type}).from(t).where(o(r(t.isCurrent,!0),r(t.isDeleted,!1),E(this.#e.client.select({id:e.raw("id")}).from(e`remote.entities as remote`).where(o(e`remote.key = ${t.key}`,e`remote.is_current = 1`)))))):this.#e.client.select({type:t.type}).from(t).where(o(r(t.isCurrent,!0),r(t.isDeleted,!1)));return this.#e.client.select({type:t.type,count:V()}).from(i.as("combined_entities")).groupBy(t.type).where(r(t.isDeleted,!1))}async getEntityByKey(i){const c=(await(this.#t?y(this.#e.client.select(a).from(e`remote.entities`).where(o(r(t.key,i),r(e.raw("is_current"),1),r(e.raw("is_deleted"),!1))),this.#e.client.select(a).from(t).where(o(r(t.key,i),r(t.isCurrent,!0),r(t.isDeleted,!1),E(this.#e.client.select({id:a.id}).from(e`remote.entities as remote`).where(o(e`remote.key = ${t.key}`,e`remote.is_current = 1`)))))):this.#e.client.select(a).from(t).where(o(r(t.key,i),r(t.isCurrent,!0),r(t.isDeleted,!1)))).run()).rows[0];return c?g(c):null}async getOneOutdatedEntity(){const i=this.#t?y(this.#e.client.select(a).from(e`remote.entities`),this.#e.client.select(a).from(t).where(E(this.#e.client.select({id:a.id}).from(e`remote.entities as remote`).where(e`remote.key = ${t.key}`)))):this.#e.client.select(a).from(t),n=(await this.#e.client.select(a).from(i.as("combined_entities")).where(X(e`combined_entities.scorecards_status = 'OUTDATED'`,e`combined_entities.scorecards_status IS NULL`)).orderBy(e`combined_entities.updated_at ASC`).limit(1).run()).rows[0];return n?g(n):null}async getEntitiesRelations(i={}){const s=this.#t?y(this.#e.client.select(d).from(e`remote.entities_relations`),this.#e.client.select(d).from(h).where(E(this.#e.client.select({id:d.id}).from(e`remote.entities_relations as remote`).where(e`remote.source_key = ${h.sourceKey}`)))):this.#e.client.select(d).from(h),n=this.#e.client.select(d).from(s.as("combined_entities_relations")).$dynamic(),c=this.#e.client.select(d).from(s.as("combined_entities_relations")).$dynamic(),m=T(c,{...i,limit:void 0,skip:void 0,after:void 0,before:void 0}),l=this.#e.client.$count(m),u=i.limit||10,_=T(n,{...i,limit:u+1}),[f,N]=await Promise.all([_.run(),l]),k=f.rows,S=k.length>u;return{items:k.slice(0,u).map(v=>H(v)).filter(v=>v!==null),hasMore:S,total:N}}async getEntityRelationById(i){const c=(await(this.#t?y(this.#e.client.select(d).from(e`remote.entities_relations`).where(r(h.id,i)),this.#e.client.select(d).from(h).where(o(r(h.id,i),E(this.#e.client.select({id:d.id}).from(e`remote.entities_relations as remote`).where(e`remote.id = ${h.id}`))))):this.#e.client.select(d).from(h).where(r(h.id,i))).run()).rows[0];return c?H(c):null}async getEntitiesWithRelations(i={}){const n=(this.#t?y(this.#e.client.select(a).from(e`remote.entities`).where(o(r(e.raw("is_current"),1),r(e.raw("is_deleted"),!1))),this.#e.client.select(a).from(t).where(o(r(t.isCurrent,!0),r(t.isDeleted,!1),E(this.#e.client.select({id:a.id}).from(e`remote.entities as remote`).where(o(e`remote.key = ${t.key}`,e`remote.is_current = 1`)))))):this.#e.client.select(a).from(t).where(o(r(t.isCurrent,!0),r(t.isDeleted,!1)))).as("e"),m=(this.#t?y(this.#e.client.select(d).from(e`remote.entities_relations`),this.#e.client.select(d).from(h).where(E(this.#e.client.select({id:d.id}).from(e`remote.entities_relations as remote`).where(e`remote.source_key = ${h.sourceKey}`)))):this.#e.client.select(d).from(h)).as("combined_relations"),u=(this.#t?y(this.#e.client.select(a).from(e`remote.entities`).where(o(r(e.raw("is_current"),1),r(e.raw("is_deleted"),!1))),this.#e.client.select(a).from(t).where(o(r(t.isCurrent,!0),r(t.isDeleted,!1),E(this.#e.client.select({id:a.id}).from(e`remote.entities as remote`).where(o(e`remote.key = ${t.key}`,e`remote.is_current = 1`)))))):this.#e.client.select(a).from(t).where(o(r(t.isCurrent,!0),r(t.isDeleted,!1)))).as("combined_target_entities"),_=this.#e.client.with(n,m,u).select({...B("e"),domains:this.#r("e").as("domains"),owners:this.#s("e").as("owners")}).from(n).as("entities_with_relations"),f={...a,domains:_.domains,owners:_.owners},N=this.#e.client.with(_).select(f).from(_).$dynamic(),k=this.#e.client.with(_).select(f).from(_).$dynamic(),{whereCondition:S}=b(N,i.filter);S&&N.where(S);const{whereCondition:v}=b(k,i.filter);v&&k.where(v);try{const w=T(k,{...i,limit:void 0,skip:void 0,after:void 0,before:void 0}),p=this.#e.client.$count(w),R=i.limit||10,A=T(N,{...i,limit:R+1}),[M,U]=await Promise.all([A.run(),p]),D=M.rows,q=D.length>R;return{items:D.slice(0,R).map(C=>F(C)).filter(C=>C!==null),hasMore:q,total:U}}catch(w){return L.error("Error getting entities with relations:",w),{items:[],hasMore:!1,total:0}}}async#n(i,s){if(s){const l=await(this.#t?y(this.#e.client.select({version:e.raw("version"),revision:e.raw("revision")}).from(e`remote.entities`).where(o(e`key = ${i}`,e`version = ${s}`)).orderBy(e.raw("revision DESC")),this.#e.client.select({version:t.version,revision:t.revision}).from(t).where(o(r(t.key,i),r(t.version,s),E(this.#e.client.select({id:e.raw("id")}).from(e`remote.entities as remote`).where(o(e`remote.key = ${t.key}`,e`remote.version = ${s}`))))).orderBy(I(t.revision))):this.#e.client.select({version:t.version,revision:t.revision}).from(t).where(o(r(t.key,i),r(t.version,s))).orderBy(I(t.revision))).limit(1).run();if(l.rows.length>0){const u=l.rows[0];return{version:u.version||null,revision:u.revision||null}}return null}const c=await(this.#t?y(this.#e.client.select({version:e.raw("version"),revision:e.raw("revision")}).from(e`remote.entities`).where(o(e`key = ${i}`,e`is_current = 1`)),this.#e.client.select({version:t.version,revision:t.revision}).from(t).where(o(r(t.key,i),r(t.isCurrent,!0),E(this.#e.client.select({id:e.raw("id")}).from(e`remote.entities as remote`).where(o(e`remote.key = ${t.key}`,e`remote.is_current = 1`)))))):this.#e.client.select({version:t.version,revision:t.revision}).from(t).where(o(r(t.key,i),r(t.isCurrent,!0)))).limit(1).run();if(c.rows.length>0){const m=c.rows[0];return{version:m.version||null,revision:m.revision||null}}return null}async getEntityWithRelationsByKey(i,s){const n=s?.revision,c=s?.version,m=n?null:await this.#n(i,c||null),l=c??m?.version,u=n||m?.revision||null,f=(this.#t?y(this.#e.client.select(a).from(e`remote.entities`).where(r(t.key,i)),this.#e.client.select(a).from(t).where(o(r(t.key,i),E(this.#e.client.select({id:a.id}).from(e`remote.entities as remote`).where(e`remote.key = ${t.key}`))))):this.#e.client.select(a).from(t).where(r(t.key,i))).as("e"),k=(this.#t?y(this.#e.client.select(d).from(e`remote.entities_relations`),this.#e.client.select(d).from(h).where(E(this.#e.client.select({id:d.id}).from(e`remote.entities_relations as remote`).where(e`remote.source_key = ${h.sourceKey}`)))):this.#e.client.select(d).from(h)).as("combined_relations"),v=(this.#t?y(this.#e.client.select(a).from(e`remote.entities`),this.#e.client.select(a).from(t).where(E(this.#e.client.select({id:a.id}).from(e`remote.entities as remote`).where(e`remote.key = ${t.key}`)))):this.#e.client.select(a).from(t)).as("combined_target_entities"),w=this.#e.client.with(f,k,v).select({...B("e"),domains:this.#r("e").as("domains"),owners:this.#s("e").as("owners")}).from(f).$dynamic();u?w.where(o(r(e.raw("revision"),u),l?r(e.raw("version"),l):void 0)):w.where(r(e.raw("is_current"),1)),w.limit(1);const p=await w.run();if(p.rows.length===0)return null;const R=p.rows[0];return F(R)}async getRelationsForEntity(i,s,n){return this.#i.getRelationsForEntity(i,s,n)}async getRelatedEntities(i,s={}){return this.#i.getRelatedEntities(i,s)}async getCatalogFilters({entitiesTypes:i=[],emptyFilters:s=[]}){if(!s.length)return{};try{return await this.#o(i),(s.includes("domains")||s.includes("owners"))&&await this.#a(),await this.#c(s)}catch(n){return console.error("Error fetching catalog filters:",n),{}}finally{await this.#u()}}async#o(i){if(this.#t?await this.#e.client.run(e`
|
|
1
|
+
import{and as l,count as L,eq as n,isNotNull as v,notExists as E,or as D,sql as e}from"drizzle-orm";import{unionAll as f}from"drizzle-orm/sqlite-core";import{logger as b}from"../../../../../tools/notifiers/logger.js";import{VERSION_NOT_SPECIFIED as R}from"@redocly/theme/core/constants";import{applyPagination as S}from"../../../../../providers/database/pagination/index.js";import{FIELDS_TO_SELECT_FOR_ENTITY as c,FIELDS_TO_SELECT_FOR_ENTITY_RELATION as m}from"../utils.js";import{createEntityReadModel as O}from"../../mappers/create-entity-read-model.js";import{createEntityRelation as C}from"../../mappers/create-entity-relation.js";import{entitiesTable as t}from"../../../../../providers/database/databases/catalog-sqlite/schemas/entities-table.js";import{entitiesRelationsTable as y}from"../../../../../providers/database/databases/catalog-sqlite/schemas/entities-relations-table.js";import{CatalogEntitiesRelationsRepository as I}from"./catalog-entities-relations-repository.js";import{CatalogEntitiesBffRepository as g}from"./catalog-entities-bff-repository.js";class j{#e;#t=void 0;#i;#s;constructor(i){this.#e=i,this.#i=new I(this.#e,this.#t||""),this.#s=new g(this.#e,this.#t||"")}async attachDatabase(i){this.#t!==i&&(this.#t=i,await this.#e.client.run(e`ATTACH DATABASE ${i} AS remote`),this.#i=new I(this.#e,i),this.#s=new g(this.#e,i))}async getEntities(i={}){const s=this.#t?f(this.#e.client.select(c).from(e`remote.entities`),this.#e.client.select(c).from(t).where(E(this.#e.client.select({id:c.id}).from(e`remote.entities as remote`).where(e`remote.key = ${t.key}`)))):this.#e.client.select(c).from(t),r=this.#e.client.select(c).from(s.as("combined_entities")),o=this.#e.client.select(c).from(s.as("combined_entities")).$dynamic(),_=S(o,{...i,limit:void 0,skip:void 0,after:void 0,before:void 0}),a=this.#e.client.$count(_),u=r.$dynamic(),d=i.limit||10,h=S(u,{...i,limit:d+1}),[T,N]=await Promise.all([h.run(),a]),p=T.rows,w=p.length>d;return{items:p.slice(0,d).map(k=>O(k)).filter(k=>k!==null),hasMore:w,total:N}}async getEntityKeysAndVersionsBySourceFile(i){const s=this.#t?f(this.#e.client.select({keyVersion:e`key || ':' || COALESCE(version, ${R})`.as("keyVersion")}).from(e`remote.entities`).where(l(e`source_file = ${i}`,e`source = 'file'`,e`key IS NOT NULL`)),this.#e.client.select({keyVersion:e`${t.key} || ':' || COALESCE(${t.version}, ${R})`.as("keyVersion")}).from(t).where(l(n(t.sourceFile,i),n(t.source,"file"),e`${t.key} IS NOT NULL`,E(this.#e.client.select({key:e.raw("key")}).from(e`remote.entities as remote`).where(e`remote.key = ${t.key}`))))):this.#e.client.select({keyVersion:e`${t.key} || ':' || COALESCE(${t.version}, ${R})`.as("keyVersion")}).from(t).where(l(n(t.sourceFile,i),n(t.source,"file"),v(t.key))),r=await this.#e.client.selectDistinct({keyVersion:e`combined_keys_versions.keyVersion`}).from(s.as("combined_keys_versions")).run();return new Set(r.rows.map(o=>o.keyVersion))}async listEntityRevisions(i,s){const r=[n(t.key,i),n(t.isDeleted,!1),...s?[n(t.version,s)]:[]],o=this.#t?f(this.#e.client.select({version:e.raw("version"),revision:e.raw("revision"),isCurrent:e.raw("is_current"),createdAt:e.raw("created_at"),updatedAt:e.raw("updated_at"),isDefaultVersion:e.raw("is_default_version")}).from(e`remote.entities`).where(l(e`key = ${i}`,s?e`version = ${s}`:void 0)),this.#e.client.select({version:t.version,revision:t.revision,isCurrent:t.isCurrent,createdAt:t.createdAt,updatedAt:t.updatedAt,isDefaultVersion:t.isDefaultVersion}).from(t).where(l(...r,E(this.#e.client.select({version:e.raw("version")}).from(e`remote.entities as remote`).where(l(e`remote.key = ${t.key}`,e`remote.version = ${t.version}`,e`remote.revision = ${t.revision}`)))))):this.#e.client.select({version:t.version,revision:t.revision,isCurrent:t.isCurrent,createdAt:t.createdAt,updatedAt:t.updatedAt,isDefaultVersion:t.isDefaultVersion}).from(t).where(l(...r));return(await this.#e.client.select({version:e.raw("version"),revision:e.raw("revision"),isCurrent:e.raw("is_current"),createdAt:e.raw("created_at"),updatedAt:e.raw("updated_at"),isDefaultVersion:e.raw("is_default_version")}).from(o.as("combined_revisions")).orderBy(e.raw("is_current DESC"),e.raw("updated_at DESC"),e.raw("created_at DESC")).run()).rows.map(a=>({version:a.version||null,revision:a.revision??"",isCurrent:a.is_current!==null?!!a.is_current:!1,createdAt:a.created_at||null,updatedAt:a.updated_at||null,isDefaultVersion:a.is_default_version!==null?!!a.is_default_version:!1}))}async getEntitiesCountByTypes(){const i=this.#t?f(this.#e.client.select({type:e`type`}).from(e`remote.entities`).where(l(n(e.raw("is_current"),1),n(e.raw("is_deleted"),!1))),this.#e.client.select({type:t.type}).from(t).where(l(n(t.isCurrent,!0),n(t.isDeleted,!1),E(this.#e.client.select({id:e.raw("id")}).from(e`remote.entities as remote`).where(l(e`remote.key = ${t.key}`,e`remote.is_current = 1`)))))):this.#e.client.select({type:t.type}).from(t).where(l(n(t.isCurrent,!0),n(t.isDeleted,!1)));return this.#e.client.select({type:t.type,count:L()}).from(i.as("combined_entities")).groupBy(t.type).where(n(t.isDeleted,!1))}async getEntityByKey(i){const o=(await(this.#t?f(this.#e.client.select(c).from(e`remote.entities`).where(l(n(t.key,i),n(e.raw("is_current"),1),n(e.raw("is_deleted"),!1))),this.#e.client.select(c).from(t).where(l(n(t.key,i),n(t.isCurrent,!0),n(t.isDeleted,!1),E(this.#e.client.select({id:c.id}).from(e`remote.entities as remote`).where(l(e`remote.key = ${t.key}`,e`remote.is_current = 1`)))))):this.#e.client.select(c).from(t).where(l(n(t.key,i),n(t.isCurrent,!0),n(t.isDeleted,!1)))).run()).rows[0];return o?O(o):null}async getOneOutdatedEntity(){const i=this.#t?f(this.#e.client.select(c).from(e`remote.entities`),this.#e.client.select(c).from(t).where(E(this.#e.client.select({id:c.id}).from(e`remote.entities as remote`).where(e`remote.key = ${t.key}`)))):this.#e.client.select(c).from(t),r=(await this.#e.client.select(c).from(i.as("combined_entities")).where(D(e`combined_entities.scorecards_status = 'OUTDATED'`,e`combined_entities.scorecards_status IS NULL`)).orderBy(e`combined_entities.updated_at ASC`).limit(1).run()).rows[0];return r?O(r):null}async getEntitiesRelations(i={}){const s=this.#t?f(this.#e.client.select(m).from(e`remote.entities_relations`),this.#e.client.select(m).from(y).where(E(this.#e.client.select({id:m.id}).from(e`remote.entities_relations as remote`).where(e`remote.source_key = ${y.sourceKey}`)))):this.#e.client.select(m).from(y),r=this.#e.client.select(m).from(s.as("combined_entities_relations")).$dynamic(),o=this.#e.client.select(m).from(s.as("combined_entities_relations")).$dynamic(),_=S(o,{...i,limit:void 0,skip:void 0,after:void 0,before:void 0}),a=this.#e.client.$count(_),u=i.limit||10,d=S(r,{...i,limit:u+1}),[h,T]=await Promise.all([d.run(),a]),N=h.rows,p=N.length>u;return{items:N.slice(0,u).map(w=>C(w)).filter(w=>w!==null),hasMore:p,total:T}}async getEntityRelationById(i){const o=(await(this.#t?f(this.#e.client.select(m).from(e`remote.entities_relations`).where(n(y.id,i)),this.#e.client.select(m).from(y).where(l(n(y.id,i),E(this.#e.client.select({id:m.id}).from(e`remote.entities_relations as remote`).where(e`remote.id = ${y.id}`))))):this.#e.client.select(m).from(y).where(n(y.id,i))).run()).rows[0];return o?C(o):null}async getEntitiesWithRelations(i={}){return this.#s.getEntitiesWithRelations(i)}async getEntityWithRelationsByKey(i,s){return this.#s.getEntityWithRelationsByKey(i,s)}async getRelationsForEntity(i,s,r){return this.#i.getRelationsForEntity(i,s,r)}async getRelatedEntities(i,s={}){return this.#i.getRelatedEntities(i,s)}async getCatalogFilters({entitiesTypes:i=[],emptyFilters:s=[]}){if(!s.length)return{};try{return await this.#r(i),(s.includes("domains")||s.includes("owners"))&&await this.#n(),await this.#o(s)}catch(r){return console.error("Error fetching catalog filters:",r),{}}finally{await this.#l()}}async#r(i){if(this.#t?await this.#e.client.run(e`
|
|
2
2
|
CREATE TEMP TABLE IF NOT EXISTS temp_combined_entities AS
|
|
3
3
|
SELECT
|
|
4
4
|
e.key,
|
|
@@ -25,7 +25,7 @@ import{and as o,count as V,desc as I,eq as r,isNotNull as Q,notExists as E,or as
|
|
|
25
25
|
metadata
|
|
26
26
|
FROM entities
|
|
27
27
|
WHERE is_current = 1
|
|
28
|
-
`),i.length){const s=i.map(
|
|
28
|
+
`),i.length){const s=i.map(r=>`'${r.replace(/'/g,"''")}'`).join(",");await this.#e.client.run(e.raw(`
|
|
29
29
|
CREATE TEMP TABLE IF NOT EXISTS temp_filtered_entities AS
|
|
30
30
|
SELECT * FROM temp_combined_entities
|
|
31
31
|
WHERE type IN (${s})
|
|
@@ -34,7 +34,7 @@ import{and as o,count as V,desc as I,eq as r,isNotNull as Q,notExists as E,or as
|
|
|
34
34
|
SELECT * FROM temp_combined_entities
|
|
35
35
|
`);await this.#e.client.run(e`
|
|
36
36
|
CREATE INDEX IF NOT EXISTS idx_temp_filtered_type ON temp_filtered_entities(type)
|
|
37
|
-
`)}async#
|
|
37
|
+
`)}async#n(){this.#t?await this.#e.client.run(e`
|
|
38
38
|
CREATE TEMP TABLE IF NOT EXISTS temp_combined_relations AS
|
|
39
39
|
SELECT
|
|
40
40
|
source_key,
|
|
@@ -62,12 +62,12 @@ import{and as o,count as V,desc as I,eq as r,isNotNull as Q,notExists as E,or as
|
|
|
62
62
|
CREATE INDEX IF NOT EXISTS idx_temp_rel_target ON temp_combined_relations(target_key)
|
|
63
63
|
`),await this.#e.client.run(e`
|
|
64
64
|
CREATE INDEX IF NOT EXISTS idx_temp_rel_relation ON temp_combined_relations(source_to_target_relation)
|
|
65
|
-
`)}async#
|
|
65
|
+
`)}async#o(i){const s={},r=[],o=i.filter(u=>u.startsWith("metadata."));i.includes("type")&&r.push(`
|
|
66
66
|
SELECT 'type' as filter_name, type as value, COUNT(*) as count
|
|
67
67
|
FROM temp_filtered_entities
|
|
68
68
|
WHERE type IS NOT NULL
|
|
69
69
|
GROUP BY type
|
|
70
|
-
`),i.includes("tags")&&
|
|
70
|
+
`),i.includes("tags")&&r.push(`
|
|
71
71
|
SELECT 'tags' as filter_name, tag.value as value, COUNT(DISTINCT tfe.key) as count
|
|
72
72
|
FROM temp_filtered_entities tfe,
|
|
73
73
|
json_each(COALESCE(tfe.tags, json_array())) as tag
|
|
@@ -76,7 +76,7 @@ import{and as o,count as V,desc as I,eq as r,isNotNull as Q,notExists as E,or as
|
|
|
76
76
|
AND tag.value IS NOT NULL
|
|
77
77
|
AND tag.value != ''
|
|
78
78
|
GROUP BY tag.value
|
|
79
|
-
`),i.includes("domains")&&
|
|
79
|
+
`),i.includes("domains")&&r.push(`
|
|
80
80
|
SELECT 'domains' as filter_name, tce.key as value, COUNT(DISTINCT tfe.key) as count
|
|
81
81
|
FROM temp_combined_relations tcr
|
|
82
82
|
INNER JOIN temp_filtered_entities tfe ON (
|
|
@@ -89,7 +89,7 @@ import{and as o,count as V,desc as I,eq as r,isNotNull as Q,notExists as E,or as
|
|
|
89
89
|
)
|
|
90
90
|
WHERE tce.type = 'domain'
|
|
91
91
|
GROUP BY tce.key
|
|
92
|
-
`),i.includes("owners")&&
|
|
92
|
+
`),i.includes("owners")&&r.push(`
|
|
93
93
|
SELECT 'owners' as filter_name, owner_key as value, COUNT(DISTINCT entity_key) as count
|
|
94
94
|
FROM (
|
|
95
95
|
SELECT
|
|
@@ -109,130 +109,12 @@ import{and as o,count as V,desc as I,eq as r,isNotNull as Q,notExists as E,or as
|
|
|
109
109
|
INNER JOIN temp_combined_entities tce ON tce.key = owner_key
|
|
110
110
|
WHERE owner_key IS NOT NULL
|
|
111
111
|
GROUP BY owner_key
|
|
112
|
-
`);for(const u of
|
|
113
|
-
SELECT json_extract(metadata, '${
|
|
112
|
+
`);for(const u of o)await this.#a(u,s);if(r.length===0)return s;const _=r.join(" UNION ALL "),a=await this.#e.client.run(e.raw(_));if(a?.rows)for(const u of a.rows){const d=u.filter_name,h=u.value,T=Number(u.count)||0;s[d]||(s[d]=[]),h&&s[d].push({value:h,count:T})}return s}async#a(i,s){const r=i.substring(9),o=`$.${$(r)}`,_=await this.#e.client.run(e.raw(`
|
|
113
|
+
SELECT json_extract(metadata, '${o}') as value, COUNT(*) as count
|
|
114
114
|
FROM temp_filtered_entities
|
|
115
115
|
WHERE metadata IS NOT NULL
|
|
116
116
|
AND metadata != ''
|
|
117
|
-
AND json_extract(metadata, '${
|
|
118
|
-
AND json_extract(metadata, '${
|
|
117
|
+
AND json_extract(metadata, '${o}') IS NOT NULL
|
|
118
|
+
AND json_extract(metadata, '${o}') != ''
|
|
119
119
|
GROUP BY value
|
|
120
|
-
`));
|
|
121
|
-
COALESCE(
|
|
122
|
-
(
|
|
123
|
-
SELECT json_group_array(
|
|
124
|
-
json_object(
|
|
125
|
-
'id', d.id,
|
|
126
|
-
'key', d.key,
|
|
127
|
-
'type', d.type,
|
|
128
|
-
'title', d.title,
|
|
129
|
-
'summary', d.summary,
|
|
130
|
-
'created_at', d.created_at,
|
|
131
|
-
'updated_at', d.updated_at,
|
|
132
|
-
'source', d.source,
|
|
133
|
-
'source_file', d.source_file
|
|
134
|
-
)
|
|
135
|
-
)
|
|
136
|
-
FROM (
|
|
137
|
-
SELECT DISTINCT d.*
|
|
138
|
-
FROM combined_relations er
|
|
139
|
-
JOIN combined_target_entities d ON d.key = CASE
|
|
140
|
-
WHEN er.source_key = ${e.raw(`${i}.key`)} THEN er.target_key
|
|
141
|
-
WHEN er.target_key = ${e.raw(`${i}.key`)} THEN er.source_key
|
|
142
|
-
END
|
|
143
|
-
WHERE d.type = 'domain'
|
|
144
|
-
AND d.is_current = 1
|
|
145
|
-
AND (
|
|
146
|
-
(
|
|
147
|
-
er.source_key = ${e.raw(`${i}.key`)}
|
|
148
|
-
AND CASE
|
|
149
|
-
WHEN ${e.raw(`${i}.version`)} = ''
|
|
150
|
-
THEN er.source_version = ''
|
|
151
|
-
ELSE (er.source_version = ${e.raw(`${i}.version`)} OR er.source_version = '')
|
|
152
|
-
END
|
|
153
|
-
AND CASE
|
|
154
|
-
WHEN ${e.raw(`${i}.revision`)} = ''
|
|
155
|
-
THEN 1 = 1
|
|
156
|
-
ELSE (er.source_revision <= ${e.raw(`${i}.revision`)} OR er.source_revision = '')
|
|
157
|
-
END
|
|
158
|
-
)
|
|
159
|
-
OR (
|
|
160
|
-
er.target_key = ${e.raw(`${i}.key`)}
|
|
161
|
-
AND CASE
|
|
162
|
-
WHEN ${e.raw(`${i}.version`)} = ''
|
|
163
|
-
THEN er.target_version = ''
|
|
164
|
-
ELSE (er.target_version = ${e.raw(`${i}.version`)} OR er.target_version = '')
|
|
165
|
-
END
|
|
166
|
-
AND CASE
|
|
167
|
-
WHEN ${e.raw(`${i}.revision`)} = ''
|
|
168
|
-
THEN 1 = 1
|
|
169
|
-
ELSE (er.target_revision <= ${e.raw(`${i}.revision`)} OR er.target_revision = '')
|
|
170
|
-
END
|
|
171
|
-
)
|
|
172
|
-
)
|
|
173
|
-
LIMIT 10
|
|
174
|
-
) d
|
|
175
|
-
),
|
|
176
|
-
json_array()
|
|
177
|
-
)
|
|
178
|
-
`}#s(i){return e`
|
|
179
|
-
COALESCE(
|
|
180
|
-
(
|
|
181
|
-
SELECT json_group_array(
|
|
182
|
-
json_object(
|
|
183
|
-
'id', o.id,
|
|
184
|
-
'key', o.key,
|
|
185
|
-
'type', o.type,
|
|
186
|
-
'title', o.title,
|
|
187
|
-
'summary', o.summary,
|
|
188
|
-
'created_at', o.created_at,
|
|
189
|
-
'updated_at', o.updated_at,
|
|
190
|
-
'source', o.source,
|
|
191
|
-
'source_file', o.source_file
|
|
192
|
-
)
|
|
193
|
-
)
|
|
194
|
-
FROM (
|
|
195
|
-
SELECT DISTINCT o.*
|
|
196
|
-
FROM combined_relations er
|
|
197
|
-
JOIN combined_target_entities o
|
|
198
|
-
ON o.key = CASE
|
|
199
|
-
WHEN er.source_key = ${e.raw(`${i}.key`)} THEN er.target_key
|
|
200
|
-
ELSE er.source_key
|
|
201
|
-
END
|
|
202
|
-
WHERE ((er.source_key = ${e.raw(`${i}.key`)} AND er.source_to_target_relation = 'ownedBy')
|
|
203
|
-
OR (er.target_key = ${e.raw(`${i}.key`)} AND er.source_to_target_relation = 'owns'))
|
|
204
|
-
AND o.is_current = 1
|
|
205
|
-
AND (
|
|
206
|
-
(
|
|
207
|
-
er.source_key = ${e.raw(`${i}.key`)}
|
|
208
|
-
AND CASE
|
|
209
|
-
WHEN ${e.raw(`${i}.version`)} = ''
|
|
210
|
-
THEN er.source_version = ''
|
|
211
|
-
ELSE (er.source_version = ${e.raw(`${i}.version`)} OR er.source_version = '')
|
|
212
|
-
END
|
|
213
|
-
AND CASE
|
|
214
|
-
WHEN ${e.raw(`${i}.revision`)} = ''
|
|
215
|
-
THEN 1 = 1
|
|
216
|
-
ELSE (er.source_revision <= ${e.raw(`${i}.revision`)} OR er.source_revision = '')
|
|
217
|
-
END
|
|
218
|
-
)
|
|
219
|
-
OR (
|
|
220
|
-
er.target_key = ${e.raw(`${i}.key`)}
|
|
221
|
-
AND CASE
|
|
222
|
-
WHEN ${e.raw(`${i}.version`)} = ''
|
|
223
|
-
THEN er.target_version = ''
|
|
224
|
-
ELSE (er.target_version = ${e.raw(`${i}.version`)} OR er.target_version = '')
|
|
225
|
-
END
|
|
226
|
-
AND CASE
|
|
227
|
-
WHEN ${e.raw(`${i}.revision`)} = ''
|
|
228
|
-
THEN 1 = 1
|
|
229
|
-
ELSE (er.target_revision <= ${e.raw(`${i}.revision`)} OR er.target_revision = '')
|
|
230
|
-
END
|
|
231
|
-
)
|
|
232
|
-
)
|
|
233
|
-
LIMIT 10
|
|
234
|
-
) o
|
|
235
|
-
),
|
|
236
|
-
json_array()
|
|
237
|
-
)
|
|
238
|
-
`}}function j($){return $.replace(/[^a-zA-Z0-9._-]/g,"")}export{ne as CatalogEntitiesLocalReadRepository};
|
|
120
|
+
`));_?.rows&&(s[i]=_.rows.map(a=>({value:a.value,count:Number(a.count)||0})).filter(a=>a.value))}async#l(){try{await this.#e.client.run(e`DROP TABLE IF EXISTS temp_combined_entities`),await this.#e.client.run(e`DROP TABLE IF EXISTS temp_filtered_entities`),await this.#e.client.run(e`DROP TABLE IF EXISTS temp_combined_relations`)}catch(i){b.error("Error cleaning up temp tables:",i)}}}function $(A){return A.replace(/[^a-zA-Z0-9._-]/g,"")}export{j as CatalogEntitiesLocalReadRepository};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{and as d,desc as k,eq as u,isNull as v,or as R,sql as D}from"drizzle-orm";import{logger as h}from"../../../../../tools/notifiers/logger.js";import{promiseMapLimit as p}from"../../../../../utils/async/promise-map-limit.js";import{sha1 as T}from"../../../../../utils/crypto/sha1.js";import{isWebView as O}from"../../../../../../utils/env/is-web-view.js";import{VERSION_NOT_SPECIFIED as S}from"@redocly/theme/core/constants";import{createEntityDbRecord as x}from"../../mappers/create-entity-db-record.js";import{createEntityRelationDbRecordFromFileSchema as I}from"../../mappers/create-entity-relation-db-record-from-file-schema.js";import{entitiesTable as e}from"../../../../../providers/database/databases/catalog-sqlite/schemas/entities-table.js";import{entitiesRelationsTable as o}from"../../../../../providers/database/databases/catalog-sqlite/schemas/entities-relations-table.js";import{convertFilterToWhereCondition as K}from"../../../../../providers/database/pagination/filter.js";import{compareVersionsDescending as L}from"../../../utils/version-compare.js";const w=15;class Q{#e;#t;#r;constructor(t,r,s){this.#e=t,this.#t=r,this.#r=s}async createEntity({entity:t,fileHash:r,sourceFile:s,revision:a=new Date().toISOString(),isRootEntity:i=!1,isDeleted:n=!1}){try{const{relations:c=[],...l}=t,y=T(JSON.stringify(l)),f=t.version??S;if(await this.#s(t.key,f,y,i,n))return{result:"skipped",entityKey:t.key};const{shouldBeCurrent:g,shouldBeDefaultVersion:m}=await this.#o(t.key,f),E=x({entity:{...t,revision:a,hash:y,isCurrent:g,isDefaultVersion:m,isDeleted:n,version:f},sourceFile:s,organizationId:this.#t,projectId:this.#r,source:"file",fileHash:r}),{key:A,source
|
|
1
|
+
import{and as d,desc as k,eq as u,isNull as v,or as R,sql as D}from"drizzle-orm";import{logger as h}from"../../../../../tools/notifiers/logger.js";import{promiseMapLimit as p}from"../../../../../utils/async/promise-map-limit.js";import{sha1 as T}from"../../../../../utils/crypto/sha1.js";import{isWebView as O}from"../../../../../../utils/env/is-web-view.js";import{VERSION_NOT_SPECIFIED as S}from"@redocly/theme/core/constants";import{createEntityDbRecord as x}from"../../mappers/create-entity-db-record.js";import{createEntityRelationDbRecordFromFileSchema as I}from"../../mappers/create-entity-relation-db-record-from-file-schema.js";import{entitiesTable as e}from"../../../../../providers/database/databases/catalog-sqlite/schemas/entities-table.js";import{entitiesRelationsTable as o}from"../../../../../providers/database/databases/catalog-sqlite/schemas/entities-relations-table.js";import{convertFilterToWhereCondition as K}from"../../../../../providers/database/pagination/filter.js";import{compareVersionsDescending as L}from"../../../utils/version-compare.js";const w=15;class Q{#e;#t;#r;constructor(t,r,s){this.#e=t,this.#t=r,this.#r=s}async createEntity({entity:t,fileHash:r,sourceFile:s,revision:a=new Date().toISOString(),isRootEntity:i=!1,isDeleted:n=!1}){try{const{relations:c=[],...l}=t,y=T(JSON.stringify(l)),f=t.version??S;if(await this.#s(t.key,f,y,i,n))return{result:"skipped",entityKey:t.key};const{shouldBeCurrent:g,shouldBeDefaultVersion:m}=await this.#o(t.key,f),E=x({entity:{...t,revision:a,hash:y,isCurrent:g,isDefaultVersion:m,isDeleted:n,version:f},sourceFile:s,organizationId:this.#t,projectId:this.#r,source:"file",fileHash:r}),{key:A,source:U,...F}=E;if(await this.#a({key:A,isCurrent:g,isDefaultVersion:m}),O())return await this.#c(E,c),{result:"created",entityKey:t.key,entityRevision:a,entityVersion:f};const N=this.#e.client.insert(e).values(E).onConflictDoUpdate({target:[e.key,e.source,e.revision,e.version],set:F}),B=c?.length&&c.length>0?this.#e.client.insert(o).values(c.map(V=>I({relation:V,sourceFile:s,fileHash:r,sourceKey:t.key,sourceVersion:f,sourceRevision:a??null,organizationId:this.#t,projectId:this.#r}))).onConflictDoNothing({target:[o.sourceKey,o.targetKey,o.sourceVersion,o.targetVersion,o.sourceRevision,o.targetRevision]}).run():Promise.resolve();return await p([N,B],w,async V=>V),{result:"created",entityKey:t.key,entityRevision:a,entityVersion:f}}catch(c){return h.error("Error adding entity",c),{result:"error",entityKey:t.key}}}async deleteEntity(t){try{return await this.#e.client.delete(e).where(u(e.key,t)),t}catch(r){return h.error("Error deleting entity",r),null}}async deleteEntities(t){try{const r=K(t);if(!r)return!1;const s=await this.#e.client.delete(e).where(r).returning({key:e.key,source:e.source,isCurrent:e.isCurrent,isDefaultVersion:e.isDefaultVersion,version:e.version});if(s.length===0)return!0;const a=s.reduce((i,n)=>((n.isCurrent||n.isDefaultVersion)&&i.add(n.key),i),new Set);if(a.size===0)return!0;await p(Array.from(a),w,async i=>this.#u(i));for(const i of s)await this.#e.client.delete(o).where(R(d(u(o.sourceKey,i.key),...i.version?[u(o.sourceVersion,i.version)]:[v(o.sourceVersion)]),d(u(o.targetKey,i.key),...i.version?[u(o.targetVersion,i.version)]:[v(o.targetVersion)])));return!0}catch(r){return h.error("Error deleting entities",r),!1}}async deleteEntityRelation(t){try{return await this.#e.client.delete(o).where(u(o.id,t)),t}catch{return null}}async softDeleteEntities(t,r,s){try{const a=t.map(n=>{const c={type:n.type,key:n.key,title:n.title,summary:n.summary??void 0,tags:n.tags??void 0,metadata:n.metadata??void 0,git:n.git??void 0,contact:n.contact??void 0,links:n.links??void 0,version:n.version??void 0};return this.createEntity({entity:c,revision:r,sourceFile:n.sourceFile??"",fileHash:s,isDeleted:!0})});return await p(a,w,async n=>n)}catch(a){return h.error("Error soft deleting entities",a),[]}}async deleteEntityRelations(t){try{const r=K(t);return r?(await this.#e.client.delete(o).where(r),!0):!1}catch(r){return h.error("Error deleting entity relations",r),!1}}async upsertEntityRelation(t){if(!t)return null;try{const{sourceKey:r,targetKey:s,sourceVersion:a,targetVersion:i,sourceRevision:n,targetRevision:c,...l}=t,y=await this.#e.client.insert(o).values(t).onConflictDoUpdate({target:[o.sourceKey,o.targetKey,o.sourceVersion,o.targetVersion,o.sourceRevision,o.targetRevision],set:l}).returning();return y?.length?y[0]:null}catch(r){return h.error("Error creating entity relation",r),null}}async#s(t,r,s,a,i){if(a||i)return!1;const n=await this.#e.client.select({hash:e.hash,isDeleted:e.isDeleted}).from(e).where(d(u(e.key,t),u(e.source,"file"),r?u(e.version,r):v(e.version))).orderBy(k(e.revision)).limit(1).run();if(n.rows.length===0)return!1;const c=n.rows[0];return c.isDeleted?!1:c.hash===s}async#i(t){return(await this.#e.client.select({version:e.version,isDefaultVersion:e.isDefaultVersion,revision:e.revision,createdAt:e.createdAt}).from(e).where(d(u(e.key,t),u(e.source,"file"))).orderBy(k(e.createdAt)).run()).rows.map(s=>({version:s.version,isDefaultVersion:!!s.isDefaultVersion,revision:s.revision,createdAt:new Date(s.createdAt)}))}#n(t){const r=t.find(i=>i.isDefaultVersion);if(r)return r;const{versionedEntities:s,unversionedEntities:a}=t.reduce((i,n)=>(n.version!==null&&n.version!==S?i.versionedEntities.push(n):i.unversionedEntities.push(n),i),{versionedEntities:[],unversionedEntities:[]});if(s.length>0){s.sort((c,l)=>L(c.version,l.version));const i=s[0].version,n=s.filter(c=>c.version===i);return n.sort((c,l)=>l.createdAt.getTime()-c.createdAt.getTime()),n[0]}return a.length>0?(a.sort((i,n)=>n.createdAt.getTime()-i.createdAt.getTime()),a[0]):null}async#o(t,r){const s=await this.#e.client.select({currentDefaultVersion:D`max(case when ${e.isDefaultVersion} = 1 then ${e.version} else null end)`,versionMatchCount:D`count(case when ${e.version} = ${r} then 1 else null end)`}).from(e).where(d(u(e.key,t),u(e.source,"file"))).get(),a=s?.currentDefaultVersion,i=(s?.versionMatchCount??0)>0;return r&&r===a?{shouldBeCurrent:!0,shouldBeDefaultVersion:!1}:{shouldBeCurrent:!i,shouldBeDefaultVersion:!i}}async#a({key:t,isCurrent:r,isDefaultVersion:s}){if(!r&&!s)return;const a={},i=[];r&&(a.isCurrent=!1,i.push(u(e.isCurrent,!0))),s&&(a.isDefaultVersion=!1,i.push(u(e.isDefaultVersion,!0))),await this.#e.client.update(e).set(a).where(d(u(e.key,t),u(e.source,"file"),R(...i))).run()}#u=async t=>{const r=await this.#i(t),s=this.#n(r);if(!s){h.warn("No latest version found for key",t);return}await this.#e.client.update(e).set({isDefaultVersion:!0,isCurrent:!0}).where(d(u(e.key,t),u(e.revision,s.revision),s.version?u(e.version,s.version):v(e.version))).run()};async#c(t,r){const{key:s,source:a,version:i,isDefaultVersion:n,...c}=t,f=(await this.#e.client.select({id:e.id}).from(e).where(d(u(e.key,s),u(e.source,a??"file"),i?u(e.version,i):v(e.version))).limit(1).run()).rows.length>0?this.#e.client.update(e).set(c).where(d(u(e.key,s),u(e.source,a??"file"),i?u(e.version,i):v(e.version))).run():this.#e.client.insert(e).values(t).onConflictDoUpdate({target:[e.key,e.source,e.revision,e.version],set:c}).run(),C=r?.map(g=>{const m=I({relation:g,sourceFile:t.sourceFile??"",fileHash:t.fileHash??"",sourceKey:t.key,sourceVersion:t.version??null,sourceRevision:t.revision??null,organizationId:this.#t,projectId:this.#r});return this.#e.client.insert(o).values(m).onConflictDoUpdate({target:[o.sourceKey,o.targetKey,o.sourceVersion,o.targetVersion,o.sourceRevision,o.targetRevision],set:m}).run()})??[];await p([f,...C],w,async g=>g)}async updateEntityScorecardsStatus(t,r){try{return(await this.#e.client.update(e).set({scorecardsStatus:r}).where(u(e.id,t)).returning()).length>0}catch(s){return h.error("Error updating entity scorecards status",s),!1}}async updateEntityScorecardsStatusIfCalculating(t,r){try{return(await this.#e.client.update(e).set({scorecardsStatus:r}).where(D`${e.id} = ${t} AND ${e.scorecardsStatus} = 'CALCULATING'`).returning()).length>0}catch(s){return h.error("Error updating entity scorecards status if calculating",s),!1}}}export{Q as CatalogEntitiesLocalWriteRepository};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { EntityRelationDtoSchema } from '../schemas/dto-schemas.js';
|
|
2
|
+
/**
|
|
3
|
+
* Normalized relation data structure.
|
|
4
|
+
* Derived from EntityRelationDtoSchema with required (but nullable) version/revision fields.
|
|
5
|
+
*/
|
|
6
|
+
export type NormalizedRelation = Pick<EntityRelationDtoSchema, 'type' | 'sourceKey' | 'targetKey'> & {
|
|
7
|
+
/**
|
|
8
|
+
* Source entity version (may be swapped from original)
|
|
9
|
+
*/
|
|
10
|
+
sourceVersion: string | null;
|
|
11
|
+
/**
|
|
12
|
+
* Target entity version (may be swapped from original)
|
|
13
|
+
*/
|
|
14
|
+
targetVersion: string | null;
|
|
15
|
+
/**
|
|
16
|
+
* Source entity revision (may be swapped from original)
|
|
17
|
+
*/
|
|
18
|
+
sourceRevision: string | null;
|
|
19
|
+
/**
|
|
20
|
+
* Target entity revision (may be swapped from original)
|
|
21
|
+
*/
|
|
22
|
+
targetRevision: string | null;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Input relation data structure.
|
|
26
|
+
* Core relation fields from EntityRelationDtoSchema.
|
|
27
|
+
*/
|
|
28
|
+
export type RelationInput = Pick<EntityRelationDtoSchema, 'type' | 'sourceKey' | 'targetKey' | 'sourceVersion' | 'targetVersion' | 'sourceRevision' | 'targetRevision'>;
|
|
29
|
+
/**
|
|
30
|
+
* Normalizes a relation to its canonical form.
|
|
31
|
+
*
|
|
32
|
+
* This function:
|
|
33
|
+
* 1. Looks up the canonical relation type in the normalization map
|
|
34
|
+
* 2. If the relation type differs from its canonical form, swaps source and target (including versions and revisions)
|
|
35
|
+
* 3. Uses the canonical relation type
|
|
36
|
+
* 4. For unknown relation types, passes through without modification
|
|
37
|
+
*
|
|
38
|
+
* @param relation - The relation to normalize
|
|
39
|
+
* @returns Normalized relation with canonical type and potentially swapped entities
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* // Normalize ownedBy -> owns with swapped entities
|
|
43
|
+
* normalizeRelation({
|
|
44
|
+
* type: 'ownedBy',
|
|
45
|
+
* sourceKey: 'api-1',
|
|
46
|
+
* targetKey: 'team-1',
|
|
47
|
+
* sourceVersion: 'v1',
|
|
48
|
+
* targetVersion: null,
|
|
49
|
+
* sourceRevision: null,
|
|
50
|
+
* targetRevision: null,
|
|
51
|
+
* })
|
|
52
|
+
* // Returns:
|
|
53
|
+
* // {
|
|
54
|
+
* // type: 'owns',
|
|
55
|
+
* // sourceKey: 'team-1', // swapped
|
|
56
|
+
* // targetKey: 'api-1', // swapped
|
|
57
|
+
* // sourceVersion: null, // swapped
|
|
58
|
+
* // targetVersion: 'v1', // swapped
|
|
59
|
+
* // sourceRevision: null,
|
|
60
|
+
* // targetRevision: null,
|
|
61
|
+
* // }
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* // Canonical type passes through unchanged
|
|
65
|
+
* normalizeRelation({
|
|
66
|
+
* type: 'owns',
|
|
67
|
+
* sourceKey: 'team-1',
|
|
68
|
+
* targetKey: 'api-1',
|
|
69
|
+
* sourceVersion: null,
|
|
70
|
+
* targetVersion: 'v1',
|
|
71
|
+
* sourceRevision: null,
|
|
72
|
+
* targetRevision: null,
|
|
73
|
+
* })
|
|
74
|
+
* // Returns: same as input
|
|
75
|
+
*/
|
|
76
|
+
export declare function normalizeRelation(relation: RelationInput): NormalizedRelation;
|
|
77
|
+
//# sourceMappingURL=normalize-relation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{RELATION_NORMALIZATION_MAP as r}from"../database/constants/relation-normalization.js";function o(e){const s=r[e.type];return s?e.type===s?{type:s,sourceKey:e.sourceKey,targetKey:e.targetKey,sourceVersion:e.sourceVersion??null,targetVersion:e.targetVersion??null,sourceRevision:e.sourceRevision??null,targetRevision:e.targetRevision??null}:{type:s,sourceKey:e.targetKey,targetKey:e.sourceKey,sourceVersion:e.targetVersion??null,targetVersion:e.sourceVersion??null,sourceRevision:e.targetRevision??null,targetRevision:e.sourceRevision??null}:{type:e.type,sourceKey:e.sourceKey,targetKey:e.targetKey,sourceVersion:e.sourceVersion??null,targetVersion:e.targetVersion??null,sourceRevision:e.sourceRevision??null,targetRevision:e.targetRevision??null}}export{o as normalizeRelation};
|
|
@@ -1,9 +1,17 @@
|
|
|
1
|
-
import{getLineColLocation as
|
|
1
|
+
import{getLineColLocation as u,isAbsoluteUrl as m}from"@redocly/openapi-core";import{getCodeframe as g}from"@redocly/openapi-core";import l from"node:path";import{gray as i}from"../../tools/notifiers/helpers/colors.js";function d(a,s){return`Config validation error:
|
|
2
2
|
|
|
3
|
-
`+
|
|
3
|
+
`+c(a);function c(o){const t=o.location[0];if(!t)return o.message;if(!t.source){const n=t.pointer??"";return`${o.message}${n?` at ${n}`:""}`}const e=t.source?.absoluteRef,r=e&&m(e)?e:e?l.relative(s,e):"config";try{if(typeof t.source.getAst!="function"){const $=t.pointer?i(`at ${t.pointer}`):"";return`${r} ${$}
|
|
4
4
|
|
|
5
|
-
${
|
|
5
|
+
${o.message}
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
`}const n=u(t),f=t.pointer?i(`at ${t.pointer}`):"";return`${`${r}:${n.start.line}:${n.start.col}`} ${f}
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
${o.message}
|
|
10
|
+
|
|
11
|
+
`+g(n,!0)+`
|
|
12
|
+
|
|
13
|
+
`}catch{const n=t.pointer?i(`at ${t.pointer}`):"";return`${r} ${n}
|
|
14
|
+
|
|
15
|
+
${o.message}
|
|
16
|
+
|
|
17
|
+
`}}}export{d as formatConfigProblem};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import w from"path";import{lintConfig as R,loadConfig as C,createConfigTypes as T}from"@redocly/openapi-core";import{deepMerge as U}from"../../../../../utils/object/deep-merge.js";import{readEnvVariable as A}from"../../../../utils/envs/read-env-variable.js";import{logger as l}from"../../../../tools/notifiers/logger.js";import{safeParsePartial as L}from"../../safe-parse.js";import{formatConfigProblem as $}from"../../format-error.js";import{ExternalResolver as q}from"../../../../fs/utils/external-ref-resolver.js";import{resolveMutuallyExclusiveProps as x}from"../../resolve-mutual-exclusion.js";function f(o,e,t,r){e in o&&o[e]&&typeof o[e]=="string"&&(/^https?:\/\/.*/.test(o[e])||r(new Error(`Invalid ${t} URL: "${o[e]}". ${t} must start with "http://" or "https://".`)))}function j(o,e){if("access"in o&&o.access&&typeof o.access=="object"){const t=o.access;f(t,"logoutReturnUrl","access.logoutReturnUrl",e),f(t,"residency","access.residency",e);const r=["requiresLogin","logoutReturnUrl","residency","sso","rbac"];for(const n of r)n in t&&t[n]!==void 0&&n in o&&o[n]!==void 0&&e(new Error(`Property '${n}' is defined both at root level and in 'access' object. Please use 'access.${n}' to define this configuration.`))}}async function z(o,e,t,r){const m=e.getFileInfo(o)?.realRelativePath||o;async function d(){const c=new q(e),u=w.join(e.cwd,m),i=await C({configPath:u,externalRefResolver:c}),y=await r(i.resolvedConfig);if(y===void 0)return i.resolvedConfig;const v=[...i.document?.source?await R({config:i,externalConfigTypes:T(y,i)}):[],...i.document?.source?x(i.resolvedConfig,i.document?.source):[]];if(v.length>0)for(const b of v)t(new Error($(b,e.cwd)));return i.resolvedConfig}const a=await e.exists(o)?await d():{},P=E(a),g=await r(a);f(a,"residency","Residency",t),f(a,"logoutReturnUrl","Logout return URL",t),j(a,t);let p=g?L(g,a):a;const{env:h}=p;if(h){const c=A("REDOCLY_ENV")||"development",u=h[c]||{};p=U(p,u)}const s=M(p,P);if(s.imports&&s.imports.length>0){l.warn("The 'imports' property is deprecated. Please use 'plugins' property instead.");const c=new Set([...s.plugins||[],...s.imports.map(u=>w.posix.join(u,"plugin.js"))]);s.plugins=Array.from(c),delete s.imports}if(s.catalog&&(l.warn("The 'catalog' property is deprecated. Please use 'catalogClassic' property instead."),s.catalogClassic={...s.catalog},delete s.catalog),s.scorecard&&(l.warn("The 'scorecard' property is deprecated. Please use 'scorecardClassic' property instead."),s.scorecardClassic=s.scorecard,delete s.scorecard),s.search?.ai){l.warn("The 'search.ai' property is deprecated. Please use 'aiAssistant' property instead.");const c={...s.search?.ai,...s.aiAssistant,suggestions:s.aiAssistant?.suggestions?.length?s.aiAssistant.suggestions:s.search?.ai.suggestions||[]};s.aiAssistant=c,delete s.search.ai}return k(s)}function k(o){const e="access"in o?o.access:void 0;if(!e||typeof e!="object")return o;const t={...o},r=[{name:"requiresLogin",type:"boolean"},{name:"logoutReturnUrl",type:"string"},{name:"residency",type:"string"},{name:"sso",type:"string | string[]"},{name:"rbac",type:"object"}],n=[];for(const m of r){const d=m.name;d in t&&t[d]!==void 0&&!(d in e)&&n.push(d)}return n.length>0&&l.warn(`The following properties at root level are deprecated: ${n.join(", ")}. Please move them to the 'access' object.`),"requiresLogin"in e&&e.requiresLogin!==void 0&&(t.requiresLogin=e.requiresLogin),"logoutReturnUrl"in e&&e.logoutReturnUrl!==void 0&&(t.logoutReturnUrl=e.logoutReturnUrl),"residency"in e&&e.residency!==void 0&&(t.residency=e.residency),"sso"in e&&e.sso!==void 0&&(t.sso=e.sso),"rbac"in e&&e.rbac!==void 0&&(t.rbac=e.rbac),delete t.access,t}function E(o){if(!o.theme)return[];l.warn("The 'theme' property in redocly.yaml is deprecated. Please move all of the properties from 'theme' to the root of the config.");const e=[];for(const t of Object.keys(o.theme))o[t]==null?e.push(t):l.warn(`Detected both '${t}' and 'theme.${t}' properties in redocly.yaml. The 'theme.${t}' property will be ignored and needs to be removed or merged into the '${t}'.`);return e}function M(o,e){if(!o.theme||e.length===0)return o;const t={...o};for(const r of e)t[r]=o.theme[r];return delete t.theme,t}export{z as readAndValidateConfig};
|
|
@@ -7,6 +7,7 @@ export declare function applyFilter<T extends SQLiteSelect | SQLiteDelete>(sqlBu
|
|
|
7
7
|
};
|
|
8
8
|
export declare function convertFilterToWhereCondition(filter?: Filter): SQL | undefined;
|
|
9
9
|
export declare function getFirstFilterFieldValue(filter: Filter | undefined, fieldName: string): FilterClause['value'] | undefined;
|
|
10
|
+
export declare function getAllFilterFieldValues(filter: Filter | undefined, fieldName: string): string[];
|
|
10
11
|
export declare function parseFilterQuery(query?: string, availableFields?: string[], fieldsTransformations?: Record<string, string>): Filter | undefined;
|
|
11
12
|
export declare function excludeFieldsFromFilter(filter: Filter | undefined, fieldsToExclude: string[]): Filter | undefined;
|
|
12
13
|
//# sourceMappingURL=filter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{and as O,between as R,eq as T,inArray as C,isNotNull as
|
|
1
|
+
import{and as O,between as R,eq as T,inArray as C,isNotNull as v,isNull as L,like as I,ne as y,notBetween as k,notInArray as W,notLike as b,or as d,sql as u}from"drizzle-orm";import{isFilterCondition as m}from"./types.js";import{transformToSnakeCase as q}from"./utils/transform-to-snake-case.js";import{isFieldAllowed as D}from"./utils/field-pattern-matcher.js";import{OPERATORS as h}from"./constants.js";const U=["domains","owners"],P=["tags"];function z(t,e){const o=J(e);return o?{sqlBuilder:t,whereCondition:o}:{sqlBuilder:t,whereCondition:null}}function J(t){if(t)return m(t)?A(t):j(t)}const A=t=>{const{op:e,conditions:o}=t,n=[];for(const s of o){if(m(s)){const i=A(s);i&&n.push(i);continue}const r=j(s);r&&n.push(r)}return e===h.AND?O(...n):d(...n)},H=(t,e)=>e.length>1?`$.${e.slice(1).join(".")}.${t}`:`$.${t}`,E=({operator:t,value:e,field:o,parentFields:n,negation:s})=>{const r=u.identifier(n[0]),i=H(o,n);switch(t){case"equal":return s?u`(json_extract(${r}, ${i}) != ${e} OR json_extract(${r}, ${i}) IS NULL)`:u`json_extract(${r}, ${i}) == ${e}`;case"equalNull":return s?u`json_extract(${r}, ${i}) IS NOT NULL`:u`json_extract(${r}, ${i}) IS NULL`;case"contains":return s?u`(json_extract(${r}, ${i}) NOT LIKE ${e} OR json_extract(${r}, ${i}) IS NULL)`:u`json_extract(${r}, ${i}) LIKE ${e}`;case"in":if(Array.isArray(e)){const l=e.map(c=>u`json_extract(${r}, ${i}) == ${c}`);return s?u`(NOT (${d(...l)}) OR json_extract(${r}, ${i}) IS NULL)`:d(...l)}break;case"between":if(Array.isArray(e)&&e.length===2)return s?u`(NOT (json_extract(${r}, ${i}) BETWEEN ${e[0]} AND ${e[1]}) OR json_extract(${r}, ${i}) IS NULL)`:u`json_extract(${r}, ${i}) BETWEEN ${e[0]} AND ${e[1]}`;break;default:throw new Error(`Unsupported operator: ${t} for json path`)}};function G(t,e){if(!t)return;const o=s=>{if(m(s)){for(const r of s.conditions){const i=o(r);if(i!==void 0)return i}return}if(s.field===e)return s.value},n=o(t);return typeof n=="string"?n.trim()===""?null:n.trim():n??null}function Z(t,e){if(!t)return[];const o=[],n=s=>{if(m(s)){for(const r of s.conditions)n(r);return}if(s.field===e){if(Array.isArray(s.value))o.push(...s.value.map(r=>String(r).trim()).filter(r=>r!==""));else if(s.value!==null&&s.value!==void 0){const r=String(s.value).trim();r!==""&&o.push(r)}}};return n(t),o}const M=t=>{const{field:e,operator:o,value:n,modifier:s,parentFields:r}=t,i=s==="not";if(!r||r.length<1)throw new Error("convertNestedFilterClauseToSql called without parentFields");switch(o){case"equal":return E(n===null?{operator:"equalNull",value:n,field:e,parentFields:r,negation:i}:{operator:o,value:n,field:e,parentFields:r,negation:i});case"contains":if(typeof n=="string"){const l=n.startsWith("%")||n.endsWith("%")?n:`%${n}%`;return E({operator:o,value:l,field:e,parentFields:r,negation:i})}break;case"in":if(Array.isArray(n))return E({operator:o,value:n,field:e,parentFields:r,negation:i});break;case"between":if(Array.isArray(n)&&n.length===2)return E({operator:o,value:n,field:e,parentFields:r,negation:i});break;default:throw new Error(`Unsupported filter operator: ${o}`)}},j=t=>{const{field:e,operator:o,value:n,modifier:s,parentFields:r}=t;if(r&&r.length>=1)return M(t);const i=s==="not",l=u.identifier(e),c=P.includes(e),f=U.includes(e);switch(o){case"equal":return n===null?i?v(l):L(l):f?i?u`NOT EXISTS (SELECT 1 FROM json_each(${l}) WHERE json_extract(json_each.value, '$.key') = ${n})`:u`EXISTS (SELECT 1 FROM json_each(${l}) WHERE json_extract(json_each.value, '$.key') = ${n})`:c?i?u`NOT EXISTS (SELECT 1 FROM json_each(${l}) WHERE json_each.value = ${n})`:u`EXISTS (SELECT 1 FROM json_each(${l}) WHERE json_each.value = ${n})`:i?y(l,n):T(l,n);case"contains":if(typeof n=="string"){const a=n.startsWith("%")||n.endsWith("%")?n:`%${n}%`;return i?b(l,a):I(l,a)}break;case"in":if(Array.isArray(n)){if(f){const a=n.map(p=>u`EXISTS (SELECT 1 FROM json_each(${l}) WHERE json_extract(json_each.value, '$.key') = ${p})`);return i?u`NOT (${d(...a)})`:d(...a)}else if(c){const a=n.map(p=>u`EXISTS (SELECT 1 FROM json_each(${l}) WHERE json_each.value = ${p})`);return i?u`NOT (${d(...a)})`:d(...a)}return i?W(l,n):C(l,n)}break;case"between":if(Array.isArray(n)&&n.length===2)return i?k(l,n[0],n[1]):R(l,n[0],n[1]);break;default:throw new Error(`Unsupported filter operator: ${o}`)}},x=["'",'"'];function ee(t="",e=[],o={}){if(!t||!t.trim())return;const n=g(t);return w(n,e,o)}function S(t){const e=X(t);if(e.operator===":"){const o=N(e.value,",");if(o.length>1)return{operator:"in",field:e.field,parentFields:e.parentFields,value:o.map(F),modifier:e.modifier};const n=N(e.value,"..");return n.length===2?{operator:"between",field:e.field,parentFields:e.parentFields,value:[F(n[0]),F(n[1])],modifier:e.modifier}:{operator:"equal",field:e.field,parentFields:e.parentFields,value:e.value==="null"?null:F(e.value),modifier:e.modifier}}else if(e.operator==="~")return{operator:"contains",field:e.field,parentFields:e.parentFields,value:F(e.value),modifier:e.modifier};throw new Error(`Unsupported filter clause: ${t}`)}function X(t){const e="^(?<modifier>^-?)",o="(?<field>[a-z][\\w.]*)",n="(?<operator>:|~)",s="(?<value>.+)",r=new RegExp(`^${e}${o}${n}${s}$`,"i"),i=t.match(r);if(!i||!i.groups)throw new Error(`Invalid filter clause: ${t}`);const{operator:l,value:c,modifier:f}=i.groups,a=i.groups.field.split(".");return{field:a.pop(),parentFields:a,operator:l,value:c,modifier:f==="-"?"not":void 0}}function N(t,e){const o=[];let n="",s=!1,r=0;for(let i=0;i<t.length;i++){const l=t[i-1],c=t[i];if(r>0){r--;continue}l!=="\\"&&x.includes(c)&&(s=!s),!s&&t.slice(i,i+e.length)===e?(o.push(n),n="",r=e.length-1):n+=c}return n&&o.push(n),o}function F(t){for(const e of x)if(t.startsWith(e)&&t.endsWith(e))return t.slice(1,-1);return t}function g(t){let e="",o=0,n=!1,s="";const r=[];for(let l=0;l<t.length;l++){const c=t[l],f=t[l+1];if(['"',"'"].includes(c)&&(l===0||t[l-1]!=="\\")){if(!n){n=!0,s=c,e+=c;continue}if(c===s){n=!1,s="",e+=c;continue}}if(n){e+=c;continue}if(o===0){const a=f==="A"&&t.slice(l+1,l+5)===`${h.AND} `;if(c===" "&&a){const $=e.trim();$&&(r.push(S($)),e="");const _=g(t.slice(l+5));return{op:h.AND,conditions:[...r,_]}}const p=f==="O"&&t.slice(l+1,l+4)===`${h.OR} `;if(c===" "&&p){const $=e.trim();$&&(r.push(S($)),e="");const _=g(t.slice(l+4));return{op:h.OR,conditions:[...r,_]}}}if(c==="("){const a=e.trim();o===0&&a&&(r.push(S(a)),e=""),o++,e+=c;continue}if(c===")"){if(o--,e+=c,o===0){const a=e.slice(1,-1);r.push(g(a)),e=""}continue}e+=c}const i=e.trim();return i&&r.push(S(i)),r.length===1?r[0]:{op:h.AND,conditions:r}}function w(t,e,o){if("field"in t){const n=t.parentFields?.length?`${t.parentFields.join(".")}.${t.field}`:t.field;if(!D(n,e))throw new Error(`Invalid filter field: ${n}`);const s=t.parentFields?.length?t.field:o[t.field]||q(t.field);return{...t,field:s}}else return{...t,conditions:t.conditions.map(n=>w(n,e,o))}}function ne(t,e){if(!t)return;const o=r=>e.includes(r),n=r=>{if(m(r)){const i=r.conditions.map(n).filter(l=>l!==null);return i.length===0?null:i.length===1?i[0]:{...r,conditions:i}}else return o(r.field)?null:r};return n(t)||void 0}export{z as applyFilter,J as convertFilterToWhereCondition,ne as excludeFieldsFromFilter,Z as getAllFilterFieldValues,G as getFirstFilterFieldValue,ee as parseFilterQuery};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import"../node-crypto-polyfill.js";import{DOMParser as U}from"@xmldom/xmldom";import{SignedXml as B}from"xml-crypto";import F from"xpath";import{deflateSync as J,inflateSync as q}from"fflate";import{createHash as H}from"crypto";import{ulid as W}from"ulid";import{AuthProviderType as
|
|
1
|
+
import"../node-crypto-polyfill.js";import{DOMParser as U}from"@xmldom/xmldom";import{SignedXml as B}from"xml-crypto";import F from"xpath";import{deflateSync as J,inflateSync as q}from"fflate";import{createHash as H}from"crypto";import{ulid as W}from"ulid";import{AuthProviderType as u,DEFAULT_TEAM_CLAIM_NAME as K}from"@redocly/config";import{AUTH_URL as Q,JWT_SECRET_KEY as _}from"../constants/common.js";import{getPathPrefix as X,withPathPrefix as Y}from"@redocly/theme/core/utils";import{DEFAULT_AUTHENTICATED_TEAM as G,REQUIRED_OIDC_SCOPES as D,ServerRoutes as P}from"../../constants/common.js";import{appendQueryParams as Z}from"../../utils/url/append-query-params.js";import{logger as ee}from"../tools/notifiers/logger.js";import{randomString as te}from"../utils/crypto/random-string.js";import{randomUUID as R}from"../utils/crypto/random-uuid.js";import{AlgorithmTypes as I,JwtTokenExpired as ne}from"./jwt/types.js";import*as p from"./jwt/jwt.js";import{parseTeamClaimToArray as re}from"../utils/index.js";import{arrayBufferToBase64 as ae,decodeBase64 as v,encodeBase64URL as oe,urlSafeBase64 as j}from"./jwt/encode.js";import{formatSamlCertificate as se}from"./utils/format-saml-certificate.js";function N(e){return e?.type===u.OIDC}function ie(e){return e?.type===u.SAML2}async function ze(e,t){if(N(t))return ce(e,t);if(ie(t))return ue(e,t)}async function ce(e,t){const n=await V(e,t),r=new Set((t.scopes||[]).concat(D)),a=t.authorizationRequestCustomParams||{};return{type:u.OIDC,idpId:e,name:"OAuth provider",authorizationEndpoint:n.authorization_endpoint,clientId:t.clientId,responseType:"code",scope:Array.from(r).join(" "),extraParams:a,pkce:t.pkce}}function ue(e,t){return{type:u.SAML2,idpId:e,name:"SAML2 provider",ssoUrl:t.ssoUrl,issuerId:t.issuerId,entityId:t.entityId||t.issuerId}}async function Be(e,t,n,r,a={}){const o=new Set((r.scopes||[]).concat(D));return await fetch(e,{method:"POST",body:new URLSearchParams({client_id:r.clientId,scope:Array.from(o).join(" "),code:t,redirect_uri:E(n),grant_type:"authorization_code",...r.clientSecret?{client_secret:r.clientSecret}:{},...a}).toString(),headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json"}}).then(s=>s.json())}function de(e,{authorizationEndpoint:t,clientId:n,responseType:r,scope:a,extraParams:o,idpId:s,pkce:l},m,A,w){if(!t||!n||!r||!a)return{loginUrl:void 0};const c=new URL(t),f=w?.redirectUriOverride??`${e}${Y(P.OIDC_CALLBACK)}`,S={state:R(),idpId:s,redirectUri:f,redirectTo:m,branch:w?.branchOverride??me(e),inviteCode:A,source:w?.sourceOverride??"portal"},h={};if(l){const d=j(te(50)),g=j(H("sha256").update(d).digest("base64")),x="S256";c.searchParams.append("code_challenge",g),c.searchParams.append("code_challenge_method",x),h.code_verifier={value:d,options:{secure:!0,httpOnly:!0,expires:new Date(Date.now()+1e3*60*10),path:X()||"/"}}}c.searchParams.append("client_id",n),c.searchParams.append("scope",a),c.searchParams.append("response_type",r),c.searchParams.append("redirect_uri",E(f)),c.searchParams.append("state",oe(JSON.stringify(S)));for(const d in o)o[d]!==void 0&&c.searchParams.append(d,o[d]);return{loginUrl:c.toString(),cookies:h}}function Fe(e,t,n,r){const a=new URL(e);return a.searchParams.append("post_logout_redirect_uri",t),r&&a.searchParams.append("state",r),a.searchParams.append("id_token_hint",n),a.toString()}async function Je(e){const t=Math.floor(Date.now()/1e3),n=t+(e.ttlSec??600);return p.sign({type:"mcp_auth_code",client_id:e.clientId,redirect_uri:e.redirectUri,id_token:e.idToken,iat:t,exp:n},_)}async function qe(e){const{header:t,payload:n}=p.decode(e),a=Object.values(I).includes(t.alg)?t.alg:I.HS256;if(await p.verify(e,_,a),n.type!=="mcp_auth_code")throw new Error("Invalid authorization code type");if(!n.client_id||!n.redirect_uri)throw new Error("Authorization code missing required claims");if(typeof n.exp=="number"&&Date.now()>=n.exp*1e3)throw new Error("Authorization code expired");return n}function He(e){const t=e||W(),n=t.startsWith("mcp_")?t:`mcp_${t}`;return{id:n,object:"mcp_session",uri:`urn:redocly:realm:mcp:session:${n}`}}function E(e){return e.match(/^https:\/\/preview-[^\.]+--/)?"https://previewauth--"+e.split("--")[1]:e.match(/^(https:\/\/[^\.]+)--[^\.]+\.preview\./)?e.replace(/^(https:\/\/[^\.]+?)--[^\.]+\.preview\./,"$1.previewauth."):e}function me(e){return e.match(/^(https:\/\/[^\.]+)--([^\.]+)\.preview\./)?.[2]||void 0}function le(e){return e.type===u.OIDC}function pe(e){return e.type===u.SAML2}function We(e,t,n,r){return le(e)?de(t,e,n,r):pe(e)?fe(t,e,n,r):{}}function fe(e,t,n,r){const o=`<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
|
|
2
2
|
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
|
|
3
3
|
Version="2.0"
|
|
4
4
|
ID="_${R()}"
|
|
@@ -9,4 +9,4 @@ import"../node-crypto-polyfill.js";import{DOMParser as U}from"@xmldom/xmldom";im
|
|
|
9
9
|
<samlp:NameIDPolicy
|
|
10
10
|
AllowCreate="true"
|
|
11
11
|
Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"/>
|
|
12
|
-
</samlp:AuthnRequest>`,s=he(
|
|
12
|
+
</samlp:AuthnRequest>`,s=he(o);return{loginUrl:Z(t.ssoUrl,{SAMLRequest:s,RelayState:JSON.stringify({idpId:t.idpId,redirectTo:n,inviteCode:r,source:"portal"})})}}function he(e){return ae(J(new TextEncoder().encode(e)).buffer)}function Ke(e){const t=v(e);if(t.startsWith("<samlp:Response")||t.indexOf("<saml2p:Response")>-1)return t;const n=q(new Uint8Array(atob(e).split("").map(r=>r.charCodeAt(0))));return new TextDecoder().decode(n)}function Qe(e){try{return JSON.parse(v(e||""))}catch{throw new Error("Invalid OAuth2 state")}}function Xe(e){const t=new U().parseFromString(e,"application/xml"),r=i(t,"//*[local-name(.)='StatusCode']/@Value")[0]?.nodeValue?.endsWith("Success")||!1,o=i(t,"//*[local-name(.)='Response']/@Destination")[0]?.nodeValue||"",s=i(t,"//*[local-name(.)='Assertion']//*[local-name(.)='Issuer']/text()")[0],l=s&&s.nodeValue||void 0,m=i(t,"//*[local-name(.)='Audience']/text()")[0],A=m&&m.nodeValue||void 0,c=i(t,"//*[local-name(.)='Assertion']//*[local-name(.)='X509Certificate']/text()")[0]?.nodeValue||"",f=i(t,"//*[local-name(.)='Subject']//*[local-name(.)='NameID']/text()")[0],S=f&&f.nodeValue||"",h=i(t,"//*[local-name(.)='Subject']//*[local-name(.)='NameID']/@Format")[0],d=h&&h.nodeValue||"",g=i(t,"//*[local-name(.)='Conditions']/@NotOnOrAfter")[0],x=ye(g),k={},T=i(t,"//*[local-name(.)='AttributeStatement']//*[local-name(.)='Attribute']");if(T.length)for(const C of T){const O=i(C,"./@Name")[0];if(O.nodeValue){const b=i(C,"./*[local-name(.)='AttributeValue']/text()")[0];b?.nodeValue&&(k[O.nodeValue]=b.nodeValue)}}return{uid:S,success:r,expiresAt:x,issuerId:l,entityId:A,attrs:k,cert:c,nameFormat:d,destination:o}}function ye(e){const t=typeof e?.nodeValue=="string"&&L(Date.parse(e.nodeValue)),n=L(Date.now()),r=L(Date.now()+720*60*1e3);return t?t>n&&t<r?r:t:n}function L(e){return Math.floor(e/1e3)}const M={},y={jwks:{}};async function V(e,t){return M[e]||(M[e]=t.configurationUrl?await $(t.configurationUrl):t.configuration),M[e]}async function we(e){for(const t of Object.keys(e)){const n=e[t];if(!N(n))continue;const r=await V(t,n);if(r.jwks_uri){const a=await $(r.jwks_uri);for(const o of a.keys)y.jwks[o.kid]={...o,idpId:t}}}}async function $(e){return fetch(e,{headers:{Accept:"application/json"}}).then(t=>t.json())}async function Ye(e){return fetch(`${Q}/oidc/userinfo`,{headers:{Accept:"application/json",Authorization:`Bearer ${e}`}}).then(t=>t.status===200?t.json():void 0).catch(()=>{})}function Ge(e){if(!e.configurationUrl)return!1;const t=new URL(e.configurationUrl);return["localhost","127.0.0.1","blueharvest.cloud","bhstage.cloud","cloud.redocly.com","beta.redocly.com","cloud.eu.redocly.com","beta.eu.redocly.com","cba.au.redocly.com"].some(r=>Ae(t.hostname,r))}function Ae(e,t){return e===t||e.endsWith(`.${t}`)}async function Ze(e,t){const n=new U().parseFromString(e),r=i(n,"//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']")[0];if(!r)throw new Error("Cannot find Signature in the SAML response");const a=se(t),o=new B({publicCert:a});o.loadSignature(r);try{return o.checkSignature(e)}catch{return!1}}function et(e,t,n,r){t==="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"&&(e=n["http://schemas.microsoft.com/identity/claims/objectidentifier"]);let a;(t==="urn:oasis:names:tc:SAML:2.0:nameid-format:email"||t==="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress")&&(a=e),t==="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"&&e?.match(/.+@.+/)&&(a=e);const o=n["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"],s=o?.match(/.+@.+/);return a=a||n["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"]||(s?o:void 0),a=a?.toLowerCase(),{sub:e,given_name:n["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname"],family_name:n["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname"],name:n["http://schemas.microsoft.com/identity/claims/displayname"]||o,email:a,email_verified:!0,teams:r?re(n[r]):[]}}function z(e,t={}){return e.map(n=>t[n]||n)}async function tt(e,t){if(!t)return{};const n=t.authorization;if(!n)return{};try{const r=p.decode(n);if(r.header.alg===I.RS256){y.jwks[r.header.kid]===void 0&&await we(e);const m=y.jwks[r.header.kid];if(!m)return y.jwks[r.header.kid]=null,{};await p.verify(n,m,r.header.alg)}else await p.verify(n,_,r.header.alg);const a=r.payload.idpId||y.jwks[r.header.kid]?.idpId,o=e[a]||{},s=xe(o),l=ge(o);return{...r.payload,email:r.payload.email?.toLowerCase(),idpId:a,teams:Array.from(new Set([...z(r.payload.teams||[],l),..."defaultTeams"in o&&o.defaultTeams||[],...z("teamsClaimName"in o&&r.payload[s||""]||[],l),G])),name:Se(r.payload),isAuthenticated:!0,idpAccessToken:t.idp_access_token,federatedAccessToken:t.federated_access_token,federatedIdToken:t.federated_id_token,authCookie:n}}catch(r){r instanceof ne||ee.error("Malformed JWT token: %s",r.message)}return{}}function Se(e){return e.name||e.given_name||e.email}function ge(e){switch(e.type){case u.SAML2:return e.teamsAttributeMap;case u.OIDC:return e.teamsClaimMap;default:return}}function xe(e){switch(e.type){case u.SAML2:return e.teamsAttributeName;case u.OIDC:return e.teamsClaimName;default:return K}}function i(e,t){return F.select(t,e)||[]}export{We as buildLoginUrl,de as buildOidcLoginUrl,Fe as buildOidcLogoutUrl,fe as buildSAML2LoginUrl,Je as createMcpAuthorizationCode,He as createMcpSessionResource,Ke as decodeSamlResponse,he as encodeSAML2,et as extractUserClaims,ze as getAuthProviderLoginParams,ce as getOidcLoginParams,V as getOidcMetadata,Ye as getRedoclyTokenPayload,ue as getSaml2LoginParams,tt as getUserParamsFromCookies,Se as getUsernameFromPayload,N as isOidcProviderConfig,Ge as isRedoclySso,ie as isSaml2ProviderConfig,Be as oidcExchangeCodeForToken,Qe as parseOidcState,me as parsePreviewBranch,Xe as parseSamlResponse,E as rewritePreviewAuthRedirectUri,qe as verifyMcpAuthorizationCode,Ze as verifySAMLResponse};
|
|
@@ -4,6 +4,7 @@ export declare function authorizeHandler(ctx: Context): Promise<Response>;
|
|
|
4
4
|
export declare function redoclyLoginCallbackHandler(): Handler;
|
|
5
5
|
export declare function oidcCallbackHandler(store: Store): Handler;
|
|
6
6
|
export declare function logoutHandler(store: Store): (ctx: Context) => Promise<Response>;
|
|
7
|
+
export declare function postLogoutHandler(store: Store): (ctx: Context) => Promise<Response>;
|
|
7
8
|
export declare function inviteHandler(store: Store): (ctx: Context) => Promise<Response>;
|
|
8
9
|
export declare function idpLoginHandler(store: Store): (ctx: Context) => Promise<Response>;
|
|
9
10
|
export declare function samlCallbackHandler(store: Store): Handler;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{setCookie as L,deleteCookie as q}from"hono/cookie";import{AuthProviderType as G}from"@redocly/config";import{withPathPrefix as M,getPathPrefix as C}from"@redocly/theme/core/utils";import{compareURIs as X}from"../../../utils/url/compare-uris.js";import{ensureArray as U}from"../../../utils/array/ensure-array.js";import{ALTERNATIVE_AUD_CLAIM_NAME as E,JWT_SECRET_KEY as v,ORG_SLUG as W}from"../../constants/common.js";import{DEFAULT_COOKIE_EXPIRATION as F,ServerRoutes as D}from"../../../constants/common.js";import{sanitizeRedirectPathname as B}from"../../../utils/url/sanitize-redirect-pathname.js";import{telemetry as k}from"../../telemetry/index.js";import{getAuthProviderLoginParams as Y,isOidcProviderConfig as $,isSaml2ProviderConfig as Q,oidcExchangeCodeForToken as Z,buildLoginUrl as x,decodeSamlResponse as ee,extractUserClaims as re,parseSamlResponse as oe,parseOidcState as ne,verifySAMLResponse as te,getUsernameFromPayload as ie,buildOidcLogoutUrl as se,getOidcMetadata as z,getRedoclyTokenPayload as ae,isRedoclySso as de,rewritePreviewAuthRedirectUri as ce,parsePreviewBranch as N,buildOidcLoginUrl as le,createMcpSessionResource as I}from"../auth.js";import*as S from"../jwt/jwt.js";import{AlgorithmTypes as ue}from"../jwt/types.js";import{handleErrorPageRender as pe}from"../utils.js";import{encodeBase64URL as ge}from"../jwt/encode.js";async function Oe(s){if(process.env.NODE_ENV==="production")return s.newResponse(null,404,{});const{password:e,...o}=await s.req.json(),a=await S.sign({...o,name:o.username||o.email||"Unknown"},v);return L(s,"authorization",a,{path:C()||"/",httpOnly:!0,secure:!0,sameSite:"none"}),s.newResponse(null,200,{})}function Pe(){return async s=>{const e=s.get("logger"),o=encodeURIComponent(s.req.query("message")||"");e.error(`Login error: ${o}`);const a=`${D.LOGIN}/?error=${encodeURIComponent(o)}`;return s.newResponse(null,301,{Location:a})}}function j(s){if(!s||!s.includes(D.MCP_CALLBACK))return null;try{const e=s.split("/"),o=e[e.length-1];if(o){const a=Buffer.from(o,"base64url").toString("utf-8");return JSON.parse(a).mcpSessionId||null}}catch{}return null}function Se(s){return async e=>{const o=e.get("logger"),a=s.getConfig().ssoDirect,n=ne(e.req.query("state")),f=n.idpId,t=n.source==="mcp"||n.redirectTo&&typeof n.redirectTo=="string"&&n.redirectTo.includes(D.MCP_CALLBACK),c=t?j(typeof n.redirectTo=="string"?n.redirectTo:void 0):null,i=a?.[f];if(!$(i))return o.error("OIDC login error: missing OIDC provider config"),e.text("Forbidden",403);const d=await z(f,i);if(a&&!d.token_endpoint){const p="Invalid OIDC configuration: token_endpoint is required";return o.error(`OIDC login error: ${p}`),e.text(p,500)}try{const p=d.token_endpoint,l=e.req.query("code"),m=e.req.query("error");if(m)return t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:`OIDC error: ${m}`,error_details:e.req.query("error_description")||null}),pe(e,s,{slug:"/"},403,"403OIDC");if(!l){const h="Code is expected but not present";return o.error(`OIDC login error: ${h}`),t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:h,error_details:null}),new Response(`Forbidden: ${h}`,{status:403})}const y=e.req.header("x-forwarded-host"),g=e.req.header("x-forwarded-proto")||"https",A=t&&typeof n.redirectUri=="string"?n.redirectUri:new URL(M(D.OIDC_CALLBACK),y?`${g}://${y}`:e.req.url).toString(),_=e.get("cookies")?.code_verifier,u=await Z(p,l,A,i,{...i.tokenRequestCustomParams,..._?{code_verifier:_}:{}});if(u.error)return o.error(`Error from OIDC provider: "${u.error}"`),t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:`Token exchange error: ${u.error}`,error_details:u.error_description||null}),e.text(`Forbidden: ${u.error_description||u.error}`,403);if(!u?.id_token){const h="No id_token, please, add openid to scopes";return o.error(`OIDC login error: ${h}`),t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:h,error_details:null}),new Response(`Forbidden: ${h}`,{status:403})}const{payload:r,header:T}=S.decode(u.id_token),H=T.alg===ue.RS256;if(i.audience?.length&&![...U(r.aud||[]),...U(r[E]||[])].some(R=>i.audience?.includes(R))){const R="No valid audience found in id_token";return o.error(`OIDC login error: ${R}`),t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:R,error_details:null}),new Response(`Forbidden: ${R}`)}const b=H?u.id_token:await S.sign({...r,idpId:f},v);ie(r)||o.warn("To display your username, the required 'email' or 'full_profile' scope must be added to the identity provider configuration");const O=i?.tokenExpirationTime?Date.now()+i.tokenExpirationTime*1e3:r.exp*1e3||Date.now()+F*1e3;if(i.introspectEndpoint){const h=await fetch(i.introspectEndpoint,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({access_token:u.access_token})});if(h.ok){const P=(await h.json()).ext?.federatedIdentity;P&&(L(e,"federated_access_token",P.access_token||"",{path:C()||"/",httpOnly:!1,expires:new Date(O)}),L(e,"federated_id_token",P.id_token||"",{path:C()||"/",httpOnly:!1,expires:new Date(O)}))}else o.warn(`OIDC introspect error: ${h.statusText}`)}if(L(e,"authorization",b,{path:C()||"/",httpOnly:!0,expires:new Date(O)}),b!==u.id_token&&L(e,"idp_id_token",u.id_token||"",{path:C()||"/",httpOnly:!0,expires:new Date(O)}),L(e,"idp_access_token",u.access_token||"",{path:C()||"/",httpOnly:!0,expires:new Date(O)}),q(e,"code_verifier",{path:C()||"/"}),t&&n.redirectTo&&typeof n.redirectTo=="string"&&n.redirectTo.includes(D.MCP_CALLBACK)){const h=e.req.url.split("?")[0].replace(D.OIDC_CALLBACK,""),R=M(n.redirectTo),P=`${h}${R}`;return e.newResponse(null,302,{Location:P})}const K=typeof n.redirectTo=="string"?n.redirectTo:void 0;let J=B(new URL(K||"/",e.req.url).pathname);const V=e.newResponse(null,302,{Location:J});return o.updateContext({email:r.email,subject:r.sub}),o.info("OIDC login successful"),V}catch(p){const l=p instanceof Error?p.message:String(p),m=p instanceof Error?p.stack:String(p);if(o.error(`OIDC login error: ${l}`),t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:l,error_details:m}),p.error==="access_denied")return o.info("Access denied"),e.text("Forbidden",403)}const w="Something went wrong";return o.error(`OIDC login error: ${w}`),t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:w,error_details:null}),e.text(w,500)}}function ve(s){return async e=>{const o=e.get("logger"),n=e.get("auth").claims?.idpId,t=s.getConfig().ssoDirect?.[n];if(e.req.method==="POST")return $(t)||q(e,"authorization",{path:C()||"/"}),o.info("Logout successful"),e.newResponse(null,200,{});let c;if($(t)){const i=(await z(n,t)).end_session_endpoint;if(i){const d=new URL(e.req.url),w=e.req.header("x-forwarded-proto")||d.protocol.slice(0,-1)||"https",p=e.req.header("x-forwarded-host")||d.host,l=`${w}://${p}`,m=N(l),y=m?ge(JSON.stringify({branch:N(l)})):void 0,g=m?`${ce(l)}/_auth/logout`:l;c=se(i,g,e.get("cookies")?.idp_id_token||e.get("cookies")?.authorization||"",y)}}return o.info("Logout successful"),q(e,"authorization",{path:C()||"/"}),e.newResponse(null,302,{Location:c||M("/")})}}function $e(s){return async e=>{const o=e.get("logger"),a=e.req.param("code"),n=process.env.BH_API_URL,f=(t,c,i)=>t&&c?`${t} ${c.charAt(0)}`:i;try{if(!n)throw new Error("BH_API_URL is not set");const t=s.getConfig().ssoDirect;if(!t||!Object.keys(t).length)return o.warn("Invite no sso configured to handle"),e.redirect(M("/"));const c=await fetch(`${n}/user-invites/public/${a}`);if(!c.ok)return c.status===404?(o.warn(`Invite ${a} not found redirect to homepage`),e.redirect(M("/"))):(o.error("Invite error",await c.text()),e.redirect(M("/")));const i=await c.json(),d=new URL(M("/invite"),e.req.url);return d.searchParams.set("code",a),d.searchParams.set("org",i.organization.name),d.searchParams.set("invitedBy",f(i.invitedBy.firstName,i.invitedBy.lastName,i.invitedBy.name)),e.newResponse(null,302,{Location:d.toString()})}catch(t){return o.error("Error processing invite",{error:t,inviteCode:a}),e.text(t.message||"Failed to process invite",400)}}}function Te(s){return async e=>{const o=e.get("logger"),a=s.getConfig().ssoDirect,n=new URL(e.req.url),f=e.req.query("inviteCode"),t=e.req.header("x-forwarded-proto")||n.protocol.slice(0,-1)||"https",c=e.req.header("x-forwarded-host")||n.host,i=`${t}://${c}`;let d=n.searchParams.get("idpId");const w=n.searchParams.get("redirectTo"),p=Object.keys(a||{})[0];d=d||p;const l=n.searchParams.get("mcp_redirect_uri"),m=!!l;if(!a?.[d]){const r="Invalid idpId";if(o.error(`IdP login error: ${r}`),m){const T=j(w||void 0);k.sendMcpAuthorizationFailedMessage({...I(T),error:r,error_details:null})}return e.text(`Forbidden: ${r}`,403)}const g=d&&a?await Y(d,a[d]):void 0,A={};for(const r of Object.keys(g?.extraParams||{}))A[r]=n.searchParams.get(r)||g?.extraParams?.[r]||void 0;let _,u={};if(m&&l&&g&&g.type===G.OIDC){o.info(`Building MCP OAuth login URL with redirect_uri: ${l}`);const r=le("",{...g,extraParams:A},w,f,{redirectUriOverride:l,sourceOverride:"mcp",branchOverride:void 0});_=r.loginUrl,u=r.cookies||{}}else if(g){const r=x({...g,extraParams:A},i,w,f);_=r.loginUrl,u=r.cookies||{}}return Object.keys(u).forEach(r=>{L(e,r,u[r].value,u[r].options)}),o.info(`IdP login initiated for ID '${d}'`),e.newResponse(null,302,{Location:_||new URL(e.req.url).pathname})}}function qe(s){return async e=>{const o=e.get("logger"),a=await e.req.formData(),n=a.get("SAMLResponse"),f=a.get("RelayState");if(typeof n!="string"||typeof f!="string"){const r="SAMLResponse is required";return o.error(`SAML2 login error: ${r}`),e.text(`Bad request: ${r}`,400)}const t=ee(n),{success:c,uid:i,nameFormat:d,attrs:w,issuerId:p,expiresAt:l}=oe(t),{idpId:m,redirectTo:y}=JSON.parse(f);if(!c){const r="SAML2 assertion is not successful";return o.error(`SAML2 login error: ${r}`),e.text(`Permission denied: ${r}`,401)}if(!l||Math.ceil(Date.now()/1e3)>=l){const r="SAML2 Token Expired";return o.error(`SAML2 login error: ${r}`),e.text(r,401)}const g=s.getConfig().ssoDirect?.[m];if(!g||!Q(g)){const r="Cannot find valid IdP";return o.error(`SAML2 login error: ${r}`),e.text(`Permission denied: ${r}`,401)}if(!(g.issuerId&&p&&X(g.issuerId,p))){const r="IssuerID is misconfigured or untrusted assertions issuer received";return o.error(`SAML2 login error: ${r}`),e.text(`Permission denied: ${r}`,401)}if(!await te(t,g.x509PublicCert)){const r="SAMLResponse signature invalid";return o.error(`SAML2 login error: ${r}`),e.text(r,401)}const _=re(i,d,w,g.teamsAttributeName);if(!_.sub){const r="The provider did not return a valid user identity.";return o.error(`SAML2 login error: ${r}`),e.text(r,400)}if(!_.email){const r="The provider did not return a valid user email.";return o.error(`SAML2 login error: ${r}`),e.text(r,400)}const u=await S.sign({..._,idpId:m},v);return L(e,"authorization",u,{path:C()||"/",httpOnly:!0,expires:new Date(l*1e3)}),o.updateContext({email:_.email,subject:_.sub}),o.info("SAML2 login successful"),e.newResponse(null,302,{Location:y||"/"})}}function Ue(s){return async e=>{const o=e.get("logger"),a=new URL(e.req.query("redirectTo")||"/",e.req.url),n=M(B(a.pathname)),f=s.getConfig().ssoDirect,t=Object.entries(f||{}).find(([,y])=>$(y)&&de(y));if(!(f&&t))return e.newResponse(null,302,{Location:n});const i=e.req.query("token"),d=i&&await ae(i);if(!d)return e.newResponse(null,302,{Location:n});if(!U(d[E]||[]).some(y=>y===W))return e.newResponse(null,302,{Location:n});const l=await S.sign({...d,idpId:t?.at(0)},v),m=Date.now()+F*1e3;return L(e,"authorization",l,{path:C()||"/",httpOnly:!0,expires:new Date(m),sameSite:"None",secure:!0}),o.info("Token login successful"),e.newResponse(null,302,{Location:n})}}export{Oe as authorizeHandler,Te as idpLoginHandler,$e as inviteHandler,ve as logoutHandler,Se as oidcCallbackHandler,Pe as redoclyLoginCallbackHandler,Ue as redoclyTokenLoginHandler,qe as samlCallbackHandler};
|
|
1
|
+
import{setCookie as L,deleteCookie as T}from"hono/cookie";import{AuthProviderType as G}from"@redocly/config";import{withPathPrefix as R,getPathPrefix as _}from"@redocly/theme/core/utils";import{compareURIs as X}from"../../../utils/url/compare-uris.js";import{ensureArray as q}from"../../../utils/array/ensure-array.js";import{ALTERNATIVE_AUD_CLAIM_NAME as E,JWT_SECRET_KEY as v,ORG_SLUG as W}from"../../constants/common.js";import{DEFAULT_COOKIE_EXPIRATION as F,ServerRoutes as D}from"../../../constants/common.js";import{sanitizeRedirectPathname as B}from"../../../utils/url/sanitize-redirect-pathname.js";import{telemetry as k}from"../../telemetry/index.js";import{getAuthProviderLoginParams as Y,isOidcProviderConfig as $,isSaml2ProviderConfig as Q,oidcExchangeCodeForToken as Z,buildLoginUrl as x,decodeSamlResponse as ee,extractUserClaims as re,parseSamlResponse as oe,parseOidcState as ne,verifySAMLResponse as te,getUsernameFromPayload as ie,buildOidcLogoutUrl as se,getOidcMetadata as z,getRedoclyTokenPayload as ae,isRedoclySso as de,rewritePreviewAuthRedirectUri as ce,parsePreviewBranch as N,buildOidcLoginUrl as le,createMcpSessionResource as I}from"../auth.js";import*as S from"../jwt/jwt.js";import{AlgorithmTypes as ue}from"../jwt/types.js";import{handleErrorPageRender as pe}from"../utils.js";import{encodeBase64URL as ge}from"../jwt/encode.js";async function Oe(i){if(process.env.NODE_ENV==="production")return i.newResponse(null,404,{});const{password:e,...r}=await i.req.json(),a=await S.sign({...r,name:r.username||r.email||"Unknown"},v);return L(i,"authorization",a,{path:_()||"/",httpOnly:!0,secure:!0,sameSite:"none"}),i.newResponse(null,200,{})}function Pe(){return async i=>{const e=i.get("logger"),r=encodeURIComponent(i.req.query("message")||"");e.error(`Login error: ${r}`);const a=`${D.LOGIN}/?error=${encodeURIComponent(r)}`;return i.newResponse(null,301,{Location:a})}}function H(i){if(!i||!i.includes(D.MCP_CALLBACK))return null;try{const e=i.split("/"),r=e[e.length-1];if(r){const a=Buffer.from(r,"base64url").toString("utf-8");return JSON.parse(a).mcpSessionId||null}}catch{}return null}function Se(i){return async e=>{const r=e.get("logger"),a=i.getConfig().ssoDirect,n=ne(e.req.query("state")),f=n.idpId,t=n.source==="mcp"||n.redirectTo&&typeof n.redirectTo=="string"&&n.redirectTo.includes(D.MCP_CALLBACK),c=t?H(typeof n.redirectTo=="string"?n.redirectTo:void 0):null,s=a?.[f];if(!$(s))return r.error("OIDC login error: missing OIDC provider config"),e.text("Forbidden",403);const d=await z(f,s);if(a&&!d.token_endpoint){const p="Invalid OIDC configuration: token_endpoint is required";return r.error(`OIDC login error: ${p}`),e.text(p,500)}try{const p=d.token_endpoint,l=e.req.query("code"),m=e.req.query("error");if(m)return t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:`OIDC error: ${m}`,error_details:e.req.query("error_description")||null}),pe(e,i,{slug:"/"},403,"403OIDC");if(!l){const h="Code is expected but not present";return r.error(`OIDC login error: ${h}`),t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:h,error_details:null}),new Response(`Forbidden: ${h}`,{status:403})}const y=e.req.header("x-forwarded-host"),g=e.req.header("x-forwarded-proto")||"https",A=t&&typeof n.redirectUri=="string"?n.redirectUri:new URL(R(D.OIDC_CALLBACK),y?`${g}://${y}`:e.req.url).toString(),C=e.get("cookies")?.code_verifier,u=await Z(p,l,A,s,{...s.tokenRequestCustomParams,...C?{code_verifier:C}:{}});if(u.error)return r.error(`Error from OIDC provider: "${u.error}"`),t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:`Token exchange error: ${u.error}`,error_details:u.error_description||null}),e.text(`Forbidden: ${u.error_description||u.error}`,403);if(!u?.id_token){const h="No id_token, please, add openid to scopes";return r.error(`OIDC login error: ${h}`),t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:h,error_details:null}),new Response(`Forbidden: ${h}`,{status:403})}const{payload:o,header:U}=S.decode(u.id_token),j=U.alg===ue.RS256;if(s.audience?.length&&![...q(o.aud||[]),...q(o[E]||[])].some(M=>s.audience?.includes(M))){const M="No valid audience found in id_token";return r.error(`OIDC login error: ${M}`),t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:M,error_details:null}),new Response(`Forbidden: ${M}`)}const b=j?u.id_token:await S.sign({...o,idpId:f},v);ie(o)||r.warn("To display your username, the required 'email' or 'full_profile' scope must be added to the identity provider configuration");const O=s?.tokenExpirationTime?Date.now()+s.tokenExpirationTime*1e3:o.exp*1e3||Date.now()+F*1e3;if(s.introspectEndpoint){const h=await fetch(s.introspectEndpoint,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({access_token:u.access_token})});if(h.ok){const P=(await h.json()).ext?.federatedIdentity;P&&(L(e,"federated_access_token",P.access_token||"",{path:_()||"/",httpOnly:!1,expires:new Date(O)}),L(e,"federated_id_token",P.id_token||"",{path:_()||"/",httpOnly:!1,expires:new Date(O)}))}else r.warn(`OIDC introspect error: ${h.statusText}`)}if(L(e,"authorization",b,{path:_()||"/",httpOnly:!0,expires:new Date(O)}),b!==u.id_token&&L(e,"idp_id_token",u.id_token||"",{path:_()||"/",httpOnly:!0,expires:new Date(O)}),L(e,"idp_access_token",u.access_token||"",{path:_()||"/",httpOnly:!0,expires:new Date(O)}),T(e,"code_verifier",{path:_()||"/"}),t&&n.redirectTo&&typeof n.redirectTo=="string"&&n.redirectTo.includes(D.MCP_CALLBACK)){const h=e.req.url.split("?")[0].replace(D.OIDC_CALLBACK,""),M=R(n.redirectTo),P=`${h}${M}`;return e.newResponse(null,302,{Location:P})}const K=typeof n.redirectTo=="string"?n.redirectTo:void 0;let J=B(new URL(K||"/",e.req.url).pathname);const V=e.newResponse(null,302,{Location:J});return r.updateContext({email:o.email,subject:o.sub}),r.info("OIDC login successful"),V}catch(p){const l=p instanceof Error?p.message:String(p),m=p instanceof Error?p.stack:String(p);if(r.error(`OIDC login error: ${l}`),t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:l,error_details:m}),p.error==="access_denied")return r.info("Access denied"),e.text("Forbidden",403)}const w="Something went wrong";return r.error(`OIDC login error: ${w}`),t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:w,error_details:null}),e.text(w,500)}}function ve(i){return async e=>{const r=e.get("logger"),n=e.get("auth").claims?.idpId,t=i.getConfig().ssoDirect?.[n];if(e.req.method==="POST")return $(t)||T(e,"authorization",{path:_()||"/"}),r.info("Logout successful"),e.newResponse(null,200,{});let c;if($(t)){const s=(await z(n,t)).end_session_endpoint;if(s){const d=new URL(e.req.url),w=e.req.header("x-forwarded-proto")||d.protocol.slice(0,-1)||"https",p=e.req.header("x-forwarded-host")||d.host,l=`${w}://${p}`,m=N(l),y=m?ge(JSON.stringify({branch:N(l)})):void 0,g=m?`${ce(l)}/_auth/logout`:`${l}/post-logout`;c=se(s,g,e.get("cookies")?.idp_id_token||e.get("cookies")?.authorization||"",y)}}return r.info("Logout successful"),T(e,"authorization",{path:_()||"/"}),e.newResponse(null,302,{Location:c||R("/")})}}function $e(i){return async e=>{const r=i.getConfig().logoutReturnUrl,a=r||R("/");return e.newResponse(null,302,{Location:a})}}function Ue(i){return async e=>{const r=e.get("logger"),a=e.req.param("code"),n=process.env.BH_API_URL,f=(t,c,s)=>t&&c?`${t} ${c.charAt(0)}`:s;try{if(!n)throw new Error("BH_API_URL is not set");const t=i.getConfig().ssoDirect;if(!t||!Object.keys(t).length)return r.warn("Invite no sso configured to handle"),e.redirect(R("/"));const c=await fetch(`${n}/user-invites/public/${a}`);if(!c.ok)return c.status===404?(r.warn(`Invite ${a} not found redirect to homepage`),e.redirect(R("/"))):(r.error("Invite error",await c.text()),e.redirect(R("/")));const s=await c.json(),d=new URL(R("/invite"),e.req.url);return d.searchParams.set("code",a),d.searchParams.set("org",s.organization.name),d.searchParams.set("invitedBy",f(s.invitedBy.firstName,s.invitedBy.lastName,s.invitedBy.name)),e.newResponse(null,302,{Location:d.toString()})}catch(t){return r.error("Error processing invite",{error:t,inviteCode:a}),e.text(t.message||"Failed to process invite",400)}}}function Te(i){return async e=>{const r=e.get("logger"),a=i.getConfig().ssoDirect,n=new URL(e.req.url),f=e.req.query("inviteCode"),t=e.req.header("x-forwarded-proto")||n.protocol.slice(0,-1)||"https",c=e.req.header("x-forwarded-host")||n.host,s=`${t}://${c}`;let d=n.searchParams.get("idpId");const w=n.searchParams.get("redirectTo"),p=Object.keys(a||{})[0];d=d||p;const l=n.searchParams.get("mcp_redirect_uri"),m=!!l;if(!a?.[d]){const o="Invalid idpId";if(r.error(`IdP login error: ${o}`),m){const U=H(w||void 0);k.sendMcpAuthorizationFailedMessage({...I(U),error:o,error_details:null})}return e.text(`Forbidden: ${o}`,403)}const g=d&&a?await Y(d,a[d]):void 0,A={};for(const o of Object.keys(g?.extraParams||{}))A[o]=n.searchParams.get(o)||g?.extraParams?.[o]||void 0;let C,u={};if(m&&l&&g&&g.type===G.OIDC){r.info(`Building MCP OAuth login URL with redirect_uri: ${l}`);const o=le("",{...g,extraParams:A},w,f,{redirectUriOverride:l,sourceOverride:"mcp",branchOverride:void 0});C=o.loginUrl,u=o.cookies||{}}else if(g){const o=x({...g,extraParams:A},s,w,f);C=o.loginUrl,u=o.cookies||{}}return Object.keys(u).forEach(o=>{L(e,o,u[o].value,u[o].options)}),r.info(`IdP login initiated for ID '${d}'`),e.newResponse(null,302,{Location:C||new URL(e.req.url).pathname})}}function qe(i){return async e=>{const r=e.get("logger"),a=await e.req.formData(),n=a.get("SAMLResponse"),f=a.get("RelayState");if(typeof n!="string"||typeof f!="string"){const o="SAMLResponse is required";return r.error(`SAML2 login error: ${o}`),e.text(`Bad request: ${o}`,400)}const t=ee(n),{success:c,uid:s,nameFormat:d,attrs:w,issuerId:p,expiresAt:l}=oe(t),{idpId:m,redirectTo:y}=JSON.parse(f);if(!c){const o="SAML2 assertion is not successful";return r.error(`SAML2 login error: ${o}`),e.text(`Permission denied: ${o}`,401)}if(!l||Math.ceil(Date.now()/1e3)>=l){const o="SAML2 Token Expired";return r.error(`SAML2 login error: ${o}`),e.text(o,401)}const g=i.getConfig().ssoDirect?.[m];if(!g||!Q(g)){const o="Cannot find valid IdP";return r.error(`SAML2 login error: ${o}`),e.text(`Permission denied: ${o}`,401)}if(!(g.issuerId&&p&&X(g.issuerId,p))){const o="IssuerID is misconfigured or untrusted assertions issuer received";return r.error(`SAML2 login error: ${o}`),e.text(`Permission denied: ${o}`,401)}if(!await te(t,g.x509PublicCert)){const o="SAMLResponse signature invalid";return r.error(`SAML2 login error: ${o}`),e.text(o,401)}const C=re(s,d,w,g.teamsAttributeName);if(!C.sub){const o="The provider did not return a valid user identity.";return r.error(`SAML2 login error: ${o}`),e.text(o,400)}if(!C.email){const o="The provider did not return a valid user email.";return r.error(`SAML2 login error: ${o}`),e.text(o,400)}const u=await S.sign({...C,idpId:m},v);return L(e,"authorization",u,{path:_()||"/",httpOnly:!0,expires:new Date(l*1e3)}),r.updateContext({email:C.email,subject:C.sub}),r.info("SAML2 login successful"),e.newResponse(null,302,{Location:y||"/"})}}function be(i){return async e=>{const r=e.get("logger"),a=new URL(e.req.query("redirectTo")||"/",e.req.url),n=R(B(a.pathname)),f=i.getConfig().ssoDirect,t=Object.entries(f||{}).find(([,y])=>$(y)&&de(y));if(!(f&&t))return e.newResponse(null,302,{Location:n});const s=e.req.query("token"),d=s&&await ae(s);if(!d)return e.newResponse(null,302,{Location:n});if(!q(d[E]||[]).some(y=>y===W))return e.newResponse(null,302,{Location:n});const l=await S.sign({...d,idpId:t?.at(0)},v),m=Date.now()+F*1e3;return L(e,"authorization",l,{path:_()||"/",httpOnly:!0,expires:new Date(m),sameSite:"None",secure:!0}),r.info("Token login successful"),e.newResponse(null,302,{Location:n})}}export{Oe as authorizeHandler,Te as idpLoginHandler,Ue as inviteHandler,ve as logoutHandler,Se as oidcCallbackHandler,$e as postLogoutHandler,Pe as redoclyLoginCallbackHandler,be as redoclyTokenLoginHandler,qe as samlCallbackHandler};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{serveStatic as f}from"hono/serve-static";import{withPathPrefix as e,withoutPathPrefix as s}from"@redocly/theme/core/utils";import{ServerRoutes as i}from"../../../constants/common.js";import{PUBLIC_STATIC_FOLDER as R}from"../../constants/common.js";import{authMiddleware as c}from"../middleware/authMiddleware.js";import{ensureSearchData as g}from"../middleware/ensureSearchData.js";import{dynamicMiddleware as S}from"../middleware/dynamic-middleware/dynamic-middleware.js";import{installRoutes as
|
|
1
|
+
import{serveStatic as f}from"hono/serve-static";import{withPathPrefix as e,withoutPathPrefix as s}from"@redocly/theme/core/utils";import{ServerRoutes as i}from"../../../constants/common.js";import{PUBLIC_STATIC_FOLDER as R}from"../../constants/common.js";import{authMiddleware as c}from"../middleware/authMiddleware.js";import{ensureSearchData as g}from"../middleware/ensureSearchData.js";import{dynamicMiddleware as S}from"../middleware/dynamic-middleware/dynamic-middleware.js";import{installRoutes as P}from"../../plugins/dev-onboarding/api/routes/index.js";import{authorizeHandler as I,oidcCallbackHandler as r,logoutHandler as d,postLogoutHandler as h,idpLoginHandler as D,redoclyLoginCallbackHandler as N,samlCallbackHandler as M,redoclyTokenLoginHandler as U,inviteHandler as B}from"./auth.js";import{appDataHandler as G}from"./app-data.js";import{searchFacetsHandler as w,searchHandler as F}from"./search.js";import{dynamicRouteHandler as v}from"./dynamic-route.js";import{pageDataHandler as K,sharedPageDataHandler as y}from"./page-data.js";import{pathPrefixRedirectHandler as k}from"./path-prefix-redirect.js";import{getRoutesByLineHandler as E,resolvePathHandler as o,resolvePathsHandler as Y,resolveSlugHandler as p}from"./resolve-route.js";import{feedbackHandler as b}from"./feedback.js";import{loggerMiddleware as V}from"../middleware/loggerMiddleware.js";import{responseHeadersMiddleware as x}from"../middleware/responseHeadersMiddleware.js";import{idleTimeoutMiddleware as z}from"../middleware/idleTimeoutMiddleware.js";import{otelTracesHandler as Z}from"./otel/otel.js";import{healthCheckHandler as $}from"./health.js";import{askAiHandler as q}from"./ask-ai.js";import{replayOauth2RedirectCallbackHandler as W}from"./replay-oauth2-redirect.js";import{mcpOAuthProtectedResourceHandler as j,mcpOAuthAuthorizationServerHandler as J,mcpDynamicClientRegistrationHandler as Q,mcpAuthorizationHandler as X,mcpTokenPortalHandler as u,mcpCallbackHandler as _}from"./mcp-oauth.js";import{corsMiddleware as O}from"../middleware/corsMiddleware.js";import{installApiRoutes as aa}from"./api-routes/api-routes.js";import{cookieMiddleware as ea}from"../middleware/cookieMiddleware.js";import{staticContentHandler as ia}from"../routes/static-content.js";import{infoHandler as L}from"./info.js";import{catalogHandler as la}from"./catalog/catalog.js";import{catalogRelationsHandler as ta}from"./catalog/catalog-relations.js";import{bffCatalogHandler as ma}from"./catalog/bff-catalog.js";import{bffCatalogRevisionsHandler as na}from"./catalog/bff-catalog-revisions.js";import{bffCatalogRelatedEntitiesHandler as Aa}from"./catalog/bff-catalog-related-entities.js";import{catalogAuthMiddleware as C}from"../middleware/catalogAuthMiddleware.js";import{telemetryMiddleware as Ta}from"../middleware/telemetry-middleware.js";import{errorHandler as da}from"./error.js";function Wa(a,l,t){const{resolveRouteData:n,readStaticAsset:A}=t;a.use("*",z()),a.use("*",ea()),a.use("*",S(l)),a.use("*",c(l)),a.use("*",V()),a.use("*",x(l)),a.use("*",Ta()),a.use(e("*"),f({root:`./${R}`,getContent:(m,H)=>ia(m,H,l,A),rewriteRequestPath:m=>s(m)})),a.use(e(i.FEEDBACK),O({allowMethods:["POST"]})),a.use(e(i.ASK_AI),O({allowMethods:["POST"]})),a.use("*",Ea(l));const T=g(l);a.use(e(i.INFO),L()),process.env.NEW_CATALOG_ENABLED==="true"&&(a.use(e(i.CATALOG_ENTITIES),C(l.serverOutDir,{secureMethods:["POST","PUT","DELETE","PATCH"]})),a.use(e(i.CATALOG_ENTITIES_RELATIONS),C(l.serverOutDir,{secureMethods:["POST","PUT","DELETE","PATCH"]})),a.use(e(i.CATALOG_ENTITIES),la(l)),a.use(e(i.CATALOG_ENTITIES_RELATIONS),ta(l)),a.get(e(i.BFF_CATALOG_ENTITIES),ma(l)),a.get(e(i.BFF_CATALOG_RELATED_ENTITIES),Aa(l)),a.get(e(i.BFF_CATALOG_REVISIONS),na(l))),a.get(e(i.SHARED_PAGE_DATA),y(l)),a.get(e(i.PAGE_DATA),K(l,n)),a.get(e(i.APP_DATA),G(l)),a.post(e(i.SEARCH),T,F(l)),a.post(e(i.SEARCH_FACETS),T,w(l)),a.post(e(i.AUTHORIZATION),I),a.post(e(i.LOGOUT),d(l)),a.get(e(i.LOGOUT),d(l)),a.get(e(i.POST_LOGOUT),h(l)),a.get(e(i.OIDC_CALLBACK),r(l)),a.get(e(i.REDOCLY_TOKEN_LOGIN),U(l)),a.get(e(i.REDOCLY_LOGIN_CALLBACK),N()),a.get(e(i.IDP_LOGIN),D(l)),a.post(e(i.SAML_CALLBACK),M(l)),a.get(e(i.INVITE),B(l)),a.get(e(i.HEALTH),$),a.get(e(i.MCP_OAUTH_PROTECTED_RESOURCE),j()),a.get(e(i.MCP_OAUTH_AUTHORIZATION_SERVER),J()),a.post(e(i.MCP_DYNAMIC_CLIENT_REGISTRATION),Q()),a.get(e(i.MCP_AUTHORIZATION),X()),a.post(e(i.MCP_TOKEN_PORTAL),u()),a.get(e(i.MCP_CALLBACK),_()),a.get(e(`${i.MCP_CALLBACK}/*`),_()),P(a,l),aa(a,l),a.post(e(i.FEEDBACK),b(l)),a.post(e(i.RESOLVE_ROUTE_BY_PATH),o(l)),a.post(e(i.RESOLVE_ROUTES_BY_PATHS),Y(l)),a.post(e(i.RESOLVE_ROUTE_BY_SLUG),p(l)),a.post(e(i.ASK_AI),q(l)),a.get(e(i.GET_ROUTES_BY_LINE),E(l)),a.post(e(i.OTEL_TRACES),Z),a.get(e(i.REPLAY_OAUTH2_CALLBACK),W),a.all(e("/*"),v(l,n,A)),a.get("*",k),a.onError(da)}function Ea(a){return async(l,t)=>{await a.waitForPluginsLifecycle(),await t()}}function ja(a,l){a.get(e(i.INFO),L()),a.post(e(i.RESOLVE_ROUTE_BY_PATH),o(l)),a.post(e(i.RESOLVE_ROUTE_BY_SLUG),p(l)),a.get(e(i.GET_ROUTES_BY_LINE),E(l))}export{ja as installDevRoutes,Wa as installProdRoutes,Ea as waitForPluginsLifecycle};
|
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
import type { Handler } from 'hono';
|
|
2
|
+
export type McpContextPayload = {
|
|
3
|
+
isMcpFlow: boolean;
|
|
4
|
+
originalRedirectUri: string | null;
|
|
5
|
+
mcpClientId: string | null;
|
|
6
|
+
mcpState: string | null;
|
|
7
|
+
mcpSessionId: string;
|
|
8
|
+
timestamp: number;
|
|
9
|
+
};
|
|
10
|
+
export declare function createMcpContextToken(context: McpContextPayload): Promise<string>;
|
|
11
|
+
export declare function verifyAndParseMcpContextToken(token: string): Promise<McpContextPayload>;
|
|
2
12
|
export declare function mcpOAuthProtectedResourceHandler(): Handler;
|
|
3
13
|
export declare function mcpOAuthAuthorizationServerHandler(): Handler;
|
|
4
14
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getCookie as
|
|
1
|
+
import{getCookie as C}from"hono/cookie";import{ulid as w}from"ulid";import{AUTH_URL as _,JWT_SECRET_KEY as y}from"../../constants/common.js";import{ServerRoutes as d}from"../../../constants/common.js";import{withPathPrefix as m}from"@redocly/theme/core/utils";import{telemetry as l}from"../../telemetry/index.js";import{createMcpAuthorizationCode as k,verifyMcpAuthorizationCode as S,createMcpSessionResource as u}from"../auth.js";import*as h from"../jwt/jwt.js";import{getRequestOrigin as g}from"../utils/get-request-origin.js";const n=(e,r,o=200,a)=>e.json(r,o,{"Content-Type":"application/json",...a??{}});async function T(e){const r=Math.floor(Date.now()/1e3);return h.sign({type:"mcp_context",...e,iat:r,exp:r+600},y)}async function I(e){await h.verify(e,y);const{payload:r}=h.decode(e);if(r.type!=="mcp_context")throw new Error("Invalid context token type");return r}function O(){return async e=>{if(e.req.method!=="GET")return n(e,{error:"Method not allowed"},405,{Allow:"GET"});const r=g(e);return n(e,{resource:`${r}/mcp`,authorization_servers:[r],bearer_methods_supported:["header"],resource_documentation:`${r}/.well-known/oauth-authorization-server`,scopes_supported:["openid","profile","email","offline_access"],bearer_token_types_supported:["Bearer"]})}}function D(){return async e=>{const r=g(e);return n(e,{issuer:_||"",authorization_endpoint:`${r}${d.MCP_AUTHORIZATION}`,token_endpoint:`${r}${d.MCP_TOKEN_PORTAL}`,jwks_uri:`${_||""}/.well-known/jwks.json`,registration_endpoint:`${r}${d.MCP_DYNAMIC_CLIENT_REGISTRATION}`,scopes_supported:["openid","profile","email","offline_access"],response_types_supported:["code"],grant_types_supported:["authorization_code","refresh_token","client_credentials"],subject_types_supported:["public"],id_token_signing_alg_values_supported:["RS256"],code_challenge_methods_supported:["S256"]})}}function $(){return async e=>{if(e.req.method!=="POST")return n(e,{error:"Method not allowed"},405);try{return n(e,{client_id:process.env.OAUTH_CLIENT_ID||"",client_name:"MCP Client",redirect_uris:[],grant_types:["authorization_code","refresh_token"],response_types:["code"],scope:"openid offline email",subject_type:"public",token_endpoint_auth_method:"none",created_at:new Date().toISOString(),updated_at:new Date().toISOString()},201)}catch(r){return n(e,{error:"invalid_request",error_description:r?.message||"Unable to register client"},500)}}}function q(){return async e=>{const r=new URL(e.req.url),{searchParams:o}=r,a=o.get("redirect_uri"),t=w();l.sendMcpAuthorizationStartedMessage({...u(t),redirect_uri:a||null});const s=g(e),c={isMcpFlow:!0,originalRedirectUri:a,mcpClientId:o.get("client_id"),mcpState:o.get("state"),mcpSessionId:t,timestamp:Date.now()};try{const i=await T(c),p=new URL(d.IDP_LOGIN,s);return p.searchParams.set("redirectTo",`${m(d.MCP_CALLBACK)}/${i}`),p.searchParams.set("idpId","oidc"),e.redirect(p.toString())}catch(i){const p=i instanceof Error?i.message:String(i),M=i instanceof Error?i.stack:String(i);l.sendMcpAuthorizationFailedMessage({...u(t),error:p,error_details:M});const f=new URL(m(`${_}/oauth2/auth`));return f.search=o.toString(),e.redirect(f.toString())}}}function H(){return async e=>{if(e.req.method!=="POST")return n(e,{error:"Method not allowed"},405);try{const r=await e.req.formData(),o=r.get("grant_type"),a=r.get("code"),t=r.get("redirect_uri")||void 0;if(o!=="authorization_code"||!a)return n(e,{error:"invalid_request",error_description:"Invalid grant type or missing authorization code"},400);try{const s=await S(a);if(t&&t!==s.redirect_uri)return n(e,{error:"invalid_grant",error_description:"redirect_uri mismatch"},400);if(process.env.OAUTH_CLIENT_ID&&s.client_id&&s.client_id!==process.env.OAUTH_CLIENT_ID)return n(e,{error:"invalid_client",error_description:"Client mismatch"},400);const c=s.id_token;return typeof c!="string"||c.length===0?n(e,{error:"invalid_grant",error_description:"Missing id_token in authorization code"},400):n(e,{access_token:c,token_type:"Bearer",expires_in:3600,scope:"openid profile email",id_token:c},200,{"Cache-Control":"no-store",Pragma:"no-cache"})}catch{return n(e,{error:"invalid_grant",error_description:"Invalid authorization code"},400)}}catch(r){const o=r instanceof Error?r.message:String(r);return n(e,{error:"server_error",error_description:"Failed to process token request",error_details:o},500)}}}function b(){return async e=>{const r=new URL(e.req.url);let o=r.searchParams.get("context");if(!o&&r.pathname.startsWith(m(`${d.MCP_CALLBACK}/`))){const t=r.pathname.split("/");o=t[t.length-1]}if(!o)return l.sendMcpAuthorizationFailedMessage({...u(null),error:"Missing context parameter",error_details:null}),e.text("Missing context parameter",400);let a=null;try{const t=await I(o);if(a=t.mcpSessionId||null,!t.isMcpFlow||!t.originalRedirectUri)throw new Error("Invalid MCP context");const s=C(e,"idp_id_token")||C(e,"authorization"),c=await k({idToken:s||"",clientId:t.mcpClientId||"",redirectUri:t.originalRedirectUri,ttlSec:600}),i=new URL(t.originalRedirectUri);return i.searchParams.set("code",c),t.mcpState&&i.searchParams.set("state",t.mcpState),l.sendMcpAuthorizationCompletedMessage({...u(a),redirect_uri:t.originalRedirectUri||null}),e.redirect(i.toString())}catch(t){const s=t instanceof Error?t.message:String(t),c=t instanceof Error?t.stack:String(t);return l.sendMcpAuthorizationFailedMessage({...u(a),error:s,error_details:c}),e.text(`Invalid MCP callback: ${s}`,400)}}}export{T as createMcpContextToken,q as mcpAuthorizationHandler,b as mcpCallbackHandler,$ as mcpDynamicClientRegistrationHandler,D as mcpOAuthAuthorizationServerHandler,O as mcpOAuthProtectedResourceHandler,H as mcpTokenPortalHandler,I as verifyAndParseMcpContextToken};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function s(r){const e=new URL(r.req.url),o=r.req.header("x-forwarded-proto")||e.protocol.slice(0,-1)||"https",t=r.req.header("x-forwarded-host")||e.host;return`${o}://${t}`}export{s as getRequestOrigin};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@redocly/redoc-revel",
|
|
3
|
-
"version": "0.129.0-next.
|
|
3
|
+
"version": "0.129.0-next.6",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
"xpath": "0.0.34",
|
|
95
95
|
"yaml-ast-parser": "0.0.43",
|
|
96
96
|
"@redocly/asyncapi-docs": "1.6.0-next.5",
|
|
97
|
-
"@redocly/config": "0.41.
|
|
97
|
+
"@redocly/config": "0.41.2",
|
|
98
98
|
"@redocly/graphql-docs": "1.6.0-next.0",
|
|
99
99
|
"@redocly/openapi-docs": "3.17.0-next.5",
|
|
100
100
|
"@redocly/portal-legacy-ui": "0.12.0-next.0",
|