@loopwise/admin-sdk 0.1.0-beta.1 → 0.1.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +56 -0
- package/README.md +89 -1
- package/dist/better-auth.d.mts +47 -6
- package/dist/better-auth.mjs +25 -28
- package/dist/index.d.mts +53 -4
- package/dist/index.mjs +47 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,61 @@
|
|
|
1
1
|
# @loopwise/admin-sdk
|
|
2
2
|
|
|
3
|
+
## 0.1.0-beta.3
|
|
4
|
+
|
|
5
|
+
DX quick wins informed by the first real integration ([nii-course-system](https://github.com/niischool-tw/nii-course-system/pull/38),
|
|
6
|
+
agent-driven, shipped against `0.1.0-beta.0`). See
|
|
7
|
+
[TEACH-18885](https://linear.app/kaik/issue/TEACH-18885) for the full
|
|
8
|
+
feedback report; [TEACH-18912](https://linear.app/kaik/issue/TEACH-18912)
|
|
9
|
+
for this PR.
|
|
10
|
+
|
|
11
|
+
### Breaking changes
|
|
12
|
+
|
|
13
|
+
- **`baseUrl` → `baseURL`** on both `LoopwiseAuthOptions` (better-auth
|
|
14
|
+
wrapper) and `LoopwiseAdminConfig` (core admin client). Casing now
|
|
15
|
+
matches better-auth's own `baseURL` / `BETTER_AUTH_URL` convention,
|
|
16
|
+
removing a same-module typo footgun. Migration is a one-character
|
|
17
|
+
rename at any call site that passes the option explicitly; TS
|
|
18
|
+
catches it at compile time. Callers relying on the default are
|
|
19
|
+
unaffected.
|
|
20
|
+
|
|
21
|
+
### Features
|
|
22
|
+
|
|
23
|
+
- **`mapProfileToUser` pass-through on `loopwise({})`** — populate
|
|
24
|
+
custom `User` columns at sign-up (e.g. `teachifyUserId: profile.sub`)
|
|
25
|
+
without joining the `account` table. Forwards verbatim to
|
|
26
|
+
better-auth's `genericOAuth`.
|
|
27
|
+
- **`getLoopwiseRedirectURI({ baseURL, audience? })` helper** exported
|
|
28
|
+
from `@loopwise/admin-sdk/better-auth`. Returns the exact redirect
|
|
29
|
+
URI to whitelist on your OAuth client — the better-auth callback
|
|
30
|
+
path includes a `/oauth2/` segment that's easy to miss otherwise.
|
|
31
|
+
|
|
32
|
+
### Behavior changes
|
|
33
|
+
|
|
34
|
+
- **`org_admin` default scope shrunk to `['openid', 'profile', 'email']`**
|
|
35
|
+
(was `['openid', 'profile', 'email', 'courses:read', 'members:read']`).
|
|
36
|
+
New OAuth clients don't have API scopes enabled by default, so the
|
|
37
|
+
old behavior caused `invalid_scope` on the first callback. Pure SSO
|
|
38
|
+
now works out of the box; callers needing admin GraphQL access pass
|
|
39
|
+
`scopes` explicitly AND enable matching scopes on their OAuth client
|
|
40
|
+
in the Loopwise admin UI.
|
|
41
|
+
|
|
42
|
+
### Docs
|
|
43
|
+
|
|
44
|
+
- README adds Redirect URI, Scopes, Profile-mapping, and Roadmap
|
|
45
|
+
sections informed by the same feedback report.
|
|
46
|
+
|
|
47
|
+
## 0.1.0-beta.2
|
|
48
|
+
|
|
49
|
+
Add second typed resource:
|
|
50
|
+
|
|
51
|
+
- `admin.members.list({ perPage, page, role })` returns `AdminMemberPage`
|
|
52
|
+
(mirrors `courses.list` shape).
|
|
53
|
+
- `AdminMember`: `id`, `name`, `email`, `phoneNumber`, `createdAt`,
|
|
54
|
+
`lastSignInAt`.
|
|
55
|
+
- `AdminMemberRole`: `'student' | 'teaching_assistant'` mirrors the
|
|
56
|
+
schema enum.
|
|
57
|
+
- Requires OAuth scope `members:read`.
|
|
58
|
+
|
|
3
59
|
## 0.1.0-beta.1
|
|
4
60
|
|
|
5
61
|
No functional changes. Validates the tag-triggered OIDC publish path
|
package/README.md
CHANGED
|
@@ -55,4 +55,92 @@ export default async function Page() {
|
|
|
55
55
|
}
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
### Redirect URI
|
|
59
|
+
|
|
60
|
+
Better Auth's catch-all handler is mounted at `/api/auth/[...all]`, but the
|
|
61
|
+
`genericOAuth` plugin's callback path is `/oauth2/callback/<providerId>` —
|
|
62
|
+
note the `oauth2/` segment (different from many OAuth tutorials that show
|
|
63
|
+
`/api/auth/callback/...`).
|
|
64
|
+
|
|
65
|
+
Use the exported helper to compute the exact URI to whitelist on your
|
|
66
|
+
OAuth client:
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { getLoopwiseRedirectURI } from '@loopwise/admin-sdk/better-auth';
|
|
70
|
+
|
|
71
|
+
console.log(
|
|
72
|
+
getLoopwiseRedirectURI({ baseURL: process.env.BETTER_AUTH_URL! }),
|
|
73
|
+
);
|
|
74
|
+
// dev: http://localhost:3000/api/auth/oauth2/callback/loopwise
|
|
75
|
+
// prod: https://your.app/api/auth/oauth2/callback/loopwise
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
For multi-audience apps, pass `audience: 'member'` to get
|
|
79
|
+
`/oauth2/callback/loopwise-member`.
|
|
80
|
+
|
|
81
|
+
### Scopes
|
|
82
|
+
|
|
83
|
+
The wrapper requests `['openid', 'profile', 'email']` by default — the
|
|
84
|
+
minimum needed to identify the user. **Pure SSO works without any
|
|
85
|
+
additional setup.**
|
|
86
|
+
|
|
87
|
+
To call the admin GraphQL API, override `scopes` with the API scopes you
|
|
88
|
+
need, **and** enable those same scopes on your OAuth client in the
|
|
89
|
+
Loopwise admin UI:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
loopwise({
|
|
93
|
+
clientId, clientSecret,
|
|
94
|
+
scopes: ['openid', 'profile', 'email', 'courses:read', 'members:read'],
|
|
95
|
+
})
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Mismatch between requested and enabled scopes surfaces as
|
|
99
|
+
`error=invalid_scope` on the first callback.
|
|
100
|
+
|
|
101
|
+
### Mapping OAuth claims to your user table
|
|
102
|
+
|
|
103
|
+
Use `mapProfileToUser` to populate custom columns (declared via
|
|
104
|
+
better-auth's `additionalFields`) at sign-up:
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
loopwise({
|
|
108
|
+
clientId, clientSecret,
|
|
109
|
+
mapProfileToUser: (profile) => ({
|
|
110
|
+
teachifyUserId: profile.sub,
|
|
111
|
+
// role: profile['loopwise:org_role'], // pending TEACH-18913
|
|
112
|
+
}),
|
|
113
|
+
})
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The `profile` is whatever `/oauth/userinfo` returns — standard OIDC
|
|
117
|
+
claims today, plus Loopwise-specific `loopwise:*` claims once
|
|
118
|
+
[TEACH-18913](https://linear.app/kaik/issue/TEACH-18913) ships.
|
|
119
|
+
|
|
120
|
+
## Typed resources roadmap
|
|
121
|
+
|
|
122
|
+
Currently typed:
|
|
123
|
+
|
|
124
|
+
- `admin.courses.list({ perPage, page })` — `AdminCoursePage`
|
|
125
|
+
- `admin.members.list({ perPage, page, role })` — `AdminMemberPage`
|
|
126
|
+
|
|
127
|
+
Coming next (in priority order, informed by integrator feedback):
|
|
128
|
+
|
|
129
|
+
1. `admin.orders.list / get` — financial flow surface
|
|
130
|
+
2. `admin.enrollments.list` — member ↔ course relationships
|
|
131
|
+
3. Course content drill-down (`lectures`, `sections`)
|
|
132
|
+
4. `admin.coupons / subscriptions / pricing`
|
|
133
|
+
5. Content surfaces (`posts`, `pages`, `comments`)
|
|
134
|
+
|
|
135
|
+
Need something not yet typed? Use `admin.graphql<TData>({ query })` —
|
|
136
|
+
the raw escape hatch is fully typed via the generic and works against
|
|
137
|
+
the entire admin schema.
|
|
138
|
+
|
|
139
|
+
## Design notes
|
|
140
|
+
|
|
141
|
+
- See [TEACH-18856](https://linear.app/kaik/issue/TEACH-18856) for the
|
|
142
|
+
SDK architectural rationale (why branded `loopwise()` wrapper, why
|
|
143
|
+
framework-agnostic core, audience model).
|
|
144
|
+
- See [TEACH-18885](https://linear.app/kaik/issue/TEACH-18885) for the
|
|
145
|
+
first-integration feedback that informed the `0.1.0-beta.3` ergonomics
|
|
146
|
+
(default scope, redirect URI helper, profile mapping).
|
package/dist/better-auth.d.mts
CHANGED
|
@@ -20,17 +20,30 @@ interface LoopwiseAuthOptions {
|
|
|
20
20
|
*/
|
|
21
21
|
audience?: LoopwiseAuthAudience;
|
|
22
22
|
/**
|
|
23
|
-
* OAuth scopes to request. Defaults to
|
|
24
|
-
*
|
|
25
|
-
*
|
|
23
|
+
* OAuth scopes to request. Defaults to SSO-minimum
|
|
24
|
+
* `['openid', 'profile', 'email']`. To call admin GraphQL, override
|
|
25
|
+
* with the scopes you need AND enable them on the OAuth client in
|
|
26
|
+
* the Loopwise admin UI (see the `AUDIENCE_PROFILES` note for why
|
|
27
|
+
* we don't ship API scopes by default).
|
|
26
28
|
*/
|
|
27
29
|
scopes?: string[];
|
|
28
30
|
/**
|
|
29
31
|
* Base URL of the Loopwise instance. Defaults to `https://app.loopwise.com`.
|
|
30
32
|
* Discovery doc fetched from
|
|
31
|
-
* `${
|
|
33
|
+
* `${baseURL}/.well-known/oauth-authorization-server`.
|
|
34
|
+
*
|
|
35
|
+
* Casing matches better-auth's `baseURL` / `BETTER_AUTH_URL` convention.
|
|
36
|
+
*/
|
|
37
|
+
baseURL?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Populate custom columns (declared via better-auth's `additionalFields`)
|
|
40
|
+
* at sign-up — avoids joining the `account` table to read OAuth `sub`
|
|
41
|
+
* or any other userinfo claim. `loopwise:org_role` lands with TEACH-18913.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* mapProfileToUser: (profile) => ({ teachifyUserId: profile.sub }),
|
|
32
45
|
*/
|
|
33
|
-
|
|
46
|
+
mapProfileToUser?: (profile: Record<string, unknown>) => Record<string, unknown>;
|
|
34
47
|
}
|
|
35
48
|
/**
|
|
36
49
|
* Better-auth plugin for "log in with Loopwise". Wraps `genericOAuth` with
|
|
@@ -60,6 +73,34 @@ interface LoopwiseAuthOptions {
|
|
|
60
73
|
* }),
|
|
61
74
|
* ]
|
|
62
75
|
*/
|
|
76
|
+
interface GetRedirectURIOptions {
|
|
77
|
+
/** Same value as better-auth's `baseURL` / `BETTER_AUTH_URL`. */
|
|
78
|
+
baseURL: string;
|
|
79
|
+
/** Defaults to `'org_admin'`. Pass `'member'` for `loopwise-member`. */
|
|
80
|
+
audience?: LoopwiseAuthAudience;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Compute the exact redirect URI you need to register on your Loopwise
|
|
84
|
+
* OAuth client. Better Auth's `genericOAuth` plugin uses the path
|
|
85
|
+
* `/api/auth/oauth2/callback/<providerId>` — note the `oauth2/` segment
|
|
86
|
+
* (different from many OAuth tutorials that show `/api/auth/callback/...`).
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* // dev
|
|
90
|
+
* getLoopwiseRedirectURI({ baseURL: 'http://localhost:3000' });
|
|
91
|
+
* // → 'http://localhost:3000/api/auth/oauth2/callback/loopwise'
|
|
92
|
+
*
|
|
93
|
+
* // prod, member audience
|
|
94
|
+
* getLoopwiseRedirectURI({
|
|
95
|
+
* baseURL: 'https://app.example.com',
|
|
96
|
+
* audience: 'member',
|
|
97
|
+
* });
|
|
98
|
+
* // → 'https://app.example.com/api/auth/oauth2/callback/loopwise-member'
|
|
99
|
+
*
|
|
100
|
+
* Use this at app boot to print the URI so you (or your agent) can paste
|
|
101
|
+
* it straight into the OAuth client's redirect-URI list.
|
|
102
|
+
*/
|
|
103
|
+
declare function getLoopwiseRedirectURI(options: GetRedirectURIOptions): string;
|
|
63
104
|
declare function loopwise(options: LoopwiseAuthOptions): BetterAuthPlugin;
|
|
64
105
|
//#endregion
|
|
65
|
-
export { LoopwiseAuthAudience, LoopwiseAuthOptions, loopwise };
|
|
106
|
+
export { GetRedirectURIOptions, LoopwiseAuthAudience, LoopwiseAuthOptions, getLoopwiseRedirectURI, loopwise };
|
package/dist/better-auth.mjs
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { genericOAuth } from "better-auth/plugins";
|
|
2
2
|
//#region src/better-auth.ts
|
|
3
3
|
const DEFAULT_BASE_URL = "https://app.loopwise.com";
|
|
4
|
+
const BETTER_AUTH_OAUTH2_CALLBACK_PATH = "/api/auth/oauth2/callback";
|
|
4
5
|
const AUDIENCE_PROFILES = {
|
|
5
6
|
org_admin: {
|
|
6
7
|
providerId: "loopwise",
|
|
7
8
|
defaultScopes: [
|
|
8
9
|
"openid",
|
|
9
10
|
"profile",
|
|
10
|
-
"email"
|
|
11
|
-
"courses:read",
|
|
12
|
-
"members:read"
|
|
11
|
+
"email"
|
|
13
12
|
]
|
|
14
13
|
},
|
|
15
14
|
member: {
|
|
@@ -22,45 +21,43 @@ const AUDIENCE_PROFILES = {
|
|
|
22
21
|
}
|
|
23
22
|
};
|
|
24
23
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
24
|
+
* Compute the exact redirect URI you need to register on your Loopwise
|
|
25
|
+
* OAuth client. Better Auth's `genericOAuth` plugin uses the path
|
|
26
|
+
* `/api/auth/oauth2/callback/<providerId>` — note the `oauth2/` segment
|
|
27
|
+
* (different from many OAuth tutorials that show `/api/auth/callback/...`).
|
|
27
28
|
*
|
|
28
29
|
* @example
|
|
29
|
-
*
|
|
30
|
-
*
|
|
30
|
+
* // dev
|
|
31
|
+
* getLoopwiseRedirectURI({ baseURL: 'http://localhost:3000' });
|
|
32
|
+
* // → 'http://localhost:3000/api/auth/oauth2/callback/loopwise'
|
|
31
33
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* clientId: process.env.LOOPWISE_CLIENT_ID!,
|
|
37
|
-
* clientSecret: process.env.LOOPWISE_CLIENT_SECRET!,
|
|
38
|
-
* }),
|
|
39
|
-
* ],
|
|
34
|
+
* // prod, member audience
|
|
35
|
+
* getLoopwiseRedirectURI({
|
|
36
|
+
* baseURL: 'https://app.example.com',
|
|
37
|
+
* audience: 'member',
|
|
40
38
|
* });
|
|
39
|
+
* // → 'https://app.example.com/api/auth/oauth2/callback/loopwise-member'
|
|
41
40
|
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* loopwise({ clientId: STAFF_ID, clientSecret: STAFF_SECRET }),
|
|
45
|
-
* loopwise({
|
|
46
|
-
* clientId: MEMBER_ID,
|
|
47
|
-
* clientSecret: MEMBER_SECRET,
|
|
48
|
-
* audience: 'member',
|
|
49
|
-
* }),
|
|
50
|
-
* ]
|
|
41
|
+
* Use this at app boot to print the URI so you (or your agent) can paste
|
|
42
|
+
* it straight into the OAuth client's redirect-URI list.
|
|
51
43
|
*/
|
|
44
|
+
function getLoopwiseRedirectURI(options) {
|
|
45
|
+
const profile = AUDIENCE_PROFILES[options.audience ?? "org_admin"];
|
|
46
|
+
return `${options.baseURL.replace(/\/$/, "")}${BETTER_AUTH_OAUTH2_CALLBACK_PATH}/${profile.providerId}`;
|
|
47
|
+
}
|
|
52
48
|
function loopwise(options) {
|
|
53
49
|
const profile = AUDIENCE_PROFILES[options.audience ?? "org_admin"];
|
|
54
|
-
const
|
|
50
|
+
const baseURL = (options.baseURL ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
55
51
|
return genericOAuth({ config: [{
|
|
56
52
|
providerId: profile.providerId,
|
|
57
53
|
clientId: options.clientId,
|
|
58
54
|
clientSecret: options.clientSecret,
|
|
59
|
-
discoveryUrl: `${
|
|
55
|
+
discoveryUrl: `${baseURL}/.well-known/oauth-authorization-server`,
|
|
60
56
|
pkce: true,
|
|
61
57
|
accessType: "offline",
|
|
62
|
-
scopes: options.scopes ?? [...profile.defaultScopes]
|
|
58
|
+
scopes: options.scopes ?? [...profile.defaultScopes],
|
|
59
|
+
mapProfileToUser: options.mapProfileToUser
|
|
63
60
|
}] });
|
|
64
61
|
}
|
|
65
62
|
//#endregion
|
|
66
|
-
export { loopwise };
|
|
63
|
+
export { getLoopwiseRedirectURI, loopwise };
|
package/dist/index.d.mts
CHANGED
|
@@ -32,12 +32,13 @@ interface LoopwiseAdminConfig {
|
|
|
32
32
|
/**
|
|
33
33
|
* Base URL of the Loopwise instance. Defaults to `https://app.loopwise.com`.
|
|
34
34
|
* Override for staging (`https://staging.loopwise.com`) or self-hosted.
|
|
35
|
-
* The SDK appends `/admin/graphql` automatically.
|
|
35
|
+
* The SDK appends `/admin/graphql` automatically. Casing matches
|
|
36
|
+
* better-auth's `baseURL` / `BETTER_AUTH_URL` convention.
|
|
36
37
|
*/
|
|
37
|
-
|
|
38
|
+
baseURL?: string;
|
|
38
39
|
/**
|
|
39
40
|
* Override the full GraphQL endpoint URL. You normally never set this —
|
|
40
|
-
* the SDK derives it from `
|
|
41
|
+
* the SDK derives it from `baseURL`. Use only for non-standard routing
|
|
41
42
|
* (proxy with path rewrite, etc.).
|
|
42
43
|
*/
|
|
43
44
|
endpoint?: string;
|
|
@@ -138,9 +139,57 @@ declare class CoursesResource {
|
|
|
138
139
|
list(args?: CoursesListArgs): Promise<AdminCoursePage>;
|
|
139
140
|
}
|
|
140
141
|
//#endregion
|
|
142
|
+
//#region src/resources/members.d.ts
|
|
143
|
+
/**
|
|
144
|
+
* "Member" = non-admin school user. Maps to `Query.users(role: ...)`,
|
|
145
|
+
* which returns `AdminUserPage` / `AdminUser` (students by default; TAs
|
|
146
|
+
* when `role: 'teaching_assistant'`). Drop down to `admin.graphql()`
|
|
147
|
+
* for fields outside this minimal projection (attended courses,
|
|
148
|
+
* subscriptions, totalSpent, etc.).
|
|
149
|
+
*/
|
|
150
|
+
interface AdminMember {
|
|
151
|
+
id: string;
|
|
152
|
+
name: string | null;
|
|
153
|
+
email: string | null;
|
|
154
|
+
phoneNumber: string | null;
|
|
155
|
+
/** Unix timestamp seconds. */
|
|
156
|
+
createdAt: number;
|
|
157
|
+
/** Unix timestamp seconds, or null when the member has never signed in. */
|
|
158
|
+
lastSignInAt: number | null;
|
|
159
|
+
}
|
|
160
|
+
interface AdminMemberPage {
|
|
161
|
+
nodes: AdminMember[];
|
|
162
|
+
nodesCount: number;
|
|
163
|
+
currentPage: number;
|
|
164
|
+
totalPages: number;
|
|
165
|
+
hasNextPage: boolean;
|
|
166
|
+
hasPreviousPage: boolean;
|
|
167
|
+
}
|
|
168
|
+
type AdminMemberRole = 'student' | 'teaching_assistant';
|
|
169
|
+
interface MembersListArgs {
|
|
170
|
+
/** Items per page. Schema default 20, max 50. */
|
|
171
|
+
perPage?: number;
|
|
172
|
+
page?: number;
|
|
173
|
+
/** Defaults to `'student'` upstream — owner / manager are not selectable. */
|
|
174
|
+
role?: AdminMemberRole;
|
|
175
|
+
}
|
|
176
|
+
declare class MembersResource {
|
|
177
|
+
private readonly client;
|
|
178
|
+
constructor(client: LoopwiseAdminClient);
|
|
179
|
+
/**
|
|
180
|
+
* List non-admin school users (students by default; TAs when
|
|
181
|
+
* `role: 'teaching_assistant'`). Returns the full `AdminUserPage`
|
|
182
|
+
* shape (nodes + pagination meta) under our `AdminMemberPage` alias.
|
|
183
|
+
*
|
|
184
|
+
* Requires OAuth scope `members:read` on the access token.
|
|
185
|
+
*/
|
|
186
|
+
list(args?: MembersListArgs): Promise<AdminMemberPage>;
|
|
187
|
+
}
|
|
188
|
+
//#endregion
|
|
141
189
|
//#region src/index.d.ts
|
|
142
190
|
interface LoopwiseAdmin {
|
|
143
191
|
readonly courses: CoursesResource;
|
|
192
|
+
readonly members: MembersResource;
|
|
144
193
|
/**
|
|
145
194
|
* Run a typed GraphQL operation against `/admin/graphql`. The SDK does
|
|
146
195
|
* not parse or validate the document — pass the query string as-is and
|
|
@@ -164,4 +213,4 @@ interface LoopwiseAdmin {
|
|
|
164
213
|
}
|
|
165
214
|
declare function createAdminClient(config: LoopwiseAdminConfig): LoopwiseAdmin;
|
|
166
215
|
//#endregion
|
|
167
|
-
export { type AdminCourse, type AdminCoursePage, type CoursesListArgs, type GraphQLError, type GraphQLRequest, LoopwiseAdmin, type LoopwiseAdminConfig, LoopwiseError, type LoopwiseErrorCode, createAdminClient };
|
|
216
|
+
export { type AdminCourse, type AdminCoursePage, type AdminMember, type AdminMemberPage, type AdminMemberRole, type CoursesListArgs, type GraphQLError, type GraphQLRequest, LoopwiseAdmin, type LoopwiseAdminConfig, LoopwiseError, type LoopwiseErrorCode, type MembersListArgs, createAdminClient };
|
package/dist/index.mjs
CHANGED
|
@@ -32,7 +32,7 @@ var LoopwiseAdminClient = class {
|
|
|
32
32
|
if (!config.accessToken || !config.accessToken.trim()) throw new LoopwiseError("CONFIG", "accessToken is required. Pass it to createAdminClient({ accessToken }).");
|
|
33
33
|
this.accessToken = config.accessToken;
|
|
34
34
|
this.refreshAccessToken = config.refreshAccessToken;
|
|
35
|
-
this.endpoint = config.endpoint ?? resolveEndpoint(config.
|
|
35
|
+
this.endpoint = config.endpoint ?? resolveEndpoint(config.baseURL);
|
|
36
36
|
if (config.fetch) this.fetchImpl = config.fetch;
|
|
37
37
|
else if (typeof globalThis.fetch === "function") this.fetchImpl = globalThis.fetch.bind(globalThis);
|
|
38
38
|
else throw new LoopwiseError("CONFIG", "globalThis.fetch is not available in this runtime. Pass a fetch implementation via createAdminClient({ fetch })");
|
|
@@ -139,12 +139,12 @@ var LoopwiseAdminClient = class {
|
|
|
139
139
|
return body.data ?? null;
|
|
140
140
|
}
|
|
141
141
|
};
|
|
142
|
-
function resolveEndpoint(
|
|
143
|
-
return `${(
|
|
142
|
+
function resolveEndpoint(baseURL) {
|
|
143
|
+
return `${(baseURL ?? DEFAULT_BASE_URL).replace(/\/$/, "")}/admin/graphql`;
|
|
144
144
|
}
|
|
145
145
|
//#endregion
|
|
146
146
|
//#region src/resources/courses.ts
|
|
147
|
-
const LIST_QUERY = `
|
|
147
|
+
const LIST_QUERY$1 = `
|
|
148
148
|
query AdminCoursesList($perPage: Int, $page: Int) {
|
|
149
149
|
courses(perPage: $perPage, page: $page) {
|
|
150
150
|
nodes {
|
|
@@ -177,18 +177,60 @@ var CoursesResource = class {
|
|
|
177
177
|
*/
|
|
178
178
|
async list(args = {}) {
|
|
179
179
|
return (await this.client.graphql({
|
|
180
|
-
query: LIST_QUERY,
|
|
180
|
+
query: LIST_QUERY$1,
|
|
181
181
|
variables: args,
|
|
182
182
|
operationName: "AdminCoursesList"
|
|
183
183
|
})).courses;
|
|
184
184
|
}
|
|
185
185
|
};
|
|
186
186
|
//#endregion
|
|
187
|
+
//#region src/resources/members.ts
|
|
188
|
+
const LIST_QUERY = `
|
|
189
|
+
query AdminMembersList($perPage: Int, $page: Int, $role: AdminUserRole) {
|
|
190
|
+
users(perPage: $perPage, page: $page, role: $role) {
|
|
191
|
+
nodes {
|
|
192
|
+
id
|
|
193
|
+
name
|
|
194
|
+
email
|
|
195
|
+
phoneNumber
|
|
196
|
+
createdAt
|
|
197
|
+
lastSignInAt
|
|
198
|
+
}
|
|
199
|
+
nodesCount
|
|
200
|
+
currentPage
|
|
201
|
+
totalPages
|
|
202
|
+
hasNextPage
|
|
203
|
+
hasPreviousPage
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
`;
|
|
207
|
+
var MembersResource = class {
|
|
208
|
+
client;
|
|
209
|
+
constructor(client) {
|
|
210
|
+
this.client = client;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* List non-admin school users (students by default; TAs when
|
|
214
|
+
* `role: 'teaching_assistant'`). Returns the full `AdminUserPage`
|
|
215
|
+
* shape (nodes + pagination meta) under our `AdminMemberPage` alias.
|
|
216
|
+
*
|
|
217
|
+
* Requires OAuth scope `members:read` on the access token.
|
|
218
|
+
*/
|
|
219
|
+
async list(args = {}) {
|
|
220
|
+
return (await this.client.graphql({
|
|
221
|
+
query: LIST_QUERY,
|
|
222
|
+
variables: args,
|
|
223
|
+
operationName: "AdminMembersList"
|
|
224
|
+
})).users;
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
//#endregion
|
|
187
228
|
//#region src/index.ts
|
|
188
229
|
function createAdminClient(config) {
|
|
189
230
|
const client = new LoopwiseAdminClient(config);
|
|
190
231
|
return {
|
|
191
232
|
courses: new CoursesResource(client),
|
|
233
|
+
members: new MembersResource(client),
|
|
192
234
|
graphql: (request) => client.graphql(request),
|
|
193
235
|
setAccessToken: (token) => client.setAccessToken(token)
|
|
194
236
|
};
|