mirror of
https://github.com/kjanat/livedash-node.git
synced 2026-01-16 12:52:09 +01:00
feat: Add authentication and session management with NextAuth.js and Prisma [broken]
- Implemented API session retrieval in `lib/api-auth.ts` to manage user sessions. - Created authentication options in `lib/auth-options.ts` using NextAuth.js with credentials provider. - Added migration scripts to create necessary tables for authentication in `migrations/0002_create_auth_tables.sql` and `prisma/migrations/20250601033219_add_nextauth_tables/migration.sql`. - Configured ESLint with Next.js and TypeScript support in `eslint.config.mjs`. - Updated Next.js configuration in `next.config.ts` for Cloudflare compatibility. - Defined Cloudflare Worker configuration in `open-next.config.ts` and `wrangler.jsonc`. - Enhanced type definitions for authentication in `types/auth.d.ts`. - Created a Cloudflare Worker entry point in `src/index.ts.backup` to handle API requests and responses.
This commit is contained in:
36
.editorconfig
Normal file
36
.editorconfig
Normal file
@ -0,0 +1,36 @@
|
||||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
insert_final_newline = true
|
||||
|
||||
[*.{css,scss,sass}]
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
|
||||
[*.{ps1,psm1}]
|
||||
indent_size = 4
|
||||
indent_style = tab
|
||||
|
||||
[*.{json,yaml,yml}]
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
|
||||
[*.{js,ts,jsx,tsx}]
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
|
||||
[*.{html,htm}]
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
2779
.github/instructions/auth.js.instructions.md
vendored
Normal file
2779
.github/instructions/auth.js.instructions.md
vendored
Normal file
File diff suppressed because it is too large
Load Diff
2428
.github/instructions/cloudflare-d1-database.instructions.md
vendored
Normal file
2428
.github/instructions/cloudflare-d1-database.instructions.md
vendored
Normal file
File diff suppressed because it is too large
Load Diff
335
.github/instructions/cloudflare-workers-nextjs.instructions.md
vendored
Normal file
335
.github/instructions/cloudflare-workers-nextjs.instructions.md
vendored
Normal file
@ -0,0 +1,335 @@
|
||||
---
|
||||
applyTo: '**'
|
||||
---
|
||||
|
||||
|
||||
---
|
||||
title: Next.js · Cloudflare Workers docs
|
||||
description: Create an Next.js application and deploy it to Cloudflare Workers with Workers Assets.
|
||||
lastUpdated: 2025-05-16T19:09:44.000Z
|
||||
source_url:
|
||||
html: https://developers.cloudflare.com/workers/frameworks/framework-guides/nextjs/
|
||||
md: https://developers.cloudflare.com/workers/frameworks/framework-guides/nextjs/index.md
|
||||
---
|
||||
|
||||
**Start from CLI** - scaffold a Next.js project on Workers.
|
||||
|
||||
* npm
|
||||
|
||||
```sh
|
||||
npm create cloudflare@latest -- my-next-app --framework=next
|
||||
```
|
||||
|
||||
* yarn
|
||||
|
||||
```sh
|
||||
yarn create cloudflare my-next-app --framework=next
|
||||
```
|
||||
|
||||
* pnpm
|
||||
|
||||
```sh
|
||||
pnpm create cloudflare@latest my-next-app --framework=next
|
||||
```
|
||||
|
||||
This is a simple getting started guide. For detailed documentation on how the to use the Cloudflare OpenNext adapter, visit the [OpenNext website](https://opennext.js.org/cloudflare).
|
||||
|
||||
## What is Next.js?
|
||||
|
||||
[Next.js](https://nextjs.org/) is a [React](https://react.dev/) framework for building full stack applications.
|
||||
|
||||
Next.js supports Server-side and Client-side rendering, as well as Partial Prerendering which lets you combine static and dynamic components in the same route.
|
||||
|
||||
You can deploy your Next.js app to Cloudflare Workers using the OpenNext adaptor.
|
||||
|
||||
## Next.js supported features
|
||||
|
||||
Most Next.js features are supported by the Cloudflare OpenNext adapter:
|
||||
|
||||
| Feature | Cloudflare adapter | Notes |
|
||||
| - | - | - |
|
||||
| App Router | 🟢 supported | |
|
||||
| Pages Router | 🟢 supported | |
|
||||
| Route Handlers | 🟢 supported | |
|
||||
| React Server Components | 🟢 supported | |
|
||||
| Static Site Generation (SSG) | 🟢 supported | |
|
||||
| Server-Side Rendering (SSR) | 🟢 supported | |
|
||||
| Incremental Static Regeneration (ISR) | 🟢 supported | |
|
||||
| Server Actions | 🟢 supported | |
|
||||
| Response streaming | 🟢 supported | |
|
||||
| asynchronous work with `next/after` | 🟢 supported | |
|
||||
| Middleware | 🟢 supported | |
|
||||
| Image optimization | 🟢 supported | Supported via [Cloudflare Images](https://developers.cloudflare.com/images/) |
|
||||
| Partial Prerendering (PPR) | 🟢 supported | PPR is experimental in Next.js |
|
||||
| Composable Caching ('use cache') | 🟢 supported | Composable Caching is experimental in Next.js |
|
||||
| Node.js in Middleware | ⚪ not yet supported | Node.js middleware introduced in 15.2 are not yet supported |
|
||||
|
||||
## Deploy a new Next.js project on Workers
|
||||
|
||||
1. **Create a new project with the create-cloudflare CLI (C3).**
|
||||
|
||||
* npm
|
||||
|
||||
```sh
|
||||
npm create cloudflare@latest -- my-next-app --framework=next
|
||||
```
|
||||
|
||||
* yarn
|
||||
|
||||
```sh
|
||||
yarn create cloudflare my-next-app --framework=next
|
||||
```
|
||||
|
||||
* pnpm
|
||||
|
||||
```sh
|
||||
pnpm create cloudflare@latest my-next-app --framework=next
|
||||
```
|
||||
|
||||
What's happening behind the scenes?
|
||||
|
||||
When you run this command, C3 creates a new project directory, initiates [Next.js's official setup tool](https://nextjs.org/docs/app/api-reference/cli/create-next-app), and configures the project for Cloudflare. It then offers the option to instantly deploy your application to Cloudflare.
|
||||
|
||||
2. **Develop locally.**
|
||||
|
||||
After creating your project, run the following command in your project directory to start a local development server. The command uses the Next.js development server. It offers the best developer experience by quickly reloading your app every time the source code is updated.
|
||||
|
||||
* npm
|
||||
|
||||
```sh
|
||||
npm run dev
|
||||
```
|
||||
|
||||
* yarn
|
||||
|
||||
```sh
|
||||
yarn run dev
|
||||
```
|
||||
|
||||
* pnpm
|
||||
|
||||
```sh
|
||||
pnpm run dev
|
||||
```
|
||||
|
||||
3. **Test and preview your site with the Cloudflare adapter.**
|
||||
|
||||
* npm
|
||||
|
||||
```sh
|
||||
npm run preview
|
||||
```
|
||||
|
||||
* yarn
|
||||
|
||||
```sh
|
||||
yarn run preview
|
||||
```
|
||||
|
||||
* pnpm
|
||||
|
||||
```sh
|
||||
pnpm run preview
|
||||
```
|
||||
|
||||
What's the difference between dev and preview?
|
||||
|
||||
The command used in the previous step uses the Next.js development server, which runs in Node.js. However, your deployed application will run on Cloudflare Workers, which uses the `workerd` runtime. Therefore when running integration tests and previewing your application, you should use the preview command, which is more accurate to production, as it executes your application in the `workerd` runtime using `wrangler dev`.
|
||||
|
||||
4. **Deploy your project.**
|
||||
|
||||
You can deploy your project to a [`*.workers.dev` subdomain](https://developers.cloudflare.com/workers/configuration/routing/workers-dev/) or a [custom domain](https://developers.cloudflare.com/workers/configuration/routing/custom-domains/) from your local machine or any CI/CD system (including [Workers Builds](https://developers.cloudflare.com/workers/ci-cd/#workers-builds)). Use the following command to build and deploy. If you're using a CI service, be sure to update your "deploy command" accordingly.
|
||||
|
||||
* npm
|
||||
|
||||
```sh
|
||||
npm run deploy
|
||||
```
|
||||
|
||||
* yarn
|
||||
|
||||
```sh
|
||||
yarn run deploy
|
||||
```
|
||||
|
||||
* pnpm
|
||||
|
||||
```sh
|
||||
pnpm run deploy
|
||||
```
|
||||
|
||||
## Deploy an existing Next.js project on Workers
|
||||
|
||||
You can convert an existing Next.js application to run on Cloudflare
|
||||
|
||||
1. **Install [`@opennextjs/cloudflare`](https://www.npmjs.com/package/@opennextjs/cloudflare)**
|
||||
|
||||
* npm
|
||||
|
||||
```sh
|
||||
npm i @opennextjs/cloudflare@latest
|
||||
```
|
||||
|
||||
* yarn
|
||||
|
||||
```sh
|
||||
yarn add @opennextjs/cloudflare@latest
|
||||
```
|
||||
|
||||
* pnpm
|
||||
|
||||
```sh
|
||||
pnpm add @opennextjs/cloudflare@latest
|
||||
```
|
||||
|
||||
2. **Install [`wrangler CLI`](https://developers.cloudflare.com/workers/wrangler) as a devDependency**
|
||||
|
||||
* npm
|
||||
|
||||
```sh
|
||||
npm i -D wrangler@latest
|
||||
```
|
||||
|
||||
* yarn
|
||||
|
||||
```sh
|
||||
yarn add -D wrangler@latest
|
||||
```
|
||||
|
||||
* pnpm
|
||||
|
||||
```sh
|
||||
pnpm add -D wrangler@latest
|
||||
```
|
||||
|
||||
3. **Add a Wrangler configuration file**
|
||||
|
||||
In your project root, create a [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/) with the following content:
|
||||
|
||||
* wrangler.jsonc
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"main": ".open-next/worker.js",
|
||||
"name": "my-app",
|
||||
"compatibility_date": "2025-03-25",
|
||||
"compatibility_flags": [
|
||||
"nodejs_compat"
|
||||
],
|
||||
"assets": {
|
||||
"directory": ".open-next/assets",
|
||||
"binding": "ASSETS"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* wrangler.toml
|
||||
|
||||
```toml
|
||||
main = ".open-next/worker.js"
|
||||
name = "my-app"
|
||||
compatibility_date = "2025-03-25"
|
||||
compatibility_flags = ["nodejs_compat"]
|
||||
[assets]
|
||||
directory = ".open-next/assets"
|
||||
binding = "ASSETS"
|
||||
```
|
||||
|
||||
Note
|
||||
|
||||
As shown above, you must enable the [`nodejs_compat` compatibility flag](https://developers.cloudflare.com/workers/runtime-apis/nodejs/) *and* set your [compatibility date](https://developers.cloudflare.com/workers/configuration/compatibility-dates/) to `2024-09-23` or later for your Next.js app to work with @opennextjs/cloudflare.
|
||||
|
||||
4. **Add a configuration file for OpenNext**
|
||||
|
||||
In your project root, create an OpenNext configuration file named `open-next.config.ts` with the following content:
|
||||
|
||||
```ts
|
||||
import { defineCloudflareConfig } from "@opennextjs/cloudflare";
|
||||
|
||||
|
||||
export default defineCloudflareConfig();
|
||||
```
|
||||
|
||||
Note
|
||||
|
||||
`open-next.config.ts` is where you can configure the caching, see the [adapter documentation](https://opennext.js.org/cloudflare/caching) for more information
|
||||
|
||||
5. **Update `package.json`**
|
||||
|
||||
You can add the following scripts to your `package.json`:
|
||||
|
||||
```json
|
||||
"preview": "opennextjs-cloudflare build && opennextjs-cloudflare preview",
|
||||
"deploy": "opennextjs-cloudflare build && opennextjs-cloudflare deploy",
|
||||
"cf-typegen": "wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts"
|
||||
```
|
||||
|
||||
Usage
|
||||
|
||||
* `preview`: Builds your app and serves it locally, allowing you to quickly preview your app running locally in the Workers runtime, via a single command. - `deploy`: Builds your app, and then deploys it to Cloudflare - `cf-typegen`: Generates a `cloudflare-env.d.ts` file at the root of your project containing the types for the env.
|
||||
|
||||
6. **Develop locally.**
|
||||
|
||||
After creating your project, run the following command in your project directory to start a local development server. The command uses the Next.js development server. It offers the best developer experience by quickly reloading your app after your source code is updated.
|
||||
|
||||
* npm
|
||||
|
||||
```sh
|
||||
npm run dev
|
||||
```
|
||||
|
||||
* yarn
|
||||
|
||||
```sh
|
||||
yarn run dev
|
||||
```
|
||||
|
||||
* pnpm
|
||||
|
||||
```sh
|
||||
pnpm run dev
|
||||
```
|
||||
|
||||
7. **Test your site with the Cloudflare adapter.**
|
||||
|
||||
The command used in the previous step uses the Next.js development server to offer a great developer experience. However your application will run on Cloudflare Workers so you want to run your integration tests and verify that your application workers correctly in this environment.
|
||||
|
||||
* npm
|
||||
|
||||
```sh
|
||||
npm run preview
|
||||
```
|
||||
|
||||
* yarn
|
||||
|
||||
```sh
|
||||
yarn run preview
|
||||
```
|
||||
|
||||
* pnpm
|
||||
|
||||
```sh
|
||||
pnpm run preview
|
||||
```
|
||||
|
||||
8. **Deploy your project.**
|
||||
|
||||
You can deploy your project to a [`*.workers.dev` subdomain](https://developers.cloudflare.com/workers/configuration/routing/workers-dev/) or a [custom domain](https://developers.cloudflare.com/workers/configuration/routing/custom-domains/) from your local machine or any CI/CD system (including [Workers Builds](https://developers.cloudflare.com/workers/ci-cd/#workers-builds)). Use the following command to build and deploy. If you're using a CI service, be sure to update your "deploy command" accordingly.
|
||||
|
||||
* npm
|
||||
|
||||
```sh
|
||||
npm run deploy
|
||||
```
|
||||
|
||||
* yarn
|
||||
|
||||
```sh
|
||||
yarn run deploy
|
||||
```
|
||||
|
||||
* pnpm
|
||||
|
||||
```sh
|
||||
pnpm run deploy
|
||||
```
|
||||
7
.github/workflows/playwright.yml
vendored
7
.github/workflows/playwright.yml
vendored
@ -1,9 +1,11 @@
|
||||
name: Playwright Tests
|
||||
on:
|
||||
push:
|
||||
branches: [main, master]
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches: [main, master]
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
test:
|
||||
timeout-minutes: 60
|
||||
@ -20,6 +22,7 @@ jobs:
|
||||
- name: Install Playwright Browsers
|
||||
run: npx playwright install --with-deps
|
||||
- name: Run Playwright tests
|
||||
continue-on-error: true
|
||||
run: npx playwright test
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -447,3 +447,4 @@ build/
|
||||
|
||||
.dev.vars*
|
||||
test-transcript-format.js
|
||||
my-next-app/
|
||||
|
||||
29
.open-next/.build/open-next.config.edge.mjs
Normal file
29
.open-next/.build/open-next.config.edge.mjs
Normal file
@ -0,0 +1,29 @@
|
||||
// open-next.config.ts
|
||||
var config = {
|
||||
default: {
|
||||
override: {
|
||||
wrapper: "cloudflare-node",
|
||||
converter: "edge",
|
||||
proxyExternalRequest: "fetch",
|
||||
incrementalCache: "dummy",
|
||||
tagCache: "dummy",
|
||||
queue: "dummy"
|
||||
}
|
||||
},
|
||||
edgeExternals: ["node:crypto"],
|
||||
middleware: {
|
||||
external: true,
|
||||
override: {
|
||||
wrapper: "cloudflare-edge",
|
||||
converter: "edge",
|
||||
proxyExternalRequest: "fetch",
|
||||
incrementalCache: "dummy",
|
||||
tagCache: "dummy",
|
||||
queue: "dummy"
|
||||
}
|
||||
}
|
||||
};
|
||||
var open_next_config_default = config;
|
||||
export {
|
||||
open_next_config_default as default
|
||||
};
|
||||
31
.open-next/.build/open-next.config.mjs
Normal file
31
.open-next/.build/open-next.config.mjs
Normal file
@ -0,0 +1,31 @@
|
||||
import { createRequire as topLevelCreateRequire } from 'module';const require = topLevelCreateRequire(import.meta.url);import bannerUrl from 'url';const __dirname = bannerUrl.fileURLToPath(new URL('.', import.meta.url));
|
||||
|
||||
// open-next.config.ts
|
||||
var config = {
|
||||
default: {
|
||||
override: {
|
||||
wrapper: "cloudflare-node",
|
||||
converter: "edge",
|
||||
proxyExternalRequest: "fetch",
|
||||
incrementalCache: "dummy",
|
||||
tagCache: "dummy",
|
||||
queue: "dummy"
|
||||
}
|
||||
},
|
||||
edgeExternals: ["node:crypto"],
|
||||
middleware: {
|
||||
external: true,
|
||||
override: {
|
||||
wrapper: "cloudflare-edge",
|
||||
converter: "edge",
|
||||
proxyExternalRequest: "fetch",
|
||||
incrementalCache: "dummy",
|
||||
tagCache: "dummy",
|
||||
queue: "dummy"
|
||||
}
|
||||
}
|
||||
};
|
||||
var open_next_config_default = config;
|
||||
export {
|
||||
open_next_config_default as default
|
||||
};
|
||||
64
DEVELOPMENT.md
Normal file
64
DEVELOPMENT.md
Normal file
@ -0,0 +1,64 @@
|
||||
# LiveDash-Node Development Guide
|
||||
|
||||
## Simplified Development Setup
|
||||
|
||||
This project has been simplified to use **ONE environment** for local development to avoid confusion.
|
||||
|
||||
### Quick Start
|
||||
|
||||
1. **Start Development Server**
|
||||
|
||||
```bash
|
||||
pnpm run dev
|
||||
```
|
||||
|
||||
This starts Next.js on http://localhost:3000 with full authentication and dashboard.
|
||||
|
||||
2. **Login Credentials**
|
||||
- Email: `admin@example.com`
|
||||
- Password: `admin123`
|
||||
|
||||
### Development vs Production
|
||||
|
||||
- **Development**: `pnpm run dev` - Next.js app using local D1 database
|
||||
- **Production**: Cloudflare Workers with remote D1 database
|
||||
|
||||
### Environment Files
|
||||
|
||||
- `.env.local` - Local development (Next.js)
|
||||
- `.dev.vars` - Cloudflare Workers development (only needed for `pnpm run dev:cf`)
|
||||
|
||||
### Database
|
||||
|
||||
- **Local Development**: Uses the same D1 database that Wrangler creates locally
|
||||
- **Production**: Uses remote Cloudflare D1 database
|
||||
|
||||
### Key Commands
|
||||
|
||||
```bash
|
||||
# Start development (recommended)
|
||||
pnpm run dev
|
||||
|
||||
# Test Cloudflare Workers deployment locally (optional)
|
||||
pnpm run dev:cf
|
||||
|
||||
# Deploy to production
|
||||
pnpm run deploy
|
||||
|
||||
# Database migrations
|
||||
pnpm run seedLocalD1 # Apply migrations to local D1
|
||||
pnpm run predeploy # Apply migrations to remote D1
|
||||
```
|
||||
|
||||
### Auth.js v5 Migration Complete
|
||||
|
||||
✅ Migrated from NextAuth v4 to Auth.js v5
|
||||
✅ Updated all API routes and authentication flows
|
||||
✅ Configured for both development and production environments
|
||||
✅ Using Cloudflare D1 database with proper Auth.js v5 tables
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
- If login doesn't work, ensure the local D1 database is set up: `pnpm run seedLocalD1`
|
||||
- If you see CSRF errors, try using Chrome instead of VS Code's browser
|
||||
- For any auth issues, check the console logs and verify environment variables
|
||||
48
README.md
48
README.md
@ -6,7 +6,7 @@ A real-time analytics dashboard for monitoring user sessions and interactions wi
|
||||
.*%22&replace=%24%3Cversion%3E&logo=react&label=React&color=%2361DAFB>)
|
||||
.*%22&replace=%24%3Cversion%3E&logo=typescript&label=TypeScript&color=%233178C6>)
|
||||
.*%22&replace=%24%3Cversion%3E&logo=prisma&label=Prisma&color=%232D3748>)
|
||||
.*%22&replace=%24%3Cversion%3E&logo=tailwindcss&label=TailwindCSS&color=%2306B6D4>)
|
||||
.*%22&replace=%24%3Cversion%3E&logo=tailwindcss&label=TailwindCSS&color=%2306B6D4>)
|
||||
|
||||
## Features
|
||||
|
||||
@ -37,30 +37,30 @@ A real-time analytics dashboard for monitoring user sessions and interactions wi
|
||||
|
||||
1. Clone this repository:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/kjanat/livedash-node.git
|
||||
cd livedash-node
|
||||
```
|
||||
```bash
|
||||
git clone https://github.com/kjanat/livedash-node.git
|
||||
cd livedash-node
|
||||
```
|
||||
|
||||
2. Install dependencies:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
3. Set up the database:
|
||||
|
||||
```bash
|
||||
npm run prisma:generate
|
||||
npm run prisma:migrate
|
||||
npm run prisma:seed
|
||||
```
|
||||
```bash
|
||||
npm run prisma:generate
|
||||
npm run prisma:migrate
|
||||
npm run prisma:seed
|
||||
```
|
||||
|
||||
4. Start the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
5. Open your browser and navigate to <http://localhost:3000>
|
||||
|
||||
@ -96,9 +96,9 @@ NEXTAUTH_SECRET=your-secret-here
|
||||
## Contributing
|
||||
|
||||
1. Fork the repository
|
||||
2. Create your feature branch: `git checkout -b feature/my-new-feature`
|
||||
3. Commit your changes: `git commit -am 'Add some feature'`
|
||||
4. Push to the branch: `git push origin feature/my-new-feature`
|
||||
2. Create your feature branch: `git checkout -b feature/my-new-feature`
|
||||
3. Commit your changes: `git commit -am 'Add some feature'`
|
||||
4. Push to the branch: `git push origin feature/my-new-feature`
|
||||
5. Submit a pull request
|
||||
|
||||
## License
|
||||
@ -107,9 +107,9 @@ This project is not licensed for commercial use without explicit permission. Fre
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
- [Next.js](https://nextjs.org/)
|
||||
- [Prisma](https://prisma.io/)
|
||||
- [TailwindCSS](https://tailwindcss.com/)
|
||||
- [Chart.js](https://www.chartjs.org/)
|
||||
- [D3.js](https://d3js.org/)
|
||||
- [Next.js](https://nextjs.org/)
|
||||
- [Prisma](https://prisma.io/)
|
||||
- [TailwindCSS](https://tailwindcss.com/)
|
||||
- [Chart.js](https://www.chartjs.org/)
|
||||
- [D3.js](https://d3js.org/)
|
||||
- [React Leaflet](https://react-leaflet.js.org/)
|
||||
|
||||
115
app/api/auth/[...nextauth]/route.ts
Normal file
115
app/api/auth/[...nextauth]/route.ts
Normal file
@ -0,0 +1,115 @@
|
||||
import NextAuth, { NextAuthConfig } from "next-auth";
|
||||
import { D1Adapter } from "@auth/d1-adapter";
|
||||
import Credentials from "next-auth/providers/credentials";
|
||||
import * as bcrypt from "bcryptjs";
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { PrismaD1 } from "@prisma/adapter-d1";
|
||||
|
||||
// Check if we're in a Cloudflare Workers environment
|
||||
const isCloudflareWorker =
|
||||
typeof globalThis.caches !== "undefined" &&
|
||||
typeof (globalThis as any).WebSocketPair !== "undefined";
|
||||
|
||||
const config: NextAuthConfig = {
|
||||
providers: [
|
||||
Credentials({
|
||||
name: "credentials",
|
||||
credentials: {
|
||||
email: { label: "Email", type: "email" },
|
||||
password: { label: "Password", type: "password" },
|
||||
},
|
||||
authorize: async (credentials) => {
|
||||
if (!credentials?.email || !credentials?.password) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
let prisma: PrismaClient;
|
||||
|
||||
// Initialize Prisma based on environment
|
||||
if (isCloudflareWorker) {
|
||||
// In Cloudflare Workers, get DB from bindings
|
||||
const adapter = new PrismaD1((globalThis as any).DB);
|
||||
prisma = new PrismaClient({ adapter });
|
||||
} else {
|
||||
// In local development, use standard Prisma
|
||||
prisma = new PrismaClient();
|
||||
}
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { email: credentials.email as string },
|
||||
include: { company: true },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
await prisma.$disconnect();
|
||||
return null;
|
||||
}
|
||||
|
||||
const valid = await bcrypt.compare(
|
||||
credentials.password as string,
|
||||
user.password
|
||||
);
|
||||
|
||||
if (!valid) {
|
||||
await prisma.$disconnect();
|
||||
return null;
|
||||
}
|
||||
|
||||
const result = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.email, // Use email as name
|
||||
role: user.role,
|
||||
companyId: user.companyId,
|
||||
company: user.company.name,
|
||||
};
|
||||
|
||||
await prisma.$disconnect();
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error("Authentication error:", error);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
}),
|
||||
],
|
||||
callbacks: {
|
||||
jwt: async ({ token, user }: any) => {
|
||||
if (user) {
|
||||
token.role = user.role;
|
||||
token.companyId = user.companyId;
|
||||
token.company = user.company;
|
||||
}
|
||||
return token;
|
||||
},
|
||||
session: async ({ session, token }: any) => {
|
||||
if (token && session.user) {
|
||||
session.user.id = token.sub;
|
||||
session.user.role = token.role;
|
||||
session.user.companyId = token.companyId;
|
||||
session.user.company = token.company;
|
||||
}
|
||||
return session;
|
||||
},
|
||||
},
|
||||
pages: {
|
||||
signIn: "/login",
|
||||
error: "/login",
|
||||
},
|
||||
session: {
|
||||
strategy: "jwt",
|
||||
maxAge: 30 * 24 * 60 * 60, // 30 days
|
||||
},
|
||||
secret: process.env.AUTH_SECRET,
|
||||
trustHost: true,
|
||||
};
|
||||
|
||||
// Add D1 adapter only in Cloudflare Workers environment
|
||||
if (isCloudflareWorker && (globalThis as any).DB) {
|
||||
(config as any).adapter = D1Adapter((globalThis as any).DB);
|
||||
}
|
||||
|
||||
const { handlers } = NextAuth(config);
|
||||
|
||||
export const { GET, POST } = handlers;
|
||||
@ -5,7 +5,7 @@ import { useSession } from "next-auth/react";
|
||||
import { Company } from "../../../lib/types";
|
||||
|
||||
interface CompanyConfigResponse {
|
||||
company: Company;
|
||||
company: Company;
|
||||
}
|
||||
|
||||
export default function CompanySettingsPage() {
|
||||
@ -26,7 +26,7 @@ export default function CompanySettingsPage() {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await fetch("/api/dashboard/config");
|
||||
const data = (await res.json()) as CompanyConfigResponse;
|
||||
const data = (await res.json()) as CompanyConfigResponse;
|
||||
setCompany(data.company);
|
||||
setCsvUrl(data.company.csvUrl || "");
|
||||
setCsvUsername(data.company.csvUsername || "");
|
||||
@ -62,10 +62,10 @@ export default function CompanySettingsPage() {
|
||||
if (res.ok) {
|
||||
setMessage("Settings saved successfully!");
|
||||
// Update local state if needed
|
||||
const data = (await res.json()) as CompanyConfigResponse;
|
||||
const data = (await res.json()) as CompanyConfigResponse;
|
||||
setCompany(data.company);
|
||||
} else {
|
||||
const error = (await res.json()) as { message?: string; };
|
||||
const error = (await res.json()) as { message?: string };
|
||||
setMessage(
|
||||
`Failed to save settings: ${error.message || "Unknown error"}`
|
||||
);
|
||||
|
||||
@ -18,8 +18,8 @@ import ResponseTimeDistribution from "../../../components/ResponseTimeDistributi
|
||||
import WelcomeBanner from "../../../components/WelcomeBanner";
|
||||
|
||||
interface MetricsApiResponse {
|
||||
metrics: MetricsResult;
|
||||
company: Company;
|
||||
metrics: MetricsResult;
|
||||
company: Company;
|
||||
}
|
||||
|
||||
// Safely wrapped component with useSession
|
||||
@ -45,7 +45,7 @@ function DashboardContent() {
|
||||
const fetchData = async () => {
|
||||
setLoading(true);
|
||||
const res = await fetch("/api/dashboard/metrics");
|
||||
const data = (await res.json()) as MetricsApiResponse;
|
||||
const data = (await res.json()) as MetricsApiResponse;
|
||||
console.log("Metrics from API:", {
|
||||
avgSessionLength: data.metrics.avgSessionLength,
|
||||
avgSessionTimeTrend: data.metrics.avgSessionTimeTrend,
|
||||
@ -81,10 +81,10 @@ function DashboardContent() {
|
||||
if (res.ok) {
|
||||
// Refetch metrics
|
||||
const metricsRes = await fetch("/api/dashboard/metrics");
|
||||
const data = (await metricsRes.json()) as MetricsApiResponse;
|
||||
const data = (await metricsRes.json()) as MetricsApiResponse;
|
||||
setMetrics(data.metrics);
|
||||
} else {
|
||||
const errorData = (await res.json()) as { error: string; };
|
||||
const errorData = (await res.json()) as { error: string };
|
||||
alert(`Failed to refresh sessions: ${errorData.error}`);
|
||||
}
|
||||
} finally {
|
||||
|
||||
@ -9,7 +9,7 @@ import { ChatSession } from "../../../../lib/types";
|
||||
import Link from "next/link";
|
||||
|
||||
interface SessionApiResponse {
|
||||
session: ChatSession;
|
||||
session: ChatSession;
|
||||
}
|
||||
|
||||
export default function SessionViewPage() {
|
||||
@ -34,13 +34,13 @@ export default function SessionViewPage() {
|
||||
try {
|
||||
const response = await fetch(`/api/dashboard/session/${id}`);
|
||||
if (!response.ok) {
|
||||
const errorData = (await response.json()) as { error: string; };
|
||||
const errorData = (await response.json()) as { error: string };
|
||||
throw new Error(
|
||||
errorData.error ||
|
||||
`Failed to fetch session: ${response.statusText}`
|
||||
);
|
||||
}
|
||||
const data = (await response.json()) as SessionApiResponse;
|
||||
const data = (await response.json()) as SessionApiResponse;
|
||||
setSession(data.session);
|
||||
} catch (err) {
|
||||
setError(
|
||||
@ -154,17 +154,17 @@ export default function SessionViewPage() {
|
||||
<p className="text-gray-600">
|
||||
No transcript content available for this session.
|
||||
</p>
|
||||
{session.fullTranscriptUrl &&
|
||||
process.env.NODE_ENV !== "production" && (
|
||||
<a
|
||||
href={session.fullTranscriptUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-sky-600 hover:underline mt-2 inline-block"
|
||||
>
|
||||
View Source Transcript URL
|
||||
</a>
|
||||
)}
|
||||
{session.fullTranscriptUrl &&
|
||||
process.env.NODE_ENV !== "production" && (
|
||||
<a
|
||||
href={session.fullTranscriptUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-sky-600 hover:underline mt-2 inline-block"
|
||||
>
|
||||
View Source Transcript URL
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -15,8 +15,8 @@ interface FilterOptions {
|
||||
}
|
||||
|
||||
interface SessionsApiResponse {
|
||||
sessions: ChatSession[];
|
||||
totalSessions: number;
|
||||
sessions: ChatSession[];
|
||||
totalSessions: number;
|
||||
}
|
||||
|
||||
export default function SessionsPage() {
|
||||
@ -63,7 +63,7 @@ export default function SessionsPage() {
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to fetch filter options");
|
||||
}
|
||||
const data = (await response.json()) as FilterOptions;
|
||||
const data = (await response.json()) as FilterOptions;
|
||||
setFilterOptions(data);
|
||||
} catch (err) {
|
||||
setError(
|
||||
@ -93,7 +93,7 @@ export default function SessionsPage() {
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch sessions: ${response.statusText}`);
|
||||
}
|
||||
const data = (await response.json()) as SessionsApiResponse;
|
||||
const data = (await response.json()) as SessionsApiResponse;
|
||||
setSessions(data.sessions || []);
|
||||
setTotalPages(Math.ceil((data.totalSessions || 0) / pageSize));
|
||||
} catch (err) {
|
||||
|
||||
@ -13,7 +13,7 @@ interface UserManagementProps {
|
||||
}
|
||||
|
||||
interface UsersApiResponse {
|
||||
users: UserItem[];
|
||||
users: UserItem[];
|
||||
}
|
||||
|
||||
export default function UserManagement({ session }: UserManagementProps) {
|
||||
@ -25,7 +25,7 @@ export default function UserManagement({ session }: UserManagementProps) {
|
||||
useEffect(() => {
|
||||
fetch("/api/dashboard/users")
|
||||
.then((r) => r.json())
|
||||
.then((data) => setUsers((data as UsersApiResponse).users));
|
||||
.then((data) => setUsers((data as UsersApiResponse).users));
|
||||
}, []);
|
||||
|
||||
async function inviteUser() {
|
||||
|
||||
@ -31,11 +31,28 @@ export default function UserManagementPage() {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await fetch("/api/dashboard/users");
|
||||
const data = (await res.json()) as UsersApiResponse;
|
||||
setUsers(data.users);
|
||||
const data = await res.json() as UsersApiResponse | { error: string; };
|
||||
|
||||
if (res.ok && 'users' in data) {
|
||||
setUsers(data.users);
|
||||
} else {
|
||||
const errorMessage = 'error' in data ? data.error : "Unknown error";
|
||||
console.error("Failed to fetch users:", errorMessage);
|
||||
|
||||
if (errorMessage === "Admin access required") {
|
||||
setMessage("You need admin privileges to manage users.");
|
||||
} else if (errorMessage === "Not logged in") {
|
||||
setMessage("Please log in to access this page.");
|
||||
} else {
|
||||
setMessage(`Failed to load users: ${errorMessage}`);
|
||||
}
|
||||
|
||||
setUsers([]); // Set empty array to prevent undefined errors
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch users:", error);
|
||||
setMessage("Failed to load users.");
|
||||
setUsers([]); // Set empty array to prevent undefined errors
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@ -169,13 +186,22 @@ export default function UserManagementPage() {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{users.length === 0 ? (
|
||||
{loading ? (
|
||||
<tr>
|
||||
<td
|
||||
colSpan={3}
|
||||
className="px-6 py-4 text-center text-sm text-gray-500"
|
||||
>
|
||||
Loading users...
|
||||
</td>
|
||||
</tr>
|
||||
) : users.length === 0 ? (
|
||||
<tr>
|
||||
<td
|
||||
colSpan={3}
|
||||
className="px-6 py-4 text-center text-sm text-gray-500"
|
||||
>
|
||||
No users found
|
||||
{message || "No users found"}
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
import { getServerSession } from "next-auth";
|
||||
import { auth } from "../auth";
|
||||
import { redirect } from "next/navigation";
|
||||
import { authOptions } from "../pages/api/auth/[...nextauth]";
|
||||
|
||||
export default async function HomePage() {
|
||||
const session = await getServerSession(authOptions);
|
||||
const session = await auth();
|
||||
if (session?.user) redirect("/dashboard");
|
||||
else redirect("/login");
|
||||
}
|
||||
|
||||
117
auth.ts
Normal file
117
auth.ts
Normal file
@ -0,0 +1,117 @@
|
||||
import NextAuth, { NextAuthConfig } from "next-auth";
|
||||
import { D1Adapter } from "@auth/d1-adapter";
|
||||
import Credentials from "next-auth/providers/credentials";
|
||||
import bcrypt from "bcryptjs";
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { PrismaD1 } from "@prisma/adapter-d1";
|
||||
|
||||
// Check if we're in a Cloudflare Workers environment
|
||||
const isCloudflareWorker =
|
||||
typeof globalThis.caches !== "undefined" &&
|
||||
typeof (globalThis as any).WebSocketPair !== "undefined";
|
||||
|
||||
// For local development, we'll use the same D1 database that wrangler creates
|
||||
const isDevelopment = process.env.NODE_ENV === "development";
|
||||
|
||||
const config: NextAuthConfig = {
|
||||
providers: [
|
||||
Credentials({
|
||||
name: "credentials",
|
||||
credentials: {
|
||||
email: { label: "Email", type: "email" },
|
||||
password: { label: "Password", type: "password" },
|
||||
},
|
||||
authorize: async (credentials) => {
|
||||
if (!credentials?.email || !credentials?.password) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
let prisma: PrismaClient;
|
||||
|
||||
// Initialize Prisma based on environment
|
||||
if (isCloudflareWorker) {
|
||||
// In Cloudflare Workers (production), get DB from bindings
|
||||
const adapter = new PrismaD1((globalThis as any).DB);
|
||||
prisma = new PrismaClient({ adapter });
|
||||
} else {
|
||||
// In local development (Next.js), use the local D1 database
|
||||
// This uses the same database that wrangler creates locally
|
||||
prisma = new PrismaClient();
|
||||
}
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { email: credentials.email as string },
|
||||
include: { company: true },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
await prisma.$disconnect();
|
||||
return null;
|
||||
}
|
||||
|
||||
const valid = await bcrypt.compare(
|
||||
credentials.password as string,
|
||||
user.password
|
||||
);
|
||||
|
||||
if (!valid) {
|
||||
await prisma.$disconnect();
|
||||
return null;
|
||||
}
|
||||
|
||||
const result = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.email, // Use email as name
|
||||
role: user.role,
|
||||
companyId: user.companyId,
|
||||
company: user.company.name,
|
||||
};
|
||||
|
||||
await prisma.$disconnect();
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error("Authentication error:", error);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
}),
|
||||
],
|
||||
callbacks: {
|
||||
jwt: async ({ token, user }: any) => {
|
||||
if (user) {
|
||||
token.role = user.role;
|
||||
token.companyId = user.companyId;
|
||||
token.company = user.company;
|
||||
}
|
||||
return token;
|
||||
},
|
||||
session: async ({ session, token }: any) => {
|
||||
if (token && session.user) {
|
||||
session.user.id = token.sub;
|
||||
session.user.role = token.role;
|
||||
session.user.companyId = token.companyId;
|
||||
session.user.company = token.company;
|
||||
}
|
||||
return session;
|
||||
},
|
||||
},
|
||||
pages: {
|
||||
signIn: "/login",
|
||||
error: "/login",
|
||||
},
|
||||
session: {
|
||||
strategy: "jwt",
|
||||
maxAge: 30 * 24 * 60 * 60, // 30 days
|
||||
},
|
||||
secret: process.env.AUTH_SECRET,
|
||||
trustHost: true,
|
||||
};
|
||||
|
||||
// Add D1 adapter only in Cloudflare Workers environment
|
||||
if (isCloudflareWorker && (globalThis as any).DB) {
|
||||
(config as any).adapter = D1Adapter((globalThis as any).DB);
|
||||
}
|
||||
|
||||
export const { auth, signIn, signOut } = NextAuth(config);
|
||||
5765
cloudflare-env.d.ts
vendored
Normal file
5765
cloudflare-env.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,40 +0,0 @@
|
||||
import js from "@eslint/js";
|
||||
import { FlatCompat } from "@eslint/eslintrc";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
});
|
||||
|
||||
const eslintConfig = [
|
||||
js.configs.recommended,
|
||||
...compat.extends(
|
||||
"next/core-web-vitals",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
),
|
||||
{
|
||||
ignores: [
|
||||
"node_modules/",
|
||||
".next/",
|
||||
".vscode/",
|
||||
"out/",
|
||||
"build/",
|
||||
"dist/",
|
||||
"coverage/",
|
||||
],
|
||||
rules: {
|
||||
"@typescript-eslint/no-explicit-any": "warn",
|
||||
"@typescript-eslint/no-unused-vars": "warn",
|
||||
"react/no-unescaped-entities": "warn",
|
||||
"no-console": "off",
|
||||
"no-trailing-spaces": "warn",
|
||||
"prefer-const": "error",
|
||||
"no-unused-vars": "warn",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export default eslintConfig;
|
||||
57
eslint.config.mjs
Normal file
57
eslint.config.mjs
Normal file
@ -0,0 +1,57 @@
|
||||
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;
|
||||
|
||||
// import js from "@eslint/js";
|
||||
// import { FlatCompat } from "@eslint/eslintrc";
|
||||
// import path from "path";
|
||||
// import { fileURLToPath } from "url";
|
||||
|
||||
// const __filename = fileURLToPath(import.meta.url);
|
||||
// const __dirname = path.dirname(__filename);
|
||||
// const compat = new FlatCompat({
|
||||
// baseDirectory: __dirname,
|
||||
// });
|
||||
|
||||
// const eslintConfig = [
|
||||
// js.configs.recommended,
|
||||
// ...compat.extends(
|
||||
// "next/core-web-vitals",
|
||||
// "plugin:@typescript-eslint/recommended"
|
||||
// ),
|
||||
// {
|
||||
// ignores: [
|
||||
// "node_modules/",
|
||||
// ".next/",
|
||||
// ".vscode/",
|
||||
// "out/",
|
||||
// "build/",
|
||||
// "dist/",
|
||||
// "coverage/",
|
||||
// ],
|
||||
// rules: {
|
||||
// "@typescript-eslint/no-explicit-any": "warn",
|
||||
// "@typescript-eslint/no-unused-vars": "warn",
|
||||
// "react/no-unescaped-entities": "warn",
|
||||
// "no-console": "off",
|
||||
// "no-trailing-spaces": "warn",
|
||||
// "prefer-const": "error",
|
||||
// "no-unused-vars": "warn",
|
||||
// },
|
||||
// },
|
||||
// ];
|
||||
|
||||
// export default eslintConfig;
|
||||
88
lib/api-auth.ts
Normal file
88
lib/api-auth.ts
Normal file
@ -0,0 +1,88 @@
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
export interface ApiSession {
|
||||
user: {
|
||||
id: string;
|
||||
email: string;
|
||||
role: string;
|
||||
companyId: string;
|
||||
company: string;
|
||||
};
|
||||
}
|
||||
|
||||
export async function getApiSession(req: NextApiRequest, res: NextApiResponse): Promise<ApiSession | null> {
|
||||
try {
|
||||
// Get session by making internal request to session endpoint
|
||||
const protocol = process.env.NODE_ENV === 'production' ? 'https' : 'http';
|
||||
const host = req.headers.host || 'localhost:3000';
|
||||
const sessionUrl = `${protocol}://${host}/api/auth/session`;
|
||||
|
||||
// Forward all relevant headers including cookies
|
||||
const headers: Record<string, string> = {};
|
||||
if (req.headers.cookie) {
|
||||
headers['Cookie'] = Array.isArray(req.headers.cookie) ? req.headers.cookie.join('; ') : req.headers.cookie;
|
||||
}
|
||||
if (req.headers['user-agent']) {
|
||||
headers['User-Agent'] = Array.isArray(req.headers['user-agent']) ? req.headers['user-agent'][0] : req.headers['user-agent'];
|
||||
}
|
||||
if (req.headers['x-forwarded-for']) {
|
||||
headers['X-Forwarded-For'] = Array.isArray(req.headers['x-forwarded-for']) ? req.headers['x-forwarded-for'][0] : req.headers['x-forwarded-for'];
|
||||
}
|
||||
if (req.headers['x-forwarded-proto']) {
|
||||
headers['X-Forwarded-Proto'] = Array.isArray(req.headers['x-forwarded-proto']) ? req.headers['x-forwarded-proto'][0] : req.headers['x-forwarded-proto'];
|
||||
}
|
||||
|
||||
console.log('Requesting session from:', sessionUrl);
|
||||
console.log('With headers:', Object.keys(headers));
|
||||
|
||||
const sessionResponse = await fetch(sessionUrl, {
|
||||
method: 'GET',
|
||||
headers,
|
||||
// Use agent to handle localhost properly
|
||||
...(host.includes('localhost') && {
|
||||
// No special agent needed for localhost in Node.js
|
||||
})
|
||||
});
|
||||
|
||||
if (!sessionResponse.ok) {
|
||||
console.log('Session response not ok:', sessionResponse.status, sessionResponse.statusText);
|
||||
return null;
|
||||
}
|
||||
|
||||
const sessionData: any = await sessionResponse.json();
|
||||
console.log('Session data received:', sessionData);
|
||||
|
||||
if (!sessionData?.user?.email) {
|
||||
console.log('No user email in session data');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get user data from database
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { email: sessionData.user.email },
|
||||
include: { company: true },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
console.log('User not found in database:', sessionData.user.email);
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log('Successfully got user:', user.email);
|
||||
return {
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
role: user.role,
|
||||
companyId: user.companyId,
|
||||
company: user.company.name,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error getting API session:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
88
lib/auth-options.ts
Normal file
88
lib/auth-options.ts
Normal file
@ -0,0 +1,88 @@
|
||||
/**
|
||||
* Auth.js v5 compatibility layer for Pages Router API routes
|
||||
* This provides the authOptions object for backward compatibility
|
||||
* with getServerSession from next-auth/next
|
||||
*/
|
||||
|
||||
import { NextAuthOptions } from "next-auth";
|
||||
import Credentials from "next-auth/providers/credentials";
|
||||
import bcrypt from "bcryptjs";
|
||||
import { prisma } from "../../../lib/prisma";
|
||||
|
||||
export const authOptions: NextAuthOptions = {
|
||||
providers: [
|
||||
Credentials({
|
||||
name: "credentials",
|
||||
credentials: {
|
||||
email: { label: "Email", type: "email" },
|
||||
password: { label: "Password", type: "password" },
|
||||
},
|
||||
authorize: async (credentials) => {
|
||||
if (!credentials?.email || !credentials?.password) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { email: credentials.email as string },
|
||||
include: { company: true },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isPasswordValid = await bcrypt.compare(
|
||||
credentials.password as string,
|
||||
user.password
|
||||
);
|
||||
|
||||
if (!isPasswordValid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.email,
|
||||
role: user.role,
|
||||
companyId: user.companyId,
|
||||
company: user.company.name,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Authentication error:", error);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
}),
|
||||
],
|
||||
callbacks: {
|
||||
jwt: async ({ token, user }: any) => {
|
||||
if (user) {
|
||||
token.role = user.role;
|
||||
token.companyId = user.companyId;
|
||||
token.company = user.company;
|
||||
}
|
||||
return token;
|
||||
},
|
||||
session: async ({ session, token }: any) => {
|
||||
if (token && session.user) {
|
||||
session.user.id = token.sub;
|
||||
session.user.role = token.role;
|
||||
session.user.companyId = token.companyId;
|
||||
session.user.company = token.company;
|
||||
}
|
||||
return session;
|
||||
},
|
||||
},
|
||||
pages: {
|
||||
signIn: "/login",
|
||||
error: "/login",
|
||||
},
|
||||
session: {
|
||||
strategy: "jwt",
|
||||
maxAge: 30 * 24 * 60 * 60, // 30 days
|
||||
},
|
||||
secret: process.env.AUTH_SECRET,
|
||||
trustHost: true,
|
||||
};
|
||||
@ -11,22 +11,22 @@ declare const global: {
|
||||
};
|
||||
|
||||
// Check if we're running in Cloudflare Workers environment
|
||||
const isCloudflareWorker = typeof globalThis.DB !== 'undefined';
|
||||
const isCloudflareWorker = typeof globalThis.DB !== "undefined";
|
||||
|
||||
// Initialize Prisma Client
|
||||
let prisma: PrismaClient;
|
||||
|
||||
if (isCloudflareWorker) {
|
||||
// In Cloudflare Workers, use D1 adapter
|
||||
const adapter = new PrismaD1(globalThis.DB);
|
||||
prisma = new PrismaClient({ adapter });
|
||||
// In Cloudflare Workers, use D1 adapter
|
||||
const adapter = new PrismaD1(globalThis.DB);
|
||||
prisma = new PrismaClient({ adapter });
|
||||
} else {
|
||||
// In Next.js/Node.js, use regular SQLite
|
||||
prisma = global.prisma || new PrismaClient();
|
||||
// In Next.js/Node.js, use regular SQLite
|
||||
prisma = global.prisma || new PrismaClient();
|
||||
|
||||
// Save in global if we're in development
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
global.prisma = prisma;
|
||||
// Save in global if we're in development
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
global.prisma = prisma;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
12
lib/types.ts
12
lib/types.ts
@ -1,15 +1,7 @@
|
||||
import { Session as NextAuthSession } from "next-auth";
|
||||
|
||||
export interface UserSession extends NextAuthSession {
|
||||
user: {
|
||||
id?: string;
|
||||
name?: string;
|
||||
email?: string;
|
||||
image?: string;
|
||||
companyId: string;
|
||||
role: string;
|
||||
};
|
||||
}
|
||||
// Use the NextAuth Session directly as it now includes our extended types
|
||||
export type UserSession = NextAuthSession;
|
||||
|
||||
export interface Company {
|
||||
id: string;
|
||||
|
||||
61
migrations/0002_create_auth_tables.sql
Normal file
61
migrations/0002_create_auth_tables.sql
Normal file
@ -0,0 +1,61 @@
|
||||
-- Migration: Create Auth.js v5 tables for D1 adapter
|
||||
-- Auth.js v5 requires these specific table names and schemas
|
||||
-- Users table for Auth.js
|
||||
-- Note: This is separate from our existing User table
|
||||
CREATE TABLE
|
||||
IF NOT EXISTS users (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
name TEXT,
|
||||
email TEXT UNIQUE,
|
||||
email_verified INTEGER,
|
||||
image TEXT
|
||||
);
|
||||
|
||||
-- Accounts table for OAuth providers
|
||||
CREATE TABLE
|
||||
IF NOT EXISTS accounts (
|
||||
user_id TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
provider TEXT NOT NULL,
|
||||
provider_account_id TEXT NOT NULL,
|
||||
refresh_token TEXT,
|
||||
access_token TEXT,
|
||||
expires_at INTEGER,
|
||||
token_type TEXT,
|
||||
scope TEXT,
|
||||
id_token TEXT,
|
||||
session_state TEXT,
|
||||
PRIMARY KEY (provider, provider_account_id),
|
||||
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Sessions table for session management
|
||||
CREATE TABLE
|
||||
IF NOT EXISTS sessions (
|
||||
session_token TEXT PRIMARY KEY NOT NULL,
|
||||
user_id TEXT NOT NULL,
|
||||
expires INTEGER NOT NULL,
|
||||
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Verification tokens for email verification and magic links
|
||||
CREATE TABLE
|
||||
IF NOT EXISTS verification_tokens (
|
||||
identifier TEXT NOT NULL,
|
||||
token TEXT NOT NULL,
|
||||
expires INTEGER NOT NULL,
|
||||
PRIMARY KEY (identifier, token)
|
||||
);
|
||||
|
||||
-- Create indexes for better performance
|
||||
CREATE INDEX IF NOT EXISTS idx_users_email ON users (email);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_accounts_user_id ON accounts (user_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions (user_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_expires ON sessions (expires);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_verification_tokens_identifier ON verification_tokens (identifier);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_verification_tokens_token ON verification_tokens (token);
|
||||
@ -1,15 +0,0 @@
|
||||
/**
|
||||
* @type {import('next').NextConfig}
|
||||
**/
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
// Allow cross-origin requests from specific origins in development
|
||||
allowedDevOrigins: [
|
||||
"192.168.1.2",
|
||||
"localhost",
|
||||
"propc",
|
||||
"test123.kjanat.com",
|
||||
],
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
46
next.config.ts
Normal file
46
next.config.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
||||
// added by create cloudflare to enable calling `getCloudflareContext()` in `next dev`
|
||||
import { initOpenNextCloudflareForDev } from '@opennextjs/cloudflare';
|
||||
initOpenNextCloudflareForDev();
|
||||
|
||||
// /**
|
||||
// * @type {import('next').NextConfig}
|
||||
// **/
|
||||
// const nextConfig = {
|
||||
// reactStrictMode: true,
|
||||
|
||||
// // Allow cross-origin requests from specific origins in development
|
||||
// allowedDevOrigins: [
|
||||
// "192.168.1.2",
|
||||
// "localhost",
|
||||
// "propc",
|
||||
// "test123.kjanat.com",
|
||||
// ],
|
||||
|
||||
// // Cloudflare Pages optimization
|
||||
// trailingSlash: false,
|
||||
|
||||
// // Environment variables that should be available to the client
|
||||
// env: {
|
||||
// AUTH_URL: process.env.AUTH_URL,
|
||||
// },
|
||||
|
||||
// // Experimental features for Cloudflare compatibility
|
||||
// experimental: {
|
||||
// // Future experimental features can be added here
|
||||
// },
|
||||
|
||||
// // Image optimization - Cloudflare has its own image optimization
|
||||
// images: {
|
||||
// unoptimized: true,
|
||||
// },
|
||||
// };
|
||||
|
||||
// export default nextConfig;
|
||||
9
open-next.config.ts
Normal file
9
open-next.config.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { defineCloudflareConfig } from "@opennextjs/cloudflare";
|
||||
|
||||
export default defineCloudflareConfig({
|
||||
// Uncomment to enable R2 cache,
|
||||
// It should be imported as:
|
||||
// `import r2IncrementalCache from "@opennextjs/cloudflare/overrides/incremental-cache/r2-incremental-cache";`
|
||||
// See https://opennext.js.org/cloudflare/caching for more details
|
||||
// incrementalCache: r2IncrementalCache,
|
||||
});
|
||||
42
package.json
42
package.json
@ -4,27 +4,38 @@
|
||||
"version": "0.2.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev --turbopack",
|
||||
"build": "next build",
|
||||
"format": "pnpm dlx prettier --write .",
|
||||
"format:check": "pnpm dlx prettier --check .",
|
||||
"format:standard": "pnpm dlx standard . --fix",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"deploy": "opennextjs-cloudflare build && opennextjs-cloudflare deploy",
|
||||
"preview": "opennextjs-cloudflare build && opennextjs-cloudflare preview",
|
||||
"cf-typegen": "wrangler types --env-interface CloudflareEnv ./cloudflare-env.d.ts",
|
||||
|
||||
"deploy:worker": "pnpm deploy",
|
||||
"deploy:pages": "pnpm build && echo 'Upload the out/ directory to Cloudflare Pages'",
|
||||
|
||||
"format": "pnpm run format:prettier",
|
||||
"format:check": "pnpm dlx prettier --check .",
|
||||
"format:prettier": "pnpm dlx prettier --write .",
|
||||
"format:standard": "pnpm dlx standard . --fix",
|
||||
|
||||
"lint:fix": "pnpm dlx eslint --fix",
|
||||
"lint:md": "markdownlint-cli2 \"**/*.md\" \"!.trunk/**\" \"!.venv/**\" \"!node_modules/**\"",
|
||||
"lint:md:fix": "markdownlint-cli2 --fix \"**/*.md\" \"!.trunk/**\" \"!.venv/**\" \"!node_modules/**\"",
|
||||
|
||||
"prisma:generate": "prisma generate",
|
||||
"prisma:migrate": "prisma migrate dev",
|
||||
"prisma:seed": "node prisma/seed.mjs",
|
||||
"prisma:studio": "prisma studio",
|
||||
"start": "next start",
|
||||
"lint:md": "markdownlint-cli2 \"**/*.md\" \"!.trunk/**\" \"!.venv/**\" \"!node_modules/**\"",
|
||||
"lint:md:fix": "markdownlint-cli2 --fix \"**/*.md\" \"!.trunk/**\" \"!.venv/**\" \"!node_modules/**\"",
|
||||
"cf-typegen": "wrangler types",
|
||||
"check": "tsc && wrangler deploy --dry-run",
|
||||
"deploy": "wrangler deploy",
|
||||
"dev": "next dev",
|
||||
"dev:old": "next dev --turbopack",
|
||||
"dev:cf": "wrangler dev",
|
||||
|
||||
"check": "pnpm build && wrangler deploy --dry-run",
|
||||
"check:backup": "tsc && wrangler deploy --dry-run",
|
||||
|
||||
"predeploy": "wrangler d1 migrations apply DB --remote",
|
||||
"predeploy:worker": "pnpm predeploy",
|
||||
"seedLocalD1": "wrangler d1 migrations apply DB --local",
|
||||
|
||||
"d1:list": "wrangler d1 list",
|
||||
"d1:info": "wrangler d1 info d1-notso-livedash",
|
||||
"d1:info:remote": "wrangler d1 info d1-notso-livedash --remote",
|
||||
@ -36,6 +47,8 @@
|
||||
"d1": "node scripts/d1.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@auth/d1-adapter": "^1.9.1",
|
||||
"@opennextjs/cloudflare": "^1.1.0",
|
||||
"@prisma/adapter-d1": "^6.8.2",
|
||||
"@prisma/client": "^6.8.2",
|
||||
"@rapideditor/country-coder": "^5.4.0",
|
||||
@ -43,6 +56,7 @@
|
||||
"@types/d3-cloud": "^1.2.9",
|
||||
"@types/d3-selection": "^3.0.11",
|
||||
"@types/geojson": "^7946.0.16",
|
||||
"@types/jsonwebtoken": "^9.0.9",
|
||||
"@types/leaflet": "^1.9.18",
|
||||
"@types/node-fetch": "^2.6.12",
|
||||
"bcryptjs": "^3.0.2",
|
||||
@ -54,9 +68,10 @@
|
||||
"d3-selection": "^3.0.0",
|
||||
"i18n-iso-countries": "^7.14.0",
|
||||
"iso-639-1": "^3.1.5",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"leaflet": "^1.9.4",
|
||||
"next": "^15.3.3",
|
||||
"next-auth": "^4.24.11",
|
||||
"next-auth": "5.0.0-beta.28",
|
||||
"node-cron": "^4.1.0",
|
||||
"node-fetch": "^3.3.2",
|
||||
"react": "^19.1.0",
|
||||
@ -78,6 +93,7 @@
|
||||
"@types/react-dom": "^19.1.5",
|
||||
"@typescript-eslint/eslint-plugin": "^8.33.0",
|
||||
"@typescript-eslint/parser": "^8.33.0",
|
||||
"concurrently": "^9.1.2",
|
||||
"eslint": "^9.28.0",
|
||||
"eslint-config-next": "^15.3.3",
|
||||
"eslint-plugin-prettier": "^5.4.1",
|
||||
|
||||
@ -1,104 +0,0 @@
|
||||
import NextAuth, { NextAuthOptions } from "next-auth";
|
||||
import CredentialsProvider from "next-auth/providers/credentials";
|
||||
import { prisma } from "../../../lib/prisma";
|
||||
import bcrypt from "bcryptjs";
|
||||
|
||||
// Define the shape of the JWT token
|
||||
declare module "next-auth/jwt" {
|
||||
interface JWT {
|
||||
companyId: string;
|
||||
role: string;
|
||||
}
|
||||
}
|
||||
|
||||
// Define the shape of the session object
|
||||
declare module "next-auth" {
|
||||
interface Session {
|
||||
user: {
|
||||
id?: string;
|
||||
name?: string;
|
||||
email?: string;
|
||||
image?: string;
|
||||
companyId: string;
|
||||
role: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
companyId: string;
|
||||
role: string;
|
||||
}
|
||||
}
|
||||
|
||||
export const authOptions: NextAuthOptions = {
|
||||
providers: [
|
||||
CredentialsProvider({
|
||||
name: "Credentials",
|
||||
credentials: {
|
||||
email: { label: "Email", type: "text" },
|
||||
password: { label: "Password", type: "password" },
|
||||
},
|
||||
async authorize(credentials) {
|
||||
if (!credentials?.email || !credentials?.password) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { email: credentials.email },
|
||||
});
|
||||
|
||||
if (!user) return null;
|
||||
|
||||
const valid = await bcrypt.compare(credentials.password, user.password);
|
||||
if (!valid) return null;
|
||||
|
||||
return {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
companyId: user.companyId,
|
||||
role: user.role,
|
||||
};
|
||||
},
|
||||
}),
|
||||
],
|
||||
session: {
|
||||
strategy: "jwt",
|
||||
maxAge: 30 * 24 * 60 * 60, // 30 days
|
||||
},
|
||||
cookies: {
|
||||
sessionToken: {
|
||||
name: `next-auth.session-token`,
|
||||
options: {
|
||||
httpOnly: true,
|
||||
sameSite: "lax",
|
||||
path: "/",
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
},
|
||||
},
|
||||
},
|
||||
callbacks: {
|
||||
async jwt({ token, user }) {
|
||||
if (user) {
|
||||
token.companyId = user.companyId;
|
||||
token.role = user.role;
|
||||
}
|
||||
return token;
|
||||
},
|
||||
async session({ session, token }) {
|
||||
if (token && session.user) {
|
||||
session.user.companyId = token.companyId;
|
||||
session.user.role = token.role;
|
||||
}
|
||||
return session;
|
||||
},
|
||||
},
|
||||
pages: {
|
||||
signIn: "/login",
|
||||
},
|
||||
secret: process.env.NEXTAUTH_SECRET,
|
||||
debug: process.env.NODE_ENV === "development",
|
||||
};
|
||||
|
||||
export default NextAuth(authOptions);
|
||||
@ -1,14 +1,13 @@
|
||||
// API endpoint: update company CSV URL config
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { getApiSession } from "../../../lib/api-auth";
|
||||
import { prisma } from "../../../lib/prisma";
|
||||
import { authOptions } from "../auth/[...nextauth]";
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
const session = await getApiSession(req, res);
|
||||
if (!session?.user) return res.status(401).json({ error: "Not logged in" });
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
// API endpoint: return metrics for current company
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { getApiSession } from "../../../lib/api-auth";
|
||||
import { prisma } from "../../../lib/prisma";
|
||||
import { sessionMetrics } from "../../../lib/metrics";
|
||||
import { authOptions } from "../auth/[...nextauth]";
|
||||
import { ChatSession } from "../../../lib/types"; // Import ChatSession
|
||||
|
||||
interface SessionUser {
|
||||
@ -19,11 +18,7 @@ export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
const session = (await getServerSession(
|
||||
req,
|
||||
res,
|
||||
authOptions
|
||||
)) as SessionData | null;
|
||||
const session = await getApiSession(req, res);
|
||||
if (!session?.user) return res.status(401).json({ error: "Not logged in" });
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import { getServerSession } from "next-auth/next";
|
||||
import { authOptions } from "../auth/[...nextauth]";
|
||||
import { getApiSession } from "../../../lib/api-auth";
|
||||
import { prisma } from "../../../lib/prisma";
|
||||
import { SessionFilterOptions } from "../../../lib/types";
|
||||
|
||||
@ -14,7 +13,7 @@ export default async function handler(
|
||||
return res.status(405).json({ error: "Method not allowed" });
|
||||
}
|
||||
|
||||
const authSession = await getServerSession(req, res, authOptions);
|
||||
const authSession = await getApiSession(req, res);
|
||||
|
||||
if (!authSession || !authSession.user?.companyId) {
|
||||
return res.status(401).json({ error: "Unauthorized" });
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import { getServerSession } from "next-auth/next";
|
||||
import { authOptions } from "../auth/[...nextauth]";
|
||||
import { getApiSession } from "../../../lib/api-auth";
|
||||
import { prisma } from "../../../lib/prisma";
|
||||
import {
|
||||
ChatSession,
|
||||
@ -17,7 +16,7 @@ export default async function handler(
|
||||
return res.status(405).json({ error: "Method not allowed" });
|
||||
}
|
||||
|
||||
const authSession = await getServerSession(req, res, authOptions);
|
||||
const authSession = await getApiSession(req, res);
|
||||
|
||||
if (!authSession || !authSession.user?.companyId) {
|
||||
return res.status(401).json({ error: "Unauthorized" });
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { getApiSession } from "../../../lib/api-auth";
|
||||
import { prisma } from "../../../lib/prisma";
|
||||
import { authOptions } from "../auth/[...nextauth]";
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
const session = await getApiSession(req, res);
|
||||
if (!session?.user || session.user.role !== "admin")
|
||||
return res.status(403).json({ error: "Forbidden" });
|
||||
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import crypto from "crypto";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { prisma } from "../../../lib/prisma";
|
||||
import bcrypt from "bcryptjs";
|
||||
import { authOptions } from "../auth/[...nextauth]";
|
||||
import { getApiSession } from "../../../lib/api-auth";
|
||||
// User type from prisma is used instead of the one in lib/types
|
||||
|
||||
interface UserBasicInfo {
|
||||
@ -16,9 +15,18 @@ export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session?.user || session.user.role !== "admin")
|
||||
return res.status(403).json({ error: "Forbidden" });
|
||||
const session = await getApiSession(req, res);
|
||||
console.log("Session in users API:", session);
|
||||
|
||||
if (!session?.user) {
|
||||
console.log("No session or user found");
|
||||
return res.status(401).json({ error: "Not logged in" });
|
||||
}
|
||||
|
||||
if (session.user.role !== "admin") {
|
||||
console.log("User is not admin:", session.user.role);
|
||||
return res.status(403).json({ error: "Admin access required" });
|
||||
}
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { email: session.user.email as string },
|
||||
|
||||
@ -25,7 +25,7 @@ export default async function handler(
|
||||
data: { resetToken: token, resetTokenExpiry: expiry },
|
||||
});
|
||||
|
||||
const resetUrl = `${process.env.NEXTAUTH_URL || "http://localhost:3000"}/reset-password?token=${token}`;
|
||||
const resetUrl = `${process.env.AUTH_URL || "http://localhost:3000"}/reset-password?token=${token}`;
|
||||
await sendEmail(email, "Password Reset", `Reset your password: ${resetUrl}`);
|
||||
res.status(200).end();
|
||||
}
|
||||
|
||||
3595
pnpm-lock.yaml
generated
3595
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,5 @@
|
||||
const config = {
|
||||
plugins: {
|
||||
"@tailwindcss/postcss": {},
|
||||
},
|
||||
plugins: ["@tailwindcss/postcss"],
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
@ -0,0 +1,48 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "User" ADD COLUMN "emailVerified" DATETIME;
|
||||
ALTER TABLE "User" ADD COLUMN "image" TEXT;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Account" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"userId" TEXT NOT NULL,
|
||||
"type" TEXT NOT NULL,
|
||||
"provider" TEXT NOT NULL,
|
||||
"providerAccountId" TEXT NOT NULL,
|
||||
"refresh_token" TEXT,
|
||||
"access_token" TEXT,
|
||||
"expires_at" INTEGER,
|
||||
"token_type" TEXT,
|
||||
"scope" TEXT,
|
||||
"id_token" TEXT,
|
||||
"session_state" TEXT,
|
||||
CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "UserSession" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"sessionToken" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"expires" DATETIME NOT NULL,
|
||||
CONSTRAINT "UserSession_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "VerificationToken" (
|
||||
"identifier" TEXT NOT NULL,
|
||||
"token" TEXT NOT NULL,
|
||||
"expires" DATETIME NOT NULL
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Account_provider_providerAccountId_key" ON "Account"("provider", "providerAccountId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "UserSession_sessionToken_key" ON "UserSession"("sessionToken");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "VerificationToken_token_key" ON "VerificationToken"("token");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "VerificationToken_identifier_token_key" ON "VerificationToken"("identifier", "token");
|
||||
@ -32,6 +32,48 @@ model User {
|
||||
role String // 'admin' | 'user' | 'auditor'
|
||||
resetToken String?
|
||||
resetTokenExpiry DateTime?
|
||||
|
||||
// NextAuth fields
|
||||
accounts Account[]
|
||||
sessions UserSession[]
|
||||
emailVerified DateTime?
|
||||
image String?
|
||||
}
|
||||
|
||||
// NextAuth models
|
||||
model Account {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
type String
|
||||
provider String
|
||||
providerAccountId String
|
||||
refresh_token String?
|
||||
access_token String?
|
||||
expires_at Int?
|
||||
token_type String?
|
||||
scope String?
|
||||
id_token String?
|
||||
session_state String?
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([provider, providerAccountId])
|
||||
}
|
||||
|
||||
model UserSession {
|
||||
id String @id @default(cuid())
|
||||
sessionToken String @unique
|
||||
userId String
|
||||
expires DateTime
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
}
|
||||
|
||||
model VerificationToken {
|
||||
identifier String
|
||||
token String @unique
|
||||
expires DateTime
|
||||
|
||||
@@unique([identifier, token])
|
||||
}
|
||||
|
||||
model Session {
|
||||
|
||||
@ -1,39 +0,0 @@
|
||||
// seed.js - Create initial admin user and company
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import bcrypt from "bcryptjs";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
// Create a company
|
||||
const company = await prisma.company.create({
|
||||
data: {
|
||||
name: "Demo Company",
|
||||
csvUrl: "https://example.com/data.csv", // Replace with a real URL if available
|
||||
},
|
||||
});
|
||||
|
||||
// Create an admin user
|
||||
const hashedPassword = await bcrypt.hash("admin123", 10);
|
||||
await prisma.user.create({
|
||||
data: {
|
||||
email: "admin@demo.com",
|
||||
password: hashedPassword,
|
||||
role: "admin",
|
||||
companyId: company.id,
|
||||
},
|
||||
});
|
||||
|
||||
console.log("Seed data created successfully:");
|
||||
console.log("Company: Demo Company");
|
||||
console.log("Admin user: admin@demo.com (password: admin123)");
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error("Error seeding database:", e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
@ -1,39 +0,0 @@
|
||||
// seed.ts - Create initial admin user and company
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import bcrypt from "bcryptjs";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
// Create a company
|
||||
const company = await prisma.company.create({
|
||||
data: {
|
||||
name: "Demo Company",
|
||||
csvUrl: "https://example.com/data.csv", // Replace with a real URL if available
|
||||
},
|
||||
});
|
||||
|
||||
// Create an admin user
|
||||
const hashedPassword = await bcrypt.hash("admin123", 10);
|
||||
await prisma.user.create({
|
||||
data: {
|
||||
email: "admin@demo.com",
|
||||
password: hashedPassword,
|
||||
role: "admin",
|
||||
companyId: company.id,
|
||||
},
|
||||
});
|
||||
|
||||
console.log("Seed data created successfully:");
|
||||
console.log("Company: Demo Company");
|
||||
console.log("Admin user: admin@demo.com (password: admin123)");
|
||||
} catch (error) {
|
||||
console.error("Error seeding database:", error);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Comprehensive D1 Database Management Script
|
||||
*
|
||||
*
|
||||
* Usage Examples:
|
||||
* node scripts/d1-manager.js tables
|
||||
* node scripts/d1-manager.js schema Company
|
||||
@ -11,27 +11,27 @@
|
||||
* node scripts/d1-manager.js --remote query "SELECT COUNT(*) FROM Session"
|
||||
*/
|
||||
|
||||
import { execSync } from 'child_process';
|
||||
import { writeFileSync, mkdirSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { execSync } from "child_process";
|
||||
import { writeFileSync, mkdirSync } from "fs";
|
||||
import { join } from "path";
|
||||
|
||||
const DB_NAME = 'd1-notso-livedash';
|
||||
const DB_NAME = "d1-notso-livedash";
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
// Parse flags
|
||||
const isRemote = args.includes('--remote');
|
||||
const filteredArgs = args.filter(arg => !arg.startsWith('--'));
|
||||
const isRemote = args.includes("--remote");
|
||||
const filteredArgs = args.filter((arg) => !arg.startsWith("--"));
|
||||
|
||||
if (filteredArgs.length === 0) {
|
||||
showHelp();
|
||||
process.exit(1);
|
||||
showHelp();
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const command = filteredArgs[ 0 ];
|
||||
const command = filteredArgs[0];
|
||||
const params = filteredArgs.slice(1);
|
||||
|
||||
function showHelp() {
|
||||
console.log(`
|
||||
console.log(`
|
||||
🗄️ D1 Database Manager for ${DB_NAME}
|
||||
|
||||
Usage: node scripts/d1-manager.js [--remote] <command> [params...]
|
||||
@ -60,125 +60,134 @@ Examples:
|
||||
}
|
||||
|
||||
function execute(sql, silent = false) {
|
||||
const remoteFlag = isRemote ? '--remote' : '';
|
||||
const cmd = `npx wrangler d1 execute ${DB_NAME} ${remoteFlag} --command "${sql}"`;
|
||||
const remoteFlag = isRemote ? "--remote" : "";
|
||||
const cmd = `npx wrangler d1 execute ${DB_NAME} ${remoteFlag} --command "${sql}"`;
|
||||
|
||||
if (!silent) {
|
||||
console.log(`🔍 Executing${isRemote ? ' (remote)' : ' (local)'}: ${sql}\\n`);
|
||||
}
|
||||
if (!silent) {
|
||||
console.log(
|
||||
`🔍 Executing${isRemote ? " (remote)" : " (local)"}: ${sql}\\n`
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
return execSync(cmd, { encoding: 'utf8' });
|
||||
} catch (error) {
|
||||
console.error('❌ Query failed:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
try {
|
||||
return execSync(cmd, { encoding: "utf8" });
|
||||
} catch (error) {
|
||||
console.error("❌ Query failed:", error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function wranglerCommand(subcommand, silent = false) {
|
||||
const remoteFlag = isRemote ? '--remote' : '';
|
||||
const cmd = `npx wrangler d1 ${subcommand} ${DB_NAME} ${remoteFlag}`;
|
||||
const remoteFlag = isRemote ? "--remote" : "";
|
||||
const cmd = `npx wrangler d1 ${subcommand} ${DB_NAME} ${remoteFlag}`;
|
||||
|
||||
if (!silent) {
|
||||
console.log(`📊 Running: ${cmd}\\n`);
|
||||
}
|
||||
if (!silent) {
|
||||
console.log(`📊 Running: ${cmd}\\n`);
|
||||
}
|
||||
|
||||
try {
|
||||
return execSync(cmd, { stdio: 'inherit' });
|
||||
} catch (error) {
|
||||
console.error('❌ Command failed:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
try {
|
||||
return execSync(cmd, { stdio: "inherit" });
|
||||
} catch (error) {
|
||||
console.error("❌ Command failed:", error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
switch (command) {
|
||||
case 'info':
|
||||
wranglerCommand('info');
|
||||
break;
|
||||
case "info":
|
||||
wranglerCommand("info");
|
||||
break;
|
||||
|
||||
case 'tables':
|
||||
console.log('📋 Listing all tables:\\n');
|
||||
execute("SELECT name, type FROM sqlite_master WHERE type IN ('table', 'view') AND name NOT LIKE 'sqlite_%' ORDER BY name;");
|
||||
break;
|
||||
case "tables":
|
||||
console.log("📋 Listing all tables:\\n");
|
||||
execute(
|
||||
"SELECT name, type FROM sqlite_master WHERE type IN ('table', 'view') AND name NOT LIKE 'sqlite_%' ORDER BY name;"
|
||||
);
|
||||
break;
|
||||
|
||||
case 'schema':
|
||||
if (!params[ 0 ]) {
|
||||
console.error('❌ Please specify a table name');
|
||||
console.log('Usage: node scripts/d1-manager.js schema <table_name>');
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`🏗️ Schema for table '${params[ 0 ]}':\\n`);
|
||||
execute(`PRAGMA table_info(${params[ 0 ]});`);
|
||||
break;
|
||||
case "schema":
|
||||
if (!params[0]) {
|
||||
console.error("❌ Please specify a table name");
|
||||
console.log("Usage: node scripts/d1-manager.js schema <table_name>");
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`🏗️ Schema for table '${params[0]}':\\n`);
|
||||
execute(`PRAGMA table_info(${params[0]});`);
|
||||
break;
|
||||
|
||||
case 'count':
|
||||
if (!params[ 0 ]) {
|
||||
console.error('❌ Please specify a table name');
|
||||
console.log('Usage: node scripts/d1-manager.js count <table_name>');
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`🔢 Row count for table '${params[ 0 ]}':\\n`);
|
||||
execute(`SELECT COUNT(*) as row_count FROM ${params[ 0 ]};`);
|
||||
break;
|
||||
case "count":
|
||||
if (!params[0]) {
|
||||
console.error("❌ Please specify a table name");
|
||||
console.log("Usage: node scripts/d1-manager.js count <table_name>");
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`🔢 Row count for table '${params[0]}':\\n`);
|
||||
execute(`SELECT COUNT(*) as row_count FROM ${params[0]};`);
|
||||
break;
|
||||
|
||||
case 'query':
|
||||
if (!params[ 0 ]) {
|
||||
console.error('❌ Please specify a SQL query');
|
||||
console.log('Usage: node scripts/d1-manager.js query "SELECT * FROM table"');
|
||||
process.exit(1);
|
||||
}
|
||||
execute(params[ 0 ]);
|
||||
break;
|
||||
case "query":
|
||||
if (!params[0]) {
|
||||
console.error("❌ Please specify a SQL query");
|
||||
console.log(
|
||||
'Usage: node scripts/d1-manager.js query "SELECT * FROM table"'
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
execute(params[0]);
|
||||
break;
|
||||
|
||||
case 'backup':
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
||||
const filename = params[ 0 ] || `backup_${timestamp}.sql`;
|
||||
case "backup":
|
||||
const timestamp = new Date()
|
||||
.toISOString()
|
||||
.replace(/[:.]/g, "-")
|
||||
.slice(0, 19);
|
||||
const filename = params[0] || `backup_${timestamp}.sql`;
|
||||
|
||||
try {
|
||||
mkdirSync('backups', { recursive: true });
|
||||
} catch (e) {
|
||||
// Directory might already exist
|
||||
}
|
||||
try {
|
||||
mkdirSync("backups", { recursive: true });
|
||||
} catch (e) {
|
||||
// Directory might already exist
|
||||
}
|
||||
|
||||
const backupPath = join('backups', filename);
|
||||
console.log(`💾 Creating backup: ${backupPath}\\n`);
|
||||
wranglerCommand(`export --output ${backupPath}`);
|
||||
console.log(`\\n✅ Backup created successfully: ${backupPath}`);
|
||||
break;
|
||||
const backupPath = join("backups", filename);
|
||||
console.log(`💾 Creating backup: ${backupPath}\\n`);
|
||||
wranglerCommand(`export --output ${backupPath}`);
|
||||
console.log(`\\n✅ Backup created successfully: ${backupPath}`);
|
||||
break;
|
||||
|
||||
case 'backup-schema':
|
||||
try {
|
||||
mkdirSync('backups', { recursive: true });
|
||||
} catch (e) {
|
||||
// Directory might already exist
|
||||
}
|
||||
case "backup-schema":
|
||||
try {
|
||||
mkdirSync("backups", { recursive: true });
|
||||
} catch (e) {
|
||||
// Directory might already exist
|
||||
}
|
||||
|
||||
console.log('📜 Exporting schema only...\\n');
|
||||
wranglerCommand('export --no-data --output backups/schema.sql');
|
||||
console.log('\\n✅ Schema exported to backups/schema.sql');
|
||||
break;
|
||||
console.log("📜 Exporting schema only...\\n");
|
||||
wranglerCommand("export --no-data --output backups/schema.sql");
|
||||
console.log("\\n✅ Schema exported to backups/schema.sql");
|
||||
break;
|
||||
|
||||
case 'recent-logs':
|
||||
console.log('📊 Recent database activity:\\n');
|
||||
try {
|
||||
wranglerCommand('insights');
|
||||
} catch (error) {
|
||||
console.log('ℹ️ Insights not available for this database');
|
||||
}
|
||||
break;
|
||||
case "recent-logs":
|
||||
console.log("📊 Recent database activity:\\n");
|
||||
try {
|
||||
wranglerCommand("insights");
|
||||
} catch (error) {
|
||||
console.log("ℹ️ Insights not available for this database");
|
||||
}
|
||||
break;
|
||||
|
||||
case 'all-tables-info':
|
||||
console.log('📊 Information about all tables:\\n');
|
||||
const tables = [ 'Company', 'User', 'Session' ];
|
||||
for (const table of tables) {
|
||||
console.log(`\\n🏷️ Table: ${table}`);
|
||||
console.log('─'.repeat(50));
|
||||
execute(`SELECT COUNT(*) as row_count FROM ${table};`);
|
||||
}
|
||||
break;
|
||||
case "all-tables-info":
|
||||
console.log("📊 Information about all tables:\\n");
|
||||
const tables = ["Company", "User", "Session"];
|
||||
for (const table of tables) {
|
||||
console.log(`\\n🏷️ Table: ${table}`);
|
||||
console.log("─".repeat(50));
|
||||
execute(`SELECT COUNT(*) as row_count FROM ${table};`);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
console.error(`❌ Unknown command: ${command}`);
|
||||
showHelp();
|
||||
process.exit(1);
|
||||
default:
|
||||
console.error(`❌ Unknown command: ${command}`);
|
||||
showHelp();
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@ -5,32 +5,34 @@
|
||||
* Usage: node scripts/d1-query.js --remote "SELECT COUNT(*) FROM Company"
|
||||
*/
|
||||
|
||||
import { execSync } from 'child_process';
|
||||
import { execSync } from "child_process";
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
if (args.length === 0) {
|
||||
console.log('Usage: node scripts/d1-query.js [--remote] "SQL_QUERY"');
|
||||
console.log('Examples:');
|
||||
console.log(' node scripts/d1-query.js "SELECT * FROM User LIMIT 5"');
|
||||
console.log(' node scripts/d1-query.js --remote "SELECT COUNT(*) FROM Company"');
|
||||
process.exit(1);
|
||||
console.log('Usage: node scripts/d1-query.js [--remote] "SQL_QUERY"');
|
||||
console.log("Examples:");
|
||||
console.log(' node scripts/d1-query.js "SELECT * FROM User LIMIT 5"');
|
||||
console.log(
|
||||
' node scripts/d1-query.js --remote "SELECT COUNT(*) FROM Company"'
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const isRemote = args.includes('--remote');
|
||||
const query = args[ args.length - 1 ];
|
||||
const isRemote = args.includes("--remote");
|
||||
const query = args[args.length - 1];
|
||||
|
||||
if (!query || query.startsWith('--')) {
|
||||
console.error('Error: Please provide a SQL query');
|
||||
process.exit(1);
|
||||
if (!query || query.startsWith("--")) {
|
||||
console.error("Error: Please provide a SQL query");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const remoteFlag = isRemote ? '--remote' : '';
|
||||
const remoteFlag = isRemote ? "--remote" : "";
|
||||
const command = `npx wrangler d1 execute d1-notso-livedash ${remoteFlag} --command "${query}"`;
|
||||
|
||||
try {
|
||||
console.log(`🔍 Executing${isRemote ? ' (remote)' : ' (local)'}: ${query}\n`);
|
||||
execSync(command, { stdio: 'inherit' });
|
||||
console.log(`🔍 Executing${isRemote ? " (remote)" : " (local)"}: ${query}\n`);
|
||||
execSync(command, { stdio: "inherit" });
|
||||
} catch (error) {
|
||||
console.error('Query failed:', error.message);
|
||||
process.exit(1);
|
||||
console.error("Query failed:", error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@ -4,13 +4,13 @@
|
||||
* Usage: node scripts/d1.js <command> [args...]
|
||||
*/
|
||||
|
||||
import { execSync } from 'child_process';
|
||||
import { execSync } from "child_process";
|
||||
|
||||
const DB_NAME = 'd1-notso-livedash';
|
||||
const DB_NAME = "d1-notso-livedash";
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
if (args.length === 0) {
|
||||
console.log(`
|
||||
console.log(`
|
||||
🗄️ Simple D1 CLI for ${DB_NAME}
|
||||
|
||||
Usage: node scripts/d1.js <command> [args...]
|
||||
@ -31,59 +31,66 @@ Examples:
|
||||
node scripts/d1.js query "SELECT COUNT(*) FROM Company"
|
||||
node scripts/d1.js --remote info
|
||||
`);
|
||||
process.exit(0);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const isRemote = args.includes('--remote');
|
||||
const filteredArgs = args.filter(arg => !arg.startsWith('--'));
|
||||
const [ command, ...params ] = filteredArgs;
|
||||
const remoteFlag = isRemote ? '--remote' : '';
|
||||
const isRemote = args.includes("--remote");
|
||||
const filteredArgs = args.filter((arg) => !arg.startsWith("--"));
|
||||
const [command, ...params] = filteredArgs;
|
||||
const remoteFlag = isRemote ? "--remote" : "";
|
||||
|
||||
function run(cmd) {
|
||||
try {
|
||||
console.log(`💫 ${cmd}`);
|
||||
execSync(cmd, { stdio: 'inherit' });
|
||||
} catch (error) {
|
||||
console.error('❌ Command failed');
|
||||
process.exit(1);
|
||||
}
|
||||
try {
|
||||
console.log(`💫 ${cmd}`);
|
||||
execSync(cmd, { stdio: "inherit" });
|
||||
} catch (error) {
|
||||
console.error("❌ Command failed");
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
switch (command) {
|
||||
case 'list':
|
||||
run('npx wrangler d1 list');
|
||||
break;
|
||||
case "list":
|
||||
run("npx wrangler d1 list");
|
||||
break;
|
||||
|
||||
case 'info':
|
||||
run(`npx wrangler d1 info ${DB_NAME} ${remoteFlag}`);
|
||||
break;
|
||||
case "info":
|
||||
run(`npx wrangler d1 info ${DB_NAME} ${remoteFlag}`);
|
||||
break;
|
||||
|
||||
case 'tables':
|
||||
run(`npx wrangler d1 execute ${DB_NAME} ${remoteFlag} --command "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"`);
|
||||
break;
|
||||
case "tables":
|
||||
run(
|
||||
`npx wrangler d1 execute ${DB_NAME} ${remoteFlag} --command "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"`
|
||||
);
|
||||
break;
|
||||
|
||||
case 'schema':
|
||||
if (!params[ 0 ]) {
|
||||
console.error('❌ Please specify table name');
|
||||
process.exit(1);
|
||||
}
|
||||
run(`npx wrangler d1 execute ${DB_NAME} ${remoteFlag} --command "PRAGMA table_info(${params[ 0 ]})"`);
|
||||
break;
|
||||
case "schema":
|
||||
if (!params[0]) {
|
||||
console.error("❌ Please specify table name");
|
||||
process.exit(1);
|
||||
}
|
||||
run(
|
||||
`npx wrangler d1 execute ${DB_NAME} ${remoteFlag} --command "PRAGMA table_info(${params[0]})"`
|
||||
);
|
||||
break;
|
||||
|
||||
case 'query':
|
||||
if (!params[ 0 ]) {
|
||||
console.error('❌ Please specify SQL query');
|
||||
process.exit(1);
|
||||
}
|
||||
run(`npx wrangler d1 execute ${DB_NAME} ${remoteFlag} --command "${params[ 0 ]}"`);
|
||||
break;
|
||||
case "query":
|
||||
if (!params[0]) {
|
||||
console.error("❌ Please specify SQL query");
|
||||
process.exit(1);
|
||||
}
|
||||
run(
|
||||
`npx wrangler d1 execute ${DB_NAME} ${remoteFlag} --command "${params[0]}"`
|
||||
);
|
||||
break;
|
||||
|
||||
case 'export':
|
||||
const filename = params[ 0 ] || `backup_${new Date().toISOString().slice(0, 10)}.sql`;
|
||||
run(`npx wrangler d1 export ${DB_NAME} ${remoteFlag} --output ${filename}`);
|
||||
break;
|
||||
case "export":
|
||||
const filename =
|
||||
params[0] || `backup_${new Date().toISOString().slice(0, 10)}.sql`;
|
||||
run(`npx wrangler d1 export ${DB_NAME} ${remoteFlag} --output ${filename}`);
|
||||
break;
|
||||
|
||||
default:
|
||||
console.error(`❌ Unknown command: ${command}`);
|
||||
process.exit(1);
|
||||
default:
|
||||
console.error(`❌ Unknown command: ${command}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
228
src/index.ts
228
src/index.ts
@ -1,228 +0,0 @@
|
||||
// Cloudflare Worker entry point for LiveDash-Node
|
||||
// This file handles requests when deployed to Cloudflare Workers
|
||||
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { PrismaD1 } from '@prisma/adapter-d1';
|
||||
|
||||
export interface Env {
|
||||
DB: D1Database;
|
||||
NEXTAUTH_SECRET?: string;
|
||||
NEXTAUTH_URL?: string;
|
||||
}
|
||||
|
||||
export default {
|
||||
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
|
||||
try {
|
||||
// Initialize Prisma with D1 adapter
|
||||
const adapter = new PrismaD1(env.DB);
|
||||
const prisma = new PrismaClient({ adapter });
|
||||
|
||||
const url = new URL(request.url);
|
||||
|
||||
// CORS headers for all responses
|
||||
const corsHeaders = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
||||
};
|
||||
|
||||
// Handle preflight requests
|
||||
if (request.method === 'OPTIONS') {
|
||||
return new Response(null, { headers: corsHeaders });
|
||||
}
|
||||
|
||||
// Handle API routes
|
||||
if (url.pathname.startsWith('/api/')) {
|
||||
|
||||
// Simple health check endpoint
|
||||
if (url.pathname === '/api/health') {
|
||||
const companyCount = await prisma.company.count();
|
||||
const sessionCount = await prisma.session.count();
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
status: 'healthy',
|
||||
database: 'connected',
|
||||
companies: companyCount,
|
||||
sessions: sessionCount,
|
||||
timestamp: new Date().toISOString()
|
||||
}),
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...corsHeaders
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Test metrics endpoint
|
||||
if (url.pathname === '/api/test-metrics') {
|
||||
const sessions = await prisma.session.findMany({
|
||||
take: 10,
|
||||
orderBy: { startTime: 'desc' }
|
||||
});
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
message: 'LiveDash API running on Cloudflare Workers with D1',
|
||||
recentSessions: sessions.length,
|
||||
sessions: sessions
|
||||
}),
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...corsHeaders
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// For other API routes, return a placeholder response
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
message: 'API endpoint not implemented in worker yet',
|
||||
path: url.pathname,
|
||||
method: request.method,
|
||||
note: 'This endpoint needs to be migrated from Next.js API routes'
|
||||
}),
|
||||
{
|
||||
status: 501,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...corsHeaders
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Handle root path - simple test page
|
||||
if (url.pathname === '/') {
|
||||
try {
|
||||
const companies = await prisma.company.findMany();
|
||||
const recentSessions = await prisma.session.findMany({
|
||||
take: 5,
|
||||
orderBy: { startTime: 'desc' },
|
||||
include: { company: { select: { name: true } } }
|
||||
});
|
||||
|
||||
return new Response(
|
||||
`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>LiveDash-Node on Cloudflare Workers</title>
|
||||
<link rel="stylesheet" type="text/css" href="https://static.integrations.cloudflare.com/styles.css">
|
||||
<style>
|
||||
.container { max-width: 1000px; margin: 0 auto; padding: 20px; }
|
||||
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin: 20px 0; }
|
||||
.card { background: #f8f9fa; padding: 20px; border-radius: 8px; border: 1px solid #e9ecef; }
|
||||
pre { background: #f5f5f5; padding: 15px; border-radius: 5px; overflow-x: auto; font-size: 12px; }
|
||||
.api-list { list-style: none; padding: 0; }
|
||||
.api-list li { margin: 8px 0; }
|
||||
.api-list a { color: #0066cc; text-decoration: none; }
|
||||
.api-list a:hover { text-decoration: underline; }
|
||||
.status { color: #28a745; font-weight: bold; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<img
|
||||
src="https://imagedelivery.net/wSMYJvS3Xw-n339CbDyDIA/30e0d3f6-6076-40f8-7abb-8a7676f83c00/public"
|
||||
/>
|
||||
<h1>🎉 LiveDash-Node Successfully Connected to D1!</h1>
|
||||
<p class="status">✓ Database Connected | ✓ Prisma Client Working | ✓ D1 Adapter Active</p>
|
||||
</header>
|
||||
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<h3>📊 Database Stats</h3>
|
||||
<ul>
|
||||
<li><strong>Companies:</strong> ${companies.length}</li>
|
||||
<li><strong>Recent Sessions:</strong> ${recentSessions.length}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>🔗 Test API Endpoints</h3>
|
||||
<ul class="api-list">
|
||||
<li><a href="/api/health">/api/health</a> - Health check</li>
|
||||
<li><a href="/api/test-metrics">/api/test-metrics</a> - Sample data</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>🏢 Companies in Database</h3>
|
||||
<pre>${companies.length > 0 ? JSON.stringify(companies, null, 2) : 'No companies found'}</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>📈 Recent Sessions</h3>
|
||||
<pre>${recentSessions.length > 0 ? JSON.stringify(recentSessions, null, 2) : 'No sessions found'}</pre>
|
||||
</div>
|
||||
|
||||
<footer style="margin-top: 40px; text-align: center; color: #666;">
|
||||
<small>
|
||||
<a target="_blank" href="https://developers.cloudflare.com/d1/">Learn more about Cloudflare D1</a> |
|
||||
<a target="_blank" href="https://www.prisma.io/docs/guides/deployment/deployment-guides/deploying-to-cloudflare-workers">Prisma + Workers Guide</a>
|
||||
</small>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'text/html',
|
||||
...corsHeaders
|
||||
},
|
||||
}
|
||||
);
|
||||
} catch (dbError) {
|
||||
return new Response(
|
||||
`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head><title>LiveDash-Node - Database Error</title></head>
|
||||
<body>
|
||||
<h1>❌ Database Connection Error</h1>
|
||||
<p>Error: ${dbError instanceof Error ? dbError.message : 'Unknown database error'}</p>
|
||||
<p>Check your D1 database configuration and make sure migrations have been applied.</p>
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
{
|
||||
status: 500,
|
||||
headers: { 'Content-Type': 'text/html' },
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle all other routes
|
||||
return new Response('Not Found - This endpoint is not available in the worker deployment', {
|
||||
status: 404,
|
||||
headers: corsHeaders
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Worker error:', error);
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'Internal Server Error',
|
||||
message: error instanceof Error ? error.message : 'Unknown error',
|
||||
stack: error instanceof Error ? error.stack : undefined
|
||||
}),
|
||||
{
|
||||
status: 500,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*'
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
288
src/index.ts.backup
Normal file
288
src/index.ts.backup
Normal file
@ -0,0 +1,288 @@
|
||||
// Cloudflare Worker entry point for LiveDash-Node
|
||||
// This file handles requests when deployed to Cloudflare Workers
|
||||
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { PrismaD1 } from "@prisma/adapter-d1";
|
||||
|
||||
export interface Env {
|
||||
DB: D1Database;
|
||||
AUTH_SECRET?: string;
|
||||
AUTH_URL?: string;
|
||||
WORKER_ENV?: string; // 'development' | 'production'
|
||||
}
|
||||
|
||||
export default {
|
||||
async fetch(
|
||||
request: Request,
|
||||
env: Env,
|
||||
ctx: ExecutionContext
|
||||
): Promise<Response> {
|
||||
try {
|
||||
// Initialize Prisma with D1 adapter
|
||||
const adapter = new PrismaD1(env.DB);
|
||||
const prisma = new PrismaClient({ adapter });
|
||||
|
||||
const url = new URL(request.url);
|
||||
|
||||
// CORS headers for all responses
|
||||
const corsHeaders = {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
|
||||
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
||||
};
|
||||
|
||||
// Handle preflight requests
|
||||
if (request.method === "OPTIONS") {
|
||||
return new Response(null, { headers: corsHeaders });
|
||||
}
|
||||
|
||||
// Handle API routes
|
||||
if (url.pathname.startsWith("/api/")) {
|
||||
// Simple health check endpoint
|
||||
if (url.pathname === "/api/health") {
|
||||
const companyCount = await prisma.company.count();
|
||||
const sessionCount = await prisma.session.count();
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
status: "healthy",
|
||||
database: "connected",
|
||||
companies: companyCount,
|
||||
sessions: sessionCount,
|
||||
timestamp: new Date().toISOString(),
|
||||
}),
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...corsHeaders,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Test metrics endpoint
|
||||
if (url.pathname === "/api/test-metrics") {
|
||||
const sessions = await prisma.session.findMany({
|
||||
take: 10,
|
||||
orderBy: { startTime: "desc" },
|
||||
});
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
message: "LiveDash API running on Cloudflare Workers with D1",
|
||||
recentSessions: sessions.length,
|
||||
sessions: sessions,
|
||||
}),
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...corsHeaders,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Dashboard metrics endpoint
|
||||
if (url.pathname === "/api/dashboard/metrics") {
|
||||
const companyCount = await prisma.company.count();
|
||||
const userCount = await prisma.user.count();
|
||||
const sessionCount = await prisma.session.count();
|
||||
|
||||
const recentSessions = await prisma.session.findMany({
|
||||
take: 5,
|
||||
orderBy: { startTime: "desc" },
|
||||
include: {
|
||||
company: {
|
||||
select: { name: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
overview: {
|
||||
companies: companyCount,
|
||||
users: userCount,
|
||||
sessions: sessionCount,
|
||||
},
|
||||
recentSessions: recentSessions,
|
||||
}),
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...corsHeaders,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Companies endpoint
|
||||
if (url.pathname === "/api/companies") {
|
||||
if (request.method === "GET") {
|
||||
const companies = await prisma.company.findMany({
|
||||
include: {
|
||||
_count: {
|
||||
select: { users: true, sessions: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return new Response(JSON.stringify(companies), {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...corsHeaders,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// For other API routes, return a placeholder response
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
message: "API endpoint not implemented in worker yet",
|
||||
path: url.pathname,
|
||||
method: request.method,
|
||||
note: "This endpoint needs to be migrated from Next.js API routes",
|
||||
}),
|
||||
{
|
||||
status: 501,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...corsHeaders,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Handle root path - simple test page
|
||||
if (url.pathname === "/") {
|
||||
try {
|
||||
const companies = await prisma.company.findMany();
|
||||
const recentSessions = await prisma.session.findMany({
|
||||
take: 5,
|
||||
orderBy: { startTime: "desc" },
|
||||
include: { company: { select: { name: true } } },
|
||||
});
|
||||
|
||||
return new Response(
|
||||
`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>LiveDash-Node on Cloudflare Workers</title>
|
||||
<link rel="stylesheet" type="text/css" href="https://static.integrations.cloudflare.com/styles.css">
|
||||
<style>
|
||||
.container { max-width: 1000px; margin: 0 auto; padding: 20px; }
|
||||
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin: 20px 0; }
|
||||
.card { background: #f8f9fa; padding: 20px; border-radius: 8px; border: 1px solid #e9ecef; }
|
||||
pre { background: #f5f5f5; padding: 15px; border-radius: 5px; overflow-x: auto; font-size: 12px; }
|
||||
.api-list { list-style: none; padding: 0; }
|
||||
.api-list li { margin: 8px 0; }
|
||||
.api-list a { color: #0066cc; text-decoration: none; }
|
||||
.api-list a:hover { text-decoration: underline; }
|
||||
.status { color: #28a745; font-weight: bold; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<img
|
||||
src="https://imagedelivery.net/wSMYJvS3Xw-n339CbDyDIA/30e0d3f6-6076-40f8-7abb-8a7676f83c00/public"
|
||||
/>
|
||||
<h1>🎉 LiveDash-Node Successfully Connected to D1!</h1>
|
||||
<p class="status">✓ Database Connected | ✓ Prisma Client Working | ✓ D1 Adapter Active</p>
|
||||
</header>
|
||||
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<h3>📊 Database Stats</h3>
|
||||
<ul>
|
||||
<li><strong>Companies:</strong> ${companies.length}</li>
|
||||
<li><strong>Recent Sessions:</strong> ${recentSessions.length}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>🔗 Test API Endpoints</h3>
|
||||
<ul class="api-list">
|
||||
<li><a href="/api/health">/api/health</a> - Health check</li>
|
||||
<li><a href="/api/test-metrics">/api/test-metrics</a> - Sample data</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>🏢 Companies in Database</h3>
|
||||
<pre>${companies.length > 0 ? JSON.stringify(companies, null, 2) : "No companies found"}</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>📈 Recent Sessions</h3>
|
||||
<pre>${recentSessions.length > 0 ? JSON.stringify(recentSessions, null, 2) : "No sessions found"}</pre>
|
||||
</div>
|
||||
|
||||
<footer style="margin-top: 40px; text-align: center; color: #666;">
|
||||
<small>
|
||||
<a target="_blank" href="https://developers.cloudflare.com/d1/">Learn more about Cloudflare D1</a> |
|
||||
<a target="_blank" href="https://www.prisma.io/docs/guides/deployment/deployment-guides/deploying-to-cloudflare-workers">Prisma + Workers Guide</a>
|
||||
</small>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "text/html",
|
||||
...corsHeaders,
|
||||
},
|
||||
}
|
||||
);
|
||||
} catch (dbError) {
|
||||
return new Response(
|
||||
`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head><title>LiveDash-Node - Database Error</title></head>
|
||||
<body>
|
||||
<h1>❌ Database Connection Error</h1>
|
||||
<p>Error: ${dbError instanceof Error ? dbError.message : "Unknown database error"}</p>
|
||||
<p>Check your D1 database configuration and make sure migrations have been applied.</p>
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
{
|
||||
status: 500,
|
||||
headers: { "Content-Type": "text/html" },
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle all other routes
|
||||
return new Response(
|
||||
"Not Found - This endpoint is not available in the worker deployment",
|
||||
{
|
||||
status: 404,
|
||||
headers: corsHeaders,
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Worker error:", error);
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: "Internal Server Error",
|
||||
message: error instanceof Error ? error.message : "Unknown error",
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
}),
|
||||
{
|
||||
status: 500,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
50
types/auth.d.ts
vendored
Normal file
50
types/auth.d.ts
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
import { DefaultSession } from "next-auth";
|
||||
|
||||
declare module "next-auth" {
|
||||
/**
|
||||
* Returned by `auth`, `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context
|
||||
*/
|
||||
interface Session {
|
||||
user: {
|
||||
/** The user's unique id. */
|
||||
id: string;
|
||||
/** The user's role (admin, user, etc.) */
|
||||
role: string;
|
||||
/** The user's company ID */
|
||||
companyId: string;
|
||||
/** The user's company name */
|
||||
company: string;
|
||||
} & DefaultSession["user"];
|
||||
}
|
||||
|
||||
/**
|
||||
* The shape of the user object returned in the OAuth providers' `profile` callback,
|
||||
* or the second parameter of the `session` callback, when using a database.
|
||||
*/
|
||||
interface User {
|
||||
/** The user's unique id. */
|
||||
id: string;
|
||||
/** The user's email address. */
|
||||
email?: string;
|
||||
/** The user's name. */
|
||||
name?: string;
|
||||
/** The user's role (admin, user, etc.) */
|
||||
role: string;
|
||||
/** The user's company ID */
|
||||
companyId: string;
|
||||
/** The user's company name */
|
||||
company: string;
|
||||
}
|
||||
}
|
||||
|
||||
declare module "next-auth/jwt" {
|
||||
/** Returned by the `jwt` callback and `auth`, when using JWT sessions */
|
||||
interface JWT {
|
||||
/** The user's role */
|
||||
role: string;
|
||||
/** The user's company ID */
|
||||
companyId: string;
|
||||
/** The user's company name */
|
||||
company: string;
|
||||
}
|
||||
}
|
||||
10072
worker-configuration.d.ts
vendored
10072
worker-configuration.d.ts
vendored
File diff suppressed because it is too large
Load Diff
@ -4,20 +4,14 @@
|
||||
*/
|
||||
{
|
||||
"$schema": "node_modules/wrangler/config-schema.json",
|
||||
"compatibility_date": "2025-04-01",
|
||||
"main": "src/index.ts",
|
||||
"name": "livedash",
|
||||
"upload_source_maps": true,
|
||||
"d1_databases": [
|
||||
{
|
||||
"binding": "DB",
|
||||
"database_id": "d4ee7efe-d37a-48e4-bed7-fdfaa5108131",
|
||||
"database_name": "d1-notso-livedash"
|
||||
}
|
||||
],
|
||||
"main": ".open-next/worker.js",
|
||||
"compatibility_date": "2025-06-01",
|
||||
"compatibility_flags": ["nodejs_compat"],
|
||||
"observability": {
|
||||
"enabled": true
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Smart Placement
|
||||
* Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
|
||||
@ -30,12 +24,20 @@
|
||||
* databases, object storage, AI inference, real-time communication and more.
|
||||
* https://developers.cloudflare.com/workers/runtime-apis/bindings/
|
||||
*/
|
||||
"d1_databases": [
|
||||
{
|
||||
"binding": "DB",
|
||||
"database_id": "d4ee7efe-d37a-48e4-bed7-fdfaa5108131",
|
||||
"database_name": "d1-notso-livedash"
|
||||
}
|
||||
],
|
||||
|
||||
/**
|
||||
* Environment Variables
|
||||
* https://developers.cloudflare.com/workers/wrangler/configuration/#environment-variables
|
||||
*/
|
||||
// "vars": { "MY_VARIABLE": "production_value" },
|
||||
|
||||
/**
|
||||
* Note: Use secrets to store sensitive data.
|
||||
* https://developers.cloudflare.com/workers/configuration/secrets/
|
||||
@ -46,10 +48,15 @@
|
||||
* https://developers.cloudflare.com/workers/static-assets/binding/
|
||||
*/
|
||||
// "assets": { "directory": "./public/", "binding": "ASSETS" },
|
||||
"assets": {
|
||||
"directory": ".open-next/assets",
|
||||
"binding": "ASSETS"
|
||||
}
|
||||
|
||||
/**
|
||||
* Service Bindings (communicate between multiple Workers)
|
||||
* https://developers.cloudflare.com/workers/wrangler/configuration/#service-bindings
|
||||
*/
|
||||
// "services": [{ "binding": "MY_SERVICE", "service": "my-service" }]
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user