sbkl
sbkl
CCConvex Community
Created by sbkl on 11/20/2024 in #support-community
Using gemini with openai library working in action but not http action
Thanks. Could be a reason. Ended up building a simple class mocking the openai library api for gemini api. Works great.
7 replies
CCConvex Community
Created by sbkl on 10/28/2024 in #support-community
Uncaught Error: The arguments to a Convex function must be an object. Received: skip
Works like a charm! thank you. Was getting some errors because of this when the url params wasn't available. But not anymore!
5 replies
CCConvex Community
Created by sbkl on 10/11/2024 in #support-community
File serving in a nextjs app via http action authenticated with clerk
Usage with Next Image for example
<Image
src={`/api/storage?fileType=documentThumbnail&storageId=${convexDoc.storageId}`}
...other props
/>
<Image
src={`/api/storage?fileType=documentThumbnail&storageId=${convexDoc.storageId}`}
...other props
/>
4 replies
CCConvex Community
Created by sbkl on 10/11/2024 in #support-community
File serving in a nextjs app via http action authenticated with clerk
Nextjs route to pass the clerk jwt token to the fetch call to the convex http action: /api/storage/route.ts
import { NextRequest } from "next/server";
import { auth } from "@clerk/nextjs/server";

import { env } from "~/env";

export const dynamic = "force-dynamic"; // defaults to auto

export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const storageId = searchParams.get("storageId");
const fileType = searchParams.get("fileType");

if (!storageId) {
return new Response("Storage ID is required.", { status: 400 });
}

if (!fileType) {
return new Response("File type is required.", { status: 400 });
}

const { userId, getToken } = auth();

if (!userId) {
return new Response("User is not signed in.", { status: 401 });
}

try {
const token = await getToken({ template: "convex" });

if (!token) {
return new Response("User is not signed in.", { status: 401 });
}

const response = await fetch(
`${env.NEXT_PUBLIC_CONVEX_API_URL}/storage?fileType=${fileType}&storageId=${storageId}`,
{
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
},
},
);

if (!response.ok) {
return new Response("Failed to fetch image", { status: 500 });
}

const blob = await response.blob();

const headers = new Headers();

console.log("blob.type", blob.type);
headers.set("Content-Type", blob.type);

return new Response(blob, {
headers,
});
} catch (error) {
return Response.json(error);
}
}
import { NextRequest } from "next/server";
import { auth } from "@clerk/nextjs/server";

import { env } from "~/env";

export const dynamic = "force-dynamic"; // defaults to auto

export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const storageId = searchParams.get("storageId");
const fileType = searchParams.get("fileType");

if (!storageId) {
return new Response("Storage ID is required.", { status: 400 });
}

if (!fileType) {
return new Response("File type is required.", { status: 400 });
}

const { userId, getToken } = auth();

if (!userId) {
return new Response("User is not signed in.", { status: 401 });
}

try {
const token = await getToken({ template: "convex" });

if (!token) {
return new Response("User is not signed in.", { status: 401 });
}

const response = await fetch(
`${env.NEXT_PUBLIC_CONVEX_API_URL}/storage?fileType=${fileType}&storageId=${storageId}`,
{
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
},
},
);

if (!response.ok) {
return new Response("Failed to fetch image", { status: 500 });
}

const blob = await response.blob();

const headers = new Headers();

console.log("blob.type", blob.type);
headers.set("Content-Type", blob.type);

return new Response(blob, {
headers,
});
} catch (error) {
return Response.json(error);
}
}
4 replies
CCConvex Community
Created by sbkl. on 10/3/2024 in #support-community
file storage: how to make the files private and secure
oh with http actions
9 replies
CCConvex Community
Created by sbkl. on 10/3/2024 in #support-community
file storage: how to make the files private and secure
Yes, started to look into end-to-end encryption. Another point related to network input/output. Is it possible to allow only a specific domain to access the file url? So I build the url with ctx.storage.getUrl. Is it possible that GET requests to this specific url is only accessible from a specific domain and avoid being able to load it in the browser for example?
9 replies
CCConvex Community
Created by sbkl on 9/26/2024 in #support-community
LLamaindex package: External packages in convex.json doesn't work
Looks like it related to the fact that I am using a monorepo and externalPackages is not recognised by convex because the node_module where the package lives is outside of the convex project I am working on. Saw that in another post when looking at image transformation in Convex with sharp. See link
3 replies
CCConvex Community
Created by sbkl on 9/25/2024 in #support-community
Object contains extra field that is not in the validator.
Thanks. That's what I figured. Instead, I've been less lazy and made sure to pass exactly what the validator expects so I keep type safety.
4 replies
CCConvex Community
Created by sbkl on 5/12/2024 in #support-community
preloadQuery client cache
So went for the watch solution and implemented a custom hook. As I am using the preloadQuery, I need to call both the preloadQuery and the useSubscribedQuery. Does that make sense and not pushing too many requests to the server?
export function useSubscribedQuery<Query extends FunctionReference<"query">>(
query: Query,
...args: ArgsAndOptions<Query, WatchQueryOptions>
) {
const client = useConvex();
React.useEffect(() => {
const unsub = client.watchQuery(query, ...args).onUpdate(() => {});
return function cleanup() {
// unsubscribe 5 seconds after unmount
setTimeout(unsub, 5000);
};
}, []);
}

