Web Dev Cody
Web Dev Cody5mo ago

Pass auth context to actions

I am trying to call a mutation which schedules an internal action. This action uses other internal queries which check authotization, so I'm finding I often need to split one useful quey into a query and a helper function. Is there a nicer was to reuse code with my approach?
4 Replies
Web Dev Cody
Web Dev CodyOP5mo ago
async function getSegmentsWithImagesHelper(
ctx: QueryCtx,
storyId: Id<"story">,
) {
const segments = await ctx.db
.query("segments")
.withIndex("storyId_index_order", (q) => q.eq("storyId", storyId))
.order("asc")
.collect();

return await Promise.all(
segments.map(async (segment) => ({
...segment,
imageUrl: segment.image
? await ctx.storage.getUrl(segment.image)
: undefined,
})),
);
}

export const getSegmentsInternal = internalQuery({
args: {
storyId: v.id("story"),
},
async handler(ctx, args) {
return await getSegmentsWithImagesHelper(ctx, args.storyId);
},
});

export const getSegments = query({
args: {
storyId: v.id("story"),
},
async handler(ctx, args) {
await assertStoryOwnerHelper(ctx, args.storyId);
return await getSegmentsWithImagesHelper(ctx, args.storyId);
},
});
async function getSegmentsWithImagesHelper(
ctx: QueryCtx,
storyId: Id<"story">,
) {
const segments = await ctx.db
.query("segments")
.withIndex("storyId_index_order", (q) => q.eq("storyId", storyId))
.order("asc")
.collect();

return await Promise.all(
segments.map(async (segment) => ({
...segment,
imageUrl: segment.image
? await ctx.storage.getUrl(segment.image)
: undefined,
})),
);
}

export const getSegmentsInternal = internalQuery({
args: {
storyId: v.id("story"),
},
async handler(ctx, args) {
return await getSegmentsWithImagesHelper(ctx, args.storyId);
},
});

export const getSegments = query({
args: {
storyId: v.id("story"),
},
async handler(ctx, args) {
await assertStoryOwnerHelper(ctx, args.storyId);
return await getSegmentsWithImagesHelper(ctx, args.storyId);
},
});
like I end up needing to do this
Web Dev Cody
Web Dev CodyOP5mo ago
No description
Web Dev Cody
Web Dev CodyOP5mo ago
I see this, was just curious what the approach was to work around this because this is causing me to have to refactor lots of code to make it reusable
ian
ian5mo ago
My general thinking is that auth is done at the boundary between the client and server, and then within the server functions it's more trusted. So I think your question can go in two directions: 1. Access auth in queries but not internalQueries and pass along any user info you need as parameters to internal queries / mutations - and have the assertStoryOwnerHelper take in a userId instead of reading from ctx.auth 2. How to define a function that can be called from different contexts - e.g. an incremental improvement here is a way to make getSegmentsWithImagesHelper so that you don't have to duplicate the args and handler dance. Combining with a customQuery you could end up with something like
const storyQuery = customQuery({
// adds a required arg to all queries defined with storyQuery
args: { storyId: v.id("story") },
input: async (ctx, args) => {
await assertStoryOwnerHelper(ctx, args.storyId);
return { ctx: {}, args };
},
});

const getSegmentsWithImagesHelper = queryStub({
args: { storyId: v.id("story") },
async handler(ctx, args) {
const segments = ...
...
},
});

export const getSegmentsInternal = internalQuery(
getSegmentsWithImagesHelper
);

// Note: using the custom query defined above
export const getSegments = storyQuery(
getSegmentsWithImagesHelper
);
const storyQuery = customQuery({
// adds a required arg to all queries defined with storyQuery
args: { storyId: v.id("story") },
input: async (ctx, args) => {
await assertStoryOwnerHelper(ctx, args.storyId);
return { ctx: {}, args };
},
});

const getSegmentsWithImagesHelper = queryStub({
args: { storyId: v.id("story") },
async handler(ctx, args) {
const segments = ...
...
},
});

export const getSegmentsInternal = internalQuery(
getSegmentsWithImagesHelper
);

// Note: using the custom query defined above
export const getSegments = storyQuery(
getSegmentsWithImagesHelper
);
Which would imply that queryStub would be a useful handler to make

Did you find this page helpful?