@robelest/convex-auth 0.0.2-preview.0 → 0.0.2-preview.2
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/bin.cjs +17 -15
- package/dist/client/index.d.ts +84 -30
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +259 -59
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/component.d.ts +46 -120
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/index.d.ts +2 -4
- package/dist/component/index.d.ts.map +1 -1
- package/dist/component/index.js +2 -4
- package/dist/component/index.js.map +1 -1
- package/dist/component/public.d.ts +233 -167
- package/dist/component/public.d.ts.map +1 -1
- package/dist/component/public.js +328 -155
- package/dist/component/public.js.map +1 -1
- package/dist/component/schema.d.ts +127 -12
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js +136 -10
- package/dist/component/schema.js.map +1 -1
- package/dist/providers/{Anonymous.d.ts → anonymous.d.ts} +8 -8
- package/dist/providers/{Anonymous.d.ts.map → anonymous.d.ts.map} +1 -1
- package/dist/providers/{Anonymous.js → anonymous.js} +9 -10
- package/dist/providers/anonymous.js.map +1 -0
- package/dist/providers/{ConvexCredentials.d.ts → credentials.d.ts} +11 -11
- package/dist/providers/credentials.d.ts.map +1 -0
- package/dist/providers/{ConvexCredentials.js → credentials.js} +8 -8
- package/dist/providers/credentials.js.map +1 -0
- package/dist/providers/{Email.d.ts → email.d.ts} +6 -6
- package/dist/providers/email.d.ts.map +1 -0
- package/dist/providers/{Email.js → email.js} +6 -6
- package/dist/providers/email.js.map +1 -0
- package/dist/providers/{Password.d.ts → password.d.ts} +10 -10
- package/dist/providers/{Password.d.ts.map → password.d.ts.map} +1 -1
- package/dist/providers/{Password.js → password.js} +19 -20
- package/dist/providers/password.js.map +1 -0
- package/dist/providers/{Phone.d.ts → phone.d.ts} +3 -3
- package/dist/providers/{Phone.d.ts.map → phone.d.ts.map} +1 -1
- package/dist/providers/{Phone.js → phone.js} +3 -3
- package/dist/providers/{Phone.js.map → phone.js.map} +1 -1
- package/dist/server/implementation/db.d.ts +5 -2
- package/dist/server/implementation/db.d.ts.map +1 -1
- package/dist/server/implementation/db.js +2 -1
- package/dist/server/implementation/db.js.map +1 -1
- package/dist/server/implementation/index.d.ts +285 -180
- package/dist/server/implementation/index.d.ts.map +1 -1
- package/dist/server/implementation/index.js +280 -173
- package/dist/server/implementation/index.js.map +1 -1
- package/dist/server/implementation/mutations/createAccountFromCredentials.d.ts.map +1 -1
- package/dist/server/implementation/mutations/createAccountFromCredentials.js +8 -18
- package/dist/server/implementation/mutations/createAccountFromCredentials.js.map +1 -1
- package/dist/server/implementation/mutations/createVerificationCode.d.ts.map +1 -1
- package/dist/server/implementation/mutations/createVerificationCode.js +16 -44
- package/dist/server/implementation/mutations/createVerificationCode.js.map +1 -1
- package/dist/server/implementation/mutations/invalidateSessions.d.ts.map +1 -1
- package/dist/server/implementation/mutations/invalidateSessions.js +4 -8
- package/dist/server/implementation/mutations/invalidateSessions.js.map +1 -1
- package/dist/server/implementation/mutations/modifyAccount.d.ts.map +1 -1
- package/dist/server/implementation/mutations/modifyAccount.js +8 -19
- package/dist/server/implementation/mutations/modifyAccount.js.map +1 -1
- package/dist/server/implementation/mutations/refreshSession.d.ts.map +1 -1
- package/dist/server/implementation/mutations/refreshSession.js +9 -23
- package/dist/server/implementation/mutations/refreshSession.js.map +1 -1
- package/dist/server/implementation/mutations/retrieveAccountWithCredentials.d.ts.map +1 -1
- package/dist/server/implementation/mutations/retrieveAccountWithCredentials.js +6 -12
- package/dist/server/implementation/mutations/retrieveAccountWithCredentials.js.map +1 -1
- package/dist/server/implementation/mutations/signIn.d.ts.map +1 -1
- package/dist/server/implementation/mutations/signIn.js +2 -1
- package/dist/server/implementation/mutations/signIn.js.map +1 -1
- package/dist/server/implementation/mutations/signOut.d.ts.map +1 -1
- package/dist/server/implementation/mutations/signOut.js +5 -6
- package/dist/server/implementation/mutations/signOut.js.map +1 -1
- package/dist/server/implementation/mutations/storeRef.d.ts +8 -0
- package/dist/server/implementation/mutations/storeRef.d.ts.map +1 -0
- package/dist/server/implementation/mutations/storeRef.js +8 -0
- package/dist/server/implementation/mutations/storeRef.js.map +1 -0
- package/dist/server/implementation/mutations/userOAuth.d.ts.map +1 -1
- package/dist/server/implementation/mutations/userOAuth.js +16 -53
- package/dist/server/implementation/mutations/userOAuth.js.map +1 -1
- package/dist/server/implementation/mutations/verifier.d.ts.map +1 -1
- package/dist/server/implementation/mutations/verifier.js +4 -8
- package/dist/server/implementation/mutations/verifier.js.map +1 -1
- package/dist/server/implementation/mutations/verifierSignature.d.ts.map +1 -1
- package/dist/server/implementation/mutations/verifierSignature.js +6 -10
- package/dist/server/implementation/mutations/verifierSignature.js.map +1 -1
- package/dist/server/implementation/mutations/verifyCodeAndSignIn.d.ts.map +1 -1
- package/dist/server/implementation/mutations/verifyCodeAndSignIn.js +7 -16
- package/dist/server/implementation/mutations/verifyCodeAndSignIn.js.map +1 -1
- package/dist/server/implementation/provider.d.ts +2 -1
- package/dist/server/implementation/provider.d.ts.map +1 -1
- package/dist/server/implementation/provider.js.map +1 -1
- package/dist/server/implementation/rateLimit.d.ts.map +1 -1
- package/dist/server/implementation/rateLimit.js +13 -39
- package/dist/server/implementation/rateLimit.js.map +1 -1
- package/dist/server/implementation/refreshTokens.d.ts +1 -8
- package/dist/server/implementation/refreshTokens.d.ts.map +1 -1
- package/dist/server/implementation/refreshTokens.js +14 -58
- package/dist/server/implementation/refreshTokens.js.map +1 -1
- package/dist/server/implementation/sessions.d.ts +2 -20
- package/dist/server/implementation/sessions.d.ts.map +1 -1
- package/dist/server/implementation/sessions.js +8 -35
- package/dist/server/implementation/sessions.js.map +1 -1
- package/dist/server/implementation/types.d.ts +11 -267
- package/dist/server/implementation/types.d.ts.map +1 -1
- package/dist/server/implementation/types.js +1 -181
- package/dist/server/implementation/types.js.map +1 -1
- package/dist/server/implementation/users.d.ts.map +1 -1
- package/dist/server/implementation/users.js +19 -67
- package/dist/server/implementation/users.js.map +1 -1
- package/dist/server/index.d.ts +18 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +255 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/provider_utils.d.ts +1 -1
- package/dist/server/provider_utils.d.ts.map +1 -1
- package/dist/server/provider_utils.js +2 -2
- package/dist/server/provider_utils.js.map +1 -1
- package/dist/server/types.d.ts +91 -52
- package/dist/server/types.d.ts.map +1 -1
- package/package.json +3 -6
- package/src/cli/index.ts +20 -19
- package/src/client/index.ts +347 -110
- package/src/component/_generated/component.ts +55 -214
- package/src/component/index.ts +1 -11
- package/src/component/public.ts +366 -178
- package/src/component/schema.ts +150 -19
- package/src/providers/{Anonymous.ts → anonymous.ts} +10 -11
- package/src/providers/{ConvexCredentials.ts → credentials.ts} +11 -11
- package/src/providers/{Email.ts → email.ts} +5 -5
- package/src/providers/{Password.ts → password.ts} +22 -27
- package/src/providers/{Phone.ts → phone.ts} +2 -2
- package/src/server/implementation/db.ts +5 -2
- package/src/server/implementation/index.ts +368 -313
- package/src/server/implementation/mutations/createAccountFromCredentials.ts +11 -25
- package/src/server/implementation/mutations/createVerificationCode.ts +16 -47
- package/src/server/implementation/mutations/invalidateSessions.ts +4 -9
- package/src/server/implementation/mutations/modifyAccount.ts +8 -22
- package/src/server/implementation/mutations/refreshSession.ts +11 -24
- package/src/server/implementation/mutations/retrieveAccountWithCredentials.ts +9 -17
- package/src/server/implementation/mutations/signIn.ts +2 -1
- package/src/server/implementation/mutations/signOut.ts +5 -8
- package/src/server/implementation/mutations/storeRef.ts +7 -0
- package/src/server/implementation/mutations/userOAuth.ts +10 -50
- package/src/server/implementation/mutations/verifier.ts +4 -9
- package/src/server/implementation/mutations/verifierSignature.ts +6 -12
- package/src/server/implementation/mutations/verifyCodeAndSignIn.ts +7 -18
- package/src/server/implementation/provider.ts +2 -1
- package/src/server/implementation/rateLimit.ts +15 -41
- package/src/server/implementation/refreshTokens.ts +26 -76
- package/src/server/implementation/sessions.ts +8 -39
- package/src/server/implementation/types.ts +16 -191
- package/src/server/implementation/users.ts +19 -66
- package/src/server/index.ts +373 -0
- package/src/server/provider_utils.ts +2 -2
- package/src/server/types.ts +116 -51
- package/dist/providers/Anonymous.js.map +0 -1
- package/dist/providers/ConvexCredentials.d.ts.map +0 -1
- package/dist/providers/ConvexCredentials.js.map +0 -1
- package/dist/providers/Email.d.ts.map +0 -1
- package/dist/providers/Email.js.map +0 -1
- package/dist/providers/Password.js.map +0 -1
- package/providers/Anonymous/package.json +0 -6
- package/providers/ConvexCredentials/package.json +0 -6
- package/providers/Email/package.json +0 -6
- package/providers/Password/package.json +0 -6
- package/providers/Phone/package.json +0 -6
- package/server/package.json +0 -6
package/src/component/public.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import { v } from "convex/values";
|
|
1
|
+
import { ConvexError, v } from "convex/values";
|
|
2
2
|
import { mutation, query } from "./_generated/server";
|
|
3
3
|
|
|
4
|
+
// ============================================================================
|
|
4
5
|
// Users
|
|
6
|
+
// ============================================================================
|
|
7
|
+
|
|
8
|
+
/** Retrieve a user by their document ID. */
|
|
5
9
|
export const userGetById = query({
|
|
6
10
|
args: { userId: v.id("user") },
|
|
7
11
|
handler: async (ctx, { userId }) => {
|
|
@@ -9,6 +13,11 @@ export const userGetById = query({
|
|
|
9
13
|
},
|
|
10
14
|
});
|
|
11
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Find a user by their verified email address. Returns `null` if no user
|
|
18
|
+
* has this email verified, or if multiple users share the same verified email
|
|
19
|
+
* (ambiguous — should not happen in normal operation).
|
|
20
|
+
*/
|
|
12
21
|
export const userFindByVerifiedEmail = query({
|
|
13
22
|
args: { email: v.string() },
|
|
14
23
|
handler: async (ctx, { email }) => {
|
|
@@ -21,6 +30,11 @@ export const userFindByVerifiedEmail = query({
|
|
|
21
30
|
},
|
|
22
31
|
});
|
|
23
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Find a user by their verified phone number. Returns `null` if no user
|
|
35
|
+
* has this phone verified, or if multiple users share the same verified phone
|
|
36
|
+
* (ambiguous — should not happen in normal operation).
|
|
37
|
+
*/
|
|
24
38
|
export const userFindByVerifiedPhone = query({
|
|
25
39
|
args: { phone: v.string() },
|
|
26
40
|
handler: async (ctx, { phone }) => {
|
|
@@ -33,6 +47,7 @@ export const userFindByVerifiedPhone = query({
|
|
|
33
47
|
},
|
|
34
48
|
});
|
|
35
49
|
|
|
50
|
+
/** Insert a new user document. */
|
|
36
51
|
export const userInsert = mutation({
|
|
37
52
|
args: { data: v.any() },
|
|
38
53
|
handler: async (ctx, { data }) => {
|
|
@@ -40,6 +55,7 @@ export const userInsert = mutation({
|
|
|
40
55
|
},
|
|
41
56
|
});
|
|
42
57
|
|
|
58
|
+
/** Insert a new user or update an existing one. */
|
|
43
59
|
export const userUpsert = mutation({
|
|
44
60
|
args: { userId: v.optional(v.id("user")), data: v.any() },
|
|
45
61
|
handler: async (ctx, { userId, data }) => {
|
|
@@ -51,6 +67,7 @@ export const userUpsert = mutation({
|
|
|
51
67
|
},
|
|
52
68
|
});
|
|
53
69
|
|
|
70
|
+
/** Patch an existing user document with partial data. */
|
|
54
71
|
export const userPatch = mutation({
|
|
55
72
|
args: { userId: v.id("user"), data: v.any() },
|
|
56
73
|
handler: async (ctx, { userId, data }) => {
|
|
@@ -58,7 +75,11 @@ export const userPatch = mutation({
|
|
|
58
75
|
},
|
|
59
76
|
});
|
|
60
77
|
|
|
78
|
+
// ============================================================================
|
|
61
79
|
// Accounts
|
|
80
|
+
// ============================================================================
|
|
81
|
+
|
|
82
|
+
/** Look up an account by provider and provider-specific account ID. */
|
|
62
83
|
export const accountGet = query({
|
|
63
84
|
args: { provider: v.string(), providerAccountId: v.string() },
|
|
64
85
|
handler: async (ctx, { provider, providerAccountId }) => {
|
|
@@ -71,6 +92,7 @@ export const accountGet = query({
|
|
|
71
92
|
},
|
|
72
93
|
});
|
|
73
94
|
|
|
95
|
+
/** Retrieve an account by its document ID. */
|
|
74
96
|
export const accountGetById = query({
|
|
75
97
|
args: { accountId: v.id("account") },
|
|
76
98
|
handler: async (ctx, { accountId }) => {
|
|
@@ -78,6 +100,7 @@ export const accountGetById = query({
|
|
|
78
100
|
},
|
|
79
101
|
});
|
|
80
102
|
|
|
103
|
+
/** Create a new account linking a user to an auth provider. */
|
|
81
104
|
export const accountInsert = mutation({
|
|
82
105
|
args: {
|
|
83
106
|
userId: v.id("user"),
|
|
@@ -90,6 +113,7 @@ export const accountInsert = mutation({
|
|
|
90
113
|
},
|
|
91
114
|
});
|
|
92
115
|
|
|
116
|
+
/** Patch an existing account document with partial data. */
|
|
93
117
|
export const accountPatch = mutation({
|
|
94
118
|
args: { accountId: v.id("account"), data: v.any() },
|
|
95
119
|
handler: async (ctx, { accountId, data }) => {
|
|
@@ -97,6 +121,7 @@ export const accountPatch = mutation({
|
|
|
97
121
|
},
|
|
98
122
|
});
|
|
99
123
|
|
|
124
|
+
/** Delete an account document. */
|
|
100
125
|
export const accountDelete = mutation({
|
|
101
126
|
args: { accountId: v.id("account") },
|
|
102
127
|
handler: async (ctx, { accountId }) => {
|
|
@@ -104,7 +129,11 @@ export const accountDelete = mutation({
|
|
|
104
129
|
},
|
|
105
130
|
});
|
|
106
131
|
|
|
132
|
+
// ============================================================================
|
|
107
133
|
// Sessions
|
|
134
|
+
// ============================================================================
|
|
135
|
+
|
|
136
|
+
/** Create a new session for a user with an expiration time. */
|
|
108
137
|
export const sessionCreate = mutation({
|
|
109
138
|
args: { userId: v.id("user"), expirationTime: v.number() },
|
|
110
139
|
handler: async (ctx, { userId, expirationTime }) => {
|
|
@@ -115,6 +144,7 @@ export const sessionCreate = mutation({
|
|
|
115
144
|
},
|
|
116
145
|
});
|
|
117
146
|
|
|
147
|
+
/** Retrieve a session by its document ID. */
|
|
118
148
|
export const sessionGetById = query({
|
|
119
149
|
args: { sessionId: v.id("session") },
|
|
120
150
|
handler: async (ctx, { sessionId }) => {
|
|
@@ -122,6 +152,7 @@ export const sessionGetById = query({
|
|
|
122
152
|
},
|
|
123
153
|
});
|
|
124
154
|
|
|
155
|
+
/** Delete a session. No-op if the session does not exist. */
|
|
125
156
|
export const sessionDelete = mutation({
|
|
126
157
|
args: { sessionId: v.id("session") },
|
|
127
158
|
handler: async (ctx, { sessionId }) => {
|
|
@@ -131,6 +162,7 @@ export const sessionDelete = mutation({
|
|
|
131
162
|
},
|
|
132
163
|
});
|
|
133
164
|
|
|
165
|
+
/** List all sessions for a user. */
|
|
134
166
|
export const sessionListByUser = query({
|
|
135
167
|
args: { userId: v.id("user") },
|
|
136
168
|
handler: async (ctx, { userId }) => {
|
|
@@ -141,7 +173,11 @@ export const sessionListByUser = query({
|
|
|
141
173
|
},
|
|
142
174
|
});
|
|
143
175
|
|
|
176
|
+
// ============================================================================
|
|
144
177
|
// Verifiers
|
|
178
|
+
// ============================================================================
|
|
179
|
+
|
|
180
|
+
/** Create a new PKCE verifier, optionally linked to a session. */
|
|
145
181
|
export const verifierCreate = mutation({
|
|
146
182
|
args: { sessionId: v.optional(v.id("session")) },
|
|
147
183
|
handler: async (ctx, { sessionId }) => {
|
|
@@ -149,6 +185,7 @@ export const verifierCreate = mutation({
|
|
|
149
185
|
},
|
|
150
186
|
});
|
|
151
187
|
|
|
188
|
+
/** Retrieve a verifier by its document ID. */
|
|
152
189
|
export const verifierGetById = query({
|
|
153
190
|
args: { verifierId: v.id("verifier") },
|
|
154
191
|
handler: async (ctx, { verifierId }) => {
|
|
@@ -156,6 +193,7 @@ export const verifierGetById = query({
|
|
|
156
193
|
},
|
|
157
194
|
});
|
|
158
195
|
|
|
196
|
+
/** Look up a verifier by its cryptographic signature. */
|
|
159
197
|
export const verifierGetBySignature = query({
|
|
160
198
|
args: { signature: v.string() },
|
|
161
199
|
handler: async (ctx, { signature }) => {
|
|
@@ -166,6 +204,7 @@ export const verifierGetBySignature = query({
|
|
|
166
204
|
},
|
|
167
205
|
});
|
|
168
206
|
|
|
207
|
+
/** Patch a verifier document with partial data. */
|
|
169
208
|
export const verifierPatch = mutation({
|
|
170
209
|
args: { verifierId: v.id("verifier"), data: v.any() },
|
|
171
210
|
handler: async (ctx, { verifierId, data }) => {
|
|
@@ -173,6 +212,7 @@ export const verifierPatch = mutation({
|
|
|
173
212
|
},
|
|
174
213
|
});
|
|
175
214
|
|
|
215
|
+
/** Delete a verifier document. */
|
|
176
216
|
export const verifierDelete = mutation({
|
|
177
217
|
args: { verifierId: v.id("verifier") },
|
|
178
218
|
handler: async (ctx, { verifierId }) => {
|
|
@@ -180,7 +220,11 @@ export const verifierDelete = mutation({
|
|
|
180
220
|
},
|
|
181
221
|
});
|
|
182
222
|
|
|
183
|
-
//
|
|
223
|
+
// ============================================================================
|
|
224
|
+
// Verification Codes
|
|
225
|
+
// ============================================================================
|
|
226
|
+
|
|
227
|
+
/** Find a verification code by its associated account ID. */
|
|
184
228
|
export const verificationCodeGetByAccountId = query({
|
|
185
229
|
args: { accountId: v.id("account") },
|
|
186
230
|
handler: async (ctx, { accountId }) => {
|
|
@@ -191,6 +235,7 @@ export const verificationCodeGetByAccountId = query({
|
|
|
191
235
|
},
|
|
192
236
|
});
|
|
193
237
|
|
|
238
|
+
/** Find a verification code by its code string. */
|
|
194
239
|
export const verificationCodeGetByCode = query({
|
|
195
240
|
args: { code: v.string() },
|
|
196
241
|
handler: async (ctx, { code }) => {
|
|
@@ -201,6 +246,7 @@ export const verificationCodeGetByCode = query({
|
|
|
201
246
|
},
|
|
202
247
|
});
|
|
203
248
|
|
|
249
|
+
/** Create a new verification code for OTP, magic link, or OAuth flows. */
|
|
204
250
|
export const verificationCodeCreate = mutation({
|
|
205
251
|
args: {
|
|
206
252
|
accountId: v.id("account"),
|
|
@@ -216,6 +262,7 @@ export const verificationCodeCreate = mutation({
|
|
|
216
262
|
},
|
|
217
263
|
});
|
|
218
264
|
|
|
265
|
+
/** Delete a verification code document. */
|
|
219
266
|
export const verificationCodeDelete = mutation({
|
|
220
267
|
args: { verificationCodeId: v.id("verification") },
|
|
221
268
|
handler: async (ctx, { verificationCodeId }) => {
|
|
@@ -223,7 +270,11 @@ export const verificationCodeDelete = mutation({
|
|
|
223
270
|
},
|
|
224
271
|
});
|
|
225
272
|
|
|
226
|
-
//
|
|
273
|
+
// ============================================================================
|
|
274
|
+
// Refresh Tokens
|
|
275
|
+
// ============================================================================
|
|
276
|
+
|
|
277
|
+
/** Create a new refresh token for a session. */
|
|
227
278
|
export const refreshTokenCreate = mutation({
|
|
228
279
|
args: {
|
|
229
280
|
sessionId: v.id("session"),
|
|
@@ -235,6 +286,7 @@ export const refreshTokenCreate = mutation({
|
|
|
235
286
|
},
|
|
236
287
|
});
|
|
237
288
|
|
|
289
|
+
/** Retrieve a refresh token by its document ID. */
|
|
238
290
|
export const refreshTokenGetById = query({
|
|
239
291
|
args: { refreshTokenId: v.id("token") },
|
|
240
292
|
handler: async (ctx, { refreshTokenId }) => {
|
|
@@ -242,6 +294,7 @@ export const refreshTokenGetById = query({
|
|
|
242
294
|
},
|
|
243
295
|
});
|
|
244
296
|
|
|
297
|
+
/** Patch a refresh token document with partial data. */
|
|
245
298
|
export const refreshTokenPatch = mutation({
|
|
246
299
|
args: { refreshTokenId: v.id("token"), data: v.any() },
|
|
247
300
|
handler: async (ctx, { refreshTokenId, data }) => {
|
|
@@ -249,6 +302,7 @@ export const refreshTokenPatch = mutation({
|
|
|
249
302
|
},
|
|
250
303
|
});
|
|
251
304
|
|
|
305
|
+
/** Get child tokens that were created by exchanging a specific parent token. */
|
|
252
306
|
export const refreshTokenGetChildren = query({
|
|
253
307
|
args: {
|
|
254
308
|
sessionId: v.id("session"),
|
|
@@ -266,6 +320,7 @@ export const refreshTokenGetChildren = query({
|
|
|
266
320
|
},
|
|
267
321
|
});
|
|
268
322
|
|
|
323
|
+
/** List all refresh tokens for a session. */
|
|
269
324
|
export const refreshTokenListBySession = query({
|
|
270
325
|
args: { sessionId: v.id("session") },
|
|
271
326
|
handler: async (ctx, { sessionId }) => {
|
|
@@ -278,6 +333,7 @@ export const refreshTokenListBySession = query({
|
|
|
278
333
|
},
|
|
279
334
|
});
|
|
280
335
|
|
|
336
|
+
/** Delete all refresh tokens for a session. */
|
|
281
337
|
export const refreshTokenDeleteAll = mutation({
|
|
282
338
|
args: { sessionId: v.id("session") },
|
|
283
339
|
handler: async (ctx, { sessionId }) => {
|
|
@@ -291,6 +347,7 @@ export const refreshTokenDeleteAll = mutation({
|
|
|
291
347
|
},
|
|
292
348
|
});
|
|
293
349
|
|
|
350
|
+
/** Get the active (unused) refresh token for a session. */
|
|
294
351
|
export const refreshTokenGetActive = query({
|
|
295
352
|
args: { sessionId: v.id("session") },
|
|
296
353
|
handler: async (ctx, { sessionId }) => {
|
|
@@ -303,7 +360,11 @@ export const refreshTokenGetActive = query({
|
|
|
303
360
|
},
|
|
304
361
|
});
|
|
305
362
|
|
|
306
|
-
//
|
|
363
|
+
// ============================================================================
|
|
364
|
+
// Rate Limits
|
|
365
|
+
// ============================================================================
|
|
366
|
+
|
|
367
|
+
/** Look up a rate limit entry by its identifier. */
|
|
307
368
|
export const rateLimitGet = query({
|
|
308
369
|
args: { identifier: v.string() },
|
|
309
370
|
handler: async (ctx, { identifier }) => {
|
|
@@ -314,6 +375,7 @@ export const rateLimitGet = query({
|
|
|
314
375
|
},
|
|
315
376
|
});
|
|
316
377
|
|
|
378
|
+
/** Create a new rate limit entry. */
|
|
317
379
|
export const rateLimitCreate = mutation({
|
|
318
380
|
args: {
|
|
319
381
|
identifier: v.string(),
|
|
@@ -325,6 +387,7 @@ export const rateLimitCreate = mutation({
|
|
|
325
387
|
},
|
|
326
388
|
});
|
|
327
389
|
|
|
390
|
+
/** Patch a rate limit entry with partial data. */
|
|
328
391
|
export const rateLimitPatch = mutation({
|
|
329
392
|
args: { rateLimitId: v.id("limit"), data: v.any() },
|
|
330
393
|
handler: async (ctx, { rateLimitId, data }) => {
|
|
@@ -332,6 +395,7 @@ export const rateLimitPatch = mutation({
|
|
|
332
395
|
},
|
|
333
396
|
});
|
|
334
397
|
|
|
398
|
+
/** Delete a rate limit entry. */
|
|
335
399
|
export const rateLimitDelete = mutation({
|
|
336
400
|
args: { rateLimitId: v.id("limit") },
|
|
337
401
|
handler: async (ctx, { rateLimitId }) => {
|
|
@@ -339,269 +403,393 @@ export const rateLimitDelete = mutation({
|
|
|
339
403
|
},
|
|
340
404
|
});
|
|
341
405
|
|
|
342
|
-
//
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
export const verificationCreate = verificationCodeCreate;
|
|
346
|
-
export const verificationDelete = verificationCodeDelete;
|
|
347
|
-
|
|
348
|
-
export const tokenCreate = refreshTokenCreate;
|
|
349
|
-
export const tokenGetById = refreshTokenGetById;
|
|
350
|
-
export const tokenPatch = refreshTokenPatch;
|
|
351
|
-
export const tokenGetChildren = refreshTokenGetChildren;
|
|
352
|
-
export const tokenListBySession = refreshTokenListBySession;
|
|
353
|
-
export const tokenDeleteAll = refreshTokenDeleteAll;
|
|
354
|
-
export const tokenGetActive = refreshTokenGetActive;
|
|
355
|
-
|
|
356
|
-
export const limitGet = rateLimitGet;
|
|
357
|
-
export const limitCreate = rateLimitCreate;
|
|
358
|
-
export const limitPatch = rateLimitPatch;
|
|
359
|
-
export const limitDelete = rateLimitDelete;
|
|
360
|
-
|
|
361
|
-
// Organization
|
|
362
|
-
export const organizationCreate = mutation({
|
|
363
|
-
args: { data: v.any() },
|
|
364
|
-
handler: async (ctx, { data }) => {
|
|
365
|
-
return await (ctx.db as any).insert("organization", data);
|
|
366
|
-
},
|
|
367
|
-
});
|
|
368
|
-
|
|
369
|
-
export const organizationGet = query({
|
|
370
|
-
args: { organizationId: v.id("organization") },
|
|
371
|
-
handler: async (ctx, { organizationId }) => {
|
|
372
|
-
return await (ctx.db as any).get(organizationId);
|
|
373
|
-
},
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
export const organizationList = query({
|
|
377
|
-
args: { ownerUserId: v.optional(v.id("user")) },
|
|
378
|
-
handler: async (ctx, { ownerUserId }) => {
|
|
379
|
-
if (ownerUserId === undefined) {
|
|
380
|
-
return await (ctx.db as any).query("organization").collect();
|
|
381
|
-
}
|
|
382
|
-
return await (ctx.db as any)
|
|
383
|
-
.query("organization")
|
|
384
|
-
.withIndex("ownerUserId", (q: any) => q.eq("ownerUserId", ownerUserId))
|
|
385
|
-
.collect();
|
|
386
|
-
},
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
export const organizationUpdate = mutation({
|
|
390
|
-
args: { organizationId: v.id("organization"), data: v.any() },
|
|
391
|
-
handler: async (ctx, { organizationId, data }) => {
|
|
392
|
-
await (ctx.db as any).patch(organizationId, data);
|
|
393
|
-
},
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
export const organizationDelete = mutation({
|
|
397
|
-
args: { organizationId: v.id("organization") },
|
|
398
|
-
handler: async (ctx, { organizationId }) => {
|
|
399
|
-
await (ctx.db as any).delete(organizationId);
|
|
400
|
-
},
|
|
401
|
-
});
|
|
406
|
+
// ============================================================================
|
|
407
|
+
// Groups
|
|
408
|
+
// ============================================================================
|
|
402
409
|
|
|
403
|
-
|
|
404
|
-
|
|
410
|
+
/**
|
|
411
|
+
* Create a new group. Groups are hierarchical — set `parentGroupId` to nest
|
|
412
|
+
* under an existing group, or omit it to create a root-level group.
|
|
413
|
+
*
|
|
414
|
+
* @returns The ID of the newly created group.
|
|
415
|
+
*/
|
|
416
|
+
export const groupCreate = mutation({
|
|
405
417
|
args: {
|
|
406
|
-
organizationId: v.id("organization"),
|
|
407
418
|
name: v.string(),
|
|
408
419
|
slug: v.optional(v.string()),
|
|
409
|
-
|
|
410
|
-
|
|
420
|
+
parentGroupId: v.optional(v.id("group")),
|
|
421
|
+
extend: v.optional(v.any()),
|
|
411
422
|
},
|
|
412
423
|
handler: async (ctx, args) => {
|
|
413
|
-
return await
|
|
424
|
+
return await ctx.db.insert("group", args);
|
|
414
425
|
},
|
|
415
426
|
});
|
|
416
427
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
428
|
+
/** Retrieve a group by its document ID. Returns `null` if not found. */
|
|
429
|
+
export const groupGet = query({
|
|
430
|
+
args: { groupId: v.id("group") },
|
|
431
|
+
handler: async (ctx, { groupId }) => {
|
|
432
|
+
return await ctx.db.get(groupId);
|
|
421
433
|
},
|
|
422
434
|
});
|
|
423
435
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
436
|
+
/**
|
|
437
|
+
* List groups. When `parentGroupId` is provided, returns children of that
|
|
438
|
+
* group. When omitted, returns all root-level groups (groups with no parent).
|
|
439
|
+
*/
|
|
440
|
+
export const groupList = query({
|
|
441
|
+
args: { parentGroupId: v.optional(v.id("group")) },
|
|
442
|
+
handler: async (ctx, { parentGroupId }) => {
|
|
443
|
+
return await ctx.db
|
|
444
|
+
.query("group")
|
|
445
|
+
.withIndex("parentGroupId", (q) => q.eq("parentGroupId", parentGroupId))
|
|
432
446
|
.collect();
|
|
433
447
|
},
|
|
434
448
|
});
|
|
435
449
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
450
|
+
/** Update a group's fields (name, slug, extend, parentGroupId). */
|
|
451
|
+
export const groupUpdate = mutation({
|
|
452
|
+
args: { groupId: v.id("group"), data: v.any() },
|
|
453
|
+
handler: async (ctx, { groupId, data }) => {
|
|
454
|
+
await ctx.db.patch(groupId, data);
|
|
440
455
|
},
|
|
441
456
|
});
|
|
442
457
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
458
|
+
/**
|
|
459
|
+
* Delete a group and all of its descendants. This cascades to:
|
|
460
|
+
* - All child groups (recursively)
|
|
461
|
+
* - All members of this group and its descendants
|
|
462
|
+
* - All invites for this group and its descendants
|
|
463
|
+
*/
|
|
464
|
+
export const groupDelete = mutation({
|
|
465
|
+
args: { groupId: v.id("group") },
|
|
466
|
+
handler: async (ctx, { groupId }) => {
|
|
467
|
+
const deleteGroup = async (id: typeof groupId) => {
|
|
468
|
+
const children = await ctx.db
|
|
469
|
+
.query("group")
|
|
470
|
+
.withIndex("parentGroupId", (q) => q.eq("parentGroupId", id))
|
|
471
|
+
.collect();
|
|
472
|
+
for (const child of children) {
|
|
473
|
+
await deleteGroup(child._id);
|
|
474
|
+
}
|
|
449
475
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
},
|
|
458
|
-
handler: async (ctx, args) => {
|
|
459
|
-
return await (ctx.db as any).insert("teamRelation", args);
|
|
460
|
-
},
|
|
461
|
-
});
|
|
476
|
+
const members = await ctx.db
|
|
477
|
+
.query("member")
|
|
478
|
+
.withIndex("groupId", (q) => q.eq("groupId", id))
|
|
479
|
+
.collect();
|
|
480
|
+
for (const member of members) {
|
|
481
|
+
await ctx.db.delete(member._id);
|
|
482
|
+
}
|
|
462
483
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
484
|
+
const invites = await ctx.db
|
|
485
|
+
.query("invite")
|
|
486
|
+
.withIndex("groupId", (q) => q.eq("groupId", id))
|
|
487
|
+
.collect();
|
|
488
|
+
for (const invite of invites) {
|
|
489
|
+
await ctx.db.delete(invite._id);
|
|
490
|
+
}
|
|
469
491
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
organizationId: v.id("organization"),
|
|
473
|
-
parentTeamId: v.id("team"),
|
|
474
|
-
},
|
|
475
|
-
handler: async (ctx, { organizationId, parentTeamId }) => {
|
|
476
|
-
return await (ctx.db as any)
|
|
477
|
-
.query("teamRelation")
|
|
478
|
-
.withIndex("organizationIdAndParentTeamId", (q: any) =>
|
|
479
|
-
q.eq("organizationId", organizationId).eq("parentTeamId", parentTeamId),
|
|
480
|
-
)
|
|
481
|
-
.collect();
|
|
482
|
-
},
|
|
483
|
-
});
|
|
492
|
+
await ctx.db.delete(id);
|
|
493
|
+
};
|
|
484
494
|
|
|
485
|
-
|
|
486
|
-
args: { teamRelationId: v.id("teamRelation") },
|
|
487
|
-
handler: async (ctx, { teamRelationId }) => {
|
|
488
|
-
await (ctx.db as any).delete(teamRelationId);
|
|
495
|
+
await deleteGroup(groupId);
|
|
489
496
|
},
|
|
490
497
|
});
|
|
491
498
|
|
|
499
|
+
// ============================================================================
|
|
492
500
|
// Members
|
|
501
|
+
// ============================================================================
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Add a user as a member of a group.
|
|
505
|
+
*
|
|
506
|
+
* The `role` field is an application-defined string (e.g. "owner", "admin",
|
|
507
|
+
* "member", "viewer"). The auth component stores it but does not enforce
|
|
508
|
+
* access control — your application defines what each role means.
|
|
509
|
+
*
|
|
510
|
+
* Throws `ConvexError` with code `DUPLICATE_MEMBERSHIP` when the user is
|
|
511
|
+
* already a member of the target group.
|
|
512
|
+
*
|
|
513
|
+
* @returns The ID of the new member record.
|
|
514
|
+
*/
|
|
493
515
|
export const memberAdd = mutation({
|
|
494
|
-
args: {
|
|
495
|
-
|
|
496
|
-
|
|
516
|
+
args: {
|
|
517
|
+
groupId: v.id("group"),
|
|
518
|
+
userId: v.id("user"),
|
|
519
|
+
role: v.optional(v.string()),
|
|
520
|
+
status: v.optional(v.string()),
|
|
521
|
+
extend: v.optional(v.any()),
|
|
522
|
+
},
|
|
523
|
+
handler: async (ctx, args) => {
|
|
524
|
+
const existingMembership = await ctx.db
|
|
525
|
+
.query("member")
|
|
526
|
+
.withIndex("groupIdAndUserId", (q) =>
|
|
527
|
+
q.eq("groupId", args.groupId).eq("userId", args.userId),
|
|
528
|
+
)
|
|
529
|
+
.unique();
|
|
530
|
+
if (existingMembership !== null) {
|
|
531
|
+
throw new ConvexError({
|
|
532
|
+
code: "DUPLICATE_MEMBERSHIP",
|
|
533
|
+
message: "User is already a member of this group",
|
|
534
|
+
groupId: args.groupId,
|
|
535
|
+
userId: args.userId,
|
|
536
|
+
existingMemberId: existingMembership._id,
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
return await ctx.db.insert("member", args);
|
|
497
540
|
},
|
|
498
541
|
});
|
|
499
542
|
|
|
500
|
-
|
|
543
|
+
/** Retrieve a member record by its document ID. Returns `null` if not found. */
|
|
544
|
+
export const memberGet = query({
|
|
501
545
|
args: { memberId: v.id("member") },
|
|
502
546
|
handler: async (ctx, { memberId }) => {
|
|
503
|
-
await
|
|
547
|
+
return await ctx.db.get(memberId);
|
|
504
548
|
},
|
|
505
549
|
});
|
|
506
550
|
|
|
551
|
+
/** List all members of a specific group. */
|
|
507
552
|
export const memberList = query({
|
|
508
|
-
args: {
|
|
509
|
-
|
|
510
|
-
|
|
553
|
+
args: { groupId: v.id("group") },
|
|
554
|
+
handler: async (ctx, { groupId }) => {
|
|
555
|
+
return await ctx.db
|
|
556
|
+
.query("member")
|
|
557
|
+
.withIndex("groupId", (q) => q.eq("groupId", groupId))
|
|
558
|
+
.collect();
|
|
511
559
|
},
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* List all group memberships for a specific user. Returns member records
|
|
564
|
+
* which include the `groupId`, `role`, `status`, and `extend` for each
|
|
565
|
+
* group the user belongs to.
|
|
566
|
+
*/
|
|
567
|
+
export const memberListByUser = query({
|
|
568
|
+
args: { userId: v.id("user") },
|
|
569
|
+
handler: async (ctx, { userId }) => {
|
|
570
|
+
return await ctx.db
|
|
520
571
|
.query("member")
|
|
521
|
-
.withIndex("
|
|
522
|
-
q.eq("organizationId", organizationId),
|
|
523
|
-
)
|
|
572
|
+
.withIndex("userId", (q) => q.eq("userId", userId))
|
|
524
573
|
.collect();
|
|
525
574
|
},
|
|
526
575
|
});
|
|
527
576
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
577
|
+
/**
|
|
578
|
+
* Look up a specific user's membership in a specific group.
|
|
579
|
+
* Returns `null` if the user is not a member of the group.
|
|
580
|
+
*/
|
|
581
|
+
export const memberGetByGroupAndUser = query({
|
|
582
|
+
args: { groupId: v.id("group"), userId: v.id("user") },
|
|
583
|
+
handler: async (ctx, { groupId, userId }) => {
|
|
584
|
+
return await ctx.db
|
|
585
|
+
.query("member")
|
|
586
|
+
.withIndex("groupIdAndUserId", (q) =>
|
|
587
|
+
q.eq("groupId", groupId).eq("userId", userId),
|
|
588
|
+
)
|
|
589
|
+
.unique();
|
|
532
590
|
},
|
|
533
591
|
});
|
|
534
592
|
|
|
535
|
-
|
|
593
|
+
/** Remove a member from a group by deleting the member record. */
|
|
594
|
+
export const memberRemove = mutation({
|
|
536
595
|
args: { memberId: v.id("member") },
|
|
537
596
|
handler: async (ctx, { memberId }) => {
|
|
538
|
-
|
|
539
|
-
|
|
597
|
+
await ctx.db.delete(memberId);
|
|
598
|
+
},
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* Update a member record's fields (role, status, extend).
|
|
603
|
+
*
|
|
604
|
+
* Common usage: `memberUpdate({ memberId, data: { role: "admin" } })`
|
|
605
|
+
*/
|
|
606
|
+
export const memberUpdate = mutation({
|
|
607
|
+
args: { memberId: v.id("member"), data: v.any() },
|
|
608
|
+
handler: async (ctx, { memberId, data }) => {
|
|
609
|
+
await ctx.db.patch(memberId, data);
|
|
540
610
|
},
|
|
541
611
|
});
|
|
542
612
|
|
|
613
|
+
// ============================================================================
|
|
543
614
|
// Invites
|
|
615
|
+
// ============================================================================
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Create a new platform-level invitation. Optionally set `groupId` to tie
|
|
619
|
+
* the invite to a specific group. The invitation is sent to an email address
|
|
620
|
+
* and includes a hashed token for secure acceptance.
|
|
621
|
+
*
|
|
622
|
+
* Throws `ConvexError` with code `DUPLICATE_INVITE` when a pending invite
|
|
623
|
+
* already exists for the same email and scope:
|
|
624
|
+
* - group invite: same `email` + same `groupId`
|
|
625
|
+
* - platform invite: same `email` with no `groupId`
|
|
626
|
+
*
|
|
627
|
+
* @returns The ID of the new invite record.
|
|
628
|
+
*/
|
|
544
629
|
export const inviteCreate = mutation({
|
|
545
|
-
args: {
|
|
546
|
-
|
|
547
|
-
|
|
630
|
+
args: {
|
|
631
|
+
groupId: v.optional(v.id("group")),
|
|
632
|
+
invitedByUserId: v.id("user"),
|
|
633
|
+
email: v.string(),
|
|
634
|
+
tokenHash: v.string(),
|
|
635
|
+
role: v.optional(v.string()),
|
|
636
|
+
status: v.union(
|
|
637
|
+
v.literal("pending"),
|
|
638
|
+
v.literal("accepted"),
|
|
639
|
+
v.literal("revoked"),
|
|
640
|
+
v.literal("expired"),
|
|
641
|
+
),
|
|
642
|
+
expiresTime: v.number(),
|
|
643
|
+
extend: v.optional(v.any()),
|
|
644
|
+
},
|
|
645
|
+
handler: async (ctx, args) => {
|
|
646
|
+
if (args.groupId !== undefined) {
|
|
647
|
+
const existingGroupInvite = await ctx.db
|
|
648
|
+
.query("invite")
|
|
649
|
+
.withIndex("groupIdAndStatus", (q) =>
|
|
650
|
+
q.eq("groupId", args.groupId).eq("status", "pending"),
|
|
651
|
+
)
|
|
652
|
+
.filter((q) => q.eq(q.field("email"), args.email))
|
|
653
|
+
.first();
|
|
654
|
+
if (existingGroupInvite !== null) {
|
|
655
|
+
throw new ConvexError({
|
|
656
|
+
code: "DUPLICATE_INVITE",
|
|
657
|
+
message: "A pending invite already exists for this email in this group",
|
|
658
|
+
email: args.email,
|
|
659
|
+
groupId: args.groupId,
|
|
660
|
+
existingInviteId: existingGroupInvite._id,
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
} else {
|
|
664
|
+
const existingPlatformInvite = await ctx.db
|
|
665
|
+
.query("invite")
|
|
666
|
+
.withIndex("emailAndStatus", (q) =>
|
|
667
|
+
q.eq("email", args.email).eq("status", "pending"),
|
|
668
|
+
)
|
|
669
|
+
.filter((q) => q.eq(q.field("groupId"), undefined))
|
|
670
|
+
.first();
|
|
671
|
+
if (existingPlatformInvite !== null) {
|
|
672
|
+
throw new ConvexError({
|
|
673
|
+
code: "DUPLICATE_INVITE",
|
|
674
|
+
message: "A pending platform invite already exists for this email",
|
|
675
|
+
email: args.email,
|
|
676
|
+
existingInviteId: existingPlatformInvite._id,
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
return await ctx.db.insert("invite", args);
|
|
548
681
|
},
|
|
549
682
|
});
|
|
550
683
|
|
|
684
|
+
/** Retrieve an invite by its document ID. Returns `null` if not found. */
|
|
551
685
|
export const inviteGet = query({
|
|
552
686
|
args: { inviteId: v.id("invite") },
|
|
553
687
|
handler: async (ctx, { inviteId }) => {
|
|
554
|
-
return await
|
|
688
|
+
return await ctx.db.get(inviteId);
|
|
555
689
|
},
|
|
556
690
|
});
|
|
557
691
|
|
|
692
|
+
/**
|
|
693
|
+
* List invites, optionally filtered by group and/or status.
|
|
694
|
+
* Both `groupId` and `status` are optional filters.
|
|
695
|
+
*/
|
|
558
696
|
export const inviteList = query({
|
|
559
697
|
args: {
|
|
560
|
-
|
|
561
|
-
status: v.optional(
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
698
|
+
groupId: v.optional(v.id("group")),
|
|
699
|
+
status: v.optional(
|
|
700
|
+
v.union(
|
|
701
|
+
v.literal("pending"),
|
|
702
|
+
v.literal("accepted"),
|
|
703
|
+
v.literal("revoked"),
|
|
704
|
+
v.literal("expired"),
|
|
705
|
+
),
|
|
706
|
+
),
|
|
707
|
+
},
|
|
708
|
+
handler: async (ctx, { groupId, status }) => {
|
|
709
|
+
if (groupId !== undefined && status !== undefined) {
|
|
710
|
+
return await ctx.db
|
|
566
711
|
.query("invite")
|
|
567
|
-
.withIndex("
|
|
568
|
-
q.eq("
|
|
712
|
+
.withIndex("groupIdAndStatus", (q) =>
|
|
713
|
+
q.eq("groupId", groupId).eq("status", status),
|
|
569
714
|
)
|
|
570
715
|
.collect();
|
|
571
716
|
}
|
|
572
|
-
if (
|
|
573
|
-
return await
|
|
717
|
+
if (groupId !== undefined) {
|
|
718
|
+
return await ctx.db
|
|
574
719
|
.query("invite")
|
|
575
|
-
.withIndex("
|
|
576
|
-
q.eq("organizationId", organizationId),
|
|
577
|
-
)
|
|
720
|
+
.withIndex("groupId", (q) => q.eq("groupId", groupId))
|
|
578
721
|
.collect();
|
|
579
722
|
}
|
|
580
723
|
if (status !== undefined) {
|
|
581
|
-
return await
|
|
724
|
+
return await ctx.db
|
|
582
725
|
.query("invite")
|
|
583
|
-
.
|
|
726
|
+
.withIndex("status", (q) => q.eq("status", status))
|
|
584
727
|
.collect();
|
|
585
728
|
}
|
|
586
|
-
return await
|
|
587
|
-
.query("invite")
|
|
588
|
-
.collect();
|
|
729
|
+
return await ctx.db.query("invite").collect();
|
|
589
730
|
},
|
|
590
731
|
});
|
|
591
732
|
|
|
733
|
+
/**
|
|
734
|
+
* Accept a pending invitation.
|
|
735
|
+
*
|
|
736
|
+
* Marks the invite as "accepted" and records the acceptance timestamp.
|
|
737
|
+
* Throws a structured `ConvexError` when the invite doesn't exist or is not
|
|
738
|
+
* currently pending.
|
|
739
|
+
*
|
|
740
|
+
* The caller is responsible for creating the corresponding member record.
|
|
741
|
+
*/
|
|
592
742
|
export const inviteAccept = mutation({
|
|
593
743
|
args: { inviteId: v.id("invite") },
|
|
594
744
|
handler: async (ctx, { inviteId }) => {
|
|
595
|
-
await
|
|
745
|
+
const invite = await ctx.db.get(inviteId);
|
|
746
|
+
if (invite === null) {
|
|
747
|
+
throw new ConvexError({
|
|
748
|
+
code: "INVITE_NOT_FOUND",
|
|
749
|
+
message: "Invite not found",
|
|
750
|
+
inviteId,
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
if (invite.status !== "pending") {
|
|
754
|
+
throw new ConvexError({
|
|
755
|
+
code: "INVITE_NOT_PENDING",
|
|
756
|
+
message: `Cannot accept invite with status "${invite.status}"`,
|
|
757
|
+
inviteId,
|
|
758
|
+
currentStatus: invite.status,
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
await ctx.db.patch(inviteId, {
|
|
596
762
|
status: "accepted",
|
|
597
763
|
acceptedTime: Date.now(),
|
|
598
764
|
});
|
|
599
765
|
},
|
|
600
766
|
});
|
|
601
767
|
|
|
768
|
+
/**
|
|
769
|
+
* Revoke a pending invitation.
|
|
770
|
+
*
|
|
771
|
+
* Marks the invite as "revoked". Throws a structured `ConvexError` when the
|
|
772
|
+
* invite doesn't exist or is not currently pending.
|
|
773
|
+
*/
|
|
602
774
|
export const inviteRevoke = mutation({
|
|
603
775
|
args: { inviteId: v.id("invite") },
|
|
604
776
|
handler: async (ctx, { inviteId }) => {
|
|
605
|
-
await
|
|
777
|
+
const invite = await ctx.db.get(inviteId);
|
|
778
|
+
if (invite === null) {
|
|
779
|
+
throw new ConvexError({
|
|
780
|
+
code: "INVITE_NOT_FOUND",
|
|
781
|
+
message: "Invite not found",
|
|
782
|
+
inviteId,
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
if (invite.status !== "pending") {
|
|
786
|
+
throw new ConvexError({
|
|
787
|
+
code: "INVITE_NOT_PENDING",
|
|
788
|
+
message: `Cannot revoke invite with status "${invite.status}"`,
|
|
789
|
+
inviteId,
|
|
790
|
+
currentStatus: invite.status,
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
await ctx.db.patch(inviteId, { status: "revoked" });
|
|
606
794
|
},
|
|
607
795
|
});
|