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