@things-factory/auth-base 10.0.3 → 10.0.4
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.
|
@@ -156,9 +156,13 @@ async function findMemberDomains(user) {
|
|
|
156
156
|
}
|
|
157
157
|
/** 사용자가 소유한 도메인 — 대표 오너 캐시 컬럼과 `DomainOwner` 테이블 양쪽. */
|
|
158
158
|
async function findOwnedDomains(user) {
|
|
159
|
+
// 주의: `Domain.owner` 는 varchar(사용자 id 문자열 캐시)이고 `DomainOwner.user` 는 uuid FK 다.
|
|
160
|
+
// 두 비교에 **동일 파라미터를 재사용하면** Postgres 가 준비된 구문의 파라미터를 하나의
|
|
161
|
+
// 타입으로만 추론하다 `operator does not exist: uuid = text` 로 실패한다. → 값은 같아도
|
|
162
|
+
// owner(varchar) 는 :ownerId, user_id(uuid) 는 :userId 로 **파라미터를 분리**한다.
|
|
159
163
|
return await (0, shell_1.getRepository)(shell_1.Domain)
|
|
160
164
|
.createQueryBuilder('DOMAIN')
|
|
161
|
-
.where('DOMAIN.owner = :
|
|
165
|
+
.where('DOMAIN.owner = :ownerId')
|
|
162
166
|
.orWhere(qb => {
|
|
163
167
|
const subQuery = qb
|
|
164
168
|
.subQuery()
|
|
@@ -169,6 +173,7 @@ async function findOwnedDomains(user) {
|
|
|
169
173
|
.getQuery();
|
|
170
174
|
return 'DOMAIN.id IN ' + subQuery;
|
|
171
175
|
})
|
|
176
|
+
.setParameter('ownerId', user.id)
|
|
172
177
|
.setParameter('userId', user.id)
|
|
173
178
|
.getMany();
|
|
174
179
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkin-domain-facts.js","sourceRoot":"","sources":["../../server/utils/checkin-domain-facts.ts"],"names":[],"mappings":"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","sourcesContent":["import { In } from 'typeorm'\n\nimport { Domain, getRepository } from '@things-factory/shell'\n\nimport { DomainOwner } from '../service/domain-owner/domain-owner.js'\nimport { GrantedRole } from '../service/granted-role/granted-role.js'\nimport { Role } from '../service/role/role.js'\nimport { User } from '../service/user/user.js'\nimport { CheckinDomainCandidate, CheckinFacts } from './checkin-domain-rule.js'\nimport { isDomainOwner } from './domain-ownership.js'\n\n/**\n * 체크인 판정(`checkin-domain-rule.ts`)에 필요한 사실을 데이터베이스에서 수집한다.\n *\n * 판정 규칙과 조회를 분리한 이유:\n * - 규칙은 순수 함수로 두어 시나리오를 빠짐없이 단위 테스트할 수 있게 한다.\n * - 조회는 목적에 따라 두 가지 비용 프로파일을 갖는다. 도메인 하나에 대한 가부 판정(매 요청\n * 통과하는 핫패스)과, 체크인 화면용 전체 목록 계산(로그인·부트스트랩 시점)이다.\n *\n * 모든 조회는 TypeORM 리포지토리/쿼리빌더 API 만 사용한다 (raw SQL·DB 고유 문법 없음).\n * `IN` 절은 빈 배열이면 일부 드라이버에서 문법 오류가 되므로 항상 길이를 먼저 확인한다.\n */\n\n/**\n * \"이 도메인의 멤버로서 부여받은 역할\" 이 정의된 도메인 id.\n *\n * 멤버십(`users_domains`)과 역할 보유(`users_roles`)를 한 번의 조회로 함께 확인한다 —\n * 매 요청 통과하는 판정이라 왕복 횟수를 최소화한다. 결과가 비어 있으면 \"멤버가 아님\" 과\n * \"부여된 역할이 없음\" 을 구분할 수 없지만, 두 경우 모두 이 경로로는 체크인할 수 없으므로\n * 판정에는 차이가 없다.\n */\nasync function findGrantedRoleDomainIds(user: User, domainId: string, roleDomainIds: string[]): Promise<string[]> {\n if (!roleDomainIds.length) {\n return []\n }\n\n const rows = await getRepository(User)\n .createQueryBuilder('USER')\n .innerJoin('USER.domains', 'MEMBER_DOMAIN', 'MEMBER_DOMAIN.id = :domainId', { domainId })\n .innerJoin('USER.roles', 'ROLE')\n .innerJoin('ROLE.domain', 'ROLE_DOMAIN')\n .where('USER.id = :userId', { userId: user.id })\n .andWhere('ROLE_DOMAIN.id IN (:...roleDomainIds)', { roleDomainIds })\n .select('ROLE_DOMAIN.id', 'roleDomainId')\n .distinct(true)\n .getRawMany()\n\n return [...new Set<string>(rows.map(row => row.roleDomainId).filter(Boolean))]\n}\n\n/**\n * 파트너십 경로 — 이 도메인에 정의된 역할이 사용자의 소속 도메인에 grant 되어 있는지.\n * (공급자 도메인이 자기 역할을 고객 도메인에 grant 하고, 고객 사용자가 그 역할을 부여받는 구조)\n */\nasync function isPartnerGrantedDomain(user: User, domainId: string): Promise<boolean> {\n return await getRepository(GrantedRole)\n .createQueryBuilder('GRANTED_ROLE')\n .innerJoin('GRANTED_ROLE.role', 'ROLE')\n .innerJoin('ROLE.users', 'ROLE_USER')\n .innerJoin('ROLE_USER.domains', 'MEMBER_DOMAIN')\n .where('ROLE_USER.id = :userId', { userId: user.id })\n .andWhere('ROLE.domain = :domainId', { domainId })\n .andWhere('GRANTED_ROLE.domain = MEMBER_DOMAIN.id')\n .getExists()\n}\n\n/**\n * 도메인 하나에 대한 판정용 사실 수집 (핫패스).\n *\n * 통상적인 통과(그 도메인의 멤버로서 역할을 부여받은 사용자)는 **1회 조회**로 끝난다.\n * 파트너십·소유권 조회는 그것만으로 근거가 서지 않을 때만 병렬로 수행한다.\n *\n * @param domain 판정 대상 도메인. 상속·소유권 판정에 `parentId`·`owner` 가 필요하므로\n * `context.state.domain` 처럼 온전히 적재된 도메인을 넘겨야 한다.\n * @param stillNeeded 1차 사실만으로 근거가 섰는지 판단하는 콜백 (규칙 함수를 주입해 규칙의\n * 단일 정의를 유지한다).\n */\nexport async function collectCheckinFactsForDomain(\n user: User,\n domain: CheckinDomainCandidate,\n stillNeeded: (facts: CheckinFacts) => boolean\n): Promise<CheckinFacts> {\n /* 상속은 부모 1단까지 — shell 의 `applyDomainInheritanceFilter` 규약과 같다. */\n const inheritableDomainIds = [domain.id, domain.parentId].filter(Boolean) as string[]\n\n const grantedRoleDomainIds = await findGrantedRoleDomainIds(user, domain.id, inheritableDomainIds)\n\n const facts: CheckinFacts = {\n userId: user.id,\n memberDomainIds: grantedRoleDomainIds.length ? [domain.id] : [],\n roleDomainIds: grantedRoleDomainIds,\n partnerRoleDomainIds: [],\n ownedDomainIds: []\n }\n\n if (!stillNeeded(facts)) {\n return facts\n }\n\n const [partner, owner] = await Promise.all([\n isPartnerGrantedDomain(user, domain.id),\n isDomainOwner(domain, user)\n ])\n\n return {\n ...facts,\n /* 파트너 조회가 \"이 도메인에 정의된 역할 보유\" 까지 함께 확인하므로 역할 보유 사실도 채운다. */\n roleDomainIds: partner ? [...new Set([...grantedRoleDomainIds, domain.id])] : grantedRoleDomainIds,\n partnerRoleDomainIds: partner ? [domain.id] : [],\n ownedDomainIds: owner ? [domain.id] : []\n }\n}\n\n/** 전체 목록 판정용 사실 + 판정 대상 도메인(행까지 적재된 상태). */\nexport type UserCheckinCandidates = {\n facts: CheckinFacts\n candidates: Domain[]\n}\n\n/**\n * 사용자가 체크인할 수 있는 도메인 목록을 계산하기 위한 사실 + 후보 도메인 수집.\n * 로그인·체크인 화면 등 목록이 필요한 지점에서만 쓴다 (매 요청 경로에서는\n * `collectCheckinFactsForDomain` 을 쓴다).\n *\n * 조회 설계:\n * - 경로별(소속·소유·파트너) 조회가 **도메인 행을 직접** 반환한다. 후보 id 를 모은 뒤 다시\n * 적재하는 단계가 없어져 왕복이 한 묶음으로 줄었다.\n * - 네 조회는 서로 의존하지 않으므로 한 번에 병렬로 던진다.\n * - 관계를 적재한 엔티티(`findOne({relations})`)는 쓰지 않는다 — 판정에 필요한 것은 id 뿐인데\n * 엔티티 적재는 역할·도메인 전체를 hydration 한다.\n * - 판정 자체는 하지 않는다. 어떤 후보가 실제로 체크인 가능한지는 `checkinGrantsOf` 한 곳에서만\n * 결정한다.\n */\nexport async function collectCheckinCandidates(user: User): Promise<UserCheckinCandidates> {\n const [memberDomains, roleDomainIds, ownedDomains, partnerRoleDomainIds] = await Promise.all([\n findMemberDomains(user),\n findRoleDomainIds(user),\n findOwnedDomains(user),\n findPartnerRoleDomainIds(user)\n ])\n\n /* 파트너 경로로 열린 도메인은 소속 도메인이 아니므로 별도로 적재한다 (해당 사용자가 있을 때만). */\n const partnerDomains: Domain[] = partnerRoleDomainIds.size\n ? await getRepository(Domain).find({ where: { id: In([...partnerRoleDomainIds]) } })\n : []\n\n const candidateById = new Map<string, Domain>()\n for (const domain of [...memberDomains, ...ownedDomains, ...partnerDomains]) {\n candidateById.set(domain.id, domain)\n }\n\n return {\n facts: {\n userId: user.id,\n memberDomainIds: new Set<string>(memberDomains.map((domain: Domain) => domain.id)),\n roleDomainIds,\n partnerRoleDomainIds,\n ownedDomainIds: new Set<string>(ownedDomains.map((domain: Domain) => domain.id))\n },\n candidates: [...candidateById.values()]\n }\n}\n\n/** 사용자가 소속된 도메인 (`users_domains`). */\nasync function findMemberDomains(user: User): Promise<Domain[]> {\n return await getRepository(Domain)\n .createQueryBuilder('DOMAIN')\n .where(qb => {\n const subQuery = qb\n .subQuery()\n .select('MEMBER_DOMAIN.id')\n .from(User, 'MEMBER_USER')\n .innerJoin('MEMBER_USER.domains', 'MEMBER_DOMAIN')\n .where('MEMBER_USER.id = :userId')\n .getQuery()\n return 'DOMAIN.id IN ' + subQuery\n })\n .setParameter('userId', user.id)\n .getMany()\n}\n\n/** 사용자가 소유한 도메인 — 대표 오너 캐시 컬럼과 `DomainOwner` 테이블 양쪽. */\nasync function findOwnedDomains(user: User): Promise<Domain[]> {\n return await getRepository(Domain)\n .createQueryBuilder('DOMAIN')\n .where('DOMAIN.owner = :userId')\n .orWhere(qb => {\n const subQuery = qb\n .subQuery()\n .select('OWNED_DOMAIN.id')\n .from(DomainOwner, 'DOMAIN_OWNER')\n .innerJoin('DOMAIN_OWNER.domain', 'OWNED_DOMAIN')\n .where('DOMAIN_OWNER.user = :userId')\n .getQuery()\n return 'DOMAIN.id IN ' + subQuery\n })\n .setParameter('userId', user.id)\n .getMany()\n}\n\n/** 사용자가 보유한 역할이 정의된 도메인 id. */\nasync function findRoleDomainIds(user: User): Promise<Set<string>> {\n const rows = await getRepository(Role)\n .createQueryBuilder('ROLE')\n .innerJoin('ROLE.users', 'ROLE_USER')\n .innerJoin('ROLE.domain', 'ROLE_DOMAIN')\n .where('ROLE_USER.id = :userId', { userId: user.id })\n .select('ROLE_DOMAIN.id', 'roleDomainId')\n .distinct(true)\n .getRawMany()\n\n return new Set<string>(rows.map(row => row.roleDomainId).filter(Boolean))\n}\n\n/**\n * 파트너십 경로로 열린 도메인 id — 사용자가 보유한 역할 중, 그 역할이 사용자의 소속 도메인에\n * grant 된 것들의 정의 도메인. 역할 보유·grant·소속을 한 조회에서 함께 확인한다.\n */\nasync function findPartnerRoleDomainIds(user: User): Promise<Set<string>> {\n const rows = await getRepository(GrantedRole)\n .createQueryBuilder('GRANTED_ROLE')\n .innerJoin('GRANTED_ROLE.role', 'ROLE')\n .innerJoin('ROLE.domain', 'ROLE_DOMAIN')\n .innerJoin('ROLE.users', 'ROLE_USER')\n .innerJoin('ROLE_USER.domains', 'MEMBER_DOMAIN')\n .where('ROLE_USER.id = :userId', { userId: user.id })\n .andWhere('GRANTED_ROLE.domain = MEMBER_DOMAIN.id')\n .select('ROLE_DOMAIN.id', 'roleDomainId')\n .distinct(true)\n .getRawMany()\n\n return new Set<string>(rows.map(row => row.roleDomainId).filter(Boolean))\n}\n\n/** 사용자가 보유한 역할 중 주어진 권한을 가진 역할이 정의된 도메인 id 집합. */\nexport async function findRoleDomainIdsWithPrivilege(\n user: User,\n privilege: string,\n category: string\n): Promise<Set<string>> {\n const roles: Role[] = await getRepository(Role)\n .createQueryBuilder('ROLE')\n .innerJoin('ROLE.users', 'ROLE_USER')\n .innerJoin('ROLE.privileges', 'PRIVILEGE')\n .innerJoinAndSelect('ROLE.domain', 'ROLE_DOMAIN')\n .where('ROLE_USER.id = :userId', { userId: user.id })\n .andWhere('PRIVILEGE.name = :privilege', { privilege })\n .andWhere('PRIVILEGE.category = :category', { category })\n .getMany()\n\n return new Set<string>(roles.map((role: Role) => role.domain?.id).filter(Boolean))\n}\n"]}
|
|
1
|
+
{"version":3,"file":"checkin-domain-facts.js","sourceRoot":"","sources":["../../server/utils/checkin-domain-facts.ts"],"names":[],"mappings":"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","sourcesContent":["import { In } from 'typeorm'\n\nimport { Domain, getRepository } from '@things-factory/shell'\n\nimport { DomainOwner } from '../service/domain-owner/domain-owner.js'\nimport { GrantedRole } from '../service/granted-role/granted-role.js'\nimport { Role } from '../service/role/role.js'\nimport { User } from '../service/user/user.js'\nimport { CheckinDomainCandidate, CheckinFacts } from './checkin-domain-rule.js'\nimport { isDomainOwner } from './domain-ownership.js'\n\n/**\n * 체크인 판정(`checkin-domain-rule.ts`)에 필요한 사실을 데이터베이스에서 수집한다.\n *\n * 판정 규칙과 조회를 분리한 이유:\n * - 규칙은 순수 함수로 두어 시나리오를 빠짐없이 단위 테스트할 수 있게 한다.\n * - 조회는 목적에 따라 두 가지 비용 프로파일을 갖는다. 도메인 하나에 대한 가부 판정(매 요청\n * 통과하는 핫패스)과, 체크인 화면용 전체 목록 계산(로그인·부트스트랩 시점)이다.\n *\n * 모든 조회는 TypeORM 리포지토리/쿼리빌더 API 만 사용한다 (raw SQL·DB 고유 문법 없음).\n * `IN` 절은 빈 배열이면 일부 드라이버에서 문법 오류가 되므로 항상 길이를 먼저 확인한다.\n */\n\n/**\n * \"이 도메인의 멤버로서 부여받은 역할\" 이 정의된 도메인 id.\n *\n * 멤버십(`users_domains`)과 역할 보유(`users_roles`)를 한 번의 조회로 함께 확인한다 —\n * 매 요청 통과하는 판정이라 왕복 횟수를 최소화한다. 결과가 비어 있으면 \"멤버가 아님\" 과\n * \"부여된 역할이 없음\" 을 구분할 수 없지만, 두 경우 모두 이 경로로는 체크인할 수 없으므로\n * 판정에는 차이가 없다.\n */\nasync function findGrantedRoleDomainIds(user: User, domainId: string, roleDomainIds: string[]): Promise<string[]> {\n if (!roleDomainIds.length) {\n return []\n }\n\n const rows = await getRepository(User)\n .createQueryBuilder('USER')\n .innerJoin('USER.domains', 'MEMBER_DOMAIN', 'MEMBER_DOMAIN.id = :domainId', { domainId })\n .innerJoin('USER.roles', 'ROLE')\n .innerJoin('ROLE.domain', 'ROLE_DOMAIN')\n .where('USER.id = :userId', { userId: user.id })\n .andWhere('ROLE_DOMAIN.id IN (:...roleDomainIds)', { roleDomainIds })\n .select('ROLE_DOMAIN.id', 'roleDomainId')\n .distinct(true)\n .getRawMany()\n\n return [...new Set<string>(rows.map(row => row.roleDomainId).filter(Boolean))]\n}\n\n/**\n * 파트너십 경로 — 이 도메인에 정의된 역할이 사용자의 소속 도메인에 grant 되어 있는지.\n * (공급자 도메인이 자기 역할을 고객 도메인에 grant 하고, 고객 사용자가 그 역할을 부여받는 구조)\n */\nasync function isPartnerGrantedDomain(user: User, domainId: string): Promise<boolean> {\n return await getRepository(GrantedRole)\n .createQueryBuilder('GRANTED_ROLE')\n .innerJoin('GRANTED_ROLE.role', 'ROLE')\n .innerJoin('ROLE.users', 'ROLE_USER')\n .innerJoin('ROLE_USER.domains', 'MEMBER_DOMAIN')\n .where('ROLE_USER.id = :userId', { userId: user.id })\n .andWhere('ROLE.domain = :domainId', { domainId })\n .andWhere('GRANTED_ROLE.domain = MEMBER_DOMAIN.id')\n .getExists()\n}\n\n/**\n * 도메인 하나에 대한 판정용 사실 수집 (핫패스).\n *\n * 통상적인 통과(그 도메인의 멤버로서 역할을 부여받은 사용자)는 **1회 조회**로 끝난다.\n * 파트너십·소유권 조회는 그것만으로 근거가 서지 않을 때만 병렬로 수행한다.\n *\n * @param domain 판정 대상 도메인. 상속·소유권 판정에 `parentId`·`owner` 가 필요하므로\n * `context.state.domain` 처럼 온전히 적재된 도메인을 넘겨야 한다.\n * @param stillNeeded 1차 사실만으로 근거가 섰는지 판단하는 콜백 (규칙 함수를 주입해 규칙의\n * 단일 정의를 유지한다).\n */\nexport async function collectCheckinFactsForDomain(\n user: User,\n domain: CheckinDomainCandidate,\n stillNeeded: (facts: CheckinFacts) => boolean\n): Promise<CheckinFacts> {\n /* 상속은 부모 1단까지 — shell 의 `applyDomainInheritanceFilter` 규약과 같다. */\n const inheritableDomainIds = [domain.id, domain.parentId].filter(Boolean) as string[]\n\n const grantedRoleDomainIds = await findGrantedRoleDomainIds(user, domain.id, inheritableDomainIds)\n\n const facts: CheckinFacts = {\n userId: user.id,\n memberDomainIds: grantedRoleDomainIds.length ? [domain.id] : [],\n roleDomainIds: grantedRoleDomainIds,\n partnerRoleDomainIds: [],\n ownedDomainIds: []\n }\n\n if (!stillNeeded(facts)) {\n return facts\n }\n\n const [partner, owner] = await Promise.all([\n isPartnerGrantedDomain(user, domain.id),\n isDomainOwner(domain, user)\n ])\n\n return {\n ...facts,\n /* 파트너 조회가 \"이 도메인에 정의된 역할 보유\" 까지 함께 확인하므로 역할 보유 사실도 채운다. */\n roleDomainIds: partner ? [...new Set([...grantedRoleDomainIds, domain.id])] : grantedRoleDomainIds,\n partnerRoleDomainIds: partner ? [domain.id] : [],\n ownedDomainIds: owner ? [domain.id] : []\n }\n}\n\n/** 전체 목록 판정용 사실 + 판정 대상 도메인(행까지 적재된 상태). */\nexport type UserCheckinCandidates = {\n facts: CheckinFacts\n candidates: Domain[]\n}\n\n/**\n * 사용자가 체크인할 수 있는 도메인 목록을 계산하기 위한 사실 + 후보 도메인 수집.\n * 로그인·체크인 화면 등 목록이 필요한 지점에서만 쓴다 (매 요청 경로에서는\n * `collectCheckinFactsForDomain` 을 쓴다).\n *\n * 조회 설계:\n * - 경로별(소속·소유·파트너) 조회가 **도메인 행을 직접** 반환한다. 후보 id 를 모은 뒤 다시\n * 적재하는 단계가 없어져 왕복이 한 묶음으로 줄었다.\n * - 네 조회는 서로 의존하지 않으므로 한 번에 병렬로 던진다.\n * - 관계를 적재한 엔티티(`findOne({relations})`)는 쓰지 않는다 — 판정에 필요한 것은 id 뿐인데\n * 엔티티 적재는 역할·도메인 전체를 hydration 한다.\n * - 판정 자체는 하지 않는다. 어떤 후보가 실제로 체크인 가능한지는 `checkinGrantsOf` 한 곳에서만\n * 결정한다.\n */\nexport async function collectCheckinCandidates(user: User): Promise<UserCheckinCandidates> {\n const [memberDomains, roleDomainIds, ownedDomains, partnerRoleDomainIds] = await Promise.all([\n findMemberDomains(user),\n findRoleDomainIds(user),\n findOwnedDomains(user),\n findPartnerRoleDomainIds(user)\n ])\n\n /* 파트너 경로로 열린 도메인은 소속 도메인이 아니므로 별도로 적재한다 (해당 사용자가 있을 때만). */\n const partnerDomains: Domain[] = partnerRoleDomainIds.size\n ? await getRepository(Domain).find({ where: { id: In([...partnerRoleDomainIds]) } })\n : []\n\n const candidateById = new Map<string, Domain>()\n for (const domain of [...memberDomains, ...ownedDomains, ...partnerDomains]) {\n candidateById.set(domain.id, domain)\n }\n\n return {\n facts: {\n userId: user.id,\n memberDomainIds: new Set<string>(memberDomains.map((domain: Domain) => domain.id)),\n roleDomainIds,\n partnerRoleDomainIds,\n ownedDomainIds: new Set<string>(ownedDomains.map((domain: Domain) => domain.id))\n },\n candidates: [...candidateById.values()]\n }\n}\n\n/** 사용자가 소속된 도메인 (`users_domains`). */\nasync function findMemberDomains(user: User): Promise<Domain[]> {\n return await getRepository(Domain)\n .createQueryBuilder('DOMAIN')\n .where(qb => {\n const subQuery = qb\n .subQuery()\n .select('MEMBER_DOMAIN.id')\n .from(User, 'MEMBER_USER')\n .innerJoin('MEMBER_USER.domains', 'MEMBER_DOMAIN')\n .where('MEMBER_USER.id = :userId')\n .getQuery()\n return 'DOMAIN.id IN ' + subQuery\n })\n .setParameter('userId', user.id)\n .getMany()\n}\n\n/** 사용자가 소유한 도메인 — 대표 오너 캐시 컬럼과 `DomainOwner` 테이블 양쪽. */\nasync function findOwnedDomains(user: User): Promise<Domain[]> {\n // 주의: `Domain.owner` 는 varchar(사용자 id 문자열 캐시)이고 `DomainOwner.user` 는 uuid FK 다.\n // 두 비교에 **동일 파라미터를 재사용하면** Postgres 가 준비된 구문의 파라미터를 하나의\n // 타입으로만 추론하다 `operator does not exist: uuid = text` 로 실패한다. → 값은 같아도\n // owner(varchar) 는 :ownerId, user_id(uuid) 는 :userId 로 **파라미터를 분리**한다.\n return await getRepository(Domain)\n .createQueryBuilder('DOMAIN')\n .where('DOMAIN.owner = :ownerId')\n .orWhere(qb => {\n const subQuery = qb\n .subQuery()\n .select('OWNED_DOMAIN.id')\n .from(DomainOwner, 'DOMAIN_OWNER')\n .innerJoin('DOMAIN_OWNER.domain', 'OWNED_DOMAIN')\n .where('DOMAIN_OWNER.user = :userId')\n .getQuery()\n return 'DOMAIN.id IN ' + subQuery\n })\n .setParameter('ownerId', user.id)\n .setParameter('userId', user.id)\n .getMany()\n}\n\n/** 사용자가 보유한 역할이 정의된 도메인 id. */\nasync function findRoleDomainIds(user: User): Promise<Set<string>> {\n const rows = await getRepository(Role)\n .createQueryBuilder('ROLE')\n .innerJoin('ROLE.users', 'ROLE_USER')\n .innerJoin('ROLE.domain', 'ROLE_DOMAIN')\n .where('ROLE_USER.id = :userId', { userId: user.id })\n .select('ROLE_DOMAIN.id', 'roleDomainId')\n .distinct(true)\n .getRawMany()\n\n return new Set<string>(rows.map(row => row.roleDomainId).filter(Boolean))\n}\n\n/**\n * 파트너십 경로로 열린 도메인 id — 사용자가 보유한 역할 중, 그 역할이 사용자의 소속 도메인에\n * grant 된 것들의 정의 도메인. 역할 보유·grant·소속을 한 조회에서 함께 확인한다.\n */\nasync function findPartnerRoleDomainIds(user: User): Promise<Set<string>> {\n const rows = await getRepository(GrantedRole)\n .createQueryBuilder('GRANTED_ROLE')\n .innerJoin('GRANTED_ROLE.role', 'ROLE')\n .innerJoin('ROLE.domain', 'ROLE_DOMAIN')\n .innerJoin('ROLE.users', 'ROLE_USER')\n .innerJoin('ROLE_USER.domains', 'MEMBER_DOMAIN')\n .where('ROLE_USER.id = :userId', { userId: user.id })\n .andWhere('GRANTED_ROLE.domain = MEMBER_DOMAIN.id')\n .select('ROLE_DOMAIN.id', 'roleDomainId')\n .distinct(true)\n .getRawMany()\n\n return new Set<string>(rows.map(row => row.roleDomainId).filter(Boolean))\n}\n\n/** 사용자가 보유한 역할 중 주어진 권한을 가진 역할이 정의된 도메인 id 집합. */\nexport async function findRoleDomainIdsWithPrivilege(\n user: User,\n privilege: string,\n category: string\n): Promise<Set<string>> {\n const roles: Role[] = await getRepository(Role)\n .createQueryBuilder('ROLE')\n .innerJoin('ROLE.users', 'ROLE_USER')\n .innerJoin('ROLE.privileges', 'PRIVILEGE')\n .innerJoinAndSelect('ROLE.domain', 'ROLE_DOMAIN')\n .where('ROLE_USER.id = :userId', { userId: user.id })\n .andWhere('PRIVILEGE.name = :privilege', { privilege })\n .andWhere('PRIVILEGE.category = :category', { category })\n .getMany()\n\n return new Set<string>(roles.map((role: Role) => role.domain?.id).filter(Boolean))\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@things-factory/auth-base",
|
|
3
|
-
"version": "10.0.
|
|
3
|
+
"version": "10.0.4",
|
|
4
4
|
"main": "dist-server/index.js",
|
|
5
5
|
"browser": "dist-client/index.js",
|
|
6
6
|
"things-factory": true,
|
|
@@ -34,9 +34,9 @@
|
|
|
34
34
|
"@reduxjs/toolkit": "^2.2.5",
|
|
35
35
|
"@simplewebauthn/browser": "^13.0.0",
|
|
36
36
|
"@simplewebauthn/server": "^13.0.0",
|
|
37
|
-
"@things-factory/email-base": "^10.0.
|
|
37
|
+
"@things-factory/email-base": "^10.0.4",
|
|
38
38
|
"@things-factory/env": "^10.0.0",
|
|
39
|
-
"@things-factory/shell": "^10.0.
|
|
39
|
+
"@things-factory/shell": "^10.0.4",
|
|
40
40
|
"@things-factory/utils": "^10.0.0",
|
|
41
41
|
"@types/webappsec-credential-management": "^0.6.9",
|
|
42
42
|
"jsonwebtoken": "^9.0.0",
|
|
@@ -48,5 +48,5 @@
|
|
|
48
48
|
"passport-jwt": "^4.0.0",
|
|
49
49
|
"passport-local": "^1.0.0"
|
|
50
50
|
},
|
|
51
|
-
"gitHead": "
|
|
51
|
+
"gitHead": "c8f6dcfea3ffe7a9dd2a7195e83e2e4e201fd5ea"
|
|
52
52
|
}
|