diff --git a/template-component/example/convex/auth.ts b/template-component/example/convex/auth.ts new file mode 100644 index 00000000..187bb865 --- /dev/null +++ b/template-component/example/convex/auth.ts @@ -0,0 +1,6 @@ +import { Auth } from "convex/server"; + +// For demo purposes +export async function getAuthUserId(ctx: { auth: Auth }) { + return (await ctx.auth.getUserIdentity())?.subject ?? null; +} diff --git a/template-component/example/convex/example.test.ts b/template-component/example/convex/example.test.ts index 821bb395..0e4f941d 100644 --- a/template-component/example/convex/example.test.ts +++ b/template-component/example/convex/example.test.ts @@ -12,14 +12,16 @@ describe("example", () => { }); test("addComment and listComments", async () => { - const t = initConvexTest(); - const targetId = "test-subject-1"; + const t = initConvexTest().withIdentity({ + subject: "user1", + }); + const postId = "test-subject-1"; const commentId = await t.mutation(api.example.addComment, { text: "My comment", - targetId, + postId, }); expect(commentId).toBeDefined(); - const comments = await t.query(api.example.listComments, { targetId }); + const comments = await t.query(api.example.listComments, { postId }); expect(comments).toHaveLength(1); expect(comments[0].text).toBe("My comment"); }); diff --git a/template-component/example/convex/example.ts b/template-component/example/convex/example.ts index 6e2ee915..dd3d2302 100644 --- a/template-component/example/convex/example.ts +++ b/template-component/example/convex/example.ts @@ -1,29 +1,31 @@ import { action, mutation, query } from "./_generated/server.js"; import { components } from "./_generated/api.js"; -import { exposeApi } from "@example/sample-component"; +import { translate } from "@example/sample-component"; import { v } from "convex/values"; -import { Auth } from "convex/server"; - -// Environment variables aren't available in the component, -// so we need to pass it in as an argument to the component when necessary. -const BASE_URL = process.env.BASE_URL ?? "https://pirate.monkeyness.com"; +import { getAuthUserId } from "./auth.js"; export const addComment = mutation({ - args: { text: v.string(), targetId: v.string() }, + args: { text: v.string(), postId: v.string() }, handler: async (ctx, args) => { + const userId = await getAuthUserId(ctx); + // A real app might check who can comment on what. + if (!userId) { + throw new Error("Unauthorized"); + } return await ctx.runMutation(components.sampleComponent.lib.add, { text: args.text, - targetId: args.targetId, - userId: await getAuthUserId(ctx), + targetId: args.postId, + userId, }); }, }); export const listComments = query({ - args: { targetId: v.string() }, + args: { postId: v.string() }, handler: async (ctx, args) => { + // A real app might check who can see what. return await ctx.runQuery(components.sampleComponent.lib.list, { - targetId: args.targetId, + targetId: args.postId, }); }, }); @@ -31,28 +33,12 @@ export const listComments = query({ export const translateComment = action({ args: { commentId: v.string() }, handler: async (ctx, args) => { + await translate(ctx, components.sampleComponent, args.commentId); + /** Or by calling the component directly: return await ctx.runAction(components.sampleComponent.lib.translate, { - baseUrl: BASE_URL, + baseUrl: process.env.BASE_URL ?? "https://pirate.monkeyness.com", commentId: args.commentId, }); + */ }, }); - -// Here is an alternative way to use the component's methods directly by re-exporting -// the component's API: -export const { list, add, translate } = exposeApi(components.sampleComponent, { - auth: async (ctx, operation) => { - const userId = await getAuthUserId(ctx); - if (userId === null && operation.type !== "read") { - throw new Error("Unauthorized"); - } - return userId; - }, - baseUrl: BASE_URL, -}); - -// You can also register HTTP routes for the component. See http.ts for an example. - -async function getAuthUserId(ctx: { auth: Auth }) { - return (await ctx.auth.getUserIdentity())?.subject ?? "anonymous"; -} diff --git a/template-component/example/convex/reexport.ts b/template-component/example/convex/reexport.ts new file mode 100644 index 00000000..81ff98f6 --- /dev/null +++ b/template-component/example/convex/reexport.ts @@ -0,0 +1,19 @@ +import { components } from "./_generated/api.js"; +import { exposeApi } from "@example/sample-component"; +import { getAuthUserId } from "./auth.js"; +// Here is an alternative way to use the component's methods directly by re-exporting +// the component's API: +export const { list, add, translate } = exposeApi(components.sampleComponent, { + auth: async (ctx, operation) => { + const userId = await getAuthUserId(ctx); + if (userId === null && operation.type !== "read") { + throw new Error("Unauthorized"); + } + return userId ?? "anonymous"; + }, + // Environment variables aren't available in the component, + // so we need to pass it in as an argument to the component when necessary. + baseUrl: process.env.BASE_URL ?? "https://pirate.monkeyness.com", +}); + +// You can also register HTTP routes for the component. See http.ts for an example. diff --git a/template-component/example/src/App.tsx b/template-component/example/src/App.tsx index 13e203da..54636120 100644 --- a/template-component/example/src/App.tsx +++ b/template-component/example/src/App.tsx @@ -24,14 +24,14 @@ const blogPosts = [ ]; function BlogPostComments({ postId }: { postId: string }) { - const comments = useQuery(api.example.list, { targetId: postId }); - const addComment = useMutation(api.example.add); + const comments = useQuery(api.example.listComments, { postId }); + const addComment = useMutation(api.example.addComment); const translateComment = useAction(api.example.translateComment); const [commentText, setCommentText] = useState(""); const handleAddComment = () => { if (commentText.trim()) { - addComment({ text: commentText, targetId: postId }); + addComment({ text: commentText, postId }); setCommentText(""); } };