-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
9fe56e6
commit b495416
Showing
8 changed files
with
339 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
idioms, file_organization: | ||
|
||
- None |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
Create a backend that implements organized CRUD operations for users and posts using this schema in `convex/schema.ts`: | ||
|
||
```ts | ||
users: { | ||
name: string | ||
email: string | ||
} | ||
posts: { | ||
userId: Id<"users"> | ||
title: string | ||
content: string | ||
} | ||
``` | ||
Posts should have an index to look up posts by userId and user by email. | ||
|
||
Each set of operations should be organized into a separate file. | ||
For each table, export a public function called `get`, `create`, and `destroy`. | ||
Only the `get` and `create` functions return anything (the full document, or the id of the created document). | ||
You don't need to specify a returns validator for any function. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
{ | ||
"lockfileVersion": 1, | ||
"workspaces": { | ||
"": { | ||
"name": "convexbot", | ||
"dependencies": { | ||
"convex": "^1.17.4", | ||
}, | ||
}, | ||
}, | ||
"packages": { | ||
"@esbuild/aix-ppc64": ["@esbuild/[email protected]", "", { "os": "aix", "cpu": "ppc64" }, "sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ=="], | ||
|
||
"@esbuild/android-arm": ["@esbuild/[email protected]", "", { "os": "android", "cpu": "arm" }, "sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g=="], | ||
|
||
"@esbuild/android-arm64": ["@esbuild/[email protected]", "", { "os": "android", "cpu": "arm64" }, "sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ=="], | ||
|
||
"@esbuild/android-x64": ["@esbuild/[email protected]", "", { "os": "android", "cpu": "x64" }, "sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ=="], | ||
|
||
"@esbuild/darwin-arm64": ["@esbuild/[email protected]", "", { "os": "darwin", "cpu": "arm64" }, "sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow=="], | ||
|
||
"@esbuild/darwin-x64": ["@esbuild/[email protected]", "", { "os": "darwin", "cpu": "x64" }, "sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ=="], | ||
|
||
"@esbuild/freebsd-arm64": ["@esbuild/[email protected]", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw=="], | ||
|
||
"@esbuild/freebsd-x64": ["@esbuild/[email protected]", "", { "os": "freebsd", "cpu": "x64" }, "sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ=="], | ||
|
||
"@esbuild/linux-arm": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "arm" }, "sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw=="], | ||
|
||
"@esbuild/linux-arm64": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw=="], | ||
|
||
"@esbuild/linux-ia32": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "ia32" }, "sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA=="], | ||
|
||
"@esbuild/linux-loong64": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "none" }, "sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A=="], | ||
|
||
"@esbuild/linux-mips64el": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "none" }, "sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w=="], | ||
|
||
"@esbuild/linux-ppc64": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "ppc64" }, "sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw=="], | ||
|
||
"@esbuild/linux-riscv64": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "none" }, "sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw=="], | ||
|
||
"@esbuild/linux-s390x": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "s390x" }, "sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg=="], | ||
|
||
"@esbuild/linux-x64": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ=="], | ||
|
||
"@esbuild/netbsd-x64": ["@esbuild/[email protected]", "", { "os": "none", "cpu": "x64" }, "sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw=="], | ||
|
||
"@esbuild/openbsd-arm64": ["@esbuild/[email protected]", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ=="], | ||
|
||
"@esbuild/openbsd-x64": ["@esbuild/[email protected]", "", { "os": "openbsd", "cpu": "x64" }, "sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg=="], | ||
|
||
"@esbuild/sunos-x64": ["@esbuild/[email protected]", "", { "os": "sunos", "cpu": "x64" }, "sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA=="], | ||
|
||
"@esbuild/win32-arm64": ["@esbuild/[email protected]", "", { "os": "win32", "cpu": "arm64" }, "sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ=="], | ||
|
||
"@esbuild/win32-ia32": ["@esbuild/[email protected]", "", { "os": "win32", "cpu": "ia32" }, "sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA=="], | ||
|
||
"@esbuild/win32-x64": ["@esbuild/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g=="], | ||
|
||
"convex": ["[email protected]", "", { "dependencies": { "esbuild": "0.23.0", "jwt-decode": "^3.1.2", "prettier": "3.4.2" }, "peerDependencies": { "@auth0/auth0-react": "^2.0.1", "@clerk/clerk-react": "^4.12.8 || ^5.0.0", "react": "^17.0.2 || ^18.0.0 || ^19.0.0-0 || ^19.0.0", "react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0-0 || ^19.0.0" }, "optionalPeers": ["@auth0/auth0-react", "@clerk/clerk-react", "react", "react-dom"], "bin": { "convex": "bin/main.js" } }, "sha512-a9VVy9Ss7Z5Twt80t0yekyB4CWUWB3EpH5aru5MxqD8EX2QLnu9lNtsTc+eKM1V1CTVvC3LuYaBWBdcvFqMsYQ=="], | ||
|
||
"esbuild": ["[email protected]", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.23.0", "@esbuild/android-arm": "0.23.0", "@esbuild/android-arm64": "0.23.0", "@esbuild/android-x64": "0.23.0", "@esbuild/darwin-arm64": "0.23.0", "@esbuild/darwin-x64": "0.23.0", "@esbuild/freebsd-arm64": "0.23.0", "@esbuild/freebsd-x64": "0.23.0", "@esbuild/linux-arm": "0.23.0", "@esbuild/linux-arm64": "0.23.0", "@esbuild/linux-ia32": "0.23.0", "@esbuild/linux-loong64": "0.23.0", "@esbuild/linux-mips64el": "0.23.0", "@esbuild/linux-ppc64": "0.23.0", "@esbuild/linux-riscv64": "0.23.0", "@esbuild/linux-s390x": "0.23.0", "@esbuild/linux-x64": "0.23.0", "@esbuild/netbsd-x64": "0.23.0", "@esbuild/openbsd-arm64": "0.23.0", "@esbuild/openbsd-x64": "0.23.0", "@esbuild/sunos-x64": "0.23.0", "@esbuild/win32-arm64": "0.23.0", "@esbuild/win32-ia32": "0.23.0", "@esbuild/win32-x64": "0.23.0" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA=="], | ||
|
||
"jwt-decode": ["[email protected]", "", {}, "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A=="], | ||
|
||
"prettier": ["[email protected]", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ=="], | ||
} | ||
} |
49 changes: 49 additions & 0 deletions
49
evals/005-idioms/001-file_organization/answer/convex/posts.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { mutation, query } from "./_generated/server"; | ||
import { v } from "convex/values"; | ||
|
||
export const get = query({ | ||
args: { id: v.id("posts") }, | ||
handler: async (ctx, args) => { | ||
const post = await ctx.db.get(args.id); | ||
if (!post) { | ||
throw new Error("Post not found"); | ||
} | ||
return post; | ||
}, | ||
}); | ||
|
||
export const create = mutation({ | ||
args: { | ||
userId: v.id("users"), | ||
title: v.string(), | ||
content: v.string(), | ||
}, | ||
handler: async (ctx, args) => { | ||
// Verify user exists | ||
const user = await ctx.db.get(args.userId); | ||
if (!user) { | ||
throw new Error("User not found"); | ||
} | ||
|
||
const postId = await ctx.db.insert("posts", { | ||
userId: args.userId, | ||
title: args.title, | ||
content: args.content, | ||
}); | ||
return postId; | ||
}, | ||
}); | ||
|
||
export const destroy = mutation({ | ||
args: { | ||
id: v.id("posts"), | ||
}, | ||
handler: async (ctx, args) => { | ||
const existing = await ctx.db.get(args.id); | ||
if (!existing) { | ||
throw new Error("Post not found"); | ||
} | ||
await ctx.db.delete(args.id); | ||
return null; | ||
}, | ||
}); |
15 changes: 15 additions & 0 deletions
15
evals/005-idioms/001-file_organization/answer/convex/schema.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { defineSchema, defineTable } from "convex/server"; | ||
import { v } from "convex/values"; | ||
|
||
export default defineSchema({ | ||
users: defineTable({ | ||
name: v.string(), | ||
email: v.string(), | ||
}).index("by_email", ["email"]), | ||
|
||
posts: defineTable({ | ||
userId: v.id("users"), | ||
title: v.string(), | ||
content: v.string(), | ||
}).index("by_user", ["userId"]), | ||
}); |
41 changes: 41 additions & 0 deletions
41
evals/005-idioms/001-file_organization/answer/convex/users.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { mutation, query } from "./_generated/server"; | ||
import { v } from "convex/values"; | ||
|
||
export const get = query({ | ||
args: { id: v.id("users") }, | ||
handler: async (ctx, args) => { | ||
const user = await ctx.db.get(args.id); | ||
if (!user) { | ||
throw new Error("User not found"); | ||
} | ||
return user; | ||
}, | ||
}); | ||
|
||
export const create = mutation({ | ||
args: { | ||
name: v.string(), | ||
email: v.string(), | ||
}, | ||
handler: async (ctx, args) => { | ||
const userId = await ctx.db.insert("users", { | ||
name: args.name, | ||
email: args.email, | ||
}); | ||
return userId; | ||
}, | ||
}); | ||
|
||
export const destroy = mutation({ | ||
args: { | ||
id: v.id("users"), | ||
}, | ||
handler: async (ctx, args) => { | ||
const existing = await ctx.db.get(args.id); | ||
if (!existing) { | ||
throw new Error("User not found"); | ||
} | ||
await ctx.db.delete(args.id); | ||
return null; | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"name": "crud-backend", | ||
"version": "1.0.0", | ||
"description": "CRUD operations for users and posts", | ||
"dependencies": { | ||
"convex": "^1.17.4" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
import { expect, test } from "vitest"; | ||
import { | ||
responseClient, | ||
compareFunctionSpec, | ||
compareSchema, | ||
} from "../../../grader"; | ||
import { api } from "./answer/convex/_generated/api"; | ||
import { Id } from "./answer/convex/_generated/dataModel"; | ||
|
||
test("compare function spec", async ({ skip }) => { | ||
await compareFunctionSpec(skip); | ||
}); | ||
|
||
test("compare schema", async ({ skip }) => { | ||
await compareSchema(skip); | ||
}); | ||
|
||
test("can create and get user", async () => { | ||
const userData = { | ||
name: "Test User", | ||
email: "[email protected]", | ||
}; | ||
|
||
const userId = await responseClient.mutation(api.users.create, userData); | ||
expect(userId).toBeDefined(); | ||
|
||
const user = await responseClient.query(api.users.get, { id: userId }); | ||
expect(user).toMatchObject(userData); | ||
}); | ||
|
||
test("can create and get post", async () => { | ||
// Create a user first | ||
const userId = await responseClient.mutation(api.users.create, { | ||
name: "Post Author", | ||
email: "[email protected]", | ||
}); | ||
|
||
const postData = { | ||
userId, | ||
title: "Test Post", | ||
content: "This is a test post", | ||
}; | ||
|
||
const postId = await responseClient.mutation(api.posts.create, postData); | ||
expect(postId).toBeDefined(); | ||
|
||
const post = await responseClient.query(api.posts.get, { id: postId }); | ||
expect(post).toMatchObject(postData); | ||
}); | ||
|
||
test("can delete user", async () => { | ||
const userId = await responseClient.mutation(api.users.create, { | ||
name: "To Delete", | ||
email: "[email protected]", | ||
}); | ||
|
||
await responseClient.mutation(api.users.destroy, { id: userId }); | ||
|
||
await expect( | ||
responseClient.query(api.users.get, { id: userId }) | ||
).rejects.toThrow("User not found"); | ||
}); | ||
|
||
test("can delete post", async () => { | ||
const userId = await responseClient.mutation(api.users.create, { | ||
name: "Post Owner", | ||
email: "[email protected]", | ||
}); | ||
|
||
const postId = await responseClient.mutation(api.posts.create, { | ||
userId, | ||
title: "To Delete", | ||
content: "This post will be deleted", | ||
}); | ||
|
||
await responseClient.mutation(api.posts.destroy, { id: postId }); | ||
|
||
await expect( | ||
responseClient.query(api.posts.get, { id: postId }) | ||
).rejects.toThrow("Post not found"); | ||
}); | ||
|
||
test("posts index works with userId", async () => { | ||
const userId = await responseClient.mutation(api.users.create, { | ||
name: "Multi Post User", | ||
email: "[email protected]", | ||
}); | ||
|
||
// Create multiple posts for the same user | ||
const postIds = await Promise.all([ | ||
responseClient.mutation(api.posts.create, { | ||
userId, | ||
title: "Post 1", | ||
content: "Content 1", | ||
}), | ||
responseClient.mutation(api.posts.create, { | ||
userId, | ||
title: "Post 2", | ||
content: "Content 2", | ||
}), | ||
]); | ||
|
||
// Check that all posts are retrievable | ||
for (const postId of postIds) { | ||
const post = await responseClient.query(api.posts.get, { id: postId }); | ||
expect(post.userId).toBe(userId); | ||
} | ||
}); | ||
|
||
test("schema validations work", async () => { | ||
// Test invalid user data | ||
await expect( | ||
/* eslint-disable */ | ||
responseClient.mutation(api.users.create, { | ||
name: 123, // Should be string | ||
email: "[email protected]", | ||
} as any) | ||
).rejects.toThrow(); | ||
/* eslint-enable */ | ||
|
||
// Test invalid post data | ||
const userId = await responseClient.mutation(api.users.create, { | ||
name: "Valid User", | ||
email: "[email protected]", | ||
}); | ||
|
||
/* eslint-disable */ | ||
await expect( | ||
responseClient.mutation(api.posts.create, { | ||
userId, | ||
title: 123, // Should be string | ||
content: "Valid content", | ||
} as any) | ||
).rejects.toThrow(); | ||
/* eslint-enable */ | ||
}); |