@headless-lms/adapter-db 0.3.0 → 0.5.0
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/dist/client.d.ts +8 -2
- package/dist/client.js +10 -2
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +10 -4
- package/dist/index.js +9 -3
- package/dist/repositories/admin.d.ts +12 -4
- package/dist/repositories/admin.js +165 -23
- package/dist/repositories/admin.js.map +1 -1
- package/dist/repositories/assets.d.ts +2 -2
- package/dist/repositories/assets.js +11 -5
- package/dist/repositories/assets.js.map +1 -1
- package/dist/repositories/automations.d.ts +0 -1
- package/dist/repositories/automations.js +2 -18
- package/dist/repositories/automations.js.map +1 -1
- package/dist/repositories/chats.d.ts +14 -5
- package/dist/repositories/chats.js +194 -85
- package/dist/repositories/chats.js.map +1 -1
- package/dist/repositories/content.d.ts +24 -7
- package/dist/repositories/content.js +229 -48
- package/dist/repositories/content.js.map +1 -1
- package/dist/repositories/credentials.js +1 -1
- package/dist/repositories/devices.d.ts +17 -0
- package/dist/repositories/devices.js +75 -0
- package/dist/repositories/devices.js.map +1 -0
- package/dist/repositories/digest-runs.d.ts +14 -0
- package/dist/repositories/digest-runs.js +39 -0
- package/dist/repositories/digest-runs.js.map +1 -0
- package/dist/repositories/discussion.d.ts +7 -2
- package/dist/repositories/discussion.js +18 -7
- package/dist/repositories/discussion.js.map +1 -1
- package/dist/repositories/entitlements.d.ts +11 -9
- package/dist/repositories/entitlements.js +56 -59
- package/dist/repositories/entitlements.js.map +1 -1
- package/dist/repositories/event-reports.d.ts +16 -0
- package/dist/repositories/event-reports.js +199 -0
- package/dist/repositories/event-reports.js.map +1 -0
- package/dist/repositories/grant-covers.d.ts +4 -3
- package/dist/repositories/grant-covers.js +7 -4
- package/dist/repositories/grant-covers.js.map +1 -1
- package/dist/repositories/identity.js +1 -1
- package/dist/repositories/inserted-row.d.ts +5 -0
- package/dist/repositories/inserted-row.js +7 -0
- package/dist/repositories/inserted-row.js.map +1 -0
- package/dist/repositories/integrations.js +1 -1
- package/dist/repositories/member.d.ts +12 -4
- package/dist/repositories/member.js +19 -7
- package/dist/repositories/member.js.map +1 -1
- package/dist/repositories/members.d.ts +2 -5
- package/dist/repositories/members.js +39 -94
- package/dist/repositories/members.js.map +1 -1
- package/dist/repositories/mentions.d.ts +17 -0
- package/dist/repositories/mentions.js +55 -0
- package/dist/repositories/mentions.js.map +1 -0
- package/dist/repositories/notification-settings.d.ts +19 -0
- package/dist/repositories/notification-settings.js +67 -0
- package/dist/repositories/notification-settings.js.map +1 -0
- package/dist/repositories/notifications.d.ts +23 -16
- package/dist/repositories/notifications.js +152 -44
- package/dist/repositories/notifications.js.map +1 -1
- package/dist/repositories/org-user-profile.d.ts +0 -17
- package/dist/repositories/org-user-profile.js +0 -1
- package/dist/repositories/org-user-profile.js.map +1 -1
- package/dist/repositories/organizations.d.ts +4 -4
- package/dist/repositories/organizations.js +25 -47
- package/dist/repositories/organizations.js.map +1 -1
- package/dist/repositories/outbox.d.ts +5 -1
- package/dist/repositories/outbox.js +12 -3
- package/dist/repositories/outbox.js.map +1 -1
- package/dist/repositories/posts.d.ts +4 -8
- package/dist/repositories/posts.js +26 -29
- package/dist/repositories/posts.js.map +1 -1
- package/dist/repositories/progress.js +1 -1
- package/dist/repositories/roles.d.ts +35 -0
- package/dist/repositories/roles.js +244 -0
- package/dist/repositories/roles.js.map +1 -0
- package/dist/repositories/settings.js +1 -1
- package/dist/repositories/subscriptions.d.ts +18 -0
- package/dist/repositories/subscriptions.js +72 -0
- package/dist/repositories/subscriptions.js.map +1 -0
- package/dist/schema/assets.d.ts +17 -0
- package/dist/schema/assets.js +1 -0
- package/dist/schema/assets.js.map +1 -1
- package/dist/schema/automations.d.ts +524 -19
- package/dist/schema/automations.js +73 -7
- package/dist/schema/automations.js.map +1 -1
- package/dist/schema/content.d.ts +437 -3
- package/dist/schema/content.js +70 -3
- package/dist/schema/content.js.map +1 -1
- package/dist/schema/discussion.d.ts +295 -128
- package/dist/schema/discussion.js +63 -26
- package/dist/schema/discussion.js.map +1 -1
- package/dist/schema/identity.d.ts +109 -109
- package/dist/schema/identity.js +28 -70
- package/dist/schema/identity.js.map +1 -1
- package/dist/schema/index.d.ts +7 -6
- package/dist/schema/index.js +16 -7
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/organizations.d.ts +5 -22
- package/dist/schema/organizations.js +1 -13
- package/dist/schema/organizations.js.map +1 -1
- package/dist/schema/roles.d.ts +369 -0
- package/dist/schema/roles.js +71 -0
- package/dist/schema/roles.js.map +1 -0
- package/drizzle/0011_digest_runs.sql +8 -0
- package/drizzle/0012_subscriptions.sql +16 -0
- package/drizzle/0013_mentions.sql +10 -0
- package/drizzle/0014_notification_settings_devices.sql +36 -0
- package/drizzle/0015_automations_drop_source.sql +2 -0
- package/drizzle/0016_notifications_automations.sql +48 -0
- package/drizzle/0017_configurable_roles.sql +38 -0
- package/drizzle/0018_messages_client_id.sql +2 -0
- package/drizzle/0019_events.sql +44 -0
- package/drizzle/meta/0011_snapshot.json +8868 -0
- package/drizzle/meta/0012_snapshot.json +8873 -0
- package/drizzle/meta/0013_snapshot.json +8943 -0
- package/drizzle/meta/0014_snapshot.json +9305 -0
- package/drizzle/meta/0015_snapshot.json +9270 -0
- package/drizzle/meta/0016_snapshot.json +9230 -0
- package/drizzle/meta/0017_snapshot.json +9518 -0
- package/drizzle/meta/0018_snapshot.json +9557 -0
- package/drizzle/meta/0019_snapshot.json +9919 -0
- package/drizzle/meta/_journal.json +63 -0
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entitlements.js","names":[],"sources":["../../src/repositories/entitlements.ts"],"sourcesContent":["import { and, asc, desc, eq, gt, ilike, inArray, or, sql, type SQL } from \"drizzle-orm\";\nimport type { DbExecutor } from \"../client.js\";\nimport type {\n AccessFacts,\n EndingGrant,\n EndingGrantKey,\n EntitlementNotice,\n EntitlementsRepository,\n GrantKey,\n GrantTarget,\n InsertSpaceMemberInput,\n SpaceMembersQuery,\n} from \"@headless-lms/core/entitlements\";\nimport type {\n ContentType,\n Entitlement,\n EntitlementStatus,\n EntitlementsQuery,\n GrantEntitlementInput,\n Logger,\n Page,\n Role,\n SpaceMember,\n SpaceMemberStatus,\n} from \"@headless-lms/core/types\";\nimport {\n contentAccess,\n entitlementEndingSoonNotices,\n entitlementExpiryNotices,\n entitlements,\n orgUsers,\n spaceMembers,\n users,\n} from \"../schema/index.js\";\nimport {\n grantExpired,\n grantIsActive,\n communityMember,\n grantOpens,\n holdsCommunity,\n} from \"./grant-covers.js\";\nimport { bundles, communities, contentItems, courses, downloads } from \"../schema/content.js\";\nimport { noopLogger } from \"@headless-lms/core/shared/logger\";\nimport { ROLES, canModerate } from \"@headless-lms/core/organizations\";\nimport { translateDbErrors } from \"./pg-errors.js\";\n\ntype ResolvedStatus = \"active\" | \"expired\" | \"revoked\";\n\nconst MODERATING_ROLES: Role[] = ROLES.filter((role) => canModerate(role));\n\n// CASE expression: revoked beats everything; otherwise an elapsed expiry reads as\n// expired; otherwise active.\nconst derivedStatus = sql<ResolvedStatus>`case\n when ${entitlements.status} = 'revoked' then 'revoked'\n when ${grantExpired(entitlements.expiresAt)} then 'expired'\n else 'active'\nend`;\n\nfunction at(date: Date): SQL {\n return sql`${date.toISOString()}::timestamptz`;\n}\n\nconst expiryNoticed = sql`exists (\n select 1 from ${entitlementExpiryNotices}\n where ${entitlementExpiryNotices.orgId} = ${entitlements.orgId}\n and ${entitlementExpiryNotices.entitlementId} = ${entitlements.id}\n and ${entitlementExpiryNotices.expiresAt} = ${entitlements.expiresAt}\n)`;\n\nconst endingSoonNoticed = sql`exists (\n select 1 from ${entitlementEndingSoonNotices}\n where ${entitlementEndingSoonNotices.orgId} = ${entitlements.orgId}\n and ${entitlementEndingSoonNotices.entitlementId} = ${entitlements.id}\n and ${entitlementEndingSoonNotices.communityId} = ${communities.id}\n and ${entitlementEndingSoonNotices.expiresAt} = ${entitlements.expiresAt}\n)`;\n\nconst opensPublishedCommunity = and(\n eq(communities.orgId, entitlements.orgId),\n eq(communities.status, \"published\"),\n grantOpens(entitlements, sql`${communities.id}`, \"community\"),\n);\n\n// Display name of the grant's target, whatever it is. One LEFT JOIN per concrete\n// content table plus one for bundles; exactly one hits per row (a grant targets\n// either a bundle or a single content item), so the COALESCE picks the single\n// non-null title. Extended per new content type.\nconst contentTitle = sql<string>`coalesce(${courses.title}, ${downloads.title}, ${communities.title}, ${bundles.name})`;\n\n// Sortable columns by the client-facing field name. `status` sorts on the derived\n// expression so the ordering matches the displayed value; `contentTitle` on the\n// coalesced join expression.\nconst sortColumns = {\n firstName: users.firstName,\n lastName: users.lastName,\n email: users.email,\n contentTitle,\n status: derivedStatus,\n grantedAt: entitlements.grantedAt,\n expiresAt: entitlements.expiresAt,\n source: entitlements.source,\n} as const;\n\nconst selection = {\n orgId: entitlements.orgId,\n id: entitlements.id,\n orgUserId: entitlements.orgUserId,\n bundleId: entitlements.bundleId,\n contentId: entitlements.contentId,\n status: entitlements.status,\n source: entitlements.source,\n grantedAt: entitlements.grantedAt,\n expiresAt: entitlements.expiresAt,\n createdAt: entitlements.createdAt,\n updatedAt: entitlements.updatedAt,\n} as const;\n\ninterface Row {\n orgId: string;\n id: string;\n orgUserId: string;\n bundleId: string | null;\n contentId: string | null;\n status: EntitlementStatus;\n source: string;\n grantedAt: Date;\n expiresAt: Date | null;\n createdAt: Date;\n updatedAt: Date;\n}\n\nfunction toEntitlement(row: Row): Entitlement {\n return {\n orgId: row.orgId,\n id: row.id,\n orgUserId: row.orgUserId,\n bundleId: row.bundleId,\n contentId: row.contentId,\n status: row.status,\n source: row.source,\n grantedAt: row.grantedAt,\n expiresAt: row.expiresAt,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n\nexport class DrizzleEntitlementsRepository implements EntitlementsRepository {\n constructor(\n private readonly db: DbExecutor,\n private readonly logger: Logger = noopLogger,\n ) {}\n\n /** entitlements → org_users + the grant's target: content_items (type) and one\n * LEFT JOIN per concrete content table (title), or bundles (name). Both target\n * joins are LEFT — a row carries one of the two, never both. */\n private joined(where: SQL | undefined) {\n return this.db\n .select(selection)\n .from(entitlements)\n .innerJoin(\n orgUsers,\n and(eq(orgUsers.orgId, entitlements.orgId), eq(orgUsers.id, entitlements.orgUserId)),\n )\n .innerJoin(users, eq(users.id, orgUsers.userId))\n .leftJoin(\n contentItems,\n and(\n eq(contentItems.orgId, entitlements.orgId),\n eq(contentItems.id, entitlements.contentId),\n ),\n )\n .leftJoin(\n bundles,\n and(eq(bundles.orgId, entitlements.orgId), eq(bundles.id, entitlements.bundleId)),\n )\n .leftJoin(\n courses,\n and(eq(courses.orgId, entitlements.orgId), eq(courses.id, entitlements.contentId)),\n )\n .leftJoin(\n downloads,\n and(eq(downloads.orgId, entitlements.orgId), eq(downloads.id, entitlements.contentId)),\n )\n .leftJoin(\n communities,\n and(eq(communities.orgId, entitlements.orgId), eq(communities.id, entitlements.contentId)),\n )\n .where(where);\n }\n\n async list(orgId: string, query: EntitlementsQuery): Promise<Page<Entitlement>> {\n const conditions: SQL[] = [eq(entitlements.orgId, orgId)];\n if (query.status) {\n conditions.push(sql`${derivedStatus} = ${query.status}`);\n }\n if (query.source) {\n conditions.push(eq(entitlements.source, query.source));\n }\n if (query.orgUserId) {\n conditions.push(eq(entitlements.orgUserId, query.orgUserId));\n }\n if (query.contentId) {\n conditions.push(eq(entitlements.contentId, query.contentId));\n }\n if (query.bundleId) {\n conditions.push(eq(entitlements.bundleId, query.bundleId));\n }\n if (query.type) {\n conditions.push(eq(contentItems.type, query.type));\n }\n if (query.search) {\n const pattern = `%${query.search}%`;\n conditions.push(\n or(\n ilike(users.firstName, pattern),\n ilike(users.lastName, pattern),\n ilike(users.email, pattern),\n ilike(contentTitle, pattern),\n ) as SQL,\n );\n }\n const where = and(...conditions);\n\n // Sort: `-field` for descending; default to most-recently granted first.\n let orderBy: SQL;\n if (query.sort) {\n const isDesc = query.sort.startsWith(\"-\");\n const field = (isDesc ? query.sort.slice(1) : query.sort) as keyof typeof sortColumns;\n const col = sortColumns[field] ?? entitlements.grantedAt;\n orderBy = isDesc ? desc(col) : asc(col);\n } else {\n orderBy = desc(entitlements.grantedAt);\n }\n\n const offset = (query.page - 1) * query.pageSize;\n\n const rows = await this.joined(where).orderBy(orderBy).limit(query.pageSize).offset(offset);\n\n const [{ total } = { total: 0 }] = await this.db\n .select({ total: sql<number>`cast(count(*) as int)` })\n .from(entitlements)\n .innerJoin(\n orgUsers,\n and(eq(orgUsers.orgId, entitlements.orgId), eq(orgUsers.id, entitlements.orgUserId)),\n )\n .innerJoin(users, eq(users.id, orgUsers.userId))\n .leftJoin(\n contentItems,\n and(\n eq(contentItems.orgId, entitlements.orgId),\n eq(contentItems.id, entitlements.contentId),\n ),\n )\n .leftJoin(\n bundles,\n and(eq(bundles.orgId, entitlements.orgId), eq(bundles.id, entitlements.bundleId)),\n )\n .leftJoin(\n courses,\n and(eq(courses.orgId, entitlements.orgId), eq(courses.id, entitlements.contentId)),\n )\n .leftJoin(\n downloads,\n and(eq(downloads.orgId, entitlements.orgId), eq(downloads.id, entitlements.contentId)),\n )\n .leftJoin(\n communities,\n and(eq(communities.orgId, entitlements.orgId), eq(communities.id, entitlements.contentId)),\n )\n .where(where);\n\n return {\n rows: rows.map(toEntitlement),\n total,\n page: query.page,\n pageSize: query.pageSize,\n };\n }\n\n /** A grant targets exactly one of a bundle or a content item (db check\n * constraint), so the upsert is inferred on whichever of the two partial\n * uniques the target uses. */\n async insert(orgId: string, input: GrantEntitlementInput): Promise<Entitlement> {\n const bundleId = input.bundleId ?? null;\n const contentId = input.contentId ?? null;\n const target = bundleId\n ? [entitlements.orgId, entitlements.orgUserId, entitlements.bundleId]\n : [entitlements.orgId, entitlements.orgUserId, entitlements.contentId];\n\n const [row] = await this.db\n .insert(entitlements)\n .values({\n orgId,\n orgUserId: input.orgUserId,\n bundleId,\n contentId,\n status: \"active\",\n source: \"manual\",\n grantedAt: new Date(),\n expiresAt: input.expiresAt,\n })\n .onConflictDoUpdate({\n target,\n set: {\n status: \"active\",\n source: \"manual\",\n grantedAt: new Date(),\n expiresAt: input.expiresAt,\n },\n })\n .returning(selection);\n if (!row) {\n throw new Error(\"failed to insert entitlement\");\n }\n return toEntitlement(row);\n }\n\n async insertIfAbsent(orgId: string, input: GrantEntitlementInput): Promise<Entitlement | null> {\n const bundleId = input.bundleId ?? null;\n const contentId = input.contentId ?? null;\n const target = bundleId\n ? [entitlements.orgId, entitlements.orgUserId, entitlements.bundleId]\n : [entitlements.orgId, entitlements.orgUserId, entitlements.contentId];\n const [row] = await this.db\n .insert(entitlements)\n .values({\n orgId,\n orgUserId: input.orgUserId,\n bundleId,\n contentId,\n status: \"active\",\n source: \"manual\",\n grantedAt: new Date(),\n expiresAt: input.expiresAt,\n })\n .onConflictDoNothing({ target })\n .returning(selection);\n return row ? toEntitlement(row) : null;\n }\n\n async lockGrant(orgId: string, key: GrantKey): Promise<Entitlement | null> {\n const target = key.bundleId\n ? eq(entitlements.bundleId, key.bundleId)\n : key.contentId\n ? eq(entitlements.contentId, key.contentId)\n : undefined;\n if (!target) {\n return null;\n }\n const [row] = await this.db\n .select(selection)\n .from(entitlements)\n .where(and(eq(entitlements.orgId, orgId), eq(entitlements.orgUserId, key.orgUserId), target))\n .for(\"update\")\n .limit(1);\n return row ? toEntitlement(row) : null;\n }\n\n async setExpiresAt(\n orgId: string,\n id: string,\n expiresAt: Date | null,\n ): Promise<Entitlement | null> {\n const [row] = await this.db\n .update(entitlements)\n .set({ expiresAt })\n .where(and(eq(entitlements.orgId, orgId), eq(entitlements.id, id)))\n .returning(selection);\n return row ? toEntitlement(row) : null;\n }\n\n async setStatus(\n orgId: string,\n id: string,\n status: \"active\" | \"revoked\",\n ): Promise<Entitlement | null> {\n const [row] = await this.db\n .update(entitlements)\n .set({ status })\n .where(and(eq(entitlements.orgId, orgId), eq(entitlements.id, id)))\n .returning(selection);\n if (!row) {\n return null;\n }\n return toEntitlement(row);\n }\n\n async hasAccess(orgId: string, orgUserId: string, contentId: string): Promise<boolean> {\n const rows = await this.db\n .select({ id: contentAccess.entitlementId })\n .from(contentAccess)\n .where(\n and(\n eq(contentAccess.orgId, orgId),\n eq(contentAccess.orgUserId, orgUserId),\n eq(contentAccess.contentId, contentId),\n ),\n )\n .limit(1);\n return rows.length > 0;\n }\n\n async listAccessibleContent(\n orgId: string,\n orgUserId: string,\n type?: ContentType,\n ): Promise<string[]> {\n const rows = await this.db\n .selectDistinct({ contentId: contentAccess.contentId })\n .from(contentAccess)\n .innerJoin(\n contentItems,\n and(\n eq(contentItems.orgId, contentAccess.orgId),\n eq(contentItems.id, contentAccess.contentId),\n ),\n )\n .where(\n and(\n eq(contentAccess.orgId, orgId),\n eq(contentAccess.orgUserId, orgUserId),\n type ? eq(contentItems.type, type) : undefined,\n ),\n );\n return rows.map((r) => r.contentId);\n }\n\n hasCommunityAccess(orgId: string, orgUserId: string, communityId: string): Promise<boolean> {\n return this.holds(holdsCommunity(sql`${orgId}`, sql`${orgUserId}`, sql`${communityId}`));\n }\n\n private async holds(predicate: SQL): Promise<boolean> {\n const result = await this.db.execute<{ covered: boolean }>(sql`select ${predicate} as covered`);\n const rows = Array.isArray(result) ? result : result.rows;\n return rows[0]?.covered === true;\n }\n\n async communitiesOpenedBy(orgId: string, grant: GrantTarget): Promise<string[]> {\n if (!grant.contentId && !grant.bundleId) {\n return [];\n }\n const target = {\n orgId: sql`${orgId}`,\n contentId: sql`${grant.contentId ?? null}::text`,\n bundleId: sql`${grant.bundleId ?? null}::text`,\n };\n const rows = await this.db\n .select({ id: communities.id })\n .from(communities)\n .where(\n and(\n eq(communities.orgId, orgId),\n eq(communities.status, \"published\"),\n grantOpens(target, sql`${communities.id}`, \"community\"),\n ),\n )\n .orderBy(asc(communities.id));\n return rows.map((row) => row.id);\n }\n\n async accessAmong(\n orgId: string,\n communityId: string,\n spaceId: string | null,\n orgUserIds: string[],\n ): Promise<AccessFacts[]> {\n if (orgUserIds.length === 0) {\n return [];\n }\n const activeInSpace = spaceId\n ? sql<boolean>`exists (\n select 1 from space_members sm\n where sm.org_id = ${sql`${orgUsers.orgId}`}\n and sm.space_id = ${spaceId}\n and sm.org_user_id = ${sql`${orgUsers.id}`}\n and sm.status = 'active'\n )`\n : sql<boolean>`false`;\n const rows = await this.db\n .select({\n orgUserId: orgUsers.id,\n role: orgUsers.role,\n member: sql<boolean>`${communityMember(sql`${orgUsers.orgId}`, sql`${orgUsers.id}`, sql`${orgUsers.role}`, sql`${communityId}`)}`,\n activeInSpace,\n })\n .from(orgUsers)\n .where(and(eq(orgUsers.orgId, orgId), inArray(orgUsers.id, orgUserIds)));\n return rows.map((row) => ({\n orgUserId: row.orgUserId,\n role: row.role as Role,\n member: row.member === true,\n activeInSpace: row.activeInSpace === true,\n }));\n }\n\n async findSpaceMember(\n orgId: string,\n spaceId: string,\n orgUserId: string,\n ): Promise<SpaceMember | null> {\n const [row] = await this.db\n .select()\n .from(spaceMembers)\n .where(\n and(\n eq(spaceMembers.orgId, orgId),\n eq(spaceMembers.spaceId, spaceId),\n eq(spaceMembers.orgUserId, orgUserId),\n ),\n )\n .limit(1);\n return row ? toSpaceMember(row) : null;\n }\n\n async listSpaceMembersFor(\n orgId: string,\n orgUserId: string,\n communityId: string,\n ): Promise<SpaceMember[]> {\n const rows = await this.db\n .select()\n .from(spaceMembers)\n .where(\n and(\n eq(spaceMembers.orgId, orgId),\n eq(spaceMembers.orgUserId, orgUserId),\n eq(spaceMembers.communityId, communityId),\n ),\n );\n return rows.map(toSpaceMember);\n }\n\n async lockSpaceMember(\n orgId: string,\n spaceId: string,\n orgUserId: string,\n ): Promise<SpaceMember | null> {\n const [row] = await this.db\n .select()\n .from(spaceMembers)\n .where(\n and(\n eq(spaceMembers.orgId, orgId),\n eq(spaceMembers.spaceId, spaceId),\n eq(spaceMembers.orgUserId, orgUserId),\n ),\n )\n .for(\"update\")\n .limit(1);\n return row ? toSpaceMember(row) : null;\n }\n\n async listSpaceMembers(\n orgId: string,\n spaceId: string,\n query: SpaceMembersQuery,\n ): Promise<Page<SpaceMember>> {\n const where = and(\n eq(spaceMembers.orgId, orgId),\n eq(spaceMembers.spaceId, spaceId),\n query.statuses ? inArray(spaceMembers.status, query.statuses) : undefined,\n );\n const rows = await this.db\n .select()\n .from(spaceMembers)\n .where(where)\n .orderBy(asc(spaceMembers.createdAt), asc(spaceMembers.id))\n .limit(query.pageSize)\n .offset((query.page - 1) * query.pageSize);\n const [{ total } = { total: 0 }] = await this.db\n .select({ total: sql<number>`cast(count(*) as int)` })\n .from(spaceMembers)\n .where(where);\n return { rows: rows.map(toSpaceMember), total, page: query.page, pageSize: query.pageSize };\n }\n\n async insertSpaceMember(orgId: string, input: InsertSpaceMemberInput): Promise<SpaceMember> {\n const [row] = await this.db\n .insert(spaceMembers)\n .values({\n orgId,\n communityId: input.communityId,\n spaceId: input.spaceId,\n orgUserId: input.orgUserId,\n status: input.status,\n source: input.source,\n })\n .returning();\n if (!row) {\n throw new Error(\"failed to insert space member\");\n }\n return toSpaceMember(row);\n }\n\n async updateSpaceMemberStatus(\n orgId: string,\n id: string,\n status: SpaceMemberStatus,\n ): Promise<SpaceMember | null> {\n const [row] = await this.db\n .update(spaceMembers)\n .set({ status })\n .where(and(eq(spaceMembers.orgId, orgId), eq(spaceMembers.id, id)))\n .returning();\n return row ? toSpaceMember(row) : null;\n }\n\n async deleteSpaceMember(orgId: string, id: string): Promise<void> {\n await this.db\n .delete(spaceMembers)\n .where(and(eq(spaceMembers.orgId, orgId), eq(spaceMembers.id, id)));\n }\n\n async countActiveSpaceMembers(orgId: string, spaceId: string): Promise<number> {\n const [row] = await this.db\n .select({ total: sql<number>`cast(count(*) as int)` })\n .from(spaceMembers)\n .where(\n and(\n eq(spaceMembers.orgId, orgId),\n eq(spaceMembers.spaceId, spaceId),\n eq(spaceMembers.status, \"active\"),\n holdsCommunity(\n sql`${spaceMembers.orgId}`,\n sql`${spaceMembers.orgUserId}`,\n sql`${spaceMembers.communityId}`,\n ),\n ),\n );\n return row?.total ?? 0;\n }\n\n async activeSpaceIds(orgId: string, orgUserId: string, communityId: string): Promise<string[]> {\n const rows = await this.db\n .select({ spaceId: spaceMembers.spaceId })\n .from(spaceMembers)\n .where(\n and(\n eq(spaceMembers.orgId, orgId),\n eq(spaceMembers.orgUserId, orgUserId),\n eq(spaceMembers.communityId, communityId),\n eq(spaceMembers.status, \"active\"),\n ),\n );\n return rows.map((row) => row.spaceId);\n }\n\n async listGrantsExpiredBy(\n orgId: string,\n now: Date,\n limit: number,\n afterId?: string,\n ): Promise<Entitlement[]> {\n const rows = await this.db\n .select(selection)\n .from(entitlements)\n .where(\n and(\n this.expiredUnnoticed(orgId, now),\n afterId === undefined ? undefined : gt(entitlements.id, afterId),\n ),\n )\n .orderBy(asc(entitlements.id))\n .limit(limit);\n return rows.map(toEntitlement);\n }\n\n async listGrantsEndingBetween(\n orgId: string,\n after: Date,\n until: Date,\n limit: number,\n afterKey?: EndingGrantKey,\n ): Promise<EndingGrant[]> {\n const rows = await this.db\n .select({ ...selection, communityId: communities.id })\n .from(entitlements)\n .innerJoin(communities, opensPublishedCommunity)\n .where(\n and(\n this.endingUnnoticed(orgId, after, until),\n afterKey\n ? sql`(${entitlements.id}, ${communities.id}) > (${afterKey.entitlementId}, ${afterKey.communityId})`\n : undefined,\n ),\n )\n .orderBy(asc(entitlements.id), asc(communities.id))\n .limit(limit);\n return rows.map(({ communityId, ...grant }) => ({\n grant: toEntitlement(grant),\n communityId,\n }));\n }\n\n async hasDueNotices(orgId: string, now: Date, until: Date): Promise<boolean> {\n const expired = this.db\n .select({ id: entitlements.id })\n .from(entitlements)\n .where(this.expiredUnnoticed(orgId, now));\n const ending = this.db\n .select({ id: entitlements.id })\n .from(entitlements)\n .innerJoin(communities, opensPublishedCommunity)\n .where(this.endingUnnoticed(orgId, now, until));\n return this.holds(sql`(exists (${expired}) or exists (${ending}))`);\n }\n\n async claimNotice(orgId: string, notice: EntitlementNotice, now: Date): Promise<boolean> {\n const statement =\n notice.kind === \"expired\"\n ? sql`insert into ${entitlementExpiryNotices} (\"org_id\", \"entitlement_id\", \"expires_at\", \"notified_at\")\n values (${orgId}, ${notice.entitlementId}, ${at(notice.expiresAt)}, ${at(now)})\n on conflict do nothing\n returning \"entitlement_id\"`\n : sql`insert into ${entitlementEndingSoonNotices} (\"org_id\", \"entitlement_id\", \"community_id\", \"expires_at\", \"notified_at\")\n values (${orgId}, ${notice.entitlementId}, ${notice.communityId}, ${at(notice.expiresAt)}, ${at(now)})\n on conflict do nothing\n returning \"entitlement_id\"`;\n const result = await this.db.execute<{ entitlement_id: string }>(statement);\n const rows = Array.isArray(result) ? result : result.rows;\n return rows.length > 0;\n }\n\n private expiredUnnoticed(orgId: string, now: Date): SQL {\n return and(\n eq(entitlements.orgId, orgId),\n eq(entitlements.status, \"active\"),\n grantExpired(entitlements.expiresAt, at(now)),\n sql`not ${expiryNoticed}`,\n )!;\n }\n\n private endingUnnoticed(orgId: string, after: Date, until: Date): SQL {\n return and(\n eq(entitlements.orgId, orgId),\n grantIsActive(entitlements, at(after)),\n grantExpired(entitlements.expiresAt, at(until)),\n sql`not ${endingSoonNoticed}`,\n )!;\n }\n\n async communityModeratorIds(orgId: string, communityId: string): Promise<string[]> {\n const rows = await this.db\n .select({ id: orgUsers.id })\n .from(orgUsers)\n .where(\n and(\n eq(orgUsers.orgId, orgId),\n inArray(orgUsers.role, MODERATING_ROLES),\n communityMember(\n sql`${orgUsers.orgId}`,\n sql`${orgUsers.id}`,\n sql`${orgUsers.role}`,\n sql`${communityId}`,\n ),\n ),\n )\n .orderBy(asc(orgUsers.id));\n return rows.map((row) => row.id);\n }\n\n async communityMemberIds(orgId: string, communityId: string): Promise<string[]> {\n const rows = await this.db\n .select({ id: orgUsers.id })\n .from(orgUsers)\n .where(\n and(\n eq(orgUsers.orgId, orgId),\n communityMember(\n sql`${orgUsers.orgId}`,\n sql`${orgUsers.id}`,\n sql`${orgUsers.role}`,\n sql`${communityId}`,\n ),\n ),\n )\n .orderBy(asc(orgUsers.id));\n return rows.map((row) => row.id);\n }\n\n async communityIdsOpenedFor(orgId: string, orgUserId: string): Promise<string[]> {\n const rows = await this.db\n .selectDistinct({ id: communities.id })\n .from(communities)\n .where(\n and(\n eq(communities.orgId, orgId),\n holdsCommunity(sql`${communities.orgId}`, sql`${orgUserId}`, sql`${communities.id}`),\n ),\n )\n .orderBy(asc(communities.id));\n return rows.map((row) => row.id);\n }\n}\n\nfunction toSpaceMember(row: {\n orgId: string;\n id: string;\n communityId: string;\n spaceId: string;\n orgUserId: string;\n status: SpaceMemberStatus;\n source: \"request\" | \"invite\" | \"manual\";\n createdAt: Date;\n updatedAt: Date;\n}): SpaceMember {\n return {\n orgId: row.orgId,\n id: row.id,\n communityId: row.communityId,\n spaceId: row.spaceId,\n orgUserId: row.orgUserId,\n status: row.status,\n source: row.source,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n\ntranslateDbErrors(DrizzleEntitlementsRepository);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"entitlements.js","names":[],"sources":["../../src/repositories/entitlements.ts"],"sourcesContent":["import { and, asc, desc, eq, gt, ilike, inArray, or, sql, type SQL } from \"drizzle-orm\";\nimport type { DbExecutor } from \"../client.js\";\nimport type {\n AccessFacts,\n CommunityReaders,\n EndingGrant,\n EndingGrantKey,\n EntitlementNotice,\n EntitlementsRepository,\n GrantKey,\n GrantTarget,\n InsertGrantInput,\n InsertSpaceMemberInput,\n SpaceMembersQuery,\n} from \"@headless-lms/core/entitlements\";\nimport type {\n ContentType,\n Entitlement,\n EntitlementStatus,\n EntitlementsQuery,\n Logger,\n Page,\n SpaceMember,\n SpaceMemberStatus,\n} from \"@headless-lms/core/types\";\nimport {\n contentAccess,\n entitlementEndingSoonNotices,\n entitlementExpiryNotices,\n entitlements,\n orgUsers,\n spaceMembers,\n users,\n} from \"../schema/index.js\";\nimport {\n grantExpired,\n grantIsActive,\n communityMember,\n grantOpens,\n holdsCommunity,\n oneOf,\n} from \"./grant-covers.js\";\nimport {\n bundles,\n communities,\n contentItems,\n courses,\n downloads,\n events,\n} from \"../schema/content.js\";\nimport { noopLogger } from \"@headless-lms/core/shared/logger\";\nimport { pgErrorFields, translateDbErrors } from \"./pg-errors.js\";\nimport { NotFoundError } from \"@headless-lms/core/shared/errors\";\n\ntype ResolvedStatus = \"active\" | \"expired\" | \"revoked\";\n\n// CASE expression: revoked beats everything; otherwise an elapsed expiry reads as\n// expired; otherwise active.\nconst derivedStatus = sql<ResolvedStatus>`case\n when ${entitlements.status} = 'revoked' then 'revoked'\n when ${grantExpired(entitlements.expiresAt)} then 'expired'\n else 'active'\nend`;\n\nfunction at(date: Date): SQL {\n return sql`${date.toISOString()}::timestamptz`;\n}\n\nconst expiryNoticed = sql`exists (\n select 1 from ${entitlementExpiryNotices}\n where ${entitlementExpiryNotices.orgId} = ${entitlements.orgId}\n and ${entitlementExpiryNotices.entitlementId} = ${entitlements.id}\n and ${entitlementExpiryNotices.expiresAt} = ${entitlements.expiresAt}\n)`;\n\nconst endingSoonNoticed = sql`exists (\n select 1 from ${entitlementEndingSoonNotices}\n where ${entitlementEndingSoonNotices.orgId} = ${entitlements.orgId}\n and ${entitlementEndingSoonNotices.entitlementId} = ${entitlements.id}\n and ${entitlementEndingSoonNotices.communityId} = ${communities.id}\n and ${entitlementEndingSoonNotices.expiresAt} = ${entitlements.expiresAt}\n)`;\n\nconst opensPublishedCommunity = and(\n eq(communities.orgId, entitlements.orgId),\n eq(communities.status, \"published\"),\n grantOpens(entitlements, sql`${communities.id}`, \"community\"),\n);\n\n// Display name of the grant's target, whatever it is. One LEFT JOIN per concrete\n// content table plus one for bundles; exactly one hits per row (a grant targets\n// either a bundle or a single content item), so the COALESCE picks the single\n// non-null title. Extended per new content type.\nconst contentTitle = sql<string>`coalesce(${courses.title}, ${downloads.title}, ${communities.title}, ${events.title}, ${bundles.name})`;\n\n// Sortable columns by the client-facing field name. `status` sorts on the derived\n// expression so the ordering matches the displayed value; `contentTitle` on the\n// coalesced join expression.\nconst sortColumns = {\n firstName: users.firstName,\n lastName: users.lastName,\n email: users.email,\n contentTitle,\n status: derivedStatus,\n grantedAt: entitlements.grantedAt,\n expiresAt: entitlements.expiresAt,\n source: entitlements.source,\n} as const;\n\nconst selection = {\n orgId: entitlements.orgId,\n id: entitlements.id,\n orgUserId: entitlements.orgUserId,\n bundleId: entitlements.bundleId,\n contentId: entitlements.contentId,\n status: entitlements.status,\n source: entitlements.source,\n grantedAt: entitlements.grantedAt,\n expiresAt: entitlements.expiresAt,\n createdAt: entitlements.createdAt,\n updatedAt: entitlements.updatedAt,\n} as const;\n\ninterface Row {\n orgId: string;\n id: string;\n orgUserId: string;\n bundleId: string | null;\n contentId: string | null;\n status: EntitlementStatus;\n source: string;\n grantedAt: Date;\n expiresAt: Date | null;\n createdAt: Date;\n updatedAt: Date;\n}\n\nfunction toEntitlement(row: Row): Entitlement {\n return {\n orgId: row.orgId,\n id: row.id,\n orgUserId: row.orgUserId,\n bundleId: row.bundleId,\n contentId: row.contentId,\n status: row.status,\n source: row.source,\n grantedAt: row.grantedAt,\n expiresAt: row.expiresAt,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n\nexport class DrizzleEntitlementsRepository implements EntitlementsRepository {\n constructor(\n private readonly db: DbExecutor,\n private readonly logger: Logger = noopLogger,\n ) {}\n\n /** entitlements → org_users + the grant's target: content_items (type) and one\n * LEFT JOIN per concrete content table (title), or bundles (name). Both target\n * joins are LEFT — a row carries one of the two, never both. */\n private joined(where: SQL | undefined) {\n return this.db\n .select(selection)\n .from(entitlements)\n .innerJoin(\n orgUsers,\n and(eq(orgUsers.orgId, entitlements.orgId), eq(orgUsers.id, entitlements.orgUserId)),\n )\n .innerJoin(users, eq(users.id, orgUsers.userId))\n .leftJoin(\n contentItems,\n and(\n eq(contentItems.orgId, entitlements.orgId),\n eq(contentItems.id, entitlements.contentId),\n ),\n )\n .leftJoin(\n bundles,\n and(eq(bundles.orgId, entitlements.orgId), eq(bundles.id, entitlements.bundleId)),\n )\n .leftJoin(\n courses,\n and(eq(courses.orgId, entitlements.orgId), eq(courses.id, entitlements.contentId)),\n )\n .leftJoin(\n downloads,\n and(eq(downloads.orgId, entitlements.orgId), eq(downloads.id, entitlements.contentId)),\n )\n .leftJoin(\n communities,\n and(eq(communities.orgId, entitlements.orgId), eq(communities.id, entitlements.contentId)),\n )\n .leftJoin(\n events,\n and(eq(events.orgId, entitlements.orgId), eq(events.id, entitlements.contentId)),\n )\n .where(where);\n }\n\n async list(orgId: string, query: EntitlementsQuery): Promise<Page<Entitlement>> {\n const conditions: SQL[] = [eq(entitlements.orgId, orgId)];\n if (query.status) {\n conditions.push(sql`${derivedStatus} = ${query.status}`);\n }\n if (query.source) {\n conditions.push(eq(entitlements.source, query.source));\n }\n if (query.orgUserId) {\n conditions.push(eq(entitlements.orgUserId, query.orgUserId));\n }\n if (query.contentId) {\n conditions.push(eq(entitlements.contentId, query.contentId));\n }\n if (query.bundleId) {\n conditions.push(eq(entitlements.bundleId, query.bundleId));\n }\n if (query.type) {\n conditions.push(eq(contentItems.type, query.type));\n }\n if (query.search) {\n const pattern = `%${query.search}%`;\n conditions.push(\n or(\n ilike(users.firstName, pattern),\n ilike(users.lastName, pattern),\n ilike(users.email, pattern),\n ilike(contentTitle, pattern),\n ) as SQL,\n );\n }\n const where = and(...conditions);\n\n // Sort: `-field` for descending; default to most-recently granted first.\n let orderBy: SQL;\n if (query.sort) {\n const isDesc = query.sort.startsWith(\"-\");\n const field = (isDesc ? query.sort.slice(1) : query.sort) as keyof typeof sortColumns;\n const col = sortColumns[field] ?? entitlements.grantedAt;\n orderBy = isDesc ? desc(col) : asc(col);\n } else {\n orderBy = desc(entitlements.grantedAt);\n }\n\n const offset = (query.page - 1) * query.pageSize;\n\n const rows = await this.joined(where).orderBy(orderBy).limit(query.pageSize).offset(offset);\n\n const [{ total } = { total: 0 }] = await this.db\n .select({ total: sql<number>`cast(count(*) as int)` })\n .from(entitlements)\n .innerJoin(\n orgUsers,\n and(eq(orgUsers.orgId, entitlements.orgId), eq(orgUsers.id, entitlements.orgUserId)),\n )\n .innerJoin(users, eq(users.id, orgUsers.userId))\n .leftJoin(\n contentItems,\n and(\n eq(contentItems.orgId, entitlements.orgId),\n eq(contentItems.id, entitlements.contentId),\n ),\n )\n .leftJoin(\n bundles,\n and(eq(bundles.orgId, entitlements.orgId), eq(bundles.id, entitlements.bundleId)),\n )\n .leftJoin(\n courses,\n and(eq(courses.orgId, entitlements.orgId), eq(courses.id, entitlements.contentId)),\n )\n .leftJoin(\n downloads,\n and(eq(downloads.orgId, entitlements.orgId), eq(downloads.id, entitlements.contentId)),\n )\n .leftJoin(\n communities,\n and(eq(communities.orgId, entitlements.orgId), eq(communities.id, entitlements.contentId)),\n )\n .leftJoin(\n events,\n and(eq(events.orgId, entitlements.orgId), eq(events.id, entitlements.contentId)),\n )\n .where(where);\n\n return {\n rows: rows.map(toEntitlement),\n total,\n page: query.page,\n pageSize: query.pageSize,\n };\n }\n\n /** A grant targets exactly one of a bundle or a content item (db check\n * constraint), so the upsert is inferred on whichever of the two partial\n * uniques the target uses. */\n async insert(orgId: string, input: InsertGrantInput): Promise<Entitlement> {\n const bundleId = input.bundleId ?? null;\n const contentId = input.contentId ?? null;\n const target = bundleId\n ? [entitlements.orgId, entitlements.orgUserId, entitlements.bundleId]\n : [entitlements.orgId, entitlements.orgUserId, entitlements.contentId];\n\n const [row] = await orgUserMustExist(\n input.orgUserId,\n this.db\n .insert(entitlements)\n .values({\n orgId,\n orgUserId: input.orgUserId,\n bundleId,\n contentId,\n status: \"active\",\n source: input.source,\n grantedAt: new Date(),\n expiresAt: input.expiresAt,\n })\n .onConflictDoUpdate({\n target,\n set: {\n status: \"active\",\n source: input.source,\n grantedAt: new Date(),\n expiresAt: input.expiresAt,\n },\n })\n .returning(selection),\n );\n if (!row) {\n throw new Error(\"failed to insert entitlement\");\n }\n return toEntitlement(row);\n }\n\n async lockGrant(orgId: string, key: GrantKey): Promise<Entitlement | null> {\n const target = key.bundleId\n ? eq(entitlements.bundleId, key.bundleId)\n : key.contentId\n ? eq(entitlements.contentId, key.contentId)\n : undefined;\n if (!target) {\n return null;\n }\n const [row] = await this.db\n .select(selection)\n .from(entitlements)\n .where(and(eq(entitlements.orgId, orgId), eq(entitlements.orgUserId, key.orgUserId), target))\n .for(\"update\")\n .limit(1);\n return row ? toEntitlement(row) : null;\n }\n\n async setStatus(\n orgId: string,\n id: string,\n status: \"active\" | \"revoked\",\n ): Promise<Entitlement | null> {\n const [row] = await this.db\n .update(entitlements)\n .set({ status })\n .where(and(eq(entitlements.orgId, orgId), eq(entitlements.id, id)))\n .returning(selection);\n if (!row) {\n return null;\n }\n return toEntitlement(row);\n }\n\n async hasAccess(orgId: string, orgUserId: string, contentId: string): Promise<boolean> {\n const rows = await this.db\n .select({ id: contentAccess.entitlementId })\n .from(contentAccess)\n .where(\n and(\n eq(contentAccess.orgId, orgId),\n eq(contentAccess.orgUserId, orgUserId),\n eq(contentAccess.contentId, contentId),\n ),\n )\n .limit(1);\n return rows.length > 0;\n }\n\n async listAccessibleContent(\n orgId: string,\n orgUserId: string,\n type?: ContentType,\n ): Promise<string[]> {\n const rows = await this.db\n .selectDistinct({ contentId: contentAccess.contentId })\n .from(contentAccess)\n .innerJoin(\n contentItems,\n and(\n eq(contentItems.orgId, contentAccess.orgId),\n eq(contentItems.id, contentAccess.contentId),\n ),\n )\n .where(\n and(\n eq(contentAccess.orgId, orgId),\n eq(contentAccess.orgUserId, orgUserId),\n type ? eq(contentItems.type, type) : undefined,\n ),\n );\n return rows.map((r) => r.contentId);\n }\n\n hasCommunityAccess(orgId: string, orgUserId: string, communityId: string): Promise<boolean> {\n return this.holds(holdsCommunity(sql`${orgId}`, sql`${orgUserId}`, sql`${communityId}`));\n }\n\n holdsAnyGrant(orgId: string, orgUserId: string): Promise<boolean> {\n return this.holds(sql`exists (\n select 1 from ${entitlements}\n where ${entitlements.orgId} = ${orgId}\n and ${entitlements.orgUserId} = ${orgUserId}\n and ${grantIsActive(entitlements)}\n )`);\n }\n\n async contentTypeOf(orgId: string, contentId: string): Promise<ContentType | null> {\n const [row] = await this.db\n .select({ type: contentItems.type })\n .from(contentItems)\n .where(and(eq(contentItems.orgId, orgId), eq(contentItems.id, contentId)))\n .limit(1);\n return (row?.type as ContentType | undefined) ?? null;\n }\n\n private async holds(predicate: SQL): Promise<boolean> {\n const result = await this.db.execute<{ covered: boolean }>(sql`select ${predicate} as covered`);\n const rows = Array.isArray(result) ? result : result.rows;\n return rows[0]?.covered === true;\n }\n\n async communitiesOpenedBy(orgId: string, grant: GrantTarget): Promise<string[]> {\n if (!grant.contentId && !grant.bundleId) {\n return [];\n }\n const target = {\n orgId: sql`${orgId}`,\n contentId: sql`${grant.contentId ?? null}::text`,\n bundleId: sql`${grant.bundleId ?? null}::text`,\n };\n const rows = await this.db\n .select({ id: communities.id })\n .from(communities)\n .where(\n and(\n eq(communities.orgId, orgId),\n eq(communities.status, \"published\"),\n grantOpens(target, sql`${communities.id}`, \"community\"),\n ),\n )\n .orderBy(asc(communities.id));\n return rows.map((row) => row.id);\n }\n\n async accessAmong(\n orgId: string,\n communityId: string,\n spaceId: string | null,\n orgUserIds: string[],\n ): Promise<AccessFacts[]> {\n if (orgUserIds.length === 0) {\n return [];\n }\n const activeInSpace = spaceId\n ? sql<boolean>`exists (\n select 1 from space_members sm\n where sm.org_id = ${sql`${orgUsers.orgId}`}\n and sm.space_id = ${spaceId}\n and sm.org_user_id = ${sql`${orgUsers.id}`}\n and sm.status = 'active'\n )`\n : sql<boolean>`false`;\n const rows = await this.db\n .select({\n orgUserId: orgUsers.id,\n granted: sql<boolean>`${holdsCommunity(sql`${orgUsers.orgId}`, sql`${orgUsers.id}`, sql`${communityId}`)}`,\n activeInSpace,\n })\n .from(orgUsers)\n .where(and(eq(orgUsers.orgId, orgId), inArray(orgUsers.id, orgUserIds)));\n return rows.map((row) => ({\n orgUserId: row.orgUserId,\n granted: row.granted === true,\n activeInSpace: row.activeInSpace === true,\n }));\n }\n\n async findSpaceMember(\n orgId: string,\n spaceId: string,\n orgUserId: string,\n ): Promise<SpaceMember | null> {\n const [row] = await this.db\n .select()\n .from(spaceMembers)\n .where(\n and(\n eq(spaceMembers.orgId, orgId),\n eq(spaceMembers.spaceId, spaceId),\n eq(spaceMembers.orgUserId, orgUserId),\n ),\n )\n .limit(1);\n return row ? toSpaceMember(row) : null;\n }\n\n async listSpaceMembersFor(\n orgId: string,\n orgUserId: string,\n communityId: string,\n ): Promise<SpaceMember[]> {\n const rows = await this.db\n .select()\n .from(spaceMembers)\n .where(\n and(\n eq(spaceMembers.orgId, orgId),\n eq(spaceMembers.orgUserId, orgUserId),\n eq(spaceMembers.communityId, communityId),\n ),\n );\n return rows.map(toSpaceMember);\n }\n\n async lockSpaceMember(\n orgId: string,\n spaceId: string,\n orgUserId: string,\n ): Promise<SpaceMember | null> {\n const [row] = await this.db\n .select()\n .from(spaceMembers)\n .where(\n and(\n eq(spaceMembers.orgId, orgId),\n eq(spaceMembers.spaceId, spaceId),\n eq(spaceMembers.orgUserId, orgUserId),\n ),\n )\n .for(\"update\")\n .limit(1);\n return row ? toSpaceMember(row) : null;\n }\n\n async listSpaceMembers(\n orgId: string,\n spaceId: string,\n query: SpaceMembersQuery,\n ): Promise<Page<SpaceMember>> {\n const where = and(\n eq(spaceMembers.orgId, orgId),\n eq(spaceMembers.spaceId, spaceId),\n query.statuses ? inArray(spaceMembers.status, query.statuses) : undefined,\n query.excluding?.length\n ? sql`not ${oneOf(sql`${spaceMembers.orgUserId}`, query.excluding)}`\n : undefined,\n );\n const rows = await this.db\n .select()\n .from(spaceMembers)\n .where(where)\n .orderBy(asc(spaceMembers.createdAt), asc(spaceMembers.id))\n .limit(query.pageSize)\n .offset((query.page - 1) * query.pageSize);\n const [{ total } = { total: 0 }] = await this.db\n .select({ total: sql<number>`cast(count(*) as int)` })\n .from(spaceMembers)\n .where(where);\n return { rows: rows.map(toSpaceMember), total, page: query.page, pageSize: query.pageSize };\n }\n\n async insertSpaceMember(orgId: string, input: InsertSpaceMemberInput): Promise<SpaceMember> {\n const [row] = await this.db\n .insert(spaceMembers)\n .values({\n orgId,\n communityId: input.communityId,\n spaceId: input.spaceId,\n orgUserId: input.orgUserId,\n status: input.status,\n source: input.source,\n })\n .returning();\n if (!row) {\n throw new Error(\"failed to insert space member\");\n }\n return toSpaceMember(row);\n }\n\n async updateSpaceMemberStatus(\n orgId: string,\n id: string,\n status: SpaceMemberStatus,\n ): Promise<SpaceMember | null> {\n const [row] = await this.db\n .update(spaceMembers)\n .set({ status })\n .where(and(eq(spaceMembers.orgId, orgId), eq(spaceMembers.id, id)))\n .returning();\n return row ? toSpaceMember(row) : null;\n }\n\n async deleteSpaceMember(orgId: string, id: string): Promise<void> {\n await this.db\n .delete(spaceMembers)\n .where(and(eq(spaceMembers.orgId, orgId), eq(spaceMembers.id, id)));\n }\n\n async countActiveSpaceMembers(\n orgId: string,\n spaceId: string,\n excluding: string[],\n ): Promise<number> {\n const [row] = await this.db\n .select({ total: sql<number>`cast(count(*) as int)` })\n .from(spaceMembers)\n .where(\n and(\n eq(spaceMembers.orgId, orgId),\n eq(spaceMembers.spaceId, spaceId),\n eq(spaceMembers.status, \"active\"),\n holdsCommunity(\n sql`${spaceMembers.orgId}`,\n sql`${spaceMembers.orgUserId}`,\n sql`${spaceMembers.communityId}`,\n ),\n excluding.length > 0\n ? sql`not ${oneOf(sql`${spaceMembers.orgUserId}`, excluding)}`\n : undefined,\n ),\n );\n return row?.total ?? 0;\n }\n\n async activeSpaceIds(orgId: string, orgUserId: string, communityId: string): Promise<string[]> {\n const rows = await this.db\n .select({ spaceId: spaceMembers.spaceId })\n .from(spaceMembers)\n .where(\n and(\n eq(spaceMembers.orgId, orgId),\n eq(spaceMembers.orgUserId, orgUserId),\n eq(spaceMembers.communityId, communityId),\n eq(spaceMembers.status, \"active\"),\n ),\n );\n return rows.map((row) => row.spaceId);\n }\n\n async listGrantsExpiredBy(\n orgId: string,\n now: Date,\n limit: number,\n afterId?: string,\n ): Promise<Entitlement[]> {\n const rows = await this.db\n .select(selection)\n .from(entitlements)\n .where(\n and(\n this.expiredUnnoticed(orgId, now),\n afterId === undefined ? undefined : gt(entitlements.id, afterId),\n ),\n )\n .orderBy(asc(entitlements.id))\n .limit(limit);\n return rows.map(toEntitlement);\n }\n\n async listGrantsEndingBetween(\n orgId: string,\n after: Date,\n until: Date,\n limit: number,\n afterKey?: EndingGrantKey,\n ): Promise<EndingGrant[]> {\n const rows = await this.db\n .select({ ...selection, communityId: communities.id })\n .from(entitlements)\n .innerJoin(communities, opensPublishedCommunity)\n .where(\n and(\n this.endingUnnoticed(orgId, after, until),\n afterKey\n ? sql`(${entitlements.id}, ${communities.id}) > (${afterKey.entitlementId}, ${afterKey.communityId})`\n : undefined,\n ),\n )\n .orderBy(asc(entitlements.id), asc(communities.id))\n .limit(limit);\n return rows.map(({ communityId, ...grant }) => ({\n grant: toEntitlement(grant),\n communityId,\n }));\n }\n\n async orgIdsWithDueNotices(now: Date, until: Date): Promise<string[]> {\n const expired = this.db\n .selectDistinct({ orgId: entitlements.orgId })\n .from(entitlements)\n .where(this.expiredUnnoticed(null, now));\n const ending = this.db\n .selectDistinct({ orgId: entitlements.orgId })\n .from(entitlements)\n .innerJoin(communities, opensPublishedCommunity)\n .where(this.endingUnnoticed(null, now, until));\n const rows = await expired.union(ending);\n return rows.map((row) => row.orgId).sort();\n }\n\n async claimNotice(orgId: string, notice: EntitlementNotice, now: Date): Promise<boolean> {\n const statement =\n notice.kind === \"expired\"\n ? sql`insert into ${entitlementExpiryNotices} (\"org_id\", \"entitlement_id\", \"expires_at\", \"notified_at\")\n values (${orgId}, ${notice.entitlementId}, ${at(notice.expiresAt)}, ${at(now)})\n on conflict do nothing\n returning \"entitlement_id\"`\n : sql`insert into ${entitlementEndingSoonNotices} (\"org_id\", \"entitlement_id\", \"community_id\", \"expires_at\", \"notified_at\")\n values (${orgId}, ${notice.entitlementId}, ${notice.communityId}, ${at(notice.expiresAt)}, ${at(now)})\n on conflict do nothing\n returning \"entitlement_id\"`;\n const result = await this.db.execute<{ entitlement_id: string }>(statement);\n const rows = Array.isArray(result) ? result : result.rows;\n return rows.length > 0;\n }\n\n private expiredUnnoticed(orgId: string | null, now: Date): SQL {\n return and(\n orgId === null ? undefined : eq(entitlements.orgId, orgId),\n eq(entitlements.status, \"active\"),\n grantExpired(entitlements.expiresAt, at(now)),\n sql`not ${expiryNoticed}`,\n )!;\n }\n\n private endingUnnoticed(orgId: string | null, after: Date, until: Date): SQL {\n return and(\n orgId === null ? undefined : eq(entitlements.orgId, orgId),\n grantIsActive(entitlements, at(after)),\n grantExpired(entitlements.expiresAt, at(until)),\n sql`not ${endingSoonNoticed}`,\n )!;\n }\n\n async communityMemberIds(\n orgId: string,\n communityId: string,\n readerIds: string[],\n ): Promise<string[]> {\n const rows = await this.db\n .select({ id: orgUsers.id })\n .from(orgUsers)\n .where(\n and(\n eq(orgUsers.orgId, orgId),\n communityMember(\n sql`${orgUsers.orgId}`,\n sql`${orgUsers.id}`,\n sql`${communityId}`,\n readerIds,\n ),\n ),\n )\n .orderBy(asc(orgUsers.id));\n return rows.map((row) => row.id);\n }\n\n async communityIdsOpenedFor(orgId: string, orgUserId: string): Promise<string[]> {\n const rows = await this.db\n .selectDistinct({ id: communities.id })\n .from(communities)\n .where(\n and(\n eq(communities.orgId, orgId),\n holdsCommunity(sql`${communities.orgId}`, sql`${orgUserId}`, sql`${communities.id}`),\n ),\n )\n .orderBy(asc(communities.id));\n return rows.map((row) => row.id);\n }\n\n async lockCapacity(orgId: string, contentId: string): Promise<void> {\n await this.db.execute(\n sql`select pg_advisory_xact_lock(hashtext(${`entitlements:${orgId}:${contentId}`}))`,\n );\n }\n\n async grantedOrgUserIds(orgId: string, contentId: string): Promise<string[]> {\n const rows = await this.db\n .selectDistinct({ orgUserId: contentAccess.orgUserId })\n .from(contentAccess)\n .where(and(eq(contentAccess.orgId, orgId), eq(contentAccess.contentId, contentId)))\n .orderBy(asc(contentAccess.orgUserId));\n return rows.map((row) => row.orgUserId);\n }\n\n async openCommunityIds(\n orgId: string,\n orgUserId: string,\n readers: CommunityReaders,\n ): Promise<string[]> {\n const rows = await this.db\n .select({ id: communities.id })\n .from(communities)\n .where(\n and(\n eq(communities.orgId, orgId),\n readers.all\n ? undefined\n : or(\n oneOf(sql`${communities.id}`, readers.ids),\n holdsCommunity(\n sql`${communities.orgId}`,\n sql`${orgUserId}`,\n sql`${communities.id}`,\n ),\n ),\n ),\n )\n .orderBy(asc(communities.id));\n return rows.map((row) => row.id);\n }\n}\n\nconst ORG_USER_FK = \"entitlements_org_id_org_user_id_org_users_org_id_id_fk\";\n\nasync function orgUserMustExist<T>(orgUserId: string, query: PromiseLike<T>): Promise<T> {\n try {\n return await query;\n } catch (err) {\n const pg = pgErrorFields(err);\n if (pg?.code === \"23503\" && pg.constraint === ORG_USER_FK) {\n throw new NotFoundError(\"Org user\", orgUserId);\n }\n throw err;\n }\n}\n\nfunction toSpaceMember(row: {\n orgId: string;\n id: string;\n communityId: string;\n spaceId: string;\n orgUserId: string;\n status: SpaceMemberStatus;\n source: \"request\" | \"invite\" | \"manual\";\n createdAt: Date;\n updatedAt: Date;\n}): SpaceMember {\n return {\n orgId: row.orgId,\n id: row.id,\n communityId: row.communityId,\n spaceId: row.spaceId,\n orgUserId: row.orgUserId,\n status: row.status,\n source: row.source,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n\ntranslateDbErrors(DrizzleEntitlementsRepository);\n"],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { NodePgDatabase } from "drizzle-orm/node-postgres";
|
|
2
|
+
import { EventAnalytics, MemberEventsQuery, Page } from "@headless-lms/core/types";
|
|
3
|
+
import { EventAttendeeRow, EventFaces, EventReader } from "@headless-lms/core/reporting/events";
|
|
4
|
+
//#region src/repositories/event-reports.d.ts
|
|
5
|
+
declare function eventAttendeePage(db: NodePgDatabase, orgId: string, eventId: string, page: {
|
|
6
|
+
page: number;
|
|
7
|
+
pageSize: number;
|
|
8
|
+
}): Promise<Page<EventAttendeeRow>>;
|
|
9
|
+
declare function eventAnalyticsFigures(db: NodePgDatabase, orgId: string, eventId: string, startsAt: Date): Promise<Omit<EventAnalytics, "capacity">>;
|
|
10
|
+
declare function eventFacesFor(db: NodePgDatabase, orgId: string, eventIds: string[], limit: number): Promise<Record<string, EventFaces>>;
|
|
11
|
+
declare function directEventGrantIds(db: NodePgDatabase, orgId: string, orgUserId: string, eventIds: string[]): Promise<string[]>;
|
|
12
|
+
declare function memberEventIds(db: NodePgDatabase, orgId: string, query: MemberEventsQuery, reader: EventReader, now: Date): Promise<Page<string>>;
|
|
13
|
+
declare function upcomingEventCount(db: NodePgDatabase, orgId: string): Promise<number>;
|
|
14
|
+
//#endregion
|
|
15
|
+
export { directEventGrantIds, eventAnalyticsFigures, eventAttendeePage, eventFacesFor, memberEventIds, upcomingEventCount };
|
|
16
|
+
//# sourceMappingURL=event-reports.d.ts.map
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { SETTINGS_NAMESPACE } from "./content.js";
|
|
2
|
+
import { oneOf } from "./grant-covers.js";
|
|
3
|
+
import { sql } from "drizzle-orm";
|
|
4
|
+
import { eventSettingsDefaults } from "@headless-lms/core/schemas";
|
|
5
|
+
//#region src/repositories/event-reports.ts
|
|
6
|
+
function attendance(orgId, eventIds) {
|
|
7
|
+
return sql`
|
|
8
|
+
select ca.content_id, ca.org_user_id,
|
|
9
|
+
max(case when e.content_id is not null then e.source end) as direct_source,
|
|
10
|
+
min(case when e.content_id is not null then ca.granted_at end) as direct_at,
|
|
11
|
+
min(ca.granted_at) as any_at
|
|
12
|
+
from content_access ca
|
|
13
|
+
join entitlements e on e.org_id = ca.org_id and e.id = ca.entitlement_id
|
|
14
|
+
where ca.org_id = ${orgId} and ${oneOf(sql`ca.content_id`, eventIds)}
|
|
15
|
+
group by ca.content_id, ca.org_user_id
|
|
16
|
+
`;
|
|
17
|
+
}
|
|
18
|
+
const DISPLAY_NAME = sql`coalesce(
|
|
19
|
+
nullif(trim(up.display_name), ''),
|
|
20
|
+
nullif(trim(concat_ws(' ', u.first_name, u.last_name)), '')
|
|
21
|
+
)`;
|
|
22
|
+
const SOURCE = sql`case
|
|
23
|
+
when a.direct_source in ('self', 'automation') then a.direct_source
|
|
24
|
+
when a.direct_source is not null then 'manual'
|
|
25
|
+
else 'bundle'
|
|
26
|
+
end`;
|
|
27
|
+
async function eventAttendeePage(db, orgId, eventId, page) {
|
|
28
|
+
const rows = (await db.execute(sql`
|
|
29
|
+
with a as (${attendance(orgId, [eventId])})
|
|
30
|
+
select a.org_user_id,
|
|
31
|
+
${DISPLAY_NAME} as display_name,
|
|
32
|
+
case when up.image_asset_org_id = ${orgId} then up.image_asset_id end as image_asset_id,
|
|
33
|
+
${SOURCE} as source,
|
|
34
|
+
coalesce(a.direct_at, a.any_at) as granted_at,
|
|
35
|
+
count(*) over () as total
|
|
36
|
+
from a
|
|
37
|
+
join org_users ou on ou.org_id = ${orgId} and ou.id = a.org_user_id
|
|
38
|
+
left join users u on u.id = ou.user_id
|
|
39
|
+
left join user_profiles up on up.user_id = ou.user_id
|
|
40
|
+
order by coalesce(a.direct_at, a.any_at), a.org_user_id
|
|
41
|
+
limit ${page.pageSize}
|
|
42
|
+
offset ${(page.page - 1) * page.pageSize}
|
|
43
|
+
`)).rows;
|
|
44
|
+
const total = rows.length > 0 || page.page === 1 ? Number(rows[0]?.total ?? 0) : await eventAttendeeCount(db, orgId, eventId);
|
|
45
|
+
return {
|
|
46
|
+
rows: rows.map((row) => ({
|
|
47
|
+
orgUserId: row.org_user_id,
|
|
48
|
+
displayName: row.display_name,
|
|
49
|
+
imageAssetId: row.image_asset_id,
|
|
50
|
+
source: row.source,
|
|
51
|
+
grantedAt: new Date(row.granted_at)
|
|
52
|
+
})),
|
|
53
|
+
total,
|
|
54
|
+
page: page.page,
|
|
55
|
+
pageSize: page.pageSize
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
async function eventAttendeeCount(db, orgId, eventId) {
|
|
59
|
+
const result = await db.execute(sql`
|
|
60
|
+
select count(distinct ca.org_user_id) as total
|
|
61
|
+
from content_access ca
|
|
62
|
+
where ca.org_id = ${orgId} and ca.content_id = ${eventId}
|
|
63
|
+
`);
|
|
64
|
+
return Number(result.rows[0]?.total ?? 0);
|
|
65
|
+
}
|
|
66
|
+
async function eventAnalyticsFigures(db, orgId, eventId, startsAt) {
|
|
67
|
+
const [sources, revoked, overTime] = await Promise.all([
|
|
68
|
+
db.execute(sql`
|
|
69
|
+
with a as (${attendance(orgId, [eventId])})
|
|
70
|
+
select ${SOURCE} as source, count(*) as count from a group by 1
|
|
71
|
+
`),
|
|
72
|
+
db.execute(sql`
|
|
73
|
+
select count(*) as count from entitlements e
|
|
74
|
+
where e.org_id = ${orgId} and e.content_id = ${eventId} and e.status = 'revoked'
|
|
75
|
+
`),
|
|
76
|
+
db.execute(sql`
|
|
77
|
+
select to_char((e.granted_at at time zone 'utc')::date, 'YYYY-MM-DD') as date,
|
|
78
|
+
count(*) as count
|
|
79
|
+
from entitlements e
|
|
80
|
+
where e.org_id = ${orgId}
|
|
81
|
+
and e.content_id = ${eventId}
|
|
82
|
+
and e.status = 'active'
|
|
83
|
+
and (e.expires_at is null or e.expires_at > now())
|
|
84
|
+
and e.granted_at < ${startsAt}
|
|
85
|
+
group by 1
|
|
86
|
+
order by 1
|
|
87
|
+
`)
|
|
88
|
+
]);
|
|
89
|
+
const bySource = {
|
|
90
|
+
self: 0,
|
|
91
|
+
manual: 0,
|
|
92
|
+
automation: 0,
|
|
93
|
+
bundle: 0
|
|
94
|
+
};
|
|
95
|
+
for (const row of sources.rows) bySource[row.source] = Number(row.count);
|
|
96
|
+
return {
|
|
97
|
+
attendees: bySource.self + bySource.manual + bySource.automation + bySource.bundle,
|
|
98
|
+
bySource,
|
|
99
|
+
revoked: Number(revoked.rows[0]?.count ?? 0),
|
|
100
|
+
grantsOverTime: overTime.rows.map((row) => ({
|
|
101
|
+
date: row.date,
|
|
102
|
+
count: Number(row.count)
|
|
103
|
+
}))
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
async function eventFacesFor(db, orgId, eventIds, limit) {
|
|
107
|
+
const faces = Object.fromEntries(eventIds.map((id) => [id, {
|
|
108
|
+
total: 0,
|
|
109
|
+
rows: []
|
|
110
|
+
}]));
|
|
111
|
+
if (eventIds.length === 0) return faces;
|
|
112
|
+
const result = await db.execute(sql`
|
|
113
|
+
with a as (${attendance(orgId, eventIds)}),
|
|
114
|
+
ranked as (
|
|
115
|
+
select a.content_id, a.org_user_id,
|
|
116
|
+
${DISPLAY_NAME} as display_name,
|
|
117
|
+
case when up.image_asset_org_id = ${orgId} then up.image_asset_id end as image_asset_id,
|
|
118
|
+
row_number() over (
|
|
119
|
+
partition by a.content_id
|
|
120
|
+
order by coalesce(a.direct_at, a.any_at), a.org_user_id
|
|
121
|
+
) as position,
|
|
122
|
+
count(*) over (partition by a.content_id) as total
|
|
123
|
+
from a
|
|
124
|
+
join org_users ou on ou.org_id = ${orgId} and ou.id = a.org_user_id
|
|
125
|
+
left join users u on u.id = ou.user_id
|
|
126
|
+
left join user_profiles up on up.user_id = ou.user_id
|
|
127
|
+
)
|
|
128
|
+
select content_id, org_user_id, display_name, image_asset_id, total
|
|
129
|
+
from ranked
|
|
130
|
+
where position <= ${limit}
|
|
131
|
+
order by content_id, position
|
|
132
|
+
`);
|
|
133
|
+
for (const row of result.rows) {
|
|
134
|
+
const entry = faces[row.content_id];
|
|
135
|
+
if (!entry) continue;
|
|
136
|
+
entry.total = Number(row.total);
|
|
137
|
+
entry.rows.push({
|
|
138
|
+
orgUserId: row.org_user_id,
|
|
139
|
+
displayName: row.display_name,
|
|
140
|
+
imageAssetId: row.image_asset_id
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
return faces;
|
|
144
|
+
}
|
|
145
|
+
async function directEventGrantIds(db, orgId, orgUserId, eventIds) {
|
|
146
|
+
return (await db.execute(sql`
|
|
147
|
+
select distinct e.content_id from entitlements e
|
|
148
|
+
where e.org_id = ${orgId}
|
|
149
|
+
and e.org_user_id = ${orgUserId}
|
|
150
|
+
and ${oneOf(sql`e.content_id`, eventIds)}
|
|
151
|
+
and e.status = 'active'
|
|
152
|
+
and (e.expires_at is null or e.expires_at > now())
|
|
153
|
+
`)).rows.map((row) => row.content_id);
|
|
154
|
+
}
|
|
155
|
+
async function memberEventIds(db, orgId, query, reader, now) {
|
|
156
|
+
const attends = sql`exists (
|
|
157
|
+
select 1 from content_access ca
|
|
158
|
+
where ca.org_id = ev.org_id and ca.content_id = ev.id and ca.org_user_id = ${reader.orgUserId}
|
|
159
|
+
)`;
|
|
160
|
+
const sees = reader.readAll ? sql`true` : sql`(
|
|
161
|
+
${oneOf(sql`ev.id`, reader.readIds)}
|
|
162
|
+
or (ev.status = 'published' and coalesce(s.value ->> 'type', ${eventSettingsDefaults.type}) = 'public')
|
|
163
|
+
or ${attends}
|
|
164
|
+
)`;
|
|
165
|
+
const past = query.when === "past";
|
|
166
|
+
const where = sql`
|
|
167
|
+
ev.org_id = ${orgId}
|
|
168
|
+
and ev.status in ('published', 'canceled')
|
|
169
|
+
and ${sees}
|
|
170
|
+
and ${query.attending ? attends : sql`true`}
|
|
171
|
+
and ${past ? sql`ev.ends_at <= ${now}` : sql`ev.ends_at > ${now}`}
|
|
172
|
+
`;
|
|
173
|
+
const from = sql`
|
|
174
|
+
from events ev
|
|
175
|
+
left join settings s on s.org_id = ev.org_id and s.namespace = ${SETTINGS_NAMESPACE} and s.scope_id = ev.id
|
|
176
|
+
`;
|
|
177
|
+
const order = past ? sql`ev.starts_at desc, ev.id desc` : sql`ev.starts_at asc, ev.id asc`;
|
|
178
|
+
const [rows, totals] = await Promise.all([db.execute(sql`
|
|
179
|
+
select ev.id ${from} where ${where} order by ${order}
|
|
180
|
+
limit ${query.pageSize} offset ${(query.page - 1) * query.pageSize}
|
|
181
|
+
`), db.execute(sql`select count(*) as total ${from} where ${where}`)]);
|
|
182
|
+
return {
|
|
183
|
+
rows: rows.rows.map((row) => row.id),
|
|
184
|
+
total: Number(totals.rows[0]?.total ?? 0),
|
|
185
|
+
page: query.page,
|
|
186
|
+
pageSize: query.pageSize
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
async function upcomingEventCount(db, orgId) {
|
|
190
|
+
const result = await db.execute(sql`
|
|
191
|
+
select count(*) as total from events ev
|
|
192
|
+
where ev.org_id = ${orgId} and ev.status = 'published' and ev.ends_at > now()
|
|
193
|
+
`);
|
|
194
|
+
return Number(result.rows[0]?.total ?? 0);
|
|
195
|
+
}
|
|
196
|
+
//#endregion
|
|
197
|
+
export { directEventGrantIds, eventAnalyticsFigures, eventAttendeePage, eventFacesFor, memberEventIds, upcomingEventCount };
|
|
198
|
+
|
|
199
|
+
//# sourceMappingURL=event-reports.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-reports.js","names":[],"sources":["../../src/repositories/event-reports.ts"],"sourcesContent":["import { sql, type SQL } from \"drizzle-orm\";\nimport type { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport type {\n EventAnalytics,\n EventAttendeeSource,\n MemberEventsQuery,\n Page,\n} from \"@headless-lms/core/types\";\nimport { eventSettingsDefaults } from \"@headless-lms/core/schemas\";\nimport type {\n EventAttendeeRow,\n EventFaces,\n EventReader,\n} from \"@headless-lms/core/reporting/events\";\nimport { oneOf } from \"./grant-covers.js\";\nimport { SETTINGS_NAMESPACE } from \"./content.js\";\n\nfunction attendance(orgId: string, eventIds: string[]): SQL {\n return sql`\n select ca.content_id, ca.org_user_id,\n max(case when e.content_id is not null then e.source end) as direct_source,\n min(case when e.content_id is not null then ca.granted_at end) as direct_at,\n min(ca.granted_at) as any_at\n from content_access ca\n join entitlements e on e.org_id = ca.org_id and e.id = ca.entitlement_id\n where ca.org_id = ${orgId} and ${oneOf(sql`ca.content_id`, eventIds)}\n group by ca.content_id, ca.org_user_id\n `;\n}\n\nconst DISPLAY_NAME = sql`coalesce(\n nullif(trim(up.display_name), ''),\n nullif(trim(concat_ws(' ', u.first_name, u.last_name)), '')\n)`;\n\nconst SOURCE = sql`case\n when a.direct_source in ('self', 'automation') then a.direct_source\n when a.direct_source is not null then 'manual'\n else 'bundle'\nend`;\n\nexport async function eventAttendeePage(\n db: NodePgDatabase,\n orgId: string,\n eventId: string,\n page: { page: number; pageSize: number },\n): Promise<Page<EventAttendeeRow>> {\n const result = await db.execute(sql`\n with a as (${attendance(orgId, [eventId])})\n select a.org_user_id,\n ${DISPLAY_NAME} as display_name,\n case when up.image_asset_org_id = ${orgId} then up.image_asset_id end as image_asset_id,\n ${SOURCE} as source,\n coalesce(a.direct_at, a.any_at) as granted_at,\n count(*) over () as total\n from a\n join org_users ou on ou.org_id = ${orgId} and ou.id = a.org_user_id\n left join users u on u.id = ou.user_id\n left join user_profiles up on up.user_id = ou.user_id\n order by coalesce(a.direct_at, a.any_at), a.org_user_id\n limit ${page.pageSize}\n offset ${(page.page - 1) * page.pageSize}\n `);\n const rows = result.rows as {\n org_user_id: string;\n display_name: string | null;\n image_asset_id: string | null;\n source: EventAttendeeSource;\n granted_at: Date | string;\n total: string | number;\n }[];\n const total =\n rows.length > 0 || page.page === 1\n ? Number(rows[0]?.total ?? 0)\n : await eventAttendeeCount(db, orgId, eventId);\n return {\n rows: rows.map((row) => ({\n orgUserId: row.org_user_id,\n displayName: row.display_name,\n imageAssetId: row.image_asset_id,\n source: row.source,\n grantedAt: new Date(row.granted_at),\n })),\n total,\n page: page.page,\n pageSize: page.pageSize,\n };\n}\n\nasync function eventAttendeeCount(\n db: NodePgDatabase,\n orgId: string,\n eventId: string,\n): Promise<number> {\n const result = await db.execute(sql`\n select count(distinct ca.org_user_id) as total\n from content_access ca\n where ca.org_id = ${orgId} and ca.content_id = ${eventId}\n `);\n return Number((result.rows[0] as { total: string | number } | undefined)?.total ?? 0);\n}\n\nexport async function eventAnalyticsFigures(\n db: NodePgDatabase,\n orgId: string,\n eventId: string,\n startsAt: Date,\n): Promise<Omit<EventAnalytics, \"capacity\">> {\n const [sources, revoked, overTime] = await Promise.all([\n db.execute(sql`\n with a as (${attendance(orgId, [eventId])})\n select ${SOURCE} as source, count(*) as count from a group by 1\n `),\n db.execute(sql`\n select count(*) as count from entitlements e\n where e.org_id = ${orgId} and e.content_id = ${eventId} and e.status = 'revoked'\n `),\n db.execute(sql`\n select to_char((e.granted_at at time zone 'utc')::date, 'YYYY-MM-DD') as date,\n count(*) as count\n from entitlements e\n where e.org_id = ${orgId}\n and e.content_id = ${eventId}\n and e.status = 'active'\n and (e.expires_at is null or e.expires_at > now())\n and e.granted_at < ${startsAt}\n group by 1\n order by 1\n `),\n ]);\n const bySource = { self: 0, manual: 0, automation: 0, bundle: 0 };\n for (const row of sources.rows as { source: EventAttendeeSource; count: string | number }[]) {\n bySource[row.source] = Number(row.count);\n }\n return {\n attendees: bySource.self + bySource.manual + bySource.automation + bySource.bundle,\n bySource,\n revoked: Number((revoked.rows[0] as { count: string | number } | undefined)?.count ?? 0),\n grantsOverTime: (overTime.rows as { date: string; count: string | number }[]).map((row) => ({\n date: row.date,\n count: Number(row.count),\n })),\n };\n}\n\nexport async function eventFacesFor(\n db: NodePgDatabase,\n orgId: string,\n eventIds: string[],\n limit: number,\n): Promise<Record<string, EventFaces>> {\n const faces: Record<string, EventFaces> = Object.fromEntries(\n eventIds.map((id) => [id, { total: 0, rows: [] }]),\n );\n if (eventIds.length === 0) {\n return faces;\n }\n const result = await db.execute(sql`\n with a as (${attendance(orgId, eventIds)}),\n ranked as (\n select a.content_id, a.org_user_id,\n ${DISPLAY_NAME} as display_name,\n case when up.image_asset_org_id = ${orgId} then up.image_asset_id end as image_asset_id,\n row_number() over (\n partition by a.content_id\n order by coalesce(a.direct_at, a.any_at), a.org_user_id\n ) as position,\n count(*) over (partition by a.content_id) as total\n from a\n join org_users ou on ou.org_id = ${orgId} and ou.id = a.org_user_id\n left join users u on u.id = ou.user_id\n left join user_profiles up on up.user_id = ou.user_id\n )\n select content_id, org_user_id, display_name, image_asset_id, total\n from ranked\n where position <= ${limit}\n order by content_id, position\n `);\n for (const row of result.rows as {\n content_id: string;\n org_user_id: string;\n display_name: string | null;\n image_asset_id: string | null;\n total: string | number;\n }[]) {\n const entry = faces[row.content_id];\n if (!entry) {\n continue;\n }\n entry.total = Number(row.total);\n entry.rows.push({\n orgUserId: row.org_user_id,\n displayName: row.display_name,\n imageAssetId: row.image_asset_id,\n });\n }\n return faces;\n}\n\nexport async function directEventGrantIds(\n db: NodePgDatabase,\n orgId: string,\n orgUserId: string,\n eventIds: string[],\n): Promise<string[]> {\n const result = await db.execute(sql`\n select distinct e.content_id from entitlements e\n where e.org_id = ${orgId}\n and e.org_user_id = ${orgUserId}\n and ${oneOf(sql`e.content_id`, eventIds)}\n and e.status = 'active'\n and (e.expires_at is null or e.expires_at > now())\n `);\n return (result.rows as { content_id: string }[]).map((row) => row.content_id);\n}\n\nexport async function memberEventIds(\n db: NodePgDatabase,\n orgId: string,\n query: MemberEventsQuery,\n reader: EventReader,\n now: Date,\n): Promise<Page<string>> {\n const attends = sql`exists (\n select 1 from content_access ca\n where ca.org_id = ev.org_id and ca.content_id = ev.id and ca.org_user_id = ${reader.orgUserId}\n )`;\n const sees = reader.readAll\n ? sql`true`\n : sql`(\n ${oneOf(sql`ev.id`, reader.readIds)}\n or (ev.status = 'published' and coalesce(s.value ->> 'type', ${eventSettingsDefaults.type}) = 'public')\n or ${attends}\n )`;\n const past = query.when === \"past\";\n const where = sql`\n ev.org_id = ${orgId}\n and ev.status in ('published', 'canceled')\n and ${sees}\n and ${query.attending ? attends : sql`true`}\n and ${past ? sql`ev.ends_at <= ${now}` : sql`ev.ends_at > ${now}`}\n `;\n const from = sql`\n from events ev\n left join settings s on s.org_id = ev.org_id and s.namespace = ${SETTINGS_NAMESPACE} and s.scope_id = ev.id\n `;\n const order = past ? sql`ev.starts_at desc, ev.id desc` : sql`ev.starts_at asc, ev.id asc`;\n const [rows, totals] = await Promise.all([\n db.execute(sql`\n select ev.id ${from} where ${where} order by ${order}\n limit ${query.pageSize} offset ${(query.page - 1) * query.pageSize}\n `),\n db.execute(sql`select count(*) as total ${from} where ${where}`),\n ]);\n return {\n rows: (rows.rows as { id: string }[]).map((row) => row.id),\n total: Number((totals.rows[0] as { total: string | number } | undefined)?.total ?? 0),\n page: query.page,\n pageSize: query.pageSize,\n };\n}\n\nexport async function upcomingEventCount(db: NodePgDatabase, orgId: string): Promise<number> {\n const result = await db.execute(sql`\n select count(*) as total from events ev\n where ev.org_id = ${orgId} and ev.status = 'published' and ev.ends_at > now()\n `);\n return Number((result.rows[0] as { total: string | number } | undefined)?.total ?? 0);\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { entitlements } from "../schema/entitlements.js";
|
|
2
2
|
import { SQL, SQLWrapper } from "drizzle-orm";
|
|
3
3
|
//#region src/repositories/grant-covers.d.ts
|
|
4
|
-
type GrantedContentType = "course" | "download" | "community";
|
|
4
|
+
type GrantedContentType = "course" | "download" | "community" | "event";
|
|
5
5
|
interface GrantColumns {
|
|
6
6
|
orgId: SQLWrapper;
|
|
7
7
|
contentId: SQLWrapper;
|
|
@@ -14,7 +14,8 @@ declare function grantOpens(row: GrantColumns, contentId: SQL, type: GrantedCont
|
|
|
14
14
|
declare function activeGrantCovers(orgId: SQL, orgUserId: SQL, contentId: SQL, type: GrantedContentType): SQL;
|
|
15
15
|
declare function communityPublished(orgId: SQL, communityId: SQL): SQL;
|
|
16
16
|
declare function holdsCommunity(orgId: SQL, orgUserId: SQL, communityId: SQL): SQL;
|
|
17
|
-
declare function
|
|
17
|
+
declare function oneOf(column: SQL, ids: readonly string[]): SQL;
|
|
18
|
+
declare function communityMember(orgId: SQL, orgUserId: SQL, communityId: SQL, readerIds: readonly string[]): SQL;
|
|
18
19
|
//#endregion
|
|
19
|
-
export { GrantColumns, GrantedContentType, activeGrantCovers, communityMember, communityPublished, grantExpired, grantIsActive, grantOpens, grantUnexpired, holdsCommunity };
|
|
20
|
+
export { GrantColumns, GrantedContentType, activeGrantCovers, communityMember, communityPublished, grantExpired, grantIsActive, grantOpens, grantUnexpired, holdsCommunity, oneOf };
|
|
20
21
|
//# sourceMappingURL=grant-covers.d.ts.map
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { entitlements } from "../schema/entitlements.js";
|
|
2
2
|
import { sql } from "drizzle-orm";
|
|
3
|
-
import { STAFF_ROLES } from "@headless-lms/core/organizations";
|
|
4
3
|
//#region src/repositories/grant-covers.ts
|
|
5
4
|
function grantUnexpired(expiresAt = entitlements.expiresAt, at = sql`now()`) {
|
|
6
5
|
return sql`(${expiresAt} is null or ${expiresAt} > ${at})`;
|
|
@@ -46,10 +45,14 @@ function communityPublished(orgId, communityId) {
|
|
|
46
45
|
function holdsCommunity(orgId, orgUserId, communityId) {
|
|
47
46
|
return sql`(${communityPublished(orgId, communityId)} and ${activeGrantCovers(orgId, orgUserId, communityId, "community")})`;
|
|
48
47
|
}
|
|
49
|
-
function
|
|
50
|
-
|
|
48
|
+
function oneOf(column, ids) {
|
|
49
|
+
if (ids.length === 0) return sql`false`;
|
|
50
|
+
return sql`${column} in (${sql.join(ids.map((id) => sql`${id}`), sql`, `)})`;
|
|
51
|
+
}
|
|
52
|
+
function communityMember(orgId, orgUserId, communityId, readerIds) {
|
|
53
|
+
return sql`(${oneOf(orgUserId, readerIds)} or ${holdsCommunity(orgId, orgUserId, communityId)})`;
|
|
51
54
|
}
|
|
52
55
|
//#endregion
|
|
53
|
-
export { activeGrantCovers, communityMember, communityPublished, grantExpired, grantIsActive, grantOpens, grantUnexpired, holdsCommunity };
|
|
56
|
+
export { activeGrantCovers, communityMember, communityPublished, grantExpired, grantIsActive, grantOpens, grantUnexpired, holdsCommunity, oneOf };
|
|
54
57
|
|
|
55
58
|
//# sourceMappingURL=grant-covers.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grant-covers.js","names":[],"sources":["../../src/repositories/grant-covers.ts"],"sourcesContent":["import { sql, type SQL, type SQLWrapper } from \"drizzle-orm\";\nimport {
|
|
1
|
+
{"version":3,"file":"grant-covers.js","names":[],"sources":["../../src/repositories/grant-covers.ts"],"sourcesContent":["import { sql, type SQL, type SQLWrapper } from \"drizzle-orm\";\nimport { entitlements } from \"../schema/entitlements.js\";\n\nexport type GrantedContentType = \"course\" | \"download\" | \"community\" | \"event\";\n\nexport interface GrantColumns {\n orgId: SQLWrapper;\n contentId: SQLWrapper;\n bundleId: SQLWrapper;\n}\n\nexport function grantUnexpired(\n expiresAt: SQLWrapper = entitlements.expiresAt,\n at: SQL = sql`now()`,\n): SQL {\n return sql`(${expiresAt} is null or ${expiresAt} > ${at})`;\n}\n\nexport function grantExpired(\n expiresAt: SQLWrapper = entitlements.expiresAt,\n at: SQL = sql`now()`,\n): SQL {\n return sql`(${expiresAt} is not null and ${expiresAt} <= ${at})`;\n}\n\nexport function grantIsActive(row: typeof entitlements = entitlements, at?: SQL): SQL {\n return sql`(${row.status} = 'active' and ${grantUnexpired(row.expiresAt, at)})`;\n}\n\nexport function grantOpens(row: GrantColumns, contentId: SQL, type: GrantedContentType): SQL {\n return sql`(\n exists (\n select 1 from content_items ci\n where ci.org_id = ${row.orgId} and ci.id = ${contentId} and ci.type = ${type}\n )\n and (\n ${row.contentId} = ${contentId}\n or exists (\n select 1 from bundle_items bi\n where bi.org_id = ${row.orgId}\n and bi.bundle_id = ${row.bundleId}\n and bi.content_id = ${contentId}\n )\n )\n )`;\n}\n\nexport function activeGrantCovers(\n orgId: SQL,\n orgUserId: SQL,\n contentId: SQL,\n type: GrantedContentType,\n): SQL {\n return sql`exists (\n select 1 from content_access ca\n join content_items ci on ci.org_id = ca.org_id and ci.id = ca.content_id and ci.type = ${type}\n where ca.org_id = ${orgId}\n and ca.org_user_id = ${orgUserId}\n and ca.content_id = ${contentId}\n )`;\n}\n\nexport function communityPublished(orgId: SQL, communityId: SQL): SQL {\n return sql`exists (\n select 1 from communities pc\n where pc.org_id = ${orgId} and pc.id = ${communityId} and pc.status = 'published'\n )`;\n}\n\nexport function holdsCommunity(orgId: SQL, orgUserId: SQL, communityId: SQL): SQL {\n return sql`(${communityPublished(orgId, communityId)} and ${activeGrantCovers(orgId, orgUserId, communityId, \"community\")})`;\n}\n\nexport function oneOf(column: SQL, ids: readonly string[]): SQL {\n if (ids.length === 0) {\n return sql`false`;\n }\n return sql`${column} in (${sql.join(\n ids.map((id) => sql`${id}`),\n sql`, `,\n )})`;\n}\n\nexport function communityMember(\n orgId: SQL,\n orgUserId: SQL,\n communityId: SQL,\n readerIds: readonly string[],\n): SQL {\n return sql`(${oneOf(orgUserId, readerIds)} or ${holdsCommunity(orgId, orgUserId, communityId)})`;\n}\n"],"mappings":";;;AAWA,SAAgB,eACd,YAAwB,aAAa,WACrC,KAAU,GAAG,SACR;CACL,OAAO,GAAG,IAAI,UAAU,cAAc,UAAU,KAAK,GAAG;AAC1D;AAEA,SAAgB,aACd,YAAwB,aAAa,WACrC,KAAU,GAAG,SACR;CACL,OAAO,GAAG,IAAI,UAAU,mBAAmB,UAAU,MAAM,GAAG;AAChE;AAEA,SAAgB,cAAc,MAA2B,cAAc,IAAe;CACpF,OAAO,GAAG,IAAI,IAAI,OAAO,kBAAkB,eAAe,IAAI,WAAW,EAAE,EAAE;AAC/E;AAEA,SAAgB,WAAW,KAAmB,WAAgB,MAA+B;CAC3F,OAAO,GAAG;;;0BAGc,IAAI,MAAM,eAAe,UAAU,iBAAiB,KAAK;;;QAG3E,IAAI,UAAU,KAAK,UAAU;;;4BAGT,IAAI,MAAM;+BACP,IAAI,SAAS;gCACZ,UAAU;;;;AAI1C;AAEA,SAAgB,kBACd,OACA,WACA,WACA,MACK;CACL,OAAO,GAAG;;6FAEiF,KAAK;wBAC1E,MAAM;6BACD,UAAU;4BACX,UAAU;;AAEtC;AAEA,SAAgB,mBAAmB,OAAY,aAAuB;CACpE,OAAO,GAAG;;wBAEY,MAAM,eAAe,YAAY;;AAEzD;AAEA,SAAgB,eAAe,OAAY,WAAgB,aAAuB;CAChF,OAAO,GAAG,IAAI,mBAAmB,OAAO,WAAW,EAAE,OAAO,kBAAkB,OAAO,WAAW,aAAa,WAAW,EAAE;AAC5H;AAEA,SAAgB,MAAM,QAAa,KAA6B;CAC9D,IAAI,IAAI,WAAW,GACjB,OAAO,GAAG;CAEZ,OAAO,GAAG,GAAG,OAAO,OAAO,IAAI,KAC7B,IAAI,KAAK,OAAO,GAAG,GAAG,IAAI,GAC1B,GAAG,IACL,EAAE;AACJ;AAEA,SAAgB,gBACd,OACA,WACA,aACA,WACK;CACL,OAAO,GAAG,IAAI,MAAM,WAAW,SAAS,EAAE,MAAM,eAAe,OAAO,WAAW,WAAW,EAAE;AAChG"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { userProfiles, users } from "../schema/identity.js";
|
|
2
2
|
import { translateDbErrors } from "./pg-errors.js";
|
|
3
|
-
import { eq, sql } from "drizzle-orm";
|
|
4
3
|
import { noopLogger } from "@headless-lms/core/shared/logger";
|
|
4
|
+
import { eq, sql } from "drizzle-orm";
|
|
5
5
|
//#region src/repositories/identity.ts
|
|
6
6
|
var DrizzleIdentityRepository = class {
|
|
7
7
|
db;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inserted-row.js","names":[],"sources":["../../src/repositories/inserted-row.ts"],"sourcesContent":["import { sql } from \"drizzle-orm\";\n\nexport const insertedRow = sql<boolean>`(xmax = 0)`;\n"],"mappings":";;AAEA,MAAa,cAAc,GAAY"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { connections } from "../schema/integrations.js";
|
|
2
2
|
import { translateDbErrors } from "./pg-errors.js";
|
|
3
|
-
import { and, asc, eq } from "drizzle-orm";
|
|
4
3
|
import { noopLogger } from "@headless-lms/core/shared/logger";
|
|
4
|
+
import { and, asc, eq } from "drizzle-orm";
|
|
5
5
|
//#region src/repositories/integrations.ts
|
|
6
6
|
function toConnection(row) {
|
|
7
7
|
return {
|
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
import { NodePgDatabase } from "drizzle-orm/node-postgres";
|
|
2
2
|
import { Logger } from "@headless-lms/core/shared/ports";
|
|
3
|
-
import { Page } from "@headless-lms/core/types";
|
|
4
|
-
import { DirectoryQuery, DirectoryRow, MemberReportRepository } from "@headless-lms/core/reporting/member";
|
|
3
|
+
import { MemberEventsQuery, Page } from "@headless-lms/core/types";
|
|
4
|
+
import { DirectoryQuery, DirectoryRow, EventAttendeeRow, EventReader, MemberReportRepository } from "@headless-lms/core/reporting/member";
|
|
5
|
+
import { EventFaces } from "@headless-lms/core/reporting/events";
|
|
5
6
|
//#region src/repositories/member.d.ts
|
|
6
7
|
declare class DrizzleMemberRepository implements MemberReportRepository {
|
|
7
8
|
private readonly db;
|
|
8
9
|
private readonly logger;
|
|
9
10
|
constructor(db: NodePgDatabase, logger?: Logger);
|
|
10
|
-
directory(orgId: string, communityId: string, query: DirectoryQuery): Promise<Page<DirectoryRow>>;
|
|
11
|
+
directory(orgId: string, communityId: string, query: DirectoryQuery, readerIds: string[]): Promise<Page<DirectoryRow>>;
|
|
11
12
|
people(orgId: string, orgUserIds: string[]): Promise<DirectoryRow[]>;
|
|
12
|
-
|
|
13
|
+
eventAttendees(orgId: string, eventId: string, page: {
|
|
14
|
+
page: number;
|
|
15
|
+
pageSize: number;
|
|
16
|
+
}): Promise<Page<EventAttendeeRow>>;
|
|
17
|
+
eventFaces(orgId: string, eventIds: string[], limit: number): Promise<Record<string, EventFaces>>;
|
|
18
|
+
directEventGrants(orgId: string, orgUserId: string, eventIds: string[]): Promise<string[]>;
|
|
19
|
+
events(orgId: string, query: MemberEventsQuery, reader: EventReader, now: Date): Promise<Page<string>>;
|
|
20
|
+
spaceMemberCounts(orgId: string, communityId: string, excluding: string[]): Promise<Record<string, number>>;
|
|
13
21
|
}
|
|
14
22
|
//#endregion
|
|
15
23
|
export { DrizzleMemberRepository };
|