Skip to content

Commit 25530ad

Browse files
committed
enforce unique fields
1 parent eab4f0e commit 25530ad

File tree

3 files changed

+69
-26
lines changed

3 files changed

+69
-26
lines changed

auth.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import { betterAuth } from "better-auth";
2-
import { emailOTP, magicLink, twoFactor } from "better-auth/plugins";
2+
import {
3+
anonymous,
4+
emailOTP,
5+
magicLink,
6+
twoFactor,
7+
username,
8+
} from "better-auth/plugins";
39
import { convex } from "@convex-dev/better-auth/plugins";
410

511
// This is the config used to generate the schema
@@ -11,14 +17,16 @@ const config = betterAuth({
1117
twoFactor(),
1218
magicLink({ sendMagicLink: async () => {} }),
1319
emailOTP({ sendVerificationOTP: async () => {} }),
20+
anonymous(),
21+
username(),
1422
convex(),
1523
],
1624
});
1725
export { config as auth };
1826

1927
// Set fields to index on for schema generation
2028
export const indexFields = {
21-
user: ["email", "userId"],
29+
user: ["email", "userId", "username"],
2230
session: ["token", "userId", "expiresAt", ["expiresAt", "userId"]],
2331
account: [
2432
"userId",

src/component/lib.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { asyncMap } from "convex-helpers";
88
import { v } from "convex/values";
99
import { api } from "../component/_generated/api";
1010
import { Doc, Id, TableNames } from "../component/_generated/dataModel";
11-
import schema from "../component/schema";
11+
import schema, { specialFields } from "../component/schema";
1212
import { paginationOptsValidator, PaginationResult } from "convex/server";
1313
import { paginator } from "convex-helpers/server/pagination";
1414
import { partial } from "convex-helpers/validators";
@@ -70,6 +70,32 @@ export const getByQuery = query({
7070
});
7171
export { getByQuery as getBy };
7272

73+
const checkUniqueFields = async (
74+
ctx: QueryCtx,
75+
table: TableNames,
76+
input: Record<string, any>,
77+
doc?: Doc<any>
78+
) => {
79+
const uniqueFields = Object.entries(
80+
specialFields[table as keyof typeof specialFields]
81+
)
82+
.filter(
83+
([key, value]) =>
84+
value.unique && Object.keys(input).includes(key as keyof typeof input)
85+
)
86+
.map(([key]) => key);
87+
for (const field of uniqueFields) {
88+
const existingDoc = await getByHelper(ctx, {
89+
table,
90+
field,
91+
value: input[field as keyof typeof input],
92+
});
93+
if (existingDoc && existingDoc._id !== doc?._id) {
94+
throw new Error(`${table} ${field} already exists`);
95+
}
96+
}
97+
};
98+
7399
export const create = mutation({
74100
args: v.object({
75101
input: v.union(
@@ -83,6 +109,9 @@ export const create = mutation({
83109
}),
84110
handler: async (ctx, args) => {
85111
const { table, ...input } = args.input;
112+
113+
await checkUniqueFields(ctx, table as TableNames, input);
114+
86115
const id = await ctx.db.insert(table as any, {
87116
...input,
88117
});
@@ -122,6 +151,7 @@ export const update = mutation({
122151
if (!doc) {
123152
throw new Error(`Failed to update ${table}`);
124153
}
154+
await checkUniqueFields(ctx, table as TableNames, value, doc);
125155
await ctx.db.patch(doc._id, value as any);
126156
const updatedDoc = await ctx.db.get(doc._id);
127157
if (!updatedDoc) {

src/component/schema.ts

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,14 @@ const schema = defineSchema({
1414
createdAt: v.number(),
1515
updatedAt: v.number(),
1616
twoFactorEnabled: v.optional(v.boolean()),
17+
isAnonymous: v.optional(v.boolean()),
18+
username: v.optional(v.string()),
19+
displayUsername: v.optional(v.string()),
1720
userId: v.optional(v.string()),
1821
})
1922
.index("email", ["email"])
20-
.index("userId", ["userId"]),
23+
.index("userId", ["userId"])
24+
.index("username", ["username"]),
2125

2226
session: defineTable({
2327
expiresAt: v.number(),
@@ -31,7 +35,7 @@ const schema = defineSchema({
3135
.index("token", ["token"])
3236
.index("userId", ["userId"])
3337
.index("expiresAt", ["expiresAt"])
34-
.index("expiresAt_userId", ["expiresAt","userId"]),
38+
.index("expiresAt_userId", ["expiresAt", "userId"]),
3539

3640
account: defineTable({
3741
accountId: v.string(),
@@ -49,8 +53,8 @@ const schema = defineSchema({
4953
})
5054
.index("userId", ["userId"])
5155
.index("accountId", ["accountId"])
52-
.index("accountId_providerId", ["accountId","providerId"])
53-
.index("providerId_userId", ["providerId","userId"]),
56+
.index("accountId_providerId", ["accountId", "providerId"])
57+
.index("providerId_userId", ["providerId", "userId"]),
5458

5559
verification: defineTable({
5660
identifier: v.string(),
@@ -66,8 +70,7 @@ const schema = defineSchema({
6670
secret: v.string(),
6771
backupCodes: v.string(),
6872
userId: v.string(),
69-
})
70-
.index("userId", ["userId"]),
73+
}).index("userId", ["userId"]),
7174

7275
jwks: defineTable({
7376
publicKey: v.string(),
@@ -79,50 +82,52 @@ const schema = defineSchema({
7982
key: v.optional(v.string()),
8083
count: v.optional(v.number()),
8184
lastRequest: v.optional(v.number()),
82-
})
83-
.index("key", ["key"]),
84-
85+
}).index("key", ["key"]),
8586
});
8687

8788
export default schema;
8889

8990
export const specialFields = {
9091
user: {
9192
name: {
92-
sortable: true
93+
sortable: true,
9394
},
9495
email: {
9596
sortable: true,
96-
unique: true
97-
}
97+
unique: true,
98+
},
99+
username: {
100+
sortable: true,
101+
unique: true,
102+
},
98103
},
99104
session: {
100105
token: {
101-
unique: true
106+
unique: true,
102107
},
103108
userId: {
104109
references: {
105110
model: "user",
106111
field: "id",
107-
onDelete: "cascade"
108-
}
109-
}
112+
onDelete: "cascade",
113+
},
114+
},
110115
},
111116
account: {
112117
userId: {
113118
references: {
114119
model: "user",
115120
field: "id",
116-
onDelete: "cascade"
117-
}
118-
}
121+
onDelete: "cascade",
122+
},
123+
},
119124
},
120125
twoFactor: {
121126
userId: {
122127
references: {
123128
model: "user",
124-
field: "id"
125-
}
126-
}
127-
}
129+
field: "id",
130+
},
131+
},
132+
},
128133
};

0 commit comments

Comments
 (0)