init commit
This commit is contained in:
47
.gitignore
vendored
Normal file
47
.gitignore
vendored
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# Hosting
|
||||||
|
/host/convex/docker/data
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.*
|
||||||
|
.yarn/*
|
||||||
|
!.yarn/patches
|
||||||
|
!.yarn/plugins
|
||||||
|
!.yarn/releases
|
||||||
|
!.yarn/versions
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# env files (can opt-in for committing if needed)
|
||||||
|
.env*
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
||||||
|
|
||||||
|
# Ignored for the template, you probably want to remove it:
|
||||||
|
package-lock.json
|
1
.prettierrc
Normal file
1
.prettierrc
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{}
|
46
README.md
Normal file
46
README.md
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# Welcome to your Convex + Next.js + Convex Auth app
|
||||||
|
|
||||||
|
This is a [Convex](https://convex.dev/) project created with [`npm create convex`](https://www.npmjs.com/package/create-convex).
|
||||||
|
|
||||||
|
After the initial setup (<2 minutes) you'll have a working full-stack app using:
|
||||||
|
|
||||||
|
- Convex as your backend (database, server logic)
|
||||||
|
- [React](https://react.dev/) as your frontend (web page interactivity)
|
||||||
|
- [Next.js](https://nextjs.org/) for optimized web hosting and page routing
|
||||||
|
- [Tailwind](https://tailwindcss.com/) for building great looking accessible UI
|
||||||
|
- [Convex Auth](https://labs.convex.dev/auth) for authentication
|
||||||
|
|
||||||
|
## Get started
|
||||||
|
|
||||||
|
If you just cloned this codebase and didn't use `npm create convex`, run:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're reading this README on GitHub and want to use this template, run:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm create convex@latest -- -t nextjs-convexauth
|
||||||
|
```
|
||||||
|
|
||||||
|
## Learn more
|
||||||
|
|
||||||
|
To learn more about developing your project with Convex, check out:
|
||||||
|
|
||||||
|
- The [Tour of Convex](https://docs.convex.dev/get-started) for a thorough introduction to Convex principles.
|
||||||
|
- The rest of [Convex docs](https://docs.convex.dev/) to learn about all Convex features.
|
||||||
|
- [Stack](https://stack.convex.dev/) for in-depth articles on advanced topics.
|
||||||
|
- [Convex Auth docs](https://labs.convex.dev/auth) for documentation on the Convex Auth library.
|
||||||
|
|
||||||
|
## Configuring other authentication methods
|
||||||
|
|
||||||
|
To configure different authentication methods, see [Configuration](https://labs.convex.dev/auth/config) in the Convex Auth docs.
|
||||||
|
|
||||||
|
## Join the community
|
||||||
|
|
||||||
|
Join thousands of developers building full-stack apps with Convex:
|
||||||
|
|
||||||
|
- Join the [Convex Discord community](https://convex.dev/community) to get help in real-time.
|
||||||
|
- Follow [Convex on GitHub](https://github.com/get-convex/), star and contribute to the open-source implementation of Convex.
|
26
app/globals.css
Normal file
26
app/globals.css
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--background: #ffffff;
|
||||||
|
--foreground: #171717;
|
||||||
|
}
|
||||||
|
|
||||||
|
@theme inline {
|
||||||
|
--color-background: var(--background);
|
||||||
|
--color-foreground: var(--foreground);
|
||||||
|
--font-sans: var(--font-geist-sans);
|
||||||
|
--font-mono: var(--font-geist-mono);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--background: #0a0a0a;
|
||||||
|
--foreground: #ededed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
color: var(--foreground);
|
||||||
|
background: var(--background);
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
}
|
40
app/layout.tsx
Normal file
40
app/layout.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
import { Geist, Geist_Mono } from "next/font/google";
|
||||||
|
import "./globals.css";
|
||||||
|
import { ConvexAuthNextjsServerProvider } from "@convex-dev/auth/nextjs/server";
|
||||||
|
import ConvexClientProvider from "@/components/ConvexClientProvider";
|
||||||
|
const geistSans = Geist({
|
||||||
|
variable: "--font-geist-sans",
|
||||||
|
subsets: ["latin"],
|
||||||
|
});
|
||||||
|
|
||||||
|
const geistMono = Geist_Mono({
|
||||||
|
variable: "--font-geist-mono",
|
||||||
|
subsets: ["latin"],
|
||||||
|
});
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Create Next App",
|
||||||
|
description: "Generated by create next app",
|
||||||
|
icons: {
|
||||||
|
icon: "/convex.svg",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: Readonly<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
}>) {
|
||||||
|
return (
|
||||||
|
<ConvexAuthNextjsServerProvider>
|
||||||
|
<html lang="en">
|
||||||
|
<body
|
||||||
|
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||||
|
>
|
||||||
|
<ConvexClientProvider>{children}</ConvexClientProvider>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
</ConvexAuthNextjsServerProvider>
|
||||||
|
);
|
||||||
|
}
|
159
app/page.tsx
Normal file
159
app/page.tsx
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useConvexAuth, useMutation, useQuery } from "convex/react";
|
||||||
|
import { api } from "../convex/_generated/api";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useAuthActions } from "@convex-dev/auth/react";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<header className="sticky top-0 z-10 bg-background p-4 border-b-2 border-slate-200 dark:border-slate-800 flex flex-row justify-between items-center">
|
||||||
|
Convex + Next.js + Convex Auth
|
||||||
|
<SignOutButton />
|
||||||
|
</header>
|
||||||
|
<main className="p-8 flex flex-col gap-8">
|
||||||
|
<h1 className="text-4xl font-bold text-center">
|
||||||
|
Convex + Next.js + Convex Auth
|
||||||
|
</h1>
|
||||||
|
<Content />
|
||||||
|
</main>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SignOutButton() {
|
||||||
|
const { isAuthenticated } = useConvexAuth();
|
||||||
|
const { signOut } = useAuthActions();
|
||||||
|
const router = useRouter();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{isAuthenticated && (
|
||||||
|
<button
|
||||||
|
className="bg-slate-200 dark:bg-slate-800 text-foreground rounded-md px-2 py-1"
|
||||||
|
onClick={() =>
|
||||||
|
void signOut().then(() => {
|
||||||
|
router.push("/signin");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Sign out
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Content() {
|
||||||
|
const { viewer, numbers } =
|
||||||
|
useQuery(api.myFunctions.listNumbers, {
|
||||||
|
count: 10,
|
||||||
|
}) ?? {};
|
||||||
|
const addNumber = useMutation(api.myFunctions.addNumber);
|
||||||
|
|
||||||
|
if (viewer === undefined || numbers === undefined) {
|
||||||
|
return (
|
||||||
|
<div className="mx-auto">
|
||||||
|
<p>loading... (consider a loading skeleton)</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-8 max-w-lg mx-auto">
|
||||||
|
<p>Welcome {viewer ?? "Anonymous"}!</p>
|
||||||
|
<p>
|
||||||
|
Click the button below and open this page in another window - this data
|
||||||
|
is persisted in the Convex cloud database!
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<button
|
||||||
|
className="bg-foreground text-background text-sm px-4 py-2 rounded-md"
|
||||||
|
onClick={() => {
|
||||||
|
void addNumber({ value: Math.floor(Math.random() * 10) });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Add a random number
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Numbers:{" "}
|
||||||
|
{numbers?.length === 0
|
||||||
|
? "Click the button!"
|
||||||
|
: (numbers?.join(", ") ?? "...")}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Edit{" "}
|
||||||
|
<code className="text-sm font-bold font-mono bg-slate-200 dark:bg-slate-800 px-1 py-0.5 rounded-md">
|
||||||
|
convex/myFunctions.ts
|
||||||
|
</code>{" "}
|
||||||
|
to change your backend
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Edit{" "}
|
||||||
|
<code className="text-sm font-bold font-mono bg-slate-200 dark:bg-slate-800 px-1 py-0.5 rounded-md">
|
||||||
|
app/page.tsx
|
||||||
|
</code>{" "}
|
||||||
|
to change your frontend
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
See the{" "}
|
||||||
|
<Link href="/server" className="underline hover:no-underline">
|
||||||
|
/server route
|
||||||
|
</Link>{" "}
|
||||||
|
for an example of loading data in a server component
|
||||||
|
</p>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<p className="text-lg font-bold">Useful resources:</p>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<div className="flex flex-col gap-2 w-1/2">
|
||||||
|
<ResourceCard
|
||||||
|
title="Convex docs"
|
||||||
|
description="Read comprehensive documentation for all Convex features."
|
||||||
|
href="https://docs.convex.dev/home"
|
||||||
|
/>
|
||||||
|
<ResourceCard
|
||||||
|
title="Stack articles"
|
||||||
|
description="Learn about best practices, use cases, and more from a growing
|
||||||
|
collection of articles, videos, and walkthroughs."
|
||||||
|
href="https://www.typescriptlang.org/docs/handbook/2/basic-types.html"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2 w-1/2">
|
||||||
|
<ResourceCard
|
||||||
|
title="Templates"
|
||||||
|
description="Browse our collection of templates to get started quickly."
|
||||||
|
href="https://www.convex.dev/templates"
|
||||||
|
/>
|
||||||
|
<ResourceCard
|
||||||
|
title="Discord"
|
||||||
|
description="Join our developer community to ask questions, trade tips & tricks,
|
||||||
|
and show off your projects."
|
||||||
|
href="https://www.convex.dev/community"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ResourceCard({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
href,
|
||||||
|
}: {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
href: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-2 bg-slate-200 dark:bg-slate-800 p-4 rounded-md h-28 overflow-auto">
|
||||||
|
<a href={href} className="text-sm underline hover:no-underline">
|
||||||
|
{title}
|
||||||
|
</a>
|
||||||
|
<p className="text-xs">{description}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
31
app/server/inner.tsx
Normal file
31
app/server/inner.tsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Preloaded, useMutation, usePreloadedQuery } from "convex/react";
|
||||||
|
import { api } from "../../convex/_generated/api";
|
||||||
|
|
||||||
|
export default function Home({
|
||||||
|
preloaded,
|
||||||
|
}: {
|
||||||
|
preloaded: Preloaded<typeof api.myFunctions.listNumbers>;
|
||||||
|
}) {
|
||||||
|
const data = usePreloadedQuery(preloaded);
|
||||||
|
const addNumber = useMutation(api.myFunctions.addNumber);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex flex-col gap-4 bg-slate-200 dark:bg-slate-800 p-4 rounded-md">
|
||||||
|
<h2 className="text-xl font-bold">Reactive client-loaded data</h2>
|
||||||
|
<code>
|
||||||
|
<pre>{JSON.stringify(data, null, 2)}</pre>
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
className="bg-foreground text-background px-4 py-2 rounded-md mx-auto"
|
||||||
|
onClick={() => {
|
||||||
|
void addNumber({ value: Math.floor(Math.random() * 10) });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Add a random number
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
24
app/server/page.tsx
Normal file
24
app/server/page.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import Home from "./inner";
|
||||||
|
import { preloadQuery, preloadedQueryResult } from "convex/nextjs";
|
||||||
|
import { api } from "@/convex/_generated/api";
|
||||||
|
|
||||||
|
export default async function ServerPage() {
|
||||||
|
const preloaded = await preloadQuery(api.myFunctions.listNumbers, {
|
||||||
|
count: 3,
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = preloadedQueryResult(preloaded);
|
||||||
|
|
||||||
|
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">Non-reactive server-loaded data</h2>
|
||||||
|
<code>
|
||||||
|
<pre>{JSON.stringify(data, null, 2)}</pre>
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
|
<Home preloaded={preloaded} />
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
71
app/signin/page.tsx
Normal file
71
app/signin/page.tsx
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useAuthActions } from "@convex-dev/auth/react";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
export default function SignIn() {
|
||||||
|
const { signIn } = useAuthActions();
|
||||||
|
const [flow, setFlow] = useState<"signIn" | "signUp">("signIn");
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const router = useRouter();
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-8 w-96 mx-auto h-screen justify-center items-center">
|
||||||
|
<p>Log in to see the numbers</p>
|
||||||
|
<form
|
||||||
|
className="flex flex-col gap-2"
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const formData = new FormData(e.target as HTMLFormElement);
|
||||||
|
formData.set("flow", flow);
|
||||||
|
void signIn("password", formData)
|
||||||
|
.catch((error) => {
|
||||||
|
setError(error.message);
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
router.push("/");
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
className="bg-background text-foreground rounded-md p-2 border-2 border-slate-200 dark:border-slate-800"
|
||||||
|
type="email"
|
||||||
|
name="email"
|
||||||
|
placeholder="Email"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
className="bg-background text-foreground rounded-md p-2 border-2 border-slate-200 dark:border-slate-800"
|
||||||
|
type="password"
|
||||||
|
name="password"
|
||||||
|
placeholder="Password"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className="bg-foreground text-background rounded-md"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
{flow === "signIn" ? "Sign in" : "Sign up"}
|
||||||
|
</button>
|
||||||
|
<div className="flex flex-row gap-2">
|
||||||
|
<span>
|
||||||
|
{flow === "signIn"
|
||||||
|
? "Don't have an account?"
|
||||||
|
: "Already have an account?"}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className="text-foreground underline hover:no-underline cursor-pointer"
|
||||||
|
onClick={() => setFlow(flow === "signIn" ? "signUp" : "signIn")}
|
||||||
|
>
|
||||||
|
{flow === "signIn" ? "Sign up instead" : "Sign in instead"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{error && (
|
||||||
|
<div className="bg-red-500/20 border-2 border-red-500/50 rounded-md p-2">
|
||||||
|
<p className="text-foreground font-mono text-xs">
|
||||||
|
Error signing in: {error}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
19
components/ConvexClientProvider.tsx
Normal file
19
components/ConvexClientProvider.tsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { ConvexAuthNextjsProvider } from "@convex-dev/auth/nextjs";
|
||||||
|
import { ConvexReactClient } from "convex/react";
|
||||||
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
|
const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);
|
||||||
|
|
||||||
|
export default function ConvexClientProvider({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: ReactNode;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<ConvexAuthNextjsProvider client={convex}>
|
||||||
|
{children}
|
||||||
|
</ConvexAuthNextjsProvider>
|
||||||
|
);
|
||||||
|
}
|
90
convex/README.md
Normal file
90
convex/README.md
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
# Welcome to your Convex functions directory!
|
||||||
|
|
||||||
|
Write your Convex functions here.
|
||||||
|
See https://docs.convex.dev/functions for more.
|
||||||
|
|
||||||
|
A query function that takes two arguments looks like:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// functions.js
|
||||||
|
import { query } from "./_generated/server";
|
||||||
|
import { v } from "convex/values";
|
||||||
|
|
||||||
|
export const myQueryFunction = query({
|
||||||
|
// Validators for arguments.
|
||||||
|
args: {
|
||||||
|
first: v.number(),
|
||||||
|
second: v.string(),
|
||||||
|
},
|
||||||
|
|
||||||
|
// Function implementation.
|
||||||
|
handler: async (ctx, args) => {
|
||||||
|
// Read the database as many times as you need here.
|
||||||
|
// See https://docs.convex.dev/database/reading-data.
|
||||||
|
const documents = await ctx.db.query("tablename").collect();
|
||||||
|
|
||||||
|
// Arguments passed from the client are properties of the args object.
|
||||||
|
console.log(args.first, args.second);
|
||||||
|
|
||||||
|
// Write arbitrary JavaScript here: filter, aggregate, build derived data,
|
||||||
|
// remove non-public properties, or create new objects.
|
||||||
|
return documents;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Using this query function in a React component looks like:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const data = useQuery(api.functions.myQueryFunction, {
|
||||||
|
first: 10,
|
||||||
|
second: "hello",
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
A mutation function looks like:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// functions.js
|
||||||
|
import { mutation } from "./_generated/server";
|
||||||
|
import { v } from "convex/values";
|
||||||
|
|
||||||
|
export const myMutationFunction = mutation({
|
||||||
|
// Validators for arguments.
|
||||||
|
args: {
|
||||||
|
first: v.string(),
|
||||||
|
second: v.string(),
|
||||||
|
},
|
||||||
|
|
||||||
|
// Function implementation.
|
||||||
|
handler: async (ctx, args) => {
|
||||||
|
// Insert or modify documents in the database here.
|
||||||
|
// Mutations can also read from the database like queries.
|
||||||
|
// See https://docs.convex.dev/database/writing-data.
|
||||||
|
const message = { body: args.first, author: args.second };
|
||||||
|
const id = await ctx.db.insert("messages", message);
|
||||||
|
|
||||||
|
// Optionally, return a value from your mutation.
|
||||||
|
return await ctx.db.get(id);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Using this mutation function in a React component looks like:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const mutation = useMutation(api.functions.myMutationFunction);
|
||||||
|
function handleButtonPress() {
|
||||||
|
// fire and forget, the most common way to use mutations
|
||||||
|
mutation({ first: "Hello!", second: "me" });
|
||||||
|
// OR
|
||||||
|
// use the result once the mutation has completed
|
||||||
|
mutation({ first: "Hello!", second: "me" }).then((result) =>
|
||||||
|
console.log(result),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Use the Convex CLI to push your functions to a deployment. See everything
|
||||||
|
the Convex CLI can do by running `npx convex -h` in your project root
|
||||||
|
directory. To learn more, launch the docs with `npx convex docs`.
|
40
convex/_generated/api.d.ts
vendored
Normal file
40
convex/_generated/api.d.ts
vendored
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
/**
|
||||||
|
* Generated `api` utility.
|
||||||
|
*
|
||||||
|
* THIS CODE IS AUTOMATICALLY GENERATED.
|
||||||
|
*
|
||||||
|
* To regenerate, run `npx convex dev`.
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type {
|
||||||
|
ApiFromModules,
|
||||||
|
FilterApi,
|
||||||
|
FunctionReference,
|
||||||
|
} from "convex/server";
|
||||||
|
import type * as auth from "../auth.js";
|
||||||
|
import type * as http from "../http.js";
|
||||||
|
import type * as myFunctions from "../myFunctions.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A utility for referencing Convex functions in your app's API.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* ```js
|
||||||
|
* const myFunctionReference = api.myModule.myFunction;
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
declare const fullApi: ApiFromModules<{
|
||||||
|
auth: typeof auth;
|
||||||
|
http: typeof http;
|
||||||
|
myFunctions: typeof myFunctions;
|
||||||
|
}>;
|
||||||
|
export declare const api: FilterApi<
|
||||||
|
typeof fullApi,
|
||||||
|
FunctionReference<any, "public">
|
||||||
|
>;
|
||||||
|
export declare const internal: FilterApi<
|
||||||
|
typeof fullApi,
|
||||||
|
FunctionReference<any, "internal">
|
||||||
|
>;
|
22
convex/_generated/api.js
Normal file
22
convex/_generated/api.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
/**
|
||||||
|
* Generated `api` utility.
|
||||||
|
*
|
||||||
|
* THIS CODE IS AUTOMATICALLY GENERATED.
|
||||||
|
*
|
||||||
|
* To regenerate, run `npx convex dev`.
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { anyApi } from "convex/server";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A utility for referencing Convex functions in your app's API.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* ```js
|
||||||
|
* const myFunctionReference = api.myModule.myFunction;
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export const api = anyApi;
|
||||||
|
export const internal = anyApi;
|
60
convex/_generated/dataModel.d.ts
vendored
Normal file
60
convex/_generated/dataModel.d.ts
vendored
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
/**
|
||||||
|
* Generated data model types.
|
||||||
|
*
|
||||||
|
* THIS CODE IS AUTOMATICALLY GENERATED.
|
||||||
|
*
|
||||||
|
* To regenerate, run `npx convex dev`.
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type {
|
||||||
|
DataModelFromSchemaDefinition,
|
||||||
|
DocumentByName,
|
||||||
|
TableNamesInDataModel,
|
||||||
|
SystemTableNames,
|
||||||
|
} from "convex/server";
|
||||||
|
import type { GenericId } from "convex/values";
|
||||||
|
import schema from "../schema.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The names of all of your Convex tables.
|
||||||
|
*/
|
||||||
|
export type TableNames = TableNamesInDataModel<DataModel>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of a document stored in Convex.
|
||||||
|
*
|
||||||
|
* @typeParam TableName - A string literal type of the table name (like "users").
|
||||||
|
*/
|
||||||
|
export type Doc<TableName extends TableNames> = DocumentByName<
|
||||||
|
DataModel,
|
||||||
|
TableName
|
||||||
|
>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An identifier for a document in Convex.
|
||||||
|
*
|
||||||
|
* Convex documents are uniquely identified by their `Id`, which is accessible
|
||||||
|
* on the `_id` field. To learn more, see [Document IDs](https://docs.convex.dev/using/document-ids).
|
||||||
|
*
|
||||||
|
* Documents can be loaded using `db.get(id)` in query and mutation functions.
|
||||||
|
*
|
||||||
|
* IDs are just strings at runtime, but this type can be used to distinguish them from other
|
||||||
|
* strings when type checking.
|
||||||
|
*
|
||||||
|
* @typeParam TableName - A string literal type of the table name (like "users").
|
||||||
|
*/
|
||||||
|
export type Id<TableName extends TableNames | SystemTableNames> =
|
||||||
|
GenericId<TableName>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A type describing your Convex data model.
|
||||||
|
*
|
||||||
|
* This type includes information about what tables you have, the type of
|
||||||
|
* documents stored in those tables, and the indexes defined on them.
|
||||||
|
*
|
||||||
|
* This type is used to parameterize methods like `queryGeneric` and
|
||||||
|
* `mutationGeneric` to make them type-safe.
|
||||||
|
*/
|
||||||
|
export type DataModel = DataModelFromSchemaDefinition<typeof schema>;
|
142
convex/_generated/server.d.ts
vendored
Normal file
142
convex/_generated/server.d.ts
vendored
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
/**
|
||||||
|
* Generated utilities for implementing server-side Convex query and mutation functions.
|
||||||
|
*
|
||||||
|
* THIS CODE IS AUTOMATICALLY GENERATED.
|
||||||
|
*
|
||||||
|
* To regenerate, run `npx convex dev`.
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
ActionBuilder,
|
||||||
|
HttpActionBuilder,
|
||||||
|
MutationBuilder,
|
||||||
|
QueryBuilder,
|
||||||
|
GenericActionCtx,
|
||||||
|
GenericMutationCtx,
|
||||||
|
GenericQueryCtx,
|
||||||
|
GenericDatabaseReader,
|
||||||
|
GenericDatabaseWriter,
|
||||||
|
} from "convex/server";
|
||||||
|
import type { DataModel } from "./dataModel.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a query in this Convex app's public API.
|
||||||
|
*
|
||||||
|
* This function will be allowed to read your Convex database and will be accessible from the client.
|
||||||
|
*
|
||||||
|
* @param func - The query function. It receives a {@link QueryCtx} as its first argument.
|
||||||
|
* @returns The wrapped query. Include this as an `export` to name it and make it accessible.
|
||||||
|
*/
|
||||||
|
export declare const query: QueryBuilder<DataModel, "public">;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a query that is only accessible from other Convex functions (but not from the client).
|
||||||
|
*
|
||||||
|
* This function will be allowed to read from your Convex database. It will not be accessible from the client.
|
||||||
|
*
|
||||||
|
* @param func - The query function. It receives a {@link QueryCtx} as its first argument.
|
||||||
|
* @returns The wrapped query. Include this as an `export` to name it and make it accessible.
|
||||||
|
*/
|
||||||
|
export declare const internalQuery: QueryBuilder<DataModel, "internal">;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a mutation in this Convex app's public API.
|
||||||
|
*
|
||||||
|
* This function will be allowed to modify your Convex database and will be accessible from the client.
|
||||||
|
*
|
||||||
|
* @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
|
||||||
|
* @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
|
||||||
|
*/
|
||||||
|
export declare const mutation: MutationBuilder<DataModel, "public">;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a mutation that is only accessible from other Convex functions (but not from the client).
|
||||||
|
*
|
||||||
|
* This function will be allowed to modify your Convex database. It will not be accessible from the client.
|
||||||
|
*
|
||||||
|
* @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
|
||||||
|
* @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
|
||||||
|
*/
|
||||||
|
export declare const internalMutation: MutationBuilder<DataModel, "internal">;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define an action in this Convex app's public API.
|
||||||
|
*
|
||||||
|
* An action is a function which can execute any JavaScript code, including non-deterministic
|
||||||
|
* code and code with side-effects, like calling third-party services.
|
||||||
|
* They can be run in Convex's JavaScript environment or in Node.js using the "use node" directive.
|
||||||
|
* They can interact with the database indirectly by calling queries and mutations using the {@link ActionCtx}.
|
||||||
|
*
|
||||||
|
* @param func - The action. It receives an {@link ActionCtx} as its first argument.
|
||||||
|
* @returns The wrapped action. Include this as an `export` to name it and make it accessible.
|
||||||
|
*/
|
||||||
|
export declare const action: ActionBuilder<DataModel, "public">;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define an action that is only accessible from other Convex functions (but not from the client).
|
||||||
|
*
|
||||||
|
* @param func - The function. It receives an {@link ActionCtx} as its first argument.
|
||||||
|
* @returns The wrapped function. Include this as an `export` to name it and make it accessible.
|
||||||
|
*/
|
||||||
|
export declare const internalAction: ActionBuilder<DataModel, "internal">;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define an HTTP action.
|
||||||
|
*
|
||||||
|
* This function will be used to respond to HTTP requests received by a Convex
|
||||||
|
* deployment if the requests matches the path and method where this action
|
||||||
|
* is routed. Be sure to route your action in `convex/http.js`.
|
||||||
|
*
|
||||||
|
* @param func - The function. It receives an {@link ActionCtx} as its first argument.
|
||||||
|
* @returns The wrapped function. Import this function from `convex/http.js` and route it to hook it up.
|
||||||
|
*/
|
||||||
|
export declare const httpAction: HttpActionBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A set of services for use within Convex query functions.
|
||||||
|
*
|
||||||
|
* The query context is passed as the first argument to any Convex query
|
||||||
|
* function run on the server.
|
||||||
|
*
|
||||||
|
* This differs from the {@link MutationCtx} because all of the services are
|
||||||
|
* read-only.
|
||||||
|
*/
|
||||||
|
export type QueryCtx = GenericQueryCtx<DataModel>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A set of services for use within Convex mutation functions.
|
||||||
|
*
|
||||||
|
* The mutation context is passed as the first argument to any Convex mutation
|
||||||
|
* function run on the server.
|
||||||
|
*/
|
||||||
|
export type MutationCtx = GenericMutationCtx<DataModel>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A set of services for use within Convex action functions.
|
||||||
|
*
|
||||||
|
* The action context is passed as the first argument to any Convex action
|
||||||
|
* function run on the server.
|
||||||
|
*/
|
||||||
|
export type ActionCtx = GenericActionCtx<DataModel>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface to read from the database within Convex query functions.
|
||||||
|
*
|
||||||
|
* The two entry points are {@link DatabaseReader.get}, which fetches a single
|
||||||
|
* document by its {@link Id}, or {@link DatabaseReader.query}, which starts
|
||||||
|
* building a query.
|
||||||
|
*/
|
||||||
|
export type DatabaseReader = GenericDatabaseReader<DataModel>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface to read from and write to the database within Convex mutation
|
||||||
|
* functions.
|
||||||
|
*
|
||||||
|
* Convex guarantees that all writes within a single mutation are
|
||||||
|
* executed atomically, so you never have to worry about partial writes leaving
|
||||||
|
* your data in an inconsistent state. See [the Convex Guide](https://docs.convex.dev/understanding/convex-fundamentals/functions#atomicity-and-optimistic-concurrency-control)
|
||||||
|
* for the guarantees Convex provides your functions.
|
||||||
|
*/
|
||||||
|
export type DatabaseWriter = GenericDatabaseWriter<DataModel>;
|
89
convex/_generated/server.js
Normal file
89
convex/_generated/server.js
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
/**
|
||||||
|
* Generated utilities for implementing server-side Convex query and mutation functions.
|
||||||
|
*
|
||||||
|
* THIS CODE IS AUTOMATICALLY GENERATED.
|
||||||
|
*
|
||||||
|
* To regenerate, run `npx convex dev`.
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
actionGeneric,
|
||||||
|
httpActionGeneric,
|
||||||
|
queryGeneric,
|
||||||
|
mutationGeneric,
|
||||||
|
internalActionGeneric,
|
||||||
|
internalMutationGeneric,
|
||||||
|
internalQueryGeneric,
|
||||||
|
} from "convex/server";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a query in this Convex app's public API.
|
||||||
|
*
|
||||||
|
* This function will be allowed to read your Convex database and will be accessible from the client.
|
||||||
|
*
|
||||||
|
* @param func - The query function. It receives a {@link QueryCtx} as its first argument.
|
||||||
|
* @returns The wrapped query. Include this as an `export` to name it and make it accessible.
|
||||||
|
*/
|
||||||
|
export const query = queryGeneric;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a query that is only accessible from other Convex functions (but not from the client).
|
||||||
|
*
|
||||||
|
* This function will be allowed to read from your Convex database. It will not be accessible from the client.
|
||||||
|
*
|
||||||
|
* @param func - The query function. It receives a {@link QueryCtx} as its first argument.
|
||||||
|
* @returns The wrapped query. Include this as an `export` to name it and make it accessible.
|
||||||
|
*/
|
||||||
|
export const internalQuery = internalQueryGeneric;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a mutation in this Convex app's public API.
|
||||||
|
*
|
||||||
|
* This function will be allowed to modify your Convex database and will be accessible from the client.
|
||||||
|
*
|
||||||
|
* @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
|
||||||
|
* @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
|
||||||
|
*/
|
||||||
|
export const mutation = mutationGeneric;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a mutation that is only accessible from other Convex functions (but not from the client).
|
||||||
|
*
|
||||||
|
* This function will be allowed to modify your Convex database. It will not be accessible from the client.
|
||||||
|
*
|
||||||
|
* @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
|
||||||
|
* @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
|
||||||
|
*/
|
||||||
|
export const internalMutation = internalMutationGeneric;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define an action in this Convex app's public API.
|
||||||
|
*
|
||||||
|
* An action is a function which can execute any JavaScript code, including non-deterministic
|
||||||
|
* code and code with side-effects, like calling third-party services.
|
||||||
|
* They can be run in Convex's JavaScript environment or in Node.js using the "use node" directive.
|
||||||
|
* They can interact with the database indirectly by calling queries and mutations using the {@link ActionCtx}.
|
||||||
|
*
|
||||||
|
* @param func - The action. It receives an {@link ActionCtx} as its first argument.
|
||||||
|
* @returns The wrapped action. Include this as an `export` to name it and make it accessible.
|
||||||
|
*/
|
||||||
|
export const action = actionGeneric;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define an action that is only accessible from other Convex functions (but not from the client).
|
||||||
|
*
|
||||||
|
* @param func - The function. It receives an {@link ActionCtx} as its first argument.
|
||||||
|
* @returns The wrapped function. Include this as an `export` to name it and make it accessible.
|
||||||
|
*/
|
||||||
|
export const internalAction = internalActionGeneric;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a Convex HTTP action.
|
||||||
|
*
|
||||||
|
* @param func - The function. It receives an {@link ActionCtx} as its first argument, and a `Request` object
|
||||||
|
* as its second.
|
||||||
|
* @returns The wrapped endpoint function. Route a URL path to this function in `convex/http.js`.
|
||||||
|
*/
|
||||||
|
export const httpAction = httpActionGeneric;
|
8
convex/auth.config.ts
Normal file
8
convex/auth.config.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default {
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
domain: process.env.CONVEX_SITE_URL,
|
||||||
|
applicationID: "convex",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
6
convex/auth.ts
Normal file
6
convex/auth.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { Password } from "@convex-dev/auth/providers/Password";
|
||||||
|
import { convexAuth } from "@convex-dev/auth/server";
|
||||||
|
|
||||||
|
export const { auth, signIn, signOut, store, isAuthenticated } = convexAuth({
|
||||||
|
providers: [Password],
|
||||||
|
});
|
8
convex/http.ts
Normal file
8
convex/http.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { httpRouter } from "convex/server";
|
||||||
|
import { auth } from "./auth";
|
||||||
|
|
||||||
|
const http = httpRouter();
|
||||||
|
|
||||||
|
auth.addHttpRoutes(http);
|
||||||
|
|
||||||
|
export default http;
|
81
convex/myFunctions.ts
Normal file
81
convex/myFunctions.ts
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import { v } from "convex/values";
|
||||||
|
import { query, mutation, action } from "./_generated/server";
|
||||||
|
import { api } from "./_generated/api";
|
||||||
|
import { getAuthUserId } from "@convex-dev/auth/server";
|
||||||
|
|
||||||
|
// Write your Convex functions in any file inside this directory (`convex`).
|
||||||
|
// See https://docs.convex.dev/functions for more.
|
||||||
|
|
||||||
|
// You can read data from the database via a query:
|
||||||
|
export const listNumbers = query({
|
||||||
|
// Validators for arguments.
|
||||||
|
args: {
|
||||||
|
count: v.number(),
|
||||||
|
},
|
||||||
|
|
||||||
|
// Query implementation.
|
||||||
|
handler: async (ctx, args) => {
|
||||||
|
//// Read the database as many times as you need here.
|
||||||
|
//// See https://docs.convex.dev/database/reading-data.
|
||||||
|
const numbers = await ctx.db
|
||||||
|
.query("numbers")
|
||||||
|
// Ordered by _creationTime, return most recent
|
||||||
|
.order("desc")
|
||||||
|
.take(args.count);
|
||||||
|
const userId = await getAuthUserId(ctx);
|
||||||
|
const user = userId === null ? null : await ctx.db.get(userId);
|
||||||
|
return {
|
||||||
|
viewer: user?.email ?? null,
|
||||||
|
numbers: numbers.reverse().map((number) => number.value),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// You can write data to the database via a mutation:
|
||||||
|
export const addNumber = mutation({
|
||||||
|
// Validators for arguments.
|
||||||
|
args: {
|
||||||
|
value: v.number(),
|
||||||
|
},
|
||||||
|
|
||||||
|
// Mutation implementation.
|
||||||
|
handler: async (ctx, args) => {
|
||||||
|
//// Insert or modify documents in the database here.
|
||||||
|
//// Mutations can also read from the database like queries.
|
||||||
|
//// See https://docs.convex.dev/database/writing-data.
|
||||||
|
|
||||||
|
const id = await ctx.db.insert("numbers", { value: args.value });
|
||||||
|
|
||||||
|
console.log("Added new document with id:", id);
|
||||||
|
// Optionally, return a value from your mutation.
|
||||||
|
// return id;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
13
convex/schema.ts
Normal file
13
convex/schema.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { defineSchema, defineTable } from "convex/server";
|
||||||
|
import { v } from "convex/values";
|
||||||
|
import { authTables } from "@convex-dev/auth/server";
|
||||||
|
|
||||||
|
// The schema is normally optional, but Convex Auth
|
||||||
|
// requires indexes defined on `authTables`.
|
||||||
|
// The schema provides more precise TypeScript types.
|
||||||
|
export default defineSchema({
|
||||||
|
...authTables,
|
||||||
|
numbers: defineTable({
|
||||||
|
value: v.number(),
|
||||||
|
}),
|
||||||
|
});
|
25
convex/tsconfig.json
Normal file
25
convex/tsconfig.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
/* This TypeScript project config describes the environment that
|
||||||
|
* Convex functions run in and is used to typecheck them.
|
||||||
|
* You can modify it, but some settings required to use Convex.
|
||||||
|
*/
|
||||||
|
"compilerOptions": {
|
||||||
|
/* These settings are not required by Convex and can be modified. */
|
||||||
|
"allowJs": true,
|
||||||
|
"strict": true,
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
|
||||||
|
/* These compiler options are required by Convex */
|
||||||
|
"target": "ESNext",
|
||||||
|
"lib": ["ES2021", "dom"],
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true
|
||||||
|
},
|
||||||
|
"include": ["./**/*"],
|
||||||
|
"exclude": ["./_generated"]
|
||||||
|
}
|
16
eslint.config.mjs
Normal file
16
eslint.config.mjs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { dirname } from "path";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
import { FlatCompat } from "@eslint/eslintrc";
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
|
const compat = new FlatCompat({
|
||||||
|
baseDirectory: __dirname,
|
||||||
|
});
|
||||||
|
|
||||||
|
const eslintConfig = [
|
||||||
|
...compat.extends("next/core-web-vitals", "next/typescript"),
|
||||||
|
];
|
||||||
|
|
||||||
|
export default eslintConfig;
|
51
host/convex/docker/compose.yml
Normal file
51
host/convex/docker/compose.yml
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
networks:
|
||||||
|
nginx-bridge:
|
||||||
|
external: true
|
||||||
|
services:
|
||||||
|
backend:
|
||||||
|
image: ghcr.io/get-convex/convex-backend:08139ef318b1898dad7731910f49ba631631c902
|
||||||
|
container_name: ${BACKEND_CONTAINER_NAME:-convex-backend}
|
||||||
|
hostname: convex-backend
|
||||||
|
domainname: convex.gbrown.org
|
||||||
|
networks: [nginx-bridge]
|
||||||
|
#ports: [3210:3210,3211:3211] # 3210: API | 3211: Base URL
|
||||||
|
volumes: [./data:/convex/data]
|
||||||
|
labels: ['com.centurylinklabs.watchtower.enable=true']
|
||||||
|
env_file: [./.env]
|
||||||
|
environment:
|
||||||
|
- INSTANCE_NAME=${INSTANCE_NAME:-}
|
||||||
|
- INSTANCE_SECRET=${INSTANCE_SECRET:-}
|
||||||
|
- CONVEX_CLOUD_ORIGIN=${CONVEX_CLOUD_ORIGIN:-}
|
||||||
|
- CONVEX_SITE_ORIGIN=${CONVEX_SITE_ORIGIN:-}
|
||||||
|
- DISABLE_BEACON=${DISABLE_BEACON:true}
|
||||||
|
- REDACT_LOGS_TO_CLIENT=${REDACT_LOGS_TO_CLIENT:true}
|
||||||
|
- DO_NOT_REQUIRE_SSL=${DO_NOT_REQUIRE_SSL:true}
|
||||||
|
stdin_open: true
|
||||||
|
tty: true
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: curl -f http://localhost:3210/version
|
||||||
|
interval: 5s
|
||||||
|
start_period: 10s
|
||||||
|
stop_grace_period: 10s
|
||||||
|
stop_signal: SIGINT
|
||||||
|
|
||||||
|
dashboard:
|
||||||
|
image: ghcr.io/get-convex/convex-dashboard:33cef775a8a6228cbacee4a09ac2c4073d62ed13
|
||||||
|
container_name: ${DASHBOARD_CONTAINER_NAME:-convex-dashboard}
|
||||||
|
hostname: convex-dashboard
|
||||||
|
domainname: dashboard.convex.gbrown.org
|
||||||
|
networks: [nginx-bridge]
|
||||||
|
stop_grace_period: 10s
|
||||||
|
stop_signal: SIGINT
|
||||||
|
#ports: [6791:6791] # Dashboard
|
||||||
|
labels: ['com.centurylinklabs.watchtower.enable=true']
|
||||||
|
env_file: [.env]
|
||||||
|
environment:
|
||||||
|
- NEXT_PUBLIC_DEPLOYMENT_URL=${NEXT_PUBLIC_DEPLOYMENT_URL:-http://${BACKEND_CONTAINER_NAME:-convex-backend}:${PORT:-3210}}
|
||||||
|
depends_on:
|
||||||
|
backend:
|
||||||
|
condition: service_healthy
|
||||||
|
stdin_open: true
|
||||||
|
tty: true
|
||||||
|
restart: unless-stopped
|
23
middleware.ts
Normal file
23
middleware.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import {
|
||||||
|
convexAuthNextjsMiddleware,
|
||||||
|
createRouteMatcher,
|
||||||
|
nextjsMiddlewareRedirect,
|
||||||
|
} from "@convex-dev/auth/nextjs/server";
|
||||||
|
|
||||||
|
const isSignInPage = createRouteMatcher(["/signin"]);
|
||||||
|
const isProtectedRoute = createRouteMatcher(["/", "/server"]);
|
||||||
|
|
||||||
|
export default convexAuthNextjsMiddleware(async (request, { convexAuth }) => {
|
||||||
|
if (isSignInPage(request) && (await convexAuth.isAuthenticated())) {
|
||||||
|
return nextjsMiddlewareRedirect(request, "/");
|
||||||
|
}
|
||||||
|
if (isProtectedRoute(request) && !(await convexAuth.isAuthenticated())) {
|
||||||
|
return nextjsMiddlewareRedirect(request, "/signin");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
// The following matcher runs middleware on all routes
|
||||||
|
// except static assets.
|
||||||
|
matcher: ["/((?!.*\\..*|_next).*)", "/", "/(api|trpc)(.*)"],
|
||||||
|
};
|
7
next.config.ts
Normal file
7
next.config.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import type { NextConfig } from "next";
|
||||||
|
|
||||||
|
const nextConfig: NextConfig = {
|
||||||
|
/* config options here */
|
||||||
|
};
|
||||||
|
|
||||||
|
export default nextConfig;
|
35
package.json
Normal file
35
package.json
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"name": "example",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "npm-run-all --parallel dev:frontend dev:backend",
|
||||||
|
"dev:frontend": "next dev",
|
||||||
|
"dev:backend": "convex dev",
|
||||||
|
"predev": "convex dev --until-success && convex dev --once --run-sh \"node setup.mjs --once\" && convex dashboard",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start",
|
||||||
|
"lint": "next lint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@convex-dev/auth": "^0.0.81",
|
||||||
|
"convex": "^1.26.0",
|
||||||
|
"next": "15.2.3",
|
||||||
|
"react": "^19.0.0",
|
||||||
|
"react-dom": "^19.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/eslintrc": "^3",
|
||||||
|
"@tailwindcss/postcss": "^4",
|
||||||
|
"@types/node": "^20",
|
||||||
|
"@types/react": "^19",
|
||||||
|
"@types/react-dom": "^19",
|
||||||
|
"dotenv": "^16.4.7",
|
||||||
|
"eslint": "^9",
|
||||||
|
"eslint-config-next": "15.2.3",
|
||||||
|
"npm-run-all": "^4.1.5",
|
||||||
|
"prettier": "^3.5.3",
|
||||||
|
"tailwindcss": "^4",
|
||||||
|
"typescript": "^5"
|
||||||
|
}
|
||||||
|
}
|
5
postcss.config.mjs
Normal file
5
postcss.config.mjs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
const config = {
|
||||||
|
plugins: ["@tailwindcss/postcss"],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
17
public/convex.svg
Normal file
17
public/convex.svg
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg width="100%" height="100%" viewBox="0 0 367 370" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||||
|
<g transform="matrix(1,0,0,1,-129.225,-127.948)">
|
||||||
|
<g id="Layer-1" serif:id="Layer 1" transform="matrix(4.16667,0,0,4.16667,0,0)">
|
||||||
|
<g transform="matrix(1,0,0,1,86.6099,107.074)">
|
||||||
|
<path d="M0,-6.544C13.098,-7.973 25.449,-14.834 32.255,-26.287C29.037,2.033 -2.48,19.936 -28.196,8.94C-30.569,7.925 -32.605,6.254 -34.008,4.088C-39.789,-4.83 -41.69,-16.18 -38.963,-26.48C-31.158,-13.247 -15.3,-5.131 0,-6.544" style="fill:rgb(245,176,26);fill-rule:nonzero;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1,0,0,1,47.1708,74.7779)">
|
||||||
|
<path d="M0,-2.489C-5.312,9.568 -5.545,23.695 0.971,35.316C-21.946,18.37 -21.692,-17.876 0.689,-34.65C2.754,-36.197 5.219,-37.124 7.797,-37.257C18.41,-37.805 29.19,-33.775 36.747,-26.264C21.384,-26.121 6.427,-16.446 0,-2.489" style="fill:rgb(141,37,118);fill-rule:nonzero;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1,0,0,1,91.325,66.4152)">
|
||||||
|
<path d="M0,-14.199C-7.749,-24.821 -19.884,-32.044 -33.173,-32.264C-7.482,-43.726 24.112,-25.143 27.557,2.322C27.877,4.876 27.458,7.469 26.305,9.769C21.503,19.345 12.602,26.776 2.203,29.527C9.838,15.64 8.889,-1.328 0,-14.199" style="fill:rgb(238,52,47);fill-rule:nonzero;"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
35
setup.mjs
Normal file
35
setup.mjs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* This script runs `npx @convex-dev/auth` to help with setting up
|
||||||
|
* environment variables for Convex Auth.
|
||||||
|
*
|
||||||
|
* You can safely delete it and remove it from package.json scripts.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import fs from "fs";
|
||||||
|
import { config as loadEnvFile } from "dotenv";
|
||||||
|
import { spawnSync } from "child_process";
|
||||||
|
|
||||||
|
if (!fs.existsSync(".env.local")) {
|
||||||
|
// Something is off, skip the script.
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = {};
|
||||||
|
loadEnvFile({ path: ".env.local", processEnv: config });
|
||||||
|
|
||||||
|
const runOnceWorkflow = process.argv.includes("--once");
|
||||||
|
|
||||||
|
if (runOnceWorkflow && config.SETUP_SCRIPT_RAN !== undefined) {
|
||||||
|
// The script has already ran once, skip.
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = spawnSync("npx", ["@convex-dev/auth", "--skip-git-check"], {
|
||||||
|
stdio: "inherit",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (runOnceWorkflow) {
|
||||||
|
fs.writeFileSync(".env.local", `\nSETUP_SCRIPT_RAN=1\n`, { flag: "a" });
|
||||||
|
}
|
||||||
|
|
||||||
|
process.exit(result.status);
|
27
tsconfig.json
Normal file
27
tsconfig.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2017",
|
||||||
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"incremental": true,
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "next"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
Reference in New Issue
Block a user