From 2e6958f79b11b81b43def77126d026a50843e27f Mon Sep 17 00:00:00 2001 From: MartinBraquet Date: Sun, 27 Jul 2025 17:36:52 +0200 Subject: [PATCH] Update --- .gitignore | 12 +- .idea/.gitignore | 8 + .idea/codeStyles/codeStyleConfig.xml | 5 + .idea/dataSources.xml | 12 + .idea/inspectionProfiles/Project_Default.xml | 6 + .../inspectionProfiles/profiles_settings.xml | 6 + .idea/meeting-intellectuals.iml | 8 + .idea/misc.xml | 6 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + .obsidian/app.json | 1 + .obsidian/appearance.json | 1 + .obsidian/core-plugins.json | 31 + .obsidian/hotkeys.json | 148 + .obsidian/workspace.json | 168 + README.md | 168 +- app/Header.tsx | 52 + app/api/auth/[...nextauth]/route.ts | 19 + app/api/posts/route.ts | 22 + {src/app => app}/favicon.ico | Bin app/globals.css | 21 + app/layout.tsx | 28 + app/login/page.tsx | 92 + app/page.tsx | 57 + app/posts/[id]/page.tsx | 68 + app/posts/new/actions.ts | 23 + app/posts/new/page.tsx | 43 + app/posts/page.tsx | 114 + app/providers.tsx | 7 + app/register/page.tsx | 105 + app/setup/code-block.tsx | 44 + app/setup/page.tsx | 19 + app/setup/setup-instructions.tsx | 11 + app/setup/setup-steps.tsx | 115 + app/users/new/page.tsx | 57 + auth.ts | 58 + lib/db-utils.ts | 18 + lib/prisma.ts | 10 + martin/Date Me Directory.html | 2 + nextjs-auth-starter.png | Bin 0 -> 677935 bytes notebooks/meeting_rational.ipynb | 468 ++ notebooks/meeting_rational_all.ipynb | 435 ++ notebooks/meeting_rational_qualitative.ipynb | 75 + notebooks/rational_qualitative.png | Bin 0 -> 33441 bytes old/.gitignore | 50 + old/README.md | 30 + {docs => old/docs}/google-oauth-setup.md | 0 old/eslint.config.mjs | 16 + middleware.ts => old/middleware.ts | 0 old/next.config.js | 45 + old/next.config.ts | 7 + old/package-lock.json | 5887 +++++++++++++++++ old/package.json | 31 + old/postcss.config.mjs | 5 + .../20250726215047_init/migration.sql | 0 old/prisma/migrations/migration_lock.toml | 3 + old/prisma/schema.prisma | 61 + {public => old/public}/file.svg | 0 {public => old/public}/globe.svg | 0 {public => old/public}/next.svg | 0 {public => old/public}/vercel.svg | 0 {public => old/public}/window.svg | 0 {scripts => old/scripts}/setup-env.js | 0 old/src/app/api/auth/[...nextauth]/route.ts | 23 + {src => old/src}/app/api/auth/signup/route.ts | 0 {src => old/src}/app/dashboard/page.tsx | 85 +- old/src/app/favicon.ico | Bin 0 -> 25931 bytes {src => old/src}/app/globals.css | 0 old/src/app/header.tsx | 19 + {src => old/src}/app/layout.tsx | 2 + {src => old/src}/app/login/page.tsx | 7 +- {src => old/src}/app/page.tsx | 0 {src => old/src}/app/signup/page.tsx | 0 old/src/auth.config.ts | 126 + {src => old/src}/auth.ts | 0 {src => old/src}/lib/prisma.ts | 0 old/src/middleware.ts | 35 + {src => old/src}/providers.tsx | 0 old/tsconfig.json | 28 + package-lock.json | 2193 ++++-- package.json | 30 +- postcss.config.mjs | 5 +- .../20250727152804_init/migration.sql | 28 + prisma/migrations/migration_lock.toml | 2 +- prisma/schema.prisma | 64 +- prisma/seed.ts | 164 + src/app/api/auth/[...nextauth]/route.ts | 25 - src/auth.config.ts | 67 - tailwind.config.ts | 18 + tsconfig.json | 5 +- 90 files changed, 10747 insertions(+), 871 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/codeStyles/codeStyleConfig.xml create mode 100644 .idea/dataSources.xml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/meeting-intellectuals.iml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 .obsidian/app.json create mode 100644 .obsidian/appearance.json create mode 100644 .obsidian/core-plugins.json create mode 100644 .obsidian/hotkeys.json create mode 100644 .obsidian/workspace.json create mode 100644 app/Header.tsx create mode 100644 app/api/auth/[...nextauth]/route.ts create mode 100644 app/api/posts/route.ts rename {src/app => app}/favicon.ico (100%) create mode 100644 app/globals.css create mode 100644 app/layout.tsx create mode 100644 app/login/page.tsx create mode 100644 app/page.tsx create mode 100644 app/posts/[id]/page.tsx create mode 100644 app/posts/new/actions.ts create mode 100644 app/posts/new/page.tsx create mode 100644 app/posts/page.tsx create mode 100644 app/providers.tsx create mode 100644 app/register/page.tsx create mode 100644 app/setup/code-block.tsx create mode 100644 app/setup/page.tsx create mode 100644 app/setup/setup-instructions.tsx create mode 100644 app/setup/setup-steps.tsx create mode 100644 app/users/new/page.tsx create mode 100644 auth.ts create mode 100644 lib/db-utils.ts create mode 100644 lib/prisma.ts create mode 100644 martin/Date Me Directory.html create mode 100644 nextjs-auth-starter.png create mode 100644 notebooks/meeting_rational.ipynb create mode 100644 notebooks/meeting_rational_all.ipynb create mode 100644 notebooks/meeting_rational_qualitative.ipynb create mode 100644 notebooks/rational_qualitative.png create mode 100644 old/.gitignore create mode 100644 old/README.md rename {docs => old/docs}/google-oauth-setup.md (100%) create mode 100644 old/eslint.config.mjs rename middleware.ts => old/middleware.ts (100%) create mode 100644 old/next.config.js create mode 100644 old/next.config.ts create mode 100644 old/package-lock.json create mode 100644 old/package.json create mode 100644 old/postcss.config.mjs rename {prisma => old/prisma}/migrations/20250726215047_init/migration.sql (100%) create mode 100644 old/prisma/migrations/migration_lock.toml create mode 100644 old/prisma/schema.prisma rename {public => old/public}/file.svg (100%) rename {public => old/public}/globe.svg (100%) rename {public => old/public}/next.svg (100%) rename {public => old/public}/vercel.svg (100%) rename {public => old/public}/window.svg (100%) rename {scripts => old/scripts}/setup-env.js (100%) create mode 100644 old/src/app/api/auth/[...nextauth]/route.ts rename {src => old/src}/app/api/auth/signup/route.ts (100%) rename {src => old/src}/app/dashboard/page.tsx (70%) create mode 100644 old/src/app/favicon.ico rename {src => old/src}/app/globals.css (100%) create mode 100644 old/src/app/header.tsx rename {src => old/src}/app/layout.tsx (93%) rename {src => old/src}/app/login/page.tsx (98%) rename {src => old/src}/app/page.tsx (100%) rename {src => old/src}/app/signup/page.tsx (100%) create mode 100644 old/src/auth.config.ts rename {src => old/src}/auth.ts (100%) rename {src => old/src}/lib/prisma.ts (100%) create mode 100644 old/src/middleware.ts rename {src => old/src}/providers.tsx (100%) create mode 100644 old/tsconfig.json create mode 100644 prisma/migrations/20250727152804_init/migration.sql create mode 100644 prisma/seed.ts delete mode 100644 src/app/api/auth/[...nextauth]/route.ts delete mode 100644 src/auth.config.ts create mode 100644 tailwind.config.ts diff --git a/.gitignore b/.gitignore index 7aeaa1b9..461172fc 100644 --- a/.gitignore +++ b/.gitignore @@ -31,7 +31,8 @@ yarn-error.log* .pnpm-debug.log* # env files (can opt-in for committing if needed) -.env* +.env +.env.local # vercel .vercel @@ -39,12 +40,3 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts - -# others -notebooks -.idea -.obsidian -martin - -/src/generated/prisma -*.db diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..13566b81 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 00000000..a55e7a17 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 00000000..7ae40895 --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,12 @@ + + + + + sqlite.xerial + true + org.sqlite.JDBC + jdbc:sqlite:$PROJECT_DIR$/prisma/dev.db + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 00000000..708ddf95 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 00000000..105ce2da --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/meeting-intellectuals.iml b/.idea/meeting-intellectuals.iml new file mode 100644 index 00000000..02f253ce --- /dev/null +++ b/.idea/meeting-intellectuals.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 00000000..5264df9f --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..c87131d9 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..94a25f7f --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.obsidian/app.json b/.obsidian/app.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/.obsidian/app.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/.obsidian/appearance.json b/.obsidian/appearance.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/.obsidian/appearance.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/.obsidian/core-plugins.json b/.obsidian/core-plugins.json new file mode 100644 index 00000000..b977c255 --- /dev/null +++ b/.obsidian/core-plugins.json @@ -0,0 +1,31 @@ +{ + "file-explorer": true, + "global-search": true, + "switcher": true, + "graph": true, + "backlink": true, + "canvas": true, + "outgoing-link": true, + "tag-pane": true, + "properties": false, + "page-preview": true, + "daily-notes": true, + "templates": true, + "note-composer": true, + "command-palette": true, + "slash-command": false, + "editor-status": true, + "bookmarks": true, + "markdown-importer": false, + "zk-prefixer": false, + "random-note": false, + "outline": true, + "word-count": true, + "slides": false, + "audio-recorder": false, + "workspaces": false, + "file-recovery": true, + "publish": false, + "sync": true, + "webviewer": false +} \ No newline at end of file diff --git a/.obsidian/hotkeys.json b/.obsidian/hotkeys.json new file mode 100644 index 00000000..d9076a84 --- /dev/null +++ b/.obsidian/hotkeys.json @@ -0,0 +1,148 @@ +{ + "insert-template": [ + { + "modifiers": [ + "Alt" + ], + "key": "T" + } + ], + "editor:delete-paragraph": [ + { + "modifiers": [ + "Mod" + ], + "key": "Y" + } + ], + "obsidian-languagetool-plugin:ltcheck-text": [ + { + "modifiers": [ + "Alt", + "Mod" + ], + "key": "C" + } + ], + "obsidian-languagetool-plugin:ltaccept-suggestion-1": [ + { + "modifiers": [ + "Mod", + "Shift" + ], + "key": "C" + } + ], + "editor:set-heading-1": [ + { + "modifiers": [ + "Alt", + "Mod" + ], + "key": "1" + } + ], + "editor:set-heading-2": [ + { + "modifiers": [ + "Alt", + "Mod" + ], + "key": "2" + } + ], + "editor:set-heading-3": [ + { + "modifiers": [ + "Alt", + "Mod" + ], + "key": "3" + } + ], + "editor:set-heading-4": [ + { + "modifiers": [ + "Alt", + "Mod" + ], + "key": "4" + } + ], + "editor:set-heading-5": [ + { + "modifiers": [ + "Alt", + "Mod" + ], + "key": "5" + } + ], + "editor:set-heading-6": [ + { + "modifiers": [ + "Alt", + "Mod" + ], + "key": "6" + } + ], + "app:go-back": [ + { + "modifiers": [ + "Mod", + "Alt" + ], + "key": "ArrowLeft" + }, + { + "modifiers": [ + "Alt", + "Shift" + ], + "key": "ArrowLeft" + } + ], + "app:go-forward": [ + { + "modifiers": [ + "Mod", + "Alt" + ], + "key": "ArrowRight" + }, + { + "modifiers": [ + "Alt", + "Shift" + ], + "key": "ArrowRight" + } + ], + "editor:context-menu": [ + { + "modifiers": [ + "Alt" + ], + "key": "Enter" + } + ], + "editor:follow-link": [ + { + "modifiers": [ + "Alt", + "Shift" + ], + "key": "Enter" + } + ], + "editor:cycle-list-checklist": [ + { + "modifiers": [ + "Alt", + "Mod" + ], + "key": "B" + } + ] +} \ No newline at end of file diff --git a/.obsidian/workspace.json b/.obsidian/workspace.json new file mode 100644 index 00000000..7a380fcf --- /dev/null +++ b/.obsidian/workspace.json @@ -0,0 +1,168 @@ +{ + "main": { + "id": "1bfbade88fcfec55", + "type": "split", + "children": [ + { + "id": "7facf39ed5015822", + "type": "tabs", + "children": [ + { + "id": "c408eff3df45f809", + "type": "leaf", + "state": { + "type": "empty", + "state": {}, + "icon": "lucide-file", + "title": "New tab" + } + } + ] + } + ], + "direction": "vertical" + }, + "left": { + "id": "be7671ba7092d719", + "type": "split", + "children": [ + { + "id": "d35c2a0a402ecd5d", + "type": "tabs", + "children": [ + { + "id": "4f11d12d34891ea7", + "type": "leaf", + "state": { + "type": "file-explorer", + "state": { + "sortOrder": "alphabetical", + "autoReveal": false + }, + "icon": "lucide-folder-closed", + "title": "Files" + } + }, + { + "id": "eaaec87adeac134e", + "type": "leaf", + "state": { + "type": "search", + "state": { + "query": "", + "matchingCase": false, + "explainSearch": false, + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical" + }, + "icon": "lucide-search", + "title": "Search" + } + }, + { + "id": "15a237b845d3e20b", + "type": "leaf", + "state": { + "type": "bookmarks", + "state": {}, + "icon": "lucide-bookmark", + "title": "Bookmarks" + } + } + ] + } + ], + "direction": "horizontal", + "width": 300 + }, + "right": { + "id": "50eec151107f9ed7", + "type": "split", + "children": [ + { + "id": "5b09bb3294264e7f", + "type": "tabs", + "children": [ + { + "id": "b2fe533e95f324e8", + "type": "leaf", + "state": { + "type": "backlink", + "state": { + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical", + "showSearch": false, + "searchQuery": "", + "backlinkCollapsed": false, + "unlinkedCollapsed": true + }, + "icon": "links-coming-in", + "title": "Backlinks" + } + }, + { + "id": "985abb1f14124830", + "type": "leaf", + "state": { + "type": "outgoing-link", + "state": { + "linksCollapsed": false, + "unlinkedCollapsed": true + }, + "icon": "links-going-out", + "title": "Outgoing links" + } + }, + { + "id": "6a75acc48fb81d4f", + "type": "leaf", + "state": { + "type": "tag", + "state": { + "sortOrder": "frequency", + "useHierarchy": true, + "showSearch": false, + "searchQuery": "" + }, + "icon": "lucide-tags", + "title": "Tags" + } + }, + { + "id": "55ebdc78da69d9f1", + "type": "leaf", + "state": { + "type": "outline", + "state": { + "followCursor": false, + "showSearch": false, + "searchQuery": "" + }, + "icon": "lucide-list", + "title": "Outline" + } + } + ] + } + ], + "direction": "horizontal", + "width": 300, + "collapsed": true + }, + "left-ribbon": { + "hiddenItems": { + "switcher:Open quick switcher": false, + "graph:Open graph view": false, + "canvas:Create new canvas": false, + "daily-notes:Open today's daily note": false, + "templates:Insert template": false, + "command-palette:Open command palette": false + } + }, + "active": "c408eff3df45f809", + "lastOpenFiles": [ + "README.md" + ] +} \ No newline at end of file diff --git a/README.md b/README.md index dd93b34c..3e5939df 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,168 @@ -This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). +# Next.js & Prisma Postgres Auth Starter -## Getting Started +This repository provides a boilerplate to quickly set up a Next.js demo application with authentication using [NextAuth.js v4](https://next-auth.js.org/), [Prisma Postgres](https://www.prisma.io/postgres) and [Prisma ORM](https://www.prisma.io/orm), and deploy it to Vercel. It includes an easy setup process and example routes that demonstrate basic CRUD operations against the database. -First, run the development server: +## Features + +- Next.js 15 app with App Router, Server Actions & API Routes +- Data modeling, database migrations, seeding & querying +- Log in and sign up authentication flows +- CRUD operations to create, view and delete blog posts +- Pagination, filtering & relations queries + +## Getting started + +### 1. Install dependencies + +After cloning the repo and navigating into it, install dependencies: + +``` +npm install +``` + +### 1. Create a Prisma Postgres instance + +Create a Prisma Postgres instance by running the following command: + +``` +npx prisma init --db +``` + +This command is interactive and will prompt you to: + +1. Log in to the [Prisma Console](https://console.prisma.io) +1. Select a **region** for your Prisma Postgres instance +1. Give a **name** to your Prisma project + +Once the command has terminated, copy the **Database URL** from the terminal output. You'll need it in the next step when you configure your `.env` file. + + + +### 2. Set up your `.env` file + +You now need to configure your database connection via an environment variable. + +First, create an `.env` file: + +```bash +touch .env +``` + +Then update the `.env` file by replacing the existing `DATABASE_URL` value with the one you previously copied. It will look similar to this: + +```bash +DATABASE_URL="prisma+postgres://accelerate.prisma-data.net/?api_key=PRISMA_POSTGRES_API_KEY" +``` + +To ensure your authentication works properly, you'll also need to set [env vars for NextAuth.js](https://next-auth.js.org/configuration/options): + +```bash +AUTH_SECRET="RANDOM_32_CHARACTER_STRING" +``` + +You can generate a random 32 character string for the `AUTH_SECRET` secret with this command: + +``` +npx auth secret +``` + +In the end, your entire `.env` file should look similar to this (but using _your own values_ for the env vars): + +```bash +DATABASE_URL="prisma+postgres://accelerate.prisma-data.net/?api_key=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcGlfa2V5IjoiMWEzMjBiYTEtYjg2Yy00ZTA5LThmZTktZDBhODA3YjQwZjBkIiwidGVuYW50X2lkIjoiY2RhYmM3ZTU1NzdmMmIxMmM0ZTI1Y2IwNWJhZmZhZmU4NjAxNzkxZThlMzhlYjI1NDgwNmIzZjI5NmU1NTkzNiIsImludGVybmFsX3NlY3JldCI6ImI3YmQzMjFhLTY2ODQtNGRiMC05ZWRiLWIyMGE2ZTQ0ZDMwMSJ9.JgKXQBatjjh7GIG3_fRHDnia6bDv8BdwvaX5F-XdBfw" + +AUTH_SECRET="gTwLSXFeNWFRpUTmxlRniOfegXYw445pd0k6JqXd7Ag=" +``` + +### 3. Migrate the database + +Run the following commands to set up your database and Prisma schema: + +```bash +npx prisma migrate dev --name init +``` + + + +### 4. Seed the database + +Add initial data to your database: + +```bash +npx prisma db seed +``` + +
+ +Expand for yarn, pnpm or bun + +```bash +# Using yarn +yarn prisma db seed + +# Using pnpm +pnpm prisma db seed + +# Using bun +bun prisma db seed +``` + +
+ +### 5. Run the app + +Start the development server: ```bash npm run dev ``` -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. +
-You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. +Expand for yarn, pnpm or bun -This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. +```bash +# Using yarn +yarn dev -## Learn More +# Using pnpm +pnpm run dev -To learn more about Next.js, take a look at the following resources: +# Using bun +bun run dev +``` -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. +
-You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! +Once the server is running, visit `http://localhost:3000` to start using the app. -## Deploy on Vercel +## Next steps -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. - -Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. +- [Prisma ORM documentation](https://www.prisma.io/docs/orm) +- [Prisma Client API reference](https://www.prisma.io/docs/orm/prisma-client) +- [Join our Discord community](https://discord.com/invite/prisma) +- [Follow us on Twitter](https://twitter.com/prisma) diff --git a/app/Header.tsx b/app/Header.tsx new file mode 100644 index 00000000..55a2ef69 --- /dev/null +++ b/app/Header.tsx @@ -0,0 +1,52 @@ +"use client"; + +import Link from "next/link"; +import { useSession, signOut } from "next-auth/react"; + +export default function Header() { + const { data: session } = useSession(); + + return ( +
+ +
+ ); +} diff --git a/app/api/auth/[...nextauth]/route.ts b/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 00000000..2205982d --- /dev/null +++ b/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1,19 @@ +import NextAuth from "next-auth"; +// import { authOptions } from "@/app/api/auth/[...nextauth]/auth"; +import { authOptions } from "@/auth"; + +const handler = NextAuth(authOptions); +export { handler as GET, handler as POST }; + +declare module "next-auth" { + interface Session { + user: { id: string; name: string; email: string }; + // user: { id: string }; + } +} + +declare module "next-auth/jwt" { + interface JWT { + id: string; + } +} diff --git a/app/api/posts/route.ts b/app/api/posts/route.ts new file mode 100644 index 00000000..a7af6e60 --- /dev/null +++ b/app/api/posts/route.ts @@ -0,0 +1,22 @@ +import prisma from "@/lib/prisma"; +import { NextResponse } from "next/server"; + +export async function GET(request: Request) { + const url = new URL(request.url); + const page = parseInt(url.searchParams.get("page") || "1"); + const postsPerPage = 5; + const offset = (page - 1) * postsPerPage; + + // Fetch paginated posts + const posts = await prisma.post.findMany({ + skip: offset, + take: postsPerPage, + orderBy: { createdAt: "desc" }, + include: { author: { select: { name: true } } }, + }); + + const totalPosts = await prisma.post.count(); + const totalPages = Math.ceil(totalPosts / postsPerPage); + + return NextResponse.json({ posts, totalPages }); +} diff --git a/src/app/favicon.ico b/app/favicon.ico similarity index 100% rename from src/app/favicon.ico rename to app/favicon.ico diff --git a/app/globals.css b/app/globals.css new file mode 100644 index 00000000..6b717ad3 --- /dev/null +++ b/app/globals.css @@ -0,0 +1,21 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + --background: #ffffff; + --foreground: #171717; +} + +@media (prefers-color-scheme: dark) { + :root { + --background: #0a0a0a; + --foreground: #ededed; + } +} + +body { + color: var(--foreground); + background: var(--background); + font-family: Arial, Helvetica, sans-serif; +} diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 00000000..0f6a4d8d --- /dev/null +++ b/app/layout.tsx @@ -0,0 +1,28 @@ +// app/layout.tsx +import "./globals.css"; +import Header from "./Header"; +import Providers from "./providers"; + +export const metadata = { + title: "BayesBond", + description: "A blog app using Next.js and Prisma", +}; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + + +
+
+
{children}
+
+
+ + + ); +} diff --git a/app/login/page.tsx b/app/login/page.tsx new file mode 100644 index 00000000..39be0cd8 --- /dev/null +++ b/app/login/page.tsx @@ -0,0 +1,92 @@ +"use client"; + +import { signIn } from "next-auth/react"; +import { useRouter } from "next/navigation"; +import { useState } from "react"; +import Link from "next/link"; + +export default function LoginPage() { + const router = useRouter(); + const [error, setError] = useState(null); + + async function handleSubmit(event: React.FormEvent) { + try { + event.preventDefault(); + const formData = new FormData(event.currentTarget); + const response = await signIn("credentials", { + ...Object.fromEntries(formData), + redirect: false, + }); + + if (response?.error) { + setError("Invalid credentials"); + return; + } + + router.push("/"); + router.refresh(); + } catch { + setError("An error occurred during login"); + } + } + + return ( +
+
+
+

+ Sign in to your account +

+
+
+
+
+ + +
+
+ + +
+
+ + {error && ( +
{error}
+ )} + +
+ +
+
+
+ + No account? Register. + +
+
+
+ ); +} diff --git a/app/page.tsx b/app/page.tsx new file mode 100644 index 00000000..89a31951 --- /dev/null +++ b/app/page.tsx @@ -0,0 +1,57 @@ +export const dynamic = "force-dynamic"; // This disables SSG and ISR + +import prisma from "@/lib/prisma"; +import Link from "next/link"; +import { redirect } from "next/navigation"; +import { checkPostTableExists } from "@/lib/db-utils"; + +export default async function Home() { + // Check if the post table exists + const tableExists = await checkPostTableExists(); + + // If the post table doesn't exist, redirect to setup page + if (!tableExists) { + redirect("/setup"); + } + + const posts = await prisma.post.findMany({ + orderBy: { + createdAt: "desc", + }, + take: 6, + include: { + author: { + select: { + name: true, + }, + }, + }, + }); + + return ( +
+

