mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-04 21:04:37 +08:00
convex example testing (#943)
<!--
Make sure you've read the CONTRIBUTING.md guidelines:
https://github.com/stack-auth/stack-auth/blob/dev/CONTRIBUTING.md
-->
<!-- RECURSEML_SUMMARY:START -->
## High-level PR Summary
This PR fixes dependency management issues by adding the missing
`wait-on` package to the Convex example's dependencies, reorganizing the
dependency order in `package.json` for consistency, and regenerating the
`pnpm-lock.yaml` file to ensure proper dependency resolution across the
monorepo.
⏱️ Estimated Review Time: 5-15 minutes
<details>
<summary>💡 Review Order Suggestion</summary>
| Order | File Path |
|-------|-----------|
| 1 | `examples/convex/package.json` |
| 2 | `pnpm-lock.yaml` |
</details>
[](https://discord.gg/n3SsVDAW6U)
[
<!-- RECURSEML_SUMMARY:END -->
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
- **New Features**
- Added UI buttons to view user info via different clients, a
server-side user info section, and an Action page to view/submit updates
to user metadata.
- Added a server-side action to update a user's client-read-only
metadata.
- **Documentation**
- In-app link and guidance to the Action route for updating user data.
- **Chores**
- Updated project dependencies/devDependencies and added .env.local to
.gitignore.
- **Bug Fixes**
- Token-missing scenario now handled gracefully instead of throwing.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Konsti Wohlwend <n2d4xc@gmail.com>
This commit is contained in:
parent
0ac2b00e1f
commit
91d8c16ffc
2
examples/convex/.gitignore
vendored
2
examples/convex/.gitignore
vendored
@ -39,3 +39,5 @@ yarn-error.log*
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
.env.local
|
||||
|
||||
37
examples/convex/app/action/page.tsx
Normal file
37
examples/convex/app/action/page.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
"use client";
|
||||
|
||||
import { useAction } from "convex/react";
|
||||
import { useState } from "react";
|
||||
import { api } from "@/convex/_generated/api";
|
||||
import { useUser } from "@stackframe/stack";
|
||||
import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
|
||||
|
||||
export default function Page() {
|
||||
const myAction = useAction(api.myActions.myAction);
|
||||
const user = useUser({ or: "redirect" });
|
||||
const [data, setData] = useState<string | null>(null);
|
||||
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-8 max-w-lg mx-auto pt-10">
|
||||
<div className="flex flex-col gap-4 bg-slate-200 dark:bg-slate-800 p-4 rounded-md">
|
||||
<h2 className="text-xl font-bold">User read-only metadata</h2>
|
||||
<code>
|
||||
<pre>{JSON.stringify(user.clientReadOnlyMetadata, null, 2)}</pre>
|
||||
</code>
|
||||
</div>
|
||||
<input type="text" placeholder="test 123" className="border border-slate-300 rounded-md p-2" onChange={(e) => setData(e.target.value)} />
|
||||
<button
|
||||
className="bg-foreground text-background text-sm px-4 py-2 rounded-md"
|
||||
onClick={() => {
|
||||
runAsynchronouslyWithAlert(async () => {
|
||||
await myAction({ testMetadata: data ?? "" })
|
||||
alert("User's client read-only metadata updated, refresh to see changes")
|
||||
})
|
||||
}}
|
||||
>
|
||||
My Action
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -4,6 +4,7 @@ import { UserButton, useUser } from "@stackframe/stack";
|
||||
import { useMutation, useQuery } from "convex/react";
|
||||
import Link from "next/link";
|
||||
import { api } from "../convex/_generated/api";
|
||||
import { getUserInfoConvexClient, getUserInfoConvexHttpClient } from "./user-info";
|
||||
|
||||
export default function Home() {
|
||||
const user = useUser();
|
||||
@ -41,11 +42,27 @@ function Content() {
|
||||
return (
|
||||
<div className="flex flex-col gap-8 max-w-lg mx-auto">
|
||||
<p>Welcome {viewer ?? "Anonymous"}!</p>
|
||||
<button className="bg-foreground text-background text-sm px-4 py-2 rounded-md" onClick={() => {
|
||||
alert(getUserInfo);
|
||||
}}>
|
||||
View user info
|
||||
</button>
|
||||
<div className="flex flex-col gap-2">
|
||||
<button className="bg-foreground text-background text-sm px-4 py-2 rounded-md" onClick={() => {
|
||||
alert(getUserInfo);
|
||||
}}>
|
||||
View user info
|
||||
</button>
|
||||
<button className="bg-foreground text-background text-sm px-4 py-2 rounded-md" onClick={() => {
|
||||
getUserInfoConvexHttpClient().then(userInfo => {
|
||||
alert(userInfo);
|
||||
});
|
||||
}}>
|
||||
View user info (http client)
|
||||
</button>
|
||||
<button className="bg-foreground text-background text-sm px-4 py-2 rounded-md" onClick={() => {
|
||||
getUserInfoConvexClient().then(userInfo => {
|
||||
alert(userInfo);
|
||||
});
|
||||
}}>
|
||||
View user info (js client)
|
||||
</button>
|
||||
</div>
|
||||
<p>
|
||||
Click the button below and open this page in another window - this data
|
||||
is persisted in the Convex cloud database!
|
||||
@ -87,6 +104,13 @@ function Content() {
|
||||
</Link>{" "}
|
||||
for an example of loading data in a server component
|
||||
</p>
|
||||
<p>
|
||||
See the{" "}
|
||||
<Link href="/action" className="underline hover:no-underline">
|
||||
/action route
|
||||
</Link>{" "}
|
||||
for an example of using a convex action to update user data in stack auth
|
||||
</p>
|
||||
<div className="flex flex-col">
|
||||
<p className="text-lg font-bold">Useful resources:</p>
|
||||
<div className="flex gap-2">
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import Home from "./inner";
|
||||
import { preloadQuery, preloadedQueryResult } from "convex/nextjs";
|
||||
import { api } from "@/convex/_generated/api";
|
||||
import { ConvexHttpClient } from "convex/browser";
|
||||
import { stackServerApp } from "@/stack/server";
|
||||
|
||||
export default async function ServerPage() {
|
||||
const preloaded = await preloadQuery(api.myFunctions.listNumbers, {
|
||||
@ -9,9 +11,20 @@ export default async function ServerPage() {
|
||||
|
||||
const data = preloadedQueryResult(preloaded);
|
||||
|
||||
const convex = new ConvexHttpClient(process.env.NEXT_PUBLIC_CONVEX_URL!);
|
||||
const token = await stackServerApp.getConvexHttpClientAuth({ tokenStore: "nextjs-cookie" });
|
||||
convex.setAuth(token);
|
||||
const userInfo = await convex.query(api.myFunctions.getUserInfo, {});
|
||||
|
||||
return (
|
||||
<main className="p-8 flex flex-col gap-4 mx-auto max-w-2xl">
|
||||
<h1 className="text-4xl font-bold text-center">Convex + Next.js</h1>
|
||||
<div className="flex flex-col gap-4 bg-slate-200 dark:bg-slate-800 p-4 rounded-md">
|
||||
<h2 className="text-xl font-bold">User info</h2>
|
||||
<code>
|
||||
<pre>{JSON.stringify(JSON.parse(userInfo), null, 2)}</pre>
|
||||
</code>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4 bg-slate-200 dark:bg-slate-800 p-4 rounded-md">
|
||||
<h2 className="text-xl font-bold">Non-reactive server-loaded data</h2>
|
||||
<code>
|
||||
|
||||
21
examples/convex/app/user-info.ts
Normal file
21
examples/convex/app/user-info.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { api } from "@/convex/_generated/api";
|
||||
import { stackClientApp } from "@/stack/client";
|
||||
import { ConvexHttpClient, ConvexClient } from "convex/browser";
|
||||
|
||||
const convexHttpClient = new ConvexHttpClient(process.env.NEXT_PUBLIC_CONVEX_URL!);
|
||||
|
||||
export async function getUserInfoConvexHttpClient() {
|
||||
const token = await stackClientApp.getConvexHttpClientAuth({ tokenStore: "nextjs-cookie" });
|
||||
convexHttpClient.setAuth(token);
|
||||
const userInfo = await convexHttpClient.query(api.myFunctions.getUserInfo, {});
|
||||
return userInfo;
|
||||
}
|
||||
|
||||
|
||||
const convexClient = new ConvexClient(process.env.NEXT_PUBLIC_CONVEX_URL!);
|
||||
convexClient.setAuth(stackClientApp.getConvexClientAuth({ tokenStore: "nextjs-cookie" }))
|
||||
|
||||
export async function getUserInfoConvexClient() {
|
||||
const userInfo = await convexClient.query(api.myFunctions.getUserInfo, {});
|
||||
return userInfo;
|
||||
}
|
||||
2
examples/convex/convex/_generated/api.d.ts
vendored
2
examples/convex/convex/_generated/api.d.ts
vendored
@ -8,6 +8,7 @@
|
||||
* @module
|
||||
*/
|
||||
|
||||
import type * as myActions from "../myActions.js";
|
||||
import type * as myFunctions from "../myFunctions.js";
|
||||
|
||||
import type {
|
||||
@ -25,6 +26,7 @@ import type {
|
||||
* ```
|
||||
*/
|
||||
declare const fullApi: ApiFromModules<{
|
||||
myActions: typeof myActions;
|
||||
myFunctions: typeof myFunctions;
|
||||
}>;
|
||||
declare const fullApiWithMounts: typeof fullApi;
|
||||
|
||||
27
examples/convex/convex/myActions.ts
Normal file
27
examples/convex/convex/myActions.ts
Normal file
@ -0,0 +1,27 @@
|
||||
"use node"
|
||||
|
||||
import { v } from "convex/values";
|
||||
import { stackServerApp } from "../stack/server";
|
||||
import { action } from "./_generated/server";
|
||||
|
||||
|
||||
export const myAction = action({
|
||||
args: {
|
||||
testMetadata: v.string(),
|
||||
},
|
||||
|
||||
handler: async (ctx, args) => {
|
||||
const partialUser = await stackServerApp.getPartialUser({ from: "convex", ctx });
|
||||
if (!partialUser) {
|
||||
return null;
|
||||
}
|
||||
const user = await stackServerApp.getUser(partialUser?.id);
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
await user.setClientReadOnlyMetadata({
|
||||
test: args.testMetadata,
|
||||
})
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
|
||||
import { v } from "convex/values";
|
||||
import { stackClientApp } from "../stack/client";
|
||||
import { stackServerApp } from "../stack/server";
|
||||
import { api } from "./_generated/api";
|
||||
import { action, mutation, query } from "./_generated/server";
|
||||
|
||||
|
||||
@ -65,30 +65,3 @@ export const addNumber = mutation({
|
||||
},
|
||||
});
|
||||
|
||||
// You can fetch data from and send data to third-party APIs via an action:
|
||||
export const myAction = action({
|
||||
// Validators for arguments.
|
||||
args: {
|
||||
first: v.number(),
|
||||
second: v.string(),
|
||||
},
|
||||
|
||||
// Action implementation.
|
||||
handler: async (ctx, args) => {
|
||||
//// Use the browser-like `fetch` API to send HTTP requests.
|
||||
//// See https://docs.convex.dev/functions/actions#calling-third-party-apis-and-using-npm-packages.
|
||||
// const response = await ctx.fetch("https://api.thirdpartyservice.com");
|
||||
// const data = await response.json();
|
||||
|
||||
//// Query data by running Convex queries.
|
||||
const data = await ctx.runQuery(api.myFunctions.listNumbers, {
|
||||
count: 10,
|
||||
});
|
||||
console.log(data);
|
||||
|
||||
//// Write data by running Convex mutations.
|
||||
await ctx.runMutation(api.myFunctions.addNumber, {
|
||||
value: args.first,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@stackframe/stack": "workspace:*",
|
||||
"@stackframe/stack-shared": "workspace:*",
|
||||
"convex": "^1.27.0",
|
||||
"next": "15.2.3",
|
||||
"react": "^19.0.0",
|
||||
@ -31,11 +32,12 @@
|
||||
"@types/react-dom": "^19",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.2.5",
|
||||
"rimraf": "^5.0.5",
|
||||
"typescript": "5.3.3",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"postcss": "^8",
|
||||
"prettier": "^3.5.3",
|
||||
"rimraf": "^5.0.5",
|
||||
"tailwindcss": "^4",
|
||||
"postcss": "^8"
|
||||
"typescript": "5.3.3",
|
||||
"wait-on": "^8.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1763,7 +1763,7 @@ export class _StackClientAppImplIncomplete<HasTokenStore extends boolean, Projec
|
||||
async getConvexHttpClientAuth(options: { tokenStore: TokenStoreInit }): Promise<string> {
|
||||
const session = await this._getSession(options.tokenStore);
|
||||
const tokens = await session.getOrFetchLikelyValidTokens(20_000);
|
||||
return tokens?.accessToken.token ?? throwErr("No access token available");
|
||||
return tokens?.accessToken.token ?? "";
|
||||
}
|
||||
|
||||
protected async _updateClientUser(update: UserUpdateOptions, session: InternalSession) {
|
||||
|
||||
@ -759,6 +759,9 @@ importers:
|
||||
'@stackframe/stack':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/stack
|
||||
'@stackframe/stack-shared':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/stack-shared
|
||||
convex:
|
||||
specifier: ^1.27.0
|
||||
version: 1.27.0(react@19.0.0)
|
||||
@ -811,6 +814,9 @@ importers:
|
||||
typescript:
|
||||
specifier: 5.3.3
|
||||
version: 5.3.3
|
||||
wait-on:
|
||||
specifier: ^8.0.1
|
||||
version: 8.0.1
|
||||
|
||||
examples/demo:
|
||||
dependencies:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user