-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit a10f2a4
Showing
33 changed files
with
8,979 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
NEXT_PUBLIC_STACK_PROJECT_ID= | ||
NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY= | ||
STACK_SECRET_SERVER_KEY= | ||
|
||
# For the `neondb_owner` role. | ||
DATABASE_URL= | ||
# For the `authenticated`, passwordless role. | ||
DATABASE_AUTHENTICATED_URL= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"extends": "next" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. | ||
|
||
# dependencies | ||
/node_modules | ||
/.pnp | ||
.pnp.js | ||
|
||
# testing | ||
/coverage | ||
|
||
# next.js | ||
/.next/ | ||
/out/ | ||
|
||
# production | ||
/build | ||
|
||
# misc | ||
.DS_Store | ||
*.pem | ||
|
||
# debug | ||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* | ||
|
||
# local env files | ||
.env*.local | ||
|
||
# vercel | ||
.vercel | ||
|
||
# typescript | ||
*.tsbuildinfo | ||
next-env.d.ts | ||
|
||
.env |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
# Neon Authorize + Stack Auth Example (SQL from the Backend) | ||
|
||
This repository is a guided getting started example for Neon Authorize + Stack Auth. | ||
|
||
1. Create a Neon project | ||
2. Sign Up for [Stack Auth](https://stack-auth.com/) and create a new project | ||
3. Once in the Stack Auth's Dashboard, create a new project. | ||
4. Head to the Neon Console, and find "Authorize" | ||
5. Inside Authorize, click "Add Authentication Provider", choose Stack Auth and paste in the following URL (replacing your Stack Auth's Project ID): | ||
|
||
``` | ||
https://api.stack-auth.com/api/v1/projects/<project-id>/.well-known/jwks.json | ||
``` | ||
|
||
(If you have an older Stack Auth project, you'll have to disable legacy JWKS in the Stack Auth's project settings) | ||
|
||
6. Clone this repository and run `npm install` or `bun install` | ||
7. Create a `.env` file in the root of this project and add the following: | ||
|
||
``` | ||
NEXT_PUBLIC_STACK_PROJECT_ID= | ||
NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY= | ||
STACK_SECRET_SERVER_KEY= | ||
# For the `neondb_owner` role. | ||
DATABASE_URL= | ||
# For the `authenticated`, passwordless role. | ||
DATABASE_AUTHENTICATED_URL= | ||
``` | ||
|
||
8. Run `npm run drizzle:migrate` or `bun run drizzle:migrate` to apply the migrations | ||
9. Run `npm run dev` or `bun run dev` | ||
10. Open your browser and go to `http://localhost:3000` | ||
11. Login and play around! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
"use server"; | ||
|
||
import { fetchWithDrizzle } from "@/app/db"; | ||
import * as schema from "@/app/schema"; | ||
import { Todo } from "@/app/schema"; | ||
import { asc, eq, sql } from "drizzle-orm"; | ||
import { revalidatePath } from "next/cache"; | ||
|
||
export async function insertTodo(newTodo: { newTodo: string; userId: string }) { | ||
await fetchWithDrizzle(async (db) => { | ||
return db.insert(schema.todos).values({ | ||
task: newTodo.newTodo, | ||
isComplete: false, | ||
}); | ||
}); | ||
|
||
revalidatePath("/"); | ||
} | ||
|
||
export async function getTodos(): Promise<Array<Todo>> { | ||
return fetchWithDrizzle(async (db) => { | ||
// WHERE filter is optional because of RLS. But we send it anyway for | ||
// performance reasons. | ||
return db | ||
.select() | ||
.from(schema.todos) | ||
.where(eq(schema.todos.userId, sql`auth.user_id()`)) | ||
.orderBy(asc(schema.todos.insertedAt)); | ||
}); | ||
} | ||
|
||
export async function deleteTodoFormAction(formData: FormData) { | ||
const id = formData.get("id"); | ||
if (!id) { | ||
throw new Error("No id"); | ||
} | ||
if (typeof id !== "string") { | ||
throw new Error("The id must be a string"); | ||
} | ||
|
||
await fetchWithDrizzle(async (db) => { | ||
return db.delete(schema.todos).where(eq(schema.todos.id, BigInt(id))); | ||
}); | ||
|
||
revalidatePath("/"); | ||
} | ||
|
||
export async function checkOrUncheckTodoFormAction(formData: FormData) { | ||
const id = formData.get("id"); | ||
const isComplete = formData.get("isComplete"); | ||
|
||
if (!id) { | ||
throw new Error("No id"); | ||
} | ||
|
||
if (!isComplete) { | ||
throw new Error("No isComplete"); | ||
} | ||
|
||
if (typeof id !== "string") { | ||
throw new Error("The id must be a string"); | ||
} | ||
|
||
if (typeof isComplete !== "string") { | ||
throw new Error("The isComplete must be a string"); | ||
} | ||
|
||
const isCompleteBool = isComplete === "true"; | ||
|
||
await fetchWithDrizzle(async (db) => { | ||
return db | ||
.update(schema.todos) | ||
.set({ isComplete: !isCompleteBool }) | ||
.where(eq(schema.todos.id, BigInt(id))) | ||
.returning(); | ||
}); | ||
|
||
revalidatePath("/"); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
"use client"; | ||
|
||
import { insertTodo } from "@/app/actions"; | ||
import { CSSProperties, useRef } from "react"; | ||
import { useUser } from "@stackframe/stack"; | ||
|
||
const styles = { | ||
form: { | ||
display: "flex", | ||
marginBottom: "20px", | ||
gap: "10px", | ||
}, | ||
input: { | ||
flex: 1, | ||
padding: "10px", | ||
fontSize: "16px", | ||
border: "1px solid #e0e0e0", | ||
borderRadius: "4px", | ||
outline: "none", | ||
}, | ||
button: { | ||
padding: "10px 20px", | ||
fontSize: "16px", | ||
backgroundColor: "#4CAF50", | ||
color: "white", | ||
border: "none", | ||
borderRadius: "4px", | ||
cursor: "pointer", | ||
transition: "background-color 0.2s ease", | ||
}, | ||
} satisfies Record<string, CSSProperties>; | ||
|
||
export function AddTodoForm() { | ||
const formRef = useRef<HTMLFormElement>(null); | ||
const user = useUser(); | ||
|
||
const onSubmit = async (formData: FormData) => { | ||
const newTodo = formData.get("newTodo"); | ||
|
||
if (!newTodo) { | ||
throw new Error("No newTodo"); | ||
} | ||
|
||
if (typeof newTodo !== "string") { | ||
throw new Error("The newTodo must be a string"); | ||
} | ||
|
||
if (!user) { | ||
throw new Error("No userId"); | ||
} | ||
|
||
await insertTodo({ newTodo: newTodo.toString(), userId: user.id }); | ||
formRef.current?.reset(); | ||
}; | ||
|
||
return ( | ||
<form ref={formRef} action={onSubmit} style={styles.form}> | ||
<input | ||
required | ||
name="newTodo" | ||
placeholder="Enter a new todo" | ||
style={styles.input} | ||
/> | ||
<button type="submit" style={styles.button}> | ||
Add Todo | ||
</button> | ||
</form> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import * as schema from "@/app/schema"; | ||
import { neon } from "@neondatabase/serverless"; | ||
import { drizzle, NeonHttpDatabase } from "drizzle-orm/neon-http"; | ||
import { stackServerApp } from "@/stack"; | ||
|
||
export async function fetchWithDrizzle<T>( | ||
callback: ( | ||
db: NeonHttpDatabase<typeof schema>, | ||
{ userId, authToken }: { userId: string; authToken: string }, | ||
) => Promise<T>, | ||
) { | ||
const user = await stackServerApp.getUser(); | ||
const authToken = (await user?.getAuthJson())?.accessToken; | ||
if (!authToken) { | ||
throw new Error("No token"); | ||
} | ||
|
||
if (!user || !user.id) { | ||
throw new Error("No userId"); | ||
} | ||
|
||
const db = drizzle( | ||
neon(process.env.DATABASE_AUTHENTICATED_URL!, { | ||
authToken: async () => { | ||
const token = (await user?.getAuthJson())?.accessToken; | ||
console.log("authToken", token); | ||
if (!token) { | ||
throw new Error("No token"); | ||
} | ||
return token; | ||
}, | ||
}), | ||
{ schema }, | ||
); | ||
|
||
return callback(db, { userId: user.id, authToken }); | ||
} |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { StackHandler } from "@stackframe/stack"; | ||
import { stackServerApp } from "@/stack"; | ||
|
||
export default function Handler(props: Object) { | ||
return <StackHandler fullPage app={stackServerApp} {...props} />; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
"use client"; | ||
|
||
import Link from "next/link"; | ||
import styles from "../styles/Home.module.css"; | ||
import { useStackApp, useUser } from "@stackframe/stack"; | ||
|
||
export function Header() { | ||
const user = useUser(); | ||
const app = useStackApp(); | ||
|
||
return ( | ||
<header className={styles.header}> | ||
<div>My Todo App</div> | ||
{user ? ( | ||
<> | ||
Hello {user.primaryEmail} | ||
<Link href={app.urls.signOut}>Sign Out</Link> | ||
</> | ||
) : ( | ||
<span> | ||
<Link href={app.urls.signIn}>Sign In</Link> |{" "} | ||
<Link href={app.urls.signUp}>Sign Up</Link> | ||
</span> | ||
)} | ||
</header> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { StackProvider, StackTheme } from "@stackframe/stack"; | ||
import { stackServerApp } from "../stack"; | ||
import "../styles/globals.css"; | ||
|
||
export default function RootLayout({ | ||
children, | ||
}: { | ||
children: React.ReactNode; | ||
}) { | ||
return ( | ||
<html> | ||
<body className={`min-h-screen flex flex-col antialiased`}> | ||
<StackProvider app={stackServerApp}> | ||
<StackTheme>{children}</StackTheme> | ||
</StackProvider> | ||
</body> | ||
</html> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export default function Loading() { | ||
// Stack uses React Suspense, which will render this page while user data is being fetched. | ||
// See: https://nextjs.org/docs/app/api-reference/file-conventions/loading | ||
return <></>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { AddTodoForm } from "@/app/add-todo"; | ||
import { Header } from "@/app/header"; | ||
import { TodoList } from "@/app/todo-list"; | ||
|
||
import styles from "../styles/Home.module.css"; | ||
import { stackServerApp } from "@/stack"; | ||
|
||
export default async function Home() { | ||
const user = await stackServerApp.getUser(); | ||
|
||
let content = null; | ||
if (user) { | ||
content = ( | ||
<main className={styles.main}> | ||
<div className={styles.container}> | ||
<AddTodoForm /> | ||
<TodoList /> | ||
</div> | ||
</main> | ||
); | ||
} | ||
|
||
return ( | ||
<> | ||
<Header /> | ||
{content} | ||
</> | ||
); | ||
} |
Oops, something went wrong.