Recent Posts

+
+ {posts.map((post) => ( + +
+

{post.title}

+

by {post.author ? post.author.name : "Anonymous"}

+

+ {new Date(post.createdAt).toLocaleDateString("en-US", { + year: "numeric", + month: "long", + day: "numeric", + })} +

+
+

{post.content || "No content available."}

+
+
+
+ + ))} +
+
+ ); +} diff --git a/app/posts/[id]/page.tsx b/app/posts/[id]/page.tsx new file mode 100644 index 00000000..d2a1d0d4 --- /dev/null +++ b/app/posts/[id]/page.tsx @@ -0,0 +1,68 @@ +export const dynamic = "force-dynamic"; // This disables SSG and ISR + +import prisma from "@/lib/prisma"; +import { notFound, redirect } from "next/navigation"; + +export default async function Post({ params }: { params: Promise<{ id: string }> }) { + const { id } = await params; + const postId = parseInt(id); + + const post = await prisma.post.findUnique({ + where: { id: postId }, + include: { + author: true, + }, + }); + + if (!post) { + notFound(); + } + + // Server action to delete the post + async function deletePost() { + "use server"; + + await prisma.post.delete({ + where: { + id: postId, + }, + }); + + redirect("/posts"); + } + + return ( +
+
+ {/* Post Title */} +