/client component

"use client";

import { Preloaded, usePreloadedQuery } from "convex/react";
import { api } from "@/convex/_generated/api";

import { useSubscribedQuery } from "@/hooks/use-cached-query";

interface preloadedCollections {
preloadedCollections: Preloaded<typeof api.collections.list>;
}

export function CollectionList(props: preloadedCollections) {
const collections = usePreloadedQuery(props.preloadedCollections);
useSubscribedQuery(api.collections.list);
return (...)
}

/server component
import { redirect } from "next/navigation";

import { preloadQuery } from "convex/nextjs";
import { api } from "@/convex/_generated/api";

import { getAuthToken } from "../_lib/auth";

import { env } from "@/env";

export default async function Page() {
const searchParams = new URLSearchParams({
redirect_url: env.NEXT_PUBLIC_CLERK_REDIRECT_URL,
});
const token = await getAuthToken();
const preloadedCollections = token
? await preloadQuery(
api.collections.list,
{},
{
token,
},
)
: null;

if (!preloadedCollections) {
return redirect(`${env.NEXT_PUBLIC_CLERK_URL}?${searchParams.toString()}`);
}

return (...)
}
export function useSubscribedQuery<Query extends FunctionReference<"query">>(
query: Query,
...args: ArgsAndOptions<Query, WatchQueryOptions>
) {
const client = useConvex();
React.useEffect(() => {
const unsub = client.watchQuery(query, ...args).onUpdate(() => {});
return function cleanup() {
// unsubscribe 5 seconds after unmount
setTimeout(unsub, 5000);
};
}, []);
}

/client component

"use client";

import { Preloaded, usePreloadedQuery } from "convex/react";
import { api } from "@/convex/_generated/api";

import { useSubscribedQuery } from "@/hooks/use-cached-query";

interface preloadedCollections {
preloadedCollections: Preloaded<typeof api.collections.list>;
}

export function CollectionList(props: preloadedCollections) {
const collections = usePreloadedQuery(props.preloadedCollections);
useSubscribedQuery(api.collections.list);
return (...)
}

/server component
import { redirect } from "next/navigation";

import { preloadQuery } from "convex/nextjs";
import { api } from "@/convex/_generated/api";

import { getAuthToken } from "../_lib/auth";

import { env } from "@/env";

export default async function Page() {
const searchParams = new URLSearchParams({
redirect_url: env.NEXT_PUBLIC_CLERK_REDIRECT_URL,
});
const token = await getAuthToken();
const preloadedCollections = token
? await preloadQuery(
api.collections.list,
{},
{
token,
},
)
: null;

if (!preloadedCollections) {
return redirect(`${env.NEXT_PUBLIC_CLERK_URL}?${searchParams.toString()}`);
}

return (...)
}
4 replies
CCConvex Community
Created by sbkl on 5/11/2024 in #support-community
preloadQuery for server side rendering getting the client component error
So I'm using a monorepo with turborepo and used convex as a package to reuse for both nextjs and expo apps. Seems like the setup I made myself didn't work out. Used this example: https://github.com/get-convex/turbo-expo-nextjs-clerk-convex-monorepo And now it is working fine. I also installed convex on the nextjs app instead of importing them from the monorepo convex package.
4 replies
CCConvex Community
Created by sbkl on 5/11/2024 in #support-community
preloadQuery for server side rendering getting the client component error
Created a new nextjs project to try reproduce the issue but it is working lol. Updgraded my existing project to nextjs and convex minor versions and reinstalled everything but still same result. I have no idea why.
4 replies
CCConvex Community
Created by sbkl. on 5/10/2024 in #support-community
AuthKit from WorkOS integration
The reason I want AuthKit is the easy integration with WorkOS enabling SSO which a requirement for most of my clients. Clerk provides it somehow but seems way more work to setup and I'm familiar with workOS. But will explore further with Clerk.
10 replies
CCConvex Community
Created by sbkl. on 5/10/2024 in #support-community
AuthKit from WorkOS integration
@Michal Srb Thank you. Reviewed next-auth but I need auth for both nextjs and expo. next-auth doesn't provide native support from what I understand. Asked workOS if they planned to provide custom fields on their access_token which doesn't seem to be the case at this time.
10 replies