tapimo 0.4.1 → 0.4.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.
- package/README.md +1 -1
- package/dist/ApiKey.d.ts +1 -9
- package/dist/ApiKey.d.ts.map +1 -1
- package/dist/ApiKey.js +2 -3
- package/dist/ApiKey.js.map +1 -1
- package/dist/ApiKeys.d.ts +53 -27
- package/dist/ApiKeys.d.ts.map +1 -1
- package/dist/ApiKeys.js +32 -17
- package/dist/ApiKeys.js.map +1 -1
- package/dist/App.d.ts +1990 -1442
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +23 -9
- package/dist/App.js.map +1 -1
- package/dist/Assets.d.ts +10 -0
- package/dist/Assets.d.ts.map +1 -1
- package/dist/Assets.js +4 -0
- package/dist/Assets.js.map +1 -1
- package/dist/Auth.d.ts +2 -1
- package/dist/Auth.d.ts.map +1 -1
- package/dist/Auth.js +2 -1
- package/dist/Auth.js.map +1 -1
- package/dist/Scope.d.ts +40 -15
- package/dist/Scope.d.ts.map +1 -1
- package/dist/Scope.js +44 -13
- package/dist/Scope.js.map +1 -1
- package/dist/admin/App.d.ts +560 -9
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/App.js +38 -6
- package/dist/admin/App.js.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +10 -24
- package/dist/admin/apps/api-keys.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.js +10 -3
- package/dist/admin/apps/api-keys.js.map +1 -1
- package/dist/admin/apps/organizations.d.ts +833 -0
- package/dist/admin/apps/organizations.d.ts.map +1 -0
- package/dist/admin/apps/organizations.js +733 -0
- package/dist/admin/apps/organizations.js.map +1 -0
- package/dist/admin/apps/scopes.d.ts +8 -0
- package/dist/admin/apps/scopes.d.ts.map +1 -1
- package/dist/admin/apps/verified-tokens.d.ts +57 -57
- package/dist/admin/ui.generated.d.ts +1 -1
- package/dist/admin/ui.generated.d.ts.map +1 -1
- package/dist/admin/ui.generated.js +53 -13
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/analytics/Analytics.d.ts +10 -0
- package/dist/analytics/Analytics.d.ts.map +1 -1
- package/dist/analytics/Analytics.js +8 -0
- package/dist/analytics/Analytics.js.map +1 -1
- package/dist/analytics/tables/requestEvents.d.ts +9 -0
- package/dist/analytics/tables/requestEvents.d.ts.map +1 -1
- package/dist/analytics/tables/requestEvents.js +14 -0
- package/dist/analytics/tables/requestEvents.js.map +1 -1
- package/dist/apps/data/App.d.ts +1304 -1304
- package/dist/apps/data/FundingRouteProviders.d.ts +4 -22
- package/dist/apps/data/FundingRouteProviders.d.ts.map +1 -1
- package/dist/apps/data/FundingRouteProviders.js +10 -120
- package/dist/apps/data/FundingRouteProviders.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +38 -38
- package/dist/apps/data/routes/addresses.d.ts +24 -24
- package/dist/apps/data/routes/blocks.d.ts +39 -39
- package/dist/apps/data/routes/fee-amm.d.ts +20 -20
- package/dist/apps/data/routes/funding.js +7 -7
- package/dist/apps/data/routes/funding.js.map +1 -1
- package/dist/apps/data/routes/indexer.d.ts +5 -5
- package/dist/apps/data/routes/tokenlist.d.ts +12 -12
- package/dist/apps/data/routes/verified-tokens.d.ts +36 -36
- package/dist/apps/management/App.d.ts +808 -267
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/App.js +2 -2
- package/dist/apps/management/Billing.d.ts +3 -0
- package/dist/apps/management/Billing.d.ts.map +1 -1
- package/dist/apps/management/Billing.js +7 -2
- package/dist/apps/management/Billing.js.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +116 -100
- package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.js +41 -30
- package/dist/apps/management/routes/api-keys.js.map +1 -1
- package/dist/apps/management/routes/billing.d.ts +182 -50
- package/dist/apps/management/routes/billing.d.ts.map +1 -1
- package/dist/apps/management/routes/billing.js +14 -14
- package/dist/apps/management/routes/billing.js.map +1 -1
- package/dist/apps/management/routes/invitations.d.ts +93 -20
- package/dist/apps/management/routes/invitations.d.ts.map +1 -1
- package/dist/apps/management/routes/invitations.js +12 -15
- package/dist/apps/management/routes/invitations.js.map +1 -1
- package/dist/apps/management/routes/members.d.ts +89 -18
- package/dist/apps/management/routes/members.d.ts.map +1 -1
- package/dist/apps/management/routes/members.js +10 -10
- package/dist/apps/management/routes/members.js.map +1 -1
- package/dist/apps/management/routes/orgs.d.ts +100 -40
- package/dist/apps/management/routes/orgs.d.ts.map +1 -1
- package/dist/apps/management/routes/orgs.js +19 -19
- package/dist/apps/management/routes/orgs.js.map +1 -1
- package/dist/apps/management/routes/projects.d.ts +122 -33
- package/dist/apps/management/routes/projects.d.ts.map +1 -1
- package/dist/apps/management/routes/projects.js +12 -13
- package/dist/apps/management/routes/projects.js.map +1 -1
- package/dist/apps/management/routes/scopes.d.ts +4 -18
- package/dist/apps/management/routes/scopes.d.ts.map +1 -1
- package/dist/apps/management/routes/scopes.js +6 -7
- package/dist/apps/management/routes/scopes.js.map +1 -1
- package/dist/apps/management/routes/usage.d.ts +63 -19
- package/dist/apps/management/routes/usage.d.ts.map +1 -1
- package/dist/apps/management/routes/usage.js +9 -10
- package/dist/apps/management/routes/usage.js.map +1 -1
- package/dist/apps/mcp.d.ts.map +1 -1
- package/dist/apps/mcp.js +38 -4
- package/dist/apps/mcp.js.map +1 -1
- package/dist/apps/relay/App.d.ts +26 -4
- package/dist/apps/relay/App.d.ts.map +1 -1
- package/dist/apps/relay/App.js +17 -14
- package/dist/apps/relay/App.js.map +1 -1
- package/dist/cloudflare.d.ts +2 -0
- package/dist/cloudflare.d.ts.map +1 -1
- package/dist/cloudflare.js +4 -0
- package/dist/cloudflare.js.map +1 -1
- package/dist/db/Db.d.ts +5 -3
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +52 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +13 -0
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +16 -1
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/adminAuditLogs.d.ts +27 -0
- package/dist/db/tables/adminAuditLogs.d.ts.map +1 -0
- package/dist/db/tables/adminAuditLogs.js +19 -0
- package/dist/db/tables/adminAuditLogs.js.map +1 -0
- package/dist/db/tables/invitations.d.ts +1 -1
- package/dist/db/tables/invitations.d.ts.map +1 -1
- package/dist/db/tables/memberships.d.ts +32 -4
- package/dist/db/tables/memberships.d.ts.map +1 -1
- package/dist/db/tables/memberships.js +31 -0
- package/dist/db/tables/memberships.js.map +1 -1
- package/dist/db/tables/organizations.d.ts +43 -1
- package/dist/db/tables/organizations.d.ts.map +1 -1
- package/dist/db/tables/organizations.js +103 -0
- package/dist/db/tables/organizations.js.map +1 -1
- package/dist/db/tables/projects.d.ts +25 -0
- package/dist/db/tables/projects.d.ts.map +1 -1
- package/dist/db/tables/projects.js +22 -0
- package/dist/db/tables/projects.js.map +1 -1
- package/dist/db/tables/webhookDeliveries.d.ts +26 -0
- package/dist/db/tables/webhookDeliveries.d.ts.map +1 -1
- package/dist/db/tables/webhookDeliveries.js +37 -0
- package/dist/db/tables/webhookDeliveries.js.map +1 -1
- package/dist/internal/Auth.d.ts +46 -40
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +118 -69
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/Cursor.d.ts +1 -1
- package/dist/internal/Cursor.d.ts.map +1 -1
- package/dist/internal/Cursor.js +4 -0
- package/dist/internal/Cursor.js.map +1 -1
- package/dist/internal/EdgeCache.d.ts.map +1 -1
- package/dist/internal/EdgeCache.js +2 -0
- package/dist/internal/EdgeCache.js.map +1 -1
- package/dist/internal/Errors.d.ts +6 -0
- package/dist/internal/Errors.d.ts.map +1 -0
- package/dist/internal/Errors.js +16 -0
- package/dist/internal/Errors.js.map +1 -0
- package/dist/internal/FundingRoutes.d.ts +15 -43
- package/dist/internal/FundingRoutes.d.ts.map +1 -1
- package/dist/internal/FundingRoutes.js +8 -17
- package/dist/internal/FundingRoutes.js.map +1 -1
- package/dist/internal/Log.d.ts +16 -1
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js +5 -3
- package/dist/internal/Log.js.map +1 -1
- package/dist/internal/Store.d.ts +61 -16
- package/dist/internal/Store.d.ts.map +1 -1
- package/dist/internal/Store.js +72 -17
- package/dist/internal/Store.js.map +1 -1
- package/dist/internal/Viem.d.ts +1 -1
- package/dist/internal/index.d.ts +27 -0
- package/dist/internal/index.d.ts.map +1 -0
- package/dist/internal/index.js +27 -0
- package/dist/internal/index.js.map +1 -0
- package/package.json +8 -3
- package/src/ApiKey.ts +2 -3
- package/src/ApiKeys.ts +95 -17
- package/src/App.test.ts +104 -6
- package/src/App.ts +29 -9
- package/src/Assets.ts +8 -0
- package/src/Auth.test-d.ts +36 -0
- package/src/Auth.ts +20 -1
- package/src/Client.test-d.ts +17 -17
- package/src/Scope.test.ts +34 -0
- package/src/Scope.ts +53 -17
- package/src/admin/App.test.ts +33 -3
- package/src/admin/App.ts +49 -14
- package/src/admin/apps/api-keys.ts +10 -3
- package/src/admin/apps/organizations.test.ts +455 -0
- package/src/admin/apps/organizations.ts +953 -0
- package/src/admin/apps/scopes.test.ts +10 -0
- package/src/admin/ui/plugin.ts +1 -1
- package/src/admin/ui/src/components/MobileProfile.tsx +2 -0
- package/src/admin/ui/src/components/ThemeToggle.tsx +20 -0
- package/src/admin/ui/src/lib/api-keys.ts +36 -19
- package/src/admin/ui/src/lib/config.ts +2 -3
- package/src/admin/ui/src/lib/organizations.test.ts +89 -0
- package/src/admin/ui/src/lib/organizations.ts +262 -0
- package/src/admin/ui/src/lib/query-client.ts +3 -1
- package/src/admin/ui/src/lib/theme.ts +57 -0
- package/src/admin/ui/src/routeTree.gen.ts +47 -4
- package/src/admin/ui/src/routes/__root.tsx +18 -3
- package/src/admin/ui/src/routes/api-keys.tsx +52 -7
- package/src/admin/ui/src/routes/index.tsx +1 -1
- package/src/admin/ui/src/routes/organizations.tsx +158 -0
- package/src/admin/ui/src/routes/organizations_.$orgId.tsx +710 -0
- package/src/admin/ui/src/start.ts +6 -0
- package/src/admin/ui.generated.ts +63 -13
- package/src/analytics/Analytics.ts +25 -0
- package/src/analytics/tables/requestEvents.ts +33 -0
- package/src/apps/composition.test.ts +82 -2
- package/src/apps/data/FundingRouteProviders.test.ts +37 -93
- package/src/apps/data/FundingRouteProviders.ts +14 -161
- package/src/apps/data/routes/funding.test.ts +7 -42
- package/src/apps/data/routes/funding.ts +8 -8
- package/src/apps/data/routes/indexer.test.ts +2 -2
- package/src/apps/data/routes/webhooks.test.ts +2 -2
- package/src/apps/management/App.ts +2 -2
- package/src/apps/management/Billing.ts +10 -2
- package/src/apps/management/routes/api-keys.test.ts +137 -1
- package/src/apps/management/routes/api-keys.ts +63 -38
- package/src/apps/management/routes/billing.ts +14 -14
- package/src/apps/management/routes/invitations.test.ts +102 -1
- package/src/apps/management/routes/invitations.ts +13 -15
- package/src/apps/management/routes/members.ts +10 -10
- package/src/apps/management/routes/orgs.test.ts +78 -16
- package/src/apps/management/routes/orgs.ts +21 -20
- package/src/apps/management/routes/projects.ts +12 -13
- package/src/apps/management/routes/scopes.test.ts +16 -0
- package/src/apps/management/routes/scopes.ts +6 -7
- package/src/apps/management/routes/usage.ts +9 -14
- package/src/apps/mcp.test.ts +72 -7
- package/src/apps/mcp.ts +46 -4
- package/src/apps/relay/App.test.ts +23 -18
- package/src/apps/relay/App.ts +35 -14
- package/src/cloudflare.ts +5 -0
- package/src/db/Db.ts +59 -3
- package/src/db/Schema.ts +20 -1
- package/src/db/tables/adminAuditLogs.ts +47 -0
- package/src/db/tables/invitations.ts +1 -1
- package/src/db/tables/memberships.ts +59 -3
- package/src/db/tables/organizations.ts +148 -1
- package/src/db/tables/projects.ts +47 -0
- package/src/db/tables/webhookDeliveries.ts +83 -0
- package/src/internal/Auth.test.ts +571 -23
- package/src/internal/Auth.ts +166 -94
- package/src/internal/Cursor.test.ts +10 -0
- package/src/internal/Cursor.ts +5 -1
- package/src/internal/EdgeCache.test.ts +11 -0
- package/src/internal/EdgeCache.ts +2 -0
- package/src/internal/FundingRoutes.test.ts +8 -8
- package/src/internal/FundingRoutes.ts +25 -29
- package/src/internal/Log.test.ts +41 -3
- package/src/internal/Log.ts +23 -4
- package/src/internal/Store.test-d.ts +15 -0
- package/src/internal/Store.test.ts +153 -2
- package/src/internal/Store.ts +157 -41
- package/src/internal/index.test.ts +34 -0
- package/src/internal/index.ts +26 -0
package/src/db/Schema.ts
CHANGED
|
@@ -2,6 +2,25 @@ import * as z from 'zod/mini'
|
|
|
2
2
|
|
|
3
3
|
import type * as Webhooks from '../internal/Webhooks.js'
|
|
4
4
|
|
|
5
|
+
/** One authenticated administrator access to a protected admin API route. */
|
|
6
|
+
export const AdminAuditLog = z.object({
|
|
7
|
+
actor: z.string().check(z.describe('Verified administrator email.')),
|
|
8
|
+
createdAt: z.string().check(z.describe('When the access completed (ISO 8601).')),
|
|
9
|
+
id: z.string().check(z.describe('Opaque audit event id (`aud_…`).')),
|
|
10
|
+
method: z.string().check(z.describe('HTTP method used for the access.')),
|
|
11
|
+
path: z.string().check(z.describe('Admin API path accessed, without query parameters.')),
|
|
12
|
+
query: z
|
|
13
|
+
.nullable(z.string())
|
|
14
|
+
.check(z.describe('Query string used for the lookup, or null when absent.')),
|
|
15
|
+
requestId: z.string().check(z.describe('Request id correlating the access with request logs.')),
|
|
16
|
+
status: z
|
|
17
|
+
.number()
|
|
18
|
+
.check(z.int(), z.describe('HTTP response status returned to the administrator.')),
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
/** Columns of the `admin_audit_logs` table. */
|
|
22
|
+
export type AdminAuditLog = z.output<typeof AdminAuditLog>
|
|
23
|
+
|
|
5
24
|
// Row schemas for database tables (`zod/mini`), the source of truth each
|
|
6
25
|
// `tables/` module derives its `Table` type from. Nullable fields map to
|
|
7
26
|
// nullable columns; the domain layer converts `null` back to absent fields.
|
|
@@ -49,7 +68,7 @@ export const Invitation = z.object({
|
|
|
49
68
|
z.describe('Opaque invitation id (`inv_…`).'),
|
|
50
69
|
z.meta({ examples: ['inv_1a2b3c4d5e6f7g8h9j0k1m2n'] }),
|
|
51
70
|
),
|
|
52
|
-
invitedBy: z.string().check(z.describe('User id
|
|
71
|
+
invitedBy: z.string().check(z.describe('User or API key id that created the invitation.')),
|
|
53
72
|
orgId: z.string().check(z.describe('Organization id (`org_…`) the invitation joins.')),
|
|
54
73
|
revokedAt: z
|
|
55
74
|
.nullable(z.string())
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Selectable } from 'kysely'
|
|
2
|
+
|
|
3
|
+
import * as Id from '../../internal/Id.js'
|
|
4
|
+
import type * as Db from '../Db.js'
|
|
5
|
+
import type * as db_Schema from '../Schema.js'
|
|
6
|
+
|
|
7
|
+
/** Columns of the `admin_audit_logs` table. */
|
|
8
|
+
export type Table = db_Schema.AdminAuditLog
|
|
9
|
+
|
|
10
|
+
/** One stored administrator access event. */
|
|
11
|
+
export type Record = Selectable<Table>
|
|
12
|
+
|
|
13
|
+
/** Records an authenticated access to a protected admin API route. */
|
|
14
|
+
export function insert(db: Db.Db, input: insert.Input): Promise<Record> {
|
|
15
|
+
return db.kysely
|
|
16
|
+
.insertInto('admin_audit_logs')
|
|
17
|
+
.values({
|
|
18
|
+
actor: input.actor,
|
|
19
|
+
createdAt: new Date().toISOString(),
|
|
20
|
+
id: Id.generate('aud'),
|
|
21
|
+
method: input.method,
|
|
22
|
+
path: input.path,
|
|
23
|
+
query: input.query ?? null,
|
|
24
|
+
requestId: input.requestId,
|
|
25
|
+
status: input.status,
|
|
26
|
+
})
|
|
27
|
+
.returningAll()
|
|
28
|
+
.executeTakeFirstOrThrow()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export declare namespace insert {
|
|
32
|
+
/** Fields captured for one administrator access. */
|
|
33
|
+
type Input = {
|
|
34
|
+
/** Verified administrator email. */
|
|
35
|
+
actor: string
|
|
36
|
+
/** HTTP method used for the access. */
|
|
37
|
+
method: string
|
|
38
|
+
/** Admin API path accessed, without query parameters. */
|
|
39
|
+
path: string
|
|
40
|
+
/** Query string used for the lookup. */
|
|
41
|
+
query?: string | undefined
|
|
42
|
+
/** Request id correlating the access with request logs. */
|
|
43
|
+
requestId: string
|
|
44
|
+
/** HTTP response status returned to the administrator. */
|
|
45
|
+
status: number
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -60,7 +60,7 @@ export declare namespace create {
|
|
|
60
60
|
email: string
|
|
61
61
|
/** When the invitation expires (ISO 8601). */
|
|
62
62
|
expiresAt: string
|
|
63
|
-
/** User id
|
|
63
|
+
/** User or API key id creating the invitation. */
|
|
64
64
|
invitedBy: string
|
|
65
65
|
/** Organization id (`org_…`) the invitation joins. */
|
|
66
66
|
orgId: string
|
|
@@ -9,6 +9,9 @@ export type Table = db_Schema.Membership
|
|
|
9
9
|
/** A stored membership row. */
|
|
10
10
|
export type Record = Selectable<Table>
|
|
11
11
|
|
|
12
|
+
/** A membership with the member's resolved user identity. */
|
|
13
|
+
export type DetailedRecord = Record & { address: string | null; email: string | null }
|
|
14
|
+
|
|
12
15
|
/** A membership role. */
|
|
13
16
|
export type Role = Record['role']
|
|
14
17
|
|
|
@@ -93,20 +96,73 @@ export function listByOrg(db: Db.Db, orgId: string): Promise<Record[]> {
|
|
|
93
96
|
* @param orgId - The organization id (`org_…`).
|
|
94
97
|
* @returns The records with `address`/`email` from the member's user row.
|
|
95
98
|
*/
|
|
96
|
-
export function listByOrgDetailed(
|
|
99
|
+
export function listByOrgDetailed(db: Db.Db, orgId: string): Promise<DetailedRecord[]> {
|
|
100
|
+
return db.kysely
|
|
101
|
+
.selectFrom('memberships')
|
|
102
|
+
.innerJoin('users', 'users.id', 'memberships.userId')
|
|
103
|
+
.where('memberships.orgId', '=', orgId)
|
|
104
|
+
.selectAll('memberships')
|
|
105
|
+
.select(['users.address', 'users.email'])
|
|
106
|
+
.orderBy('memberships.createdAt', 'asc')
|
|
107
|
+
.execute()
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Lists a bounded page of an organization's memberships with member identity,
|
|
112
|
+
* oldest first.
|
|
113
|
+
*
|
|
114
|
+
* @param db - The database.
|
|
115
|
+
* @param orgId - The organization id.
|
|
116
|
+
* @param options - Keyset and page-size options.
|
|
117
|
+
* @returns At most `limit + 1` rows for next-cursor derivation.
|
|
118
|
+
*/
|
|
119
|
+
export function listByOrgDetailedPage(
|
|
97
120
|
db: Db.Db,
|
|
98
121
|
orgId: string,
|
|
99
|
-
|
|
100
|
-
|
|
122
|
+
options: listByOrgDetailedPage.Options,
|
|
123
|
+
): Promise<DetailedRecord[]> {
|
|
124
|
+
const cursor = options.cursor
|
|
125
|
+
let query = db.kysely
|
|
101
126
|
.selectFrom('memberships')
|
|
102
127
|
.innerJoin('users', 'users.id', 'memberships.userId')
|
|
103
128
|
.where('memberships.orgId', '=', orgId)
|
|
104
129
|
.selectAll('memberships')
|
|
105
130
|
.select(['users.address', 'users.email'])
|
|
131
|
+
if (cursor)
|
|
132
|
+
query = query.where((eb) =>
|
|
133
|
+
eb.or([
|
|
134
|
+
eb('memberships.createdAt', '>', cursor.createdAt),
|
|
135
|
+
eb.and([
|
|
136
|
+
eb('memberships.createdAt', '=', cursor.createdAt),
|
|
137
|
+
eb('memberships.userId', '>', cursor.userId),
|
|
138
|
+
]),
|
|
139
|
+
]),
|
|
140
|
+
)
|
|
141
|
+
return query
|
|
106
142
|
.orderBy('memberships.createdAt', 'asc')
|
|
143
|
+
.orderBy('memberships.userId', 'asc')
|
|
144
|
+
.limit(options.limit + 1)
|
|
107
145
|
.execute()
|
|
108
146
|
}
|
|
109
147
|
|
|
148
|
+
export declare namespace listByOrgDetailedPage {
|
|
149
|
+
/** Cursor fields for the last membership on the previous page. */
|
|
150
|
+
type Cursor = {
|
|
151
|
+
/** Membership creation time. */
|
|
152
|
+
createdAt: string
|
|
153
|
+
/** Member user id, used as a deterministic tie-breaker. */
|
|
154
|
+
userId: string
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Options for {@link listByOrgDetailedPage}. */
|
|
158
|
+
type Options = {
|
|
159
|
+
/** Last membership returned by the previous page. */
|
|
160
|
+
cursor?: Cursor | undefined
|
|
161
|
+
/** Requested page size. */
|
|
162
|
+
limit: number
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
110
166
|
/**
|
|
111
167
|
* Changes a member's role atomically, refusing to demote the last owner. The
|
|
112
168
|
* org's owner rows are locked for the transaction, so a concurrent demotion or
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { sql, type Selectable } from 'kysely'
|
|
2
2
|
|
|
3
3
|
import * as Id from '../../internal/Id.js'
|
|
4
4
|
import type * as Db from '../Db.js'
|
|
@@ -133,6 +133,39 @@ export function get(db: Db.Db, id: string): Promise<Record | undefined> {
|
|
|
133
133
|
return db.kysely.selectFrom('organizations').selectAll().where('id', '=', id).executeTakeFirst()
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Reads an organization with current member and project counts.
|
|
138
|
+
*
|
|
139
|
+
* @param db - The database.
|
|
140
|
+
* @param id - The organization id.
|
|
141
|
+
* @returns The organization summary, or `undefined` when absent.
|
|
142
|
+
*/
|
|
143
|
+
export async function getSummary(db: Db.Db, id: string): Promise<search.Result | undefined> {
|
|
144
|
+
const record = await db.kysely
|
|
145
|
+
.selectFrom('organizations')
|
|
146
|
+
.selectAll('organizations')
|
|
147
|
+
.select((eb) => [
|
|
148
|
+
eb
|
|
149
|
+
.selectFrom('memberships')
|
|
150
|
+
.select((builder) => builder.fn.countAll<number>().as('count'))
|
|
151
|
+
.whereRef('memberships.orgId', '=', 'organizations.id')
|
|
152
|
+
.as('memberCount'),
|
|
153
|
+
eb
|
|
154
|
+
.selectFrom('projects')
|
|
155
|
+
.select((builder) => builder.fn.countAll<number>().as('count'))
|
|
156
|
+
.whereRef('projects.orgId', '=', 'organizations.id')
|
|
157
|
+
.as('projectCount'),
|
|
158
|
+
])
|
|
159
|
+
.where('organizations.id', '=', id)
|
|
160
|
+
.executeTakeFirst()
|
|
161
|
+
if (!record) return undefined
|
|
162
|
+
return {
|
|
163
|
+
...record,
|
|
164
|
+
memberCount: Number(record.memberCount),
|
|
165
|
+
projectCount: Number(record.projectCount),
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
136
169
|
/**
|
|
137
170
|
* Lists organizations, newest first.
|
|
138
171
|
*
|
|
@@ -143,6 +176,120 @@ export function list(db: Db.Db): Promise<Record[]> {
|
|
|
143
176
|
return db.kysely.selectFrom('organizations').selectAll().orderBy('createdAt', 'desc').execute()
|
|
144
177
|
}
|
|
145
178
|
|
|
179
|
+
/**
|
|
180
|
+
* Searches organizations for the admin lookup surface, newest first. Results
|
|
181
|
+
* include member and project counts and use a stable `(createdAt, id)` keyset.
|
|
182
|
+
*
|
|
183
|
+
* @param db - The database.
|
|
184
|
+
* @param options - Search, keyset, and page-size options.
|
|
185
|
+
* @returns At most `limit + 1` rows so the caller can derive a next cursor.
|
|
186
|
+
*/
|
|
187
|
+
export async function search(db: Db.Db, options: search.Options): Promise<search.Result[]> {
|
|
188
|
+
const { cursor, limit, query: searchQuery } = options
|
|
189
|
+
let query = db.kysely
|
|
190
|
+
.selectFrom('organizations')
|
|
191
|
+
.selectAll('organizations')
|
|
192
|
+
.select((eb) => [
|
|
193
|
+
eb
|
|
194
|
+
.selectFrom('memberships')
|
|
195
|
+
.select((builder) => builder.fn.countAll<number>().as('count'))
|
|
196
|
+
.whereRef('memberships.orgId', '=', 'organizations.id')
|
|
197
|
+
.as('memberCount'),
|
|
198
|
+
eb
|
|
199
|
+
.selectFrom('projects')
|
|
200
|
+
.select((builder) => builder.fn.countAll<number>().as('count'))
|
|
201
|
+
.whereRef('projects.orgId', '=', 'organizations.id')
|
|
202
|
+
.as('projectCount'),
|
|
203
|
+
])
|
|
204
|
+
|
|
205
|
+
if (searchQuery?.startsWith('org_')) query = query.where('organizations.id', '=', searchQuery)
|
|
206
|
+
else if (searchQuery?.startsWith('usr_'))
|
|
207
|
+
query = query.where((eb) =>
|
|
208
|
+
eb.or([
|
|
209
|
+
eb('organizations.userId', '=', searchQuery),
|
|
210
|
+
eb(
|
|
211
|
+
'organizations.id',
|
|
212
|
+
'in',
|
|
213
|
+
eb
|
|
214
|
+
.selectFrom('memberships')
|
|
215
|
+
.select('memberships.orgId')
|
|
216
|
+
.where('memberships.userId', '=', searchQuery),
|
|
217
|
+
),
|
|
218
|
+
]),
|
|
219
|
+
)
|
|
220
|
+
else if (searchQuery) {
|
|
221
|
+
const pattern = `${searchQuery
|
|
222
|
+
.toLowerCase()
|
|
223
|
+
.replaceAll('\\', '\\\\')
|
|
224
|
+
.replaceAll('%', '\\%')
|
|
225
|
+
.replaceAll('_', '\\_')}%`
|
|
226
|
+
query = query.where((eb) =>
|
|
227
|
+
eb.or([
|
|
228
|
+
sql<boolean>`lower(${sql.ref('organizations.name')}) LIKE ${pattern} ESCAPE '\\'`,
|
|
229
|
+
eb(
|
|
230
|
+
'organizations.id',
|
|
231
|
+
'in',
|
|
232
|
+
eb
|
|
233
|
+
.selectFrom('memberships')
|
|
234
|
+
.innerJoin('users', 'users.id', 'memberships.userId')
|
|
235
|
+
.select('memberships.orgId')
|
|
236
|
+
.where(sql<boolean>`lower(${sql.ref('users.email')}) LIKE ${pattern} ESCAPE '\\'`),
|
|
237
|
+
),
|
|
238
|
+
]),
|
|
239
|
+
)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (cursor)
|
|
243
|
+
query = query.where((eb) =>
|
|
244
|
+
eb.or([
|
|
245
|
+
eb('organizations.createdAt', '<', cursor.createdAt),
|
|
246
|
+
eb.and([
|
|
247
|
+
eb('organizations.createdAt', '=', cursor.createdAt),
|
|
248
|
+
eb('organizations.id', '<', cursor.id),
|
|
249
|
+
]),
|
|
250
|
+
]),
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
const records = await query
|
|
254
|
+
.orderBy('organizations.createdAt', 'desc')
|
|
255
|
+
.orderBy('organizations.id', 'desc')
|
|
256
|
+
.limit(limit + 1)
|
|
257
|
+
.execute()
|
|
258
|
+
return records.map((record) => ({
|
|
259
|
+
...record,
|
|
260
|
+
memberCount: Number(record.memberCount),
|
|
261
|
+
projectCount: Number(record.projectCount),
|
|
262
|
+
}))
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export declare namespace search {
|
|
266
|
+
/** Cursor fields for the last organization returned by the previous page. */
|
|
267
|
+
type Cursor = {
|
|
268
|
+
/** Organization creation time. */
|
|
269
|
+
createdAt: string
|
|
270
|
+
/** Organization id, used as a deterministic tie-breaker. */
|
|
271
|
+
id: string
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** Options for {@link search}. */
|
|
275
|
+
type Options = {
|
|
276
|
+
/** Last organization returned by the previous page. */
|
|
277
|
+
cursor?: Cursor | undefined
|
|
278
|
+
/** Requested page size. */
|
|
279
|
+
limit: number
|
|
280
|
+
/** Exact resource id or case-insensitive name/email prefix search. */
|
|
281
|
+
query?: string | undefined
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** Organization summary returned by {@link search}. */
|
|
285
|
+
type Result = Record & {
|
|
286
|
+
/** Number of current organization members. */
|
|
287
|
+
memberCount: number
|
|
288
|
+
/** Number of projects owned by the organization. */
|
|
289
|
+
projectCount: number
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
146
293
|
/**
|
|
147
294
|
* Lists organizations owned by `userId`, newest first.
|
|
148
295
|
*
|
|
@@ -81,6 +81,53 @@ export function listByOrg(db: Db.Db, orgId: string): Promise<Record[]> {
|
|
|
81
81
|
.execute()
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Lists a bounded page of an organization's projects, newest first.
|
|
86
|
+
*
|
|
87
|
+
* @param db - The database.
|
|
88
|
+
* @param orgId - The organization id.
|
|
89
|
+
* @param options - Keyset and page-size options.
|
|
90
|
+
* @returns At most `limit + 1` rows for next-cursor derivation.
|
|
91
|
+
*/
|
|
92
|
+
export function listByOrgPage(
|
|
93
|
+
db: Db.Db,
|
|
94
|
+
orgId: string,
|
|
95
|
+
options: listByOrgPage.Options,
|
|
96
|
+
): Promise<Record[]> {
|
|
97
|
+
const cursor = options.cursor
|
|
98
|
+
let query = db.kysely.selectFrom('projects').selectAll().where('orgId', '=', orgId)
|
|
99
|
+
if (cursor)
|
|
100
|
+
query = query.where((eb) =>
|
|
101
|
+
eb.or([
|
|
102
|
+
eb('createdAt', '<', cursor.createdAt),
|
|
103
|
+
eb.and([eb('createdAt', '=', cursor.createdAt), eb('id', '<', cursor.id)]),
|
|
104
|
+
]),
|
|
105
|
+
)
|
|
106
|
+
return query
|
|
107
|
+
.orderBy('createdAt', 'desc')
|
|
108
|
+
.orderBy('id', 'desc')
|
|
109
|
+
.limit(options.limit + 1)
|
|
110
|
+
.execute()
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export declare namespace listByOrgPage {
|
|
114
|
+
/** Cursor fields for the last project on the previous page. */
|
|
115
|
+
type Cursor = {
|
|
116
|
+
/** Project creation time. */
|
|
117
|
+
createdAt: string
|
|
118
|
+
/** Project id, used as a deterministic tie-breaker. */
|
|
119
|
+
id: string
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Options for {@link listByOrgPage}. */
|
|
123
|
+
type Options = {
|
|
124
|
+
/** Last project returned by the previous page. */
|
|
125
|
+
cursor?: Cursor | undefined
|
|
126
|
+
/** Requested page size. */
|
|
127
|
+
limit: number
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
84
131
|
/**
|
|
85
132
|
* Renames a project, bumping `updatedAt`.
|
|
86
133
|
*
|
|
@@ -10,6 +10,12 @@ export type Table = db_Schema.WebhookDelivery
|
|
|
10
10
|
/** A stored webhook-delivery row. */
|
|
11
11
|
export type Record = Selectable<Table>
|
|
12
12
|
|
|
13
|
+
/** A failed delivery joined to its organization-owned subscription. */
|
|
14
|
+
export type OrganizationFailure = Pick<
|
|
15
|
+
Record,
|
|
16
|
+
'attempt' | 'createdAt' | 'error' | 'id' | 'responseStatus' | 'subscriptionId'
|
|
17
|
+
>
|
|
18
|
+
|
|
13
19
|
/** Maps a stored row to the domain delivery (null columns → absent fields). */
|
|
14
20
|
export function toDelivery(row: Record): Webhooks.Delivery {
|
|
15
21
|
return {
|
|
@@ -85,6 +91,83 @@ export async function list(
|
|
|
85
91
|
return (await query.execute()).map(toDelivery)
|
|
86
92
|
}
|
|
87
93
|
|
|
94
|
+
/** Lists recent failed webhook deliveries across an organization. */
|
|
95
|
+
export function listFailuresByOrg(
|
|
96
|
+
db: Db.Db,
|
|
97
|
+
orgId: string,
|
|
98
|
+
options: listFailuresByOrg.Options,
|
|
99
|
+
): Promise<OrganizationFailure[]> {
|
|
100
|
+
return db.kysely
|
|
101
|
+
.selectFrom('webhook_deliveries')
|
|
102
|
+
.innerJoin(
|
|
103
|
+
'webhook_subscriptions',
|
|
104
|
+
'webhook_subscriptions.id',
|
|
105
|
+
'webhook_deliveries.subscriptionId',
|
|
106
|
+
)
|
|
107
|
+
.select([
|
|
108
|
+
'webhook_deliveries.attempt',
|
|
109
|
+
'webhook_deliveries.createdAt',
|
|
110
|
+
'webhook_deliveries.error',
|
|
111
|
+
'webhook_deliveries.id',
|
|
112
|
+
'webhook_deliveries.responseStatus',
|
|
113
|
+
'webhook_deliveries.subscriptionId',
|
|
114
|
+
])
|
|
115
|
+
.where('webhook_subscriptions.ownerId', '=', orgId)
|
|
116
|
+
.where('webhook_subscriptions.ownerType', '=', 'api_key')
|
|
117
|
+
.where('webhook_deliveries.createdAt', '>=', options.from)
|
|
118
|
+
.where('webhook_deliveries.expiresAt', '>', options.now)
|
|
119
|
+
.where('webhook_deliveries.status', '=', 'failed')
|
|
120
|
+
.orderBy('webhook_deliveries.createdAt', 'desc')
|
|
121
|
+
.orderBy('webhook_deliveries.id', 'desc')
|
|
122
|
+
.limit(options.limit)
|
|
123
|
+
.execute()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export declare namespace listFailuresByOrg {
|
|
127
|
+
/** Bounds for the organization failure scan. */
|
|
128
|
+
type Options = {
|
|
129
|
+
/** Window start (ISO 8601), inclusive. */
|
|
130
|
+
from: string
|
|
131
|
+
/** Maximum failures to return. */
|
|
132
|
+
limit: number
|
|
133
|
+
/** ISO timestamp retention is evaluated against. */
|
|
134
|
+
now: string
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Counts failed webhook deliveries across an organization in a time window. */
|
|
139
|
+
export async function countFailuresByOrg(
|
|
140
|
+
db: Db.Db,
|
|
141
|
+
orgId: string,
|
|
142
|
+
options: countFailuresByOrg.Options,
|
|
143
|
+
): Promise<number> {
|
|
144
|
+
const row = await db.kysely
|
|
145
|
+
.selectFrom('webhook_deliveries')
|
|
146
|
+
.innerJoin(
|
|
147
|
+
'webhook_subscriptions',
|
|
148
|
+
'webhook_subscriptions.id',
|
|
149
|
+
'webhook_deliveries.subscriptionId',
|
|
150
|
+
)
|
|
151
|
+
.select((eb) => eb.fn.countAll().as('count'))
|
|
152
|
+
.where('webhook_subscriptions.ownerId', '=', orgId)
|
|
153
|
+
.where('webhook_subscriptions.ownerType', '=', 'api_key')
|
|
154
|
+
.where('webhook_deliveries.createdAt', '>=', options.from)
|
|
155
|
+
.where('webhook_deliveries.expiresAt', '>', options.now)
|
|
156
|
+
.where('webhook_deliveries.status', '=', 'failed')
|
|
157
|
+
.executeTakeFirstOrThrow()
|
|
158
|
+
return Number(row.count)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export declare namespace countFailuresByOrg {
|
|
162
|
+
/** Bounds for the organization failure count. */
|
|
163
|
+
type Options = {
|
|
164
|
+
/** Window start (ISO 8601), inclusive. */
|
|
165
|
+
from: string
|
|
166
|
+
/** ISO timestamp retention is evaluated against. */
|
|
167
|
+
now: string
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
88
171
|
export declare namespace list {
|
|
89
172
|
/** Options for {@link list}. */
|
|
90
173
|
type Options = {
|