+ {post.title} +

+ + {/* Author Information */} +

+ by {post.author?.name || "Anonymous"} +

+ + {/* Content Section */} +
+ {post.content ? ( +

{post.content}

+ ) : ( +

No content available for this post.

+ )} +
+
+ + {/* Delete Button */} +
+ +
+
+ ); +} diff --git a/app/posts/new/actions.ts b/app/posts/new/actions.ts new file mode 100644 index 00000000..7ffebfd2 --- /dev/null +++ b/app/posts/new/actions.ts @@ -0,0 +1,23 @@ +"use server"; + +import prisma from "@/lib/prisma"; +import { redirect } from "next/navigation"; +import { getServerSession } from "next-auth"; +import { authOptions } from "@/auth"; + +export async function createPost(formData: FormData) { + const session = await getServerSession(authOptions); + if (!session?.user) { + throw new Error("You must be logged in to create a post"); + } + + await prisma.post.create({ + data: { + title: formData.get("title") as string, + content: formData.get("content") as string, + authorId: session.user.id, + }, + }); + + redirect("/posts"); +} \ No newline at end of file diff --git a/app/posts/new/page.tsx b/app/posts/new/page.tsx new file mode 100644 index 00000000..5f114268 --- /dev/null +++ b/app/posts/new/page.tsx @@ -0,0 +1,43 @@ +"use client"; + +import Form from "next/form"; +import { createPost } from "./actions"; + +export default function NewPost() { + return ( +
+

Create New Post

+
+
+ + +
+
+ +