From d88eae7230583c8f3a48d2dc1436c03adcf0b04a Mon Sep 17 00:00:00 2001 From: Jared Palmer Date: Mon, 22 May 2023 12:14:37 -0400 Subject: [PATCH 01/11] Try to use drizzle event though --- auth.ts | 16 + drizzle.config.ts | 6 + lib/db/db.ts | 4 + lib/db/index.ts | 270 +++++++ lib/db/migrate.ts | 18 + .../migrations/0000_flawless_moondragon.sql | 49 ++ lib/db/migrations/0001_colorful_spectrum.sql | 7 + lib/db/migrations/meta/0000_snapshot.json | 227 ++++++ lib/db/migrations/meta/0001_snapshot.json | 252 ++++++ lib/db/migrations/meta/_journal.json | 20 + lib/db/schema.ts | 353 +++++++++ package.json | 13 +- pnpm-lock.yaml | 744 +++++++++++++++++- tsconfig.migration.json | 11 + 14 files changed, 1978 insertions(+), 12 deletions(-) create mode 100644 drizzle.config.ts create mode 100644 lib/db/db.ts create mode 100644 lib/db/index.ts create mode 100644 lib/db/migrate.ts create mode 100644 lib/db/migrations/0000_flawless_moondragon.sql create mode 100644 lib/db/migrations/0001_colorful_spectrum.sql create mode 100644 lib/db/migrations/meta/0000_snapshot.json create mode 100644 lib/db/migrations/meta/0001_snapshot.json create mode 100644 lib/db/migrations/meta/_journal.json create mode 100644 lib/db/schema.ts create mode 100644 tsconfig.migration.json diff --git a/auth.ts b/auth.ts index 100244e..cab5ae5 100644 --- a/auth.ts +++ b/auth.ts @@ -1,11 +1,27 @@ import NextAuth from "@auth/nextjs"; import GitHub from "@auth/nextjs/providers/github"; import { NextResponse } from "next/server"; + +import { + db, + users, + accounts, + sessions, + verificationTokens, +} from "./lib/db/schema"; +import { DrizzleAdapterPg } from "./lib/db"; + export const { handlers: { GET, POST }, auth, CSRF_experimental, } = NextAuth({ + adapter: DrizzleAdapterPg(db, { + accounts, + users, + sessions, + verificationTokens, + }), // @ts-ignore providers: [GitHub], session: { strategy: "jwt" }, diff --git a/drizzle.config.ts b/drizzle.config.ts new file mode 100644 index 0000000..422184e --- /dev/null +++ b/drizzle.config.ts @@ -0,0 +1,6 @@ +import type { Config } from "drizzle-kit"; + +export default { + schema: "./lib/db/schema.ts", + out: "./lib/db/migrations", +} satisfies Config; diff --git a/lib/db/db.ts b/lib/db/db.ts new file mode 100644 index 0000000..066b295 --- /dev/null +++ b/lib/db/db.ts @@ -0,0 +1,4 @@ +import { drizzle } from "drizzle-orm/vercel-postgres"; +import { sql } from "@vercel/postgres"; + +export const db = drizzle(sql); diff --git a/lib/db/index.ts b/lib/db/index.ts new file mode 100644 index 0000000..c96d1c5 --- /dev/null +++ b/lib/db/index.ts @@ -0,0 +1,270 @@ +/** + *
+ *

Official Drizzle ORM adapter for Auth.js / NextAuth.js.

+ * + * + * + *
+ * + * ## Installation + * + * ```bash npm2yarn2pnpm + * npm install next-auth drizzle-orm @next-auth/drizzle-adapter + * npm install drizzle-kit --save-dev + * ``` + * + * @module @next-auth/drizzle-adapter + */ +import type { Adapter } from "@auth/nextjs/adapters"; +import { and, eq } from "drizzle-orm"; +import { v4 as uuid } from "uuid"; +import type { DbClient, Schema } from "./schema"; + +/** + * ## Setup + * + * Add this adapter to your `pages/api/[...nextauth].js` next-auth configuration object: + * + * ```js title="pages/api/auth/[...nextauth].js" + * import NextAuth from "next-auth" + * import GoogleProvider from "next-auth/providers/google" + * import { DrizzleAdapter } from "@next-auth/drizzle-adapter" + * import { db } from "./db-schema" + * + * export default NextAuth({ + * adapter: DrizzleAdapter(db), + * providers: [ + * GoogleProvider({ + * clientId: process.env.GOOGLE_CLIENT_ID, + * clientSecret: process.env.GOOGLE_CLIENT_SECRET, + * }), + * ], + * }) + * ``` + * + * ## Advanced usage + * + * ### Create the Drizzle schema from scratch + * + * You'll need to create a database schema that includes the minimal schema for a `next-auth` adapter. + * Be sure to use the Drizzle driver version that you're using for your project. + * + * > This schema is adapted for use in Drizzle and based upon our main [schema](https://authjs.dev/reference/adapters#models) + * + * + * ```json title="db-schema.ts" + * + * import { integer, pgTable, text, primaryKey } from 'drizzle-orm/pg-core'; + * import { drizzle } from 'drizzle-orm/node-postgres'; + * import { migrate } from 'drizzle-orm/node-postgres/migrator'; + * import { Pool } from 'pg' + * import { ProviderType } from 'next-auth/providers'; + * + * export const users = pgTable('users', { + * id: text('id').notNull().primaryKey(), + * name: text('name'), + * email: text("email").notNull(), + * emailVerified: integer("emailVerified"), + * image: text("image"), + * }); + * + * export const accounts = pgTable("accounts", { + * userId: text("userId").notNull().references(() => users.id, { onDelete: "cascade" }), + * type: text("type").$type().notNull(), + * provider: text("provider").notNull(), + * providerAccountId: text("providerAccountId").notNull(), + * refresh_token: text("refresh_token"), + * access_token: text("access_token"), + * expires_at: integer("expires_at"), + * token_type: text("token_type"), + * scope: text("scope"), + * id_token: text("id_token"), + * session_state: text("session_state"), + * }, (account) => ({ + * _: primaryKey(account.provider, account.providerAccountId) + * })) + * + * export const sessions = pgTable("sessions", { + * userId: text("userId").notNull().references(() => users.id, { onDelete: "cascade" }), + * sessionToken: text("sessionToken").notNull().primaryKey(), + * expires: integer("expires").notNull(), + * }) + * + * export const verificationTokens = pgTable("verificationToken", { + * identifier: text("identifier").notNull(), + * token: text("token").notNull(), + * expires: integer("expires").notNull() + * }, (vt) => ({ + * _: primaryKey(vt.identifier, vt.token) + * })) + * + * const pool = new Pool({ + * connectionString: "YOUR_CONNECTION_STRING" + * }); + * + * export const db = drizzle(pool); + * + * migrate(db, { migrationsFolder: "./drizzle" }) + * + * ``` + * + **/ +export function DrizzleAdapterPg( + client: DbClient, + { users, sessions, verificationTokens, accounts }: Schema +): Adapter { + return { + createUser: async (data) => { + return client + .insert(users) + .values({ ...data, id: uuid() }) + .returning() + .then((res) => res[0]); + }, + getUser: async (data) => { + return ( + client + .select() + .from(users) + .where(eq(users.id, data)) + .then((res) => res[0]) ?? null + ); + }, + getUserByEmail: async (data) => { + return ( + client + .select() + .from(users) + .where(eq(users.email, data)) + .then((res) => res[0]) ?? null + ); + }, + createSession: async (data) => { + return client + .insert(sessions) + .values(data) + .returning() + .then((res) => res[0]); + }, + getSessionAndUser: async (data) => { + return ( + client + .select({ + session: sessions, + user: users, + }) + .from(sessions) + .where(eq(sessions.sessionToken, data)) + .innerJoin(users, eq(users.id, sessions.userId)) + .then((res) => res[0]) ?? null + ); + }, + updateUser: async (data) => { + if (!data.id) { + throw new Error("No user id."); + } + + return client + .update(users) + .set(data) + .where(eq(users.id, data.id)) + .returning() + .then((res) => res[0]); + }, + updateSession: async (data) => { + return client + .update(sessions) + .set(data) + .where(eq(sessions.sessionToken, data.sessionToken)) + .returning() + .then((res) => res[0]); + }, + linkAccount: async (rawAccount) => { + const updatedAccount = await client + .insert(accounts) + .values(rawAccount) + .returning() + .then((res) => res[0]); + + // Drizzle will return `null` for fields that are not defined. + // However, the return type is expecting `undefined`. + const account: ReturnType = { + ...updatedAccount, + access_token: updatedAccount.access_token ?? undefined, + token_type: updatedAccount.token_type ?? undefined, + id_token: updatedAccount.id_token ?? undefined, + refresh_token: updatedAccount.refresh_token ?? undefined, + scope: updatedAccount.scope ?? undefined, + expires_at: updatedAccount.expires_at ?? undefined, + session_state: updatedAccount.session_state ?? undefined, + }; + + return account; + }, + getUserByAccount: async (account) => { + const user = + (await client + .select() + .from(users) + .innerJoin( + accounts, + and( + eq(accounts.providerAccountId, account.providerAccountId), + eq(accounts.provider, account.provider) + ) + ) + .then((res) => res[0])) ?? null; + + return user.users; + }, + deleteSession: async (sessionToken) => { + await client + .delete(sessions) + .where(eq(sessions.sessionToken, sessionToken)); + }, + createVerificationToken: async (token) => { + return client + .insert(verificationTokens) + .values(token) + .returning() + .then((res) => res[0]); + }, + useVerificationToken: async (token) => { + try { + return ( + client + .delete(verificationTokens) + .where( + and( + eq(verificationTokens.identifier, token.identifier), + eq(verificationTokens.token, token.token) + ) + ) + .returning() + .then((res) => res[0]) ?? null + ); + } catch (err) { + throw new Error("No verification token found."); + } + }, + deleteUser: async (id) => { + await client + .delete(users) + .where(eq(users.id, id)) + .returning() + .then((res) => res[0]); + }, + unlinkAccount: async (account) => { + await client + .delete(accounts) + .where( + and( + eq(accounts.providerAccountId, account.providerAccountId), + eq(accounts.provider, account.provider) + ) + ); + + return undefined; + }, + }; +} diff --git a/lib/db/migrate.ts b/lib/db/migrate.ts new file mode 100644 index 0000000..cfbe862 --- /dev/null +++ b/lib/db/migrate.ts @@ -0,0 +1,18 @@ +import { migrate } from "drizzle-orm/vercel-postgres/migrator"; +import { db } from "./schema"; +import * as dotenv from "dotenv"; + +dotenv.config(); + +migrate(db, { + migrationsFolder: "lib/db/migrations", +}) + .then(() => { + console.log("Migration was succesfull!"); + }) + .catch((err) => { + console.error("Migration failed:", err); + }) + .finally(() => { + console.info("Migration finished"); + }); diff --git a/lib/db/migrations/0000_flawless_moondragon.sql b/lib/db/migrations/0000_flawless_moondragon.sql new file mode 100644 index 0000000..e634f49 --- /dev/null +++ b/lib/db/migrations/0000_flawless_moondragon.sql @@ -0,0 +1,49 @@ +CREATE TABLE IF NOT EXISTS "accounts" ( + "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 +); +--> statement-breakpoint +ALTER TABLE "accounts" ADD CONSTRAINT "accounts_provider_providerAccountId" PRIMARY KEY("provider","providerAccountId"); + +CREATE TABLE IF NOT EXISTS "sessions" ( + "sessionToken" text PRIMARY KEY NOT NULL, + "userId" text NOT NULL, + "expires" timestamp NOT NULL +); + +CREATE TABLE IF NOT EXISTS "users" ( + "id" text PRIMARY KEY NOT NULL, + "name" text, + "email" text NOT NULL, + "emailVerified" timestamp, + "image" text +); + +CREATE TABLE IF NOT EXISTS "verificationToken" ( + "identifier" text NOT NULL, + "token" text NOT NULL, + "expires" timestamp NOT NULL +); +--> statement-breakpoint +ALTER TABLE "verificationToken" ADD CONSTRAINT "verificationToken_identifier_token" PRIMARY KEY("identifier","token"); + +DO $$ BEGIN + ALTER TABLE "accounts" ADD CONSTRAINT "accounts_userId_users_id_fk" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; + +DO $$ BEGIN + ALTER TABLE "sessions" ADD CONSTRAINT "sessions_userId_users_id_fk" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; diff --git a/lib/db/migrations/0001_colorful_spectrum.sql b/lib/db/migrations/0001_colorful_spectrum.sql new file mode 100644 index 0000000..5ff3ddb --- /dev/null +++ b/lib/db/migrations/0001_colorful_spectrum.sql @@ -0,0 +1,7 @@ +ALTER TABLE "accounts" DROP CONSTRAINT "accounts_provider_providerAccountId"; +ALTER TABLE "verificationToken" DROP CONSTRAINT "verificationToken_identifier_token"; +CREATE UNIQUE INDEX IF NOT EXISTS "Account_provider_providerAccountId_key" ON "accounts" ("provider","providerAccountId"); +CREATE UNIQUE INDEX IF NOT EXISTS "Session_sessionToken_key" ON "sessions" ("sessionToken"); +CREATE UNIQUE INDEX IF NOT EXISTS "User_email_key" ON "users" ("email"); +CREATE UNIQUE INDEX IF NOT EXISTS "VerificationToken_identifier_token_key" ON "verificationToken" ("identifier","token"); +CREATE UNIQUE INDEX IF NOT EXISTS "VerificationToken_token_key" ON "verificationToken" ("token"); \ No newline at end of file diff --git a/lib/db/migrations/meta/0000_snapshot.json b/lib/db/migrations/meta/0000_snapshot.json new file mode 100644 index 0000000..9bf3d1e --- /dev/null +++ b/lib/db/migrations/meta/0000_snapshot.json @@ -0,0 +1,227 @@ +{ + "version": "5", + "dialect": "pg", + "id": "ea430ef9-8e30-4cb6-9985-d03f43656cf8", + "prevId": "00000000-0000-0000-0000-000000000000", + "tables": { + "accounts": { + "name": "accounts", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_state": { + "name": "session_state", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "accounts_userId_users_id_fk": { + "name": "accounts_userId_users_id_fk", + "tableFrom": "accounts", + "tableTo": "users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "accounts_provider_providerAccountId": { + "name": "accounts_provider_providerAccountId", + "columns": [ + "provider", + "providerAccountId" + ] + } + } + }, + "sessions": { + "name": "sessions", + "schema": "", + "columns": { + "sessionToken": { + "name": "sessionToken", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires": { + "name": "expires", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "sessions_userId_users_id_fk": { + "name": "sessions_userId_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {} + }, + "users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "emailVerified": { + "name": "emailVerified", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {} + }, + "verificationToken": { + "name": "verificationToken", + "schema": "", + "columns": { + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires": { + "name": "expires", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "verificationToken_identifier_token": { + "name": "verificationToken_identifier_token", + "columns": [ + "identifier", + "token" + ] + } + } + } + }, + "enums": {}, + "schemas": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + } +} \ No newline at end of file diff --git a/lib/db/migrations/meta/0001_snapshot.json b/lib/db/migrations/meta/0001_snapshot.json new file mode 100644 index 0000000..9f8af6f --- /dev/null +++ b/lib/db/migrations/meta/0001_snapshot.json @@ -0,0 +1,252 @@ +{ + "version": "5", + "dialect": "pg", + "id": "34122d24-88e0-430e-862c-2168b0f866cb", + "prevId": "ea430ef9-8e30-4cb6-9985-d03f43656cf8", + "tables": { + "accounts": { + "name": "accounts", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_state": { + "name": "session_state", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "Account_provider_providerAccountId_key": { + "name": "Account_provider_providerAccountId_key", + "columns": [ + "provider", + "providerAccountId" + ], + "isUnique": true + } + }, + "foreignKeys": { + "accounts_userId_users_id_fk": { + "name": "accounts_userId_users_id_fk", + "tableFrom": "accounts", + "tableTo": "users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {} + }, + "sessions": { + "name": "sessions", + "schema": "", + "columns": { + "sessionToken": { + "name": "sessionToken", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires": { + "name": "expires", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "Session_sessionToken_key": { + "name": "Session_sessionToken_key", + "columns": [ + "sessionToken" + ], + "isUnique": true + } + }, + "foreignKeys": { + "sessions_userId_users_id_fk": { + "name": "sessions_userId_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {} + }, + "users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "emailVerified": { + "name": "emailVerified", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "User_email_key": { + "name": "User_email_key", + "columns": [ + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {} + }, + "verificationToken": { + "name": "verificationToken", + "schema": "", + "columns": { + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires": { + "name": "expires", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "VerificationToken_identifier_token_key": { + "name": "VerificationToken_identifier_token_key", + "columns": [ + "identifier", + "token" + ], + "isUnique": true + }, + "VerificationToken_token_key": { + "name": "VerificationToken_token_key", + "columns": [ + "token" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {} + } + }, + "enums": {}, + "schemas": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + } +} \ No newline at end of file diff --git a/lib/db/migrations/meta/_journal.json b/lib/db/migrations/meta/_journal.json new file mode 100644 index 0000000..c023d95 --- /dev/null +++ b/lib/db/migrations/meta/_journal.json @@ -0,0 +1,20 @@ +{ + "version": "5", + "dialect": "pg", + "entries": [ + { + "idx": 0, + "version": "5", + "when": 1684771069602, + "tag": "0000_flawless_moondragon", + "breakpoints": false + }, + { + "idx": 1, + "version": "5", + "when": 1684771628481, + "tag": "0001_colorful_spectrum", + "breakpoints": false + } + ] +} \ No newline at end of file diff --git a/lib/db/schema.ts b/lib/db/schema.ts new file mode 100644 index 0000000..44be2d6 --- /dev/null +++ b/lib/db/schema.ts @@ -0,0 +1,353 @@ +import { + integer, + timestamp, + pgTable, + text, + primaryKey, + uniqueIndex, +} from "drizzle-orm/pg-core"; +import { drizzle } from "drizzle-orm/vercel-postgres"; +import { sql } from "@vercel/postgres"; +import { ProviderType } from "@auth/nextjs/providers"; + +export const users = pgTable( + "users", + { + id: text("id").notNull().primaryKey(), + name: text("name"), + email: text("email").notNull(), + emailVerified: timestamp("emailVerified", { mode: "date" }), + image: text("image"), + }, + (user) => ({ + emailKey: uniqueIndex("User_email_key").on(user.email), + }) +); + +export const accounts = pgTable( + "accounts", + { + userId: text("userId") + .notNull() + .references(() => users.id, { onDelete: "cascade" }), + type: text("type").$type().notNull(), + provider: text("provider").notNull(), + providerAccountId: text("providerAccountId").notNull(), + refresh_token: text("refresh_token"), + access_token: text("access_token"), + expires_at: integer("expires_at"), + token_type: text("token_type"), + scope: text("scope"), + id_token: text("id_token"), + session_state: text("session_state"), + }, + (table) => { + return { + providerProviderAccountIdKey: uniqueIndex( + "Account_provider_providerAccountId_key" + ).on(table.provider, table.providerAccountId), + }; + } +); + +export const sessions = pgTable( + "sessions", + { + sessionToken: text("sessionToken").notNull().primaryKey(), + userId: text("userId") + .notNull() + .references(() => users.id, { onDelete: "cascade" }), + expires: timestamp("expires", { mode: "date" }).notNull(), + }, + (table) => { + return { + sessionTokenKey: uniqueIndex("Session_sessionToken_key").on( + table.sessionToken + ), + }; + } +); + +export const verificationTokens = pgTable( + "verificationToken", + { + identifier: text("identifier").notNull(), + token: text("token").notNull(), + expires: timestamp("expires", { mode: "date" }).notNull(), + }, + (table) => ({ + identifierTokenKey: uniqueIndex( + "VerificationToken_identifier_token_key" + ).on(table.identifier, table.token), + tokenKey: uniqueIndex("VerificationToken_token_key").on(table.token), + }) +); + +// Connect to Vercel Postgres +export const db = drizzle(sql); + +export type DbClient = typeof db; + +export type Schema = { + users: typeof users; + accounts: typeof accounts; + sessions: typeof sessions; + verificationTokens: typeof verificationTokens; +}; +// import { sql, eq, and } from "drizzle-orm"; +// import { +// PgDatabase, +// QueryResultHKT, +// integer, +// pgTable, +// text, +// timestamp, +// uniqueIndex, +// uuid, +// } from "drizzle-orm/pg-core"; +// import { drizzle } from "drizzle-orm/vercel-postgres"; +// import { sql as c } from "@vercel/postgres"; +// import { Adapter, AdapterUser } from "@auth/nextjs/adapters"; + +// export const db = drizzle(c); + +// export const users = pgTable( +// "users", +// { +// id: uuid("id") +// .notNull() +// .primaryKey() +// .default(sql`gen_random_uuid()`), +// name: text("name"), +// email: text("email"), +// emailVerified: timestamp("emailVerified", { precision: 3 }), +// image: text("image"), +// }, +// (table) => { +// return { +// emailKey: uniqueIndex("User_email_key").on(table.email), +// }; +// } +// ); + +// export const verificationTokens = pgTable( +// "verification_tokens", +// { +// identifier: text("identifier").notNull(), +// token: text("token").notNull(), +// expires: timestamp("expires", { precision: 3 }).notNull(), +// }, +// (table) => { +// return { +// identifierTokenKey: uniqueIndex( +// "VerificationToken_identifier_token_key" +// ).on(table.identifier, table.token), +// tokenKey: uniqueIndex("VerificationToken_token_key").on(table.token), +// }; +// } +// ); + +// export const sessions = pgTable( +// "sessions", +// { +// id: uuid("id") +// .notNull() +// .primaryKey() +// .default(sql`gen_random_uuid()`), +// sessionToken: text("sessionToken").notNull(), +// userId: uuid("userId") +// .notNull() +// .references(() => users.id, { onDelete: "cascade", onUpdate: "cascade" }), +// expires: timestamp("expires", { precision: 3 }).notNull(), +// }, +// (table) => { +// return { +// sessionTokenKey: uniqueIndex("Session_sessionToken_key").on( +// table.sessionToken +// ), +// }; +// } +// ); + +// export const accounts = pgTable( +// "accounts", +// { +// id: uuid("id") +// .notNull() +// .primaryKey() +// .default(sql`gen_random_uuid()`), +// userId: uuid("userId") +// .notNull() +// .references(() => users.id, { onDelete: "cascade", onUpdate: "cascade" }), +// type: text("type").notNull(), +// provider: text("provider").notNull(), +// providerAccountId: text("providerAccountId").notNull(), +// refresh_token: text("refresh_token"), +// access_token: text("access_token"), +// expires_at: integer("expires_at"), +// token_type: text("token_type"), +// scope: text("scope"), +// id_token: text("id_token"), +// session_state: text("session_state"), +// }, +// (table) => { +// return { +// providerProviderAccountIdKey: uniqueIndex( +// "Account_provider_providerAccountId_key" +// ).on(table.provider, table.providerAccountId), +// }; +// } +// ); + +// type Users = typeof users; +// type Sessions = typeof sessions; +// type Accounts = typeof accounts; +// type VerificationTokens = typeof verificationTokens; + +// export function DrizzleAdapter< +// T extends PgDatabase, +// U extends QueryResultHKT +// >({ +// db, +// schema, +// }: { +// db: T; +// schema: { +// users: Users; +// sessions: Sessions; +// accounts: Accounts; +// verificationTokens: VerificationTokens; +// }; +// }): Adapter { +// return { +// async createUser(user) { +// const [newUser] = await db.insert(schema.users).values(user).returning(); + +// return newUser as AdapterUser; +// }, +// async getUser(id) { +// const [user] = await db +// .select() +// .from(schema.users) +// .where(eq(schema.users.id, id)); + +// return (user as AdapterUser) || null; +// }, +// async getUserByEmail(email) { +// const [user] = await db +// .select() +// .from(schema.users) +// .where(eq(schema.users.email, email)); + +// return (user as AdapterUser) || null; +// }, +// async getUserByAccount({ providerAccountId, provider }) { +// const results = await db +// .select() +// .from(schema.users) +// .leftJoin(schema.accounts, eq(schema.accounts.userId, schema.users.id)) +// .where( +// and( +// eq(schema.accounts.provider, provider), +// eq(schema.accounts.providerAccountId, providerAccountId) +// ) +// ); + +// if (results.length) { +// return results[0].users as AdapterUser; +// } + +// return null; +// }, +// async updateUser(user) { +// const userId = user.id; + +// if (!userId) { +// throw new Error("Cannot update user without id"); +// } + +// const [updatedUser] = await db +// .update(schema.users) +// .set(user) +// .where(eq(schema.users.id, userId)) +// .returning(); + +// return updatedUser as AdapterUser; +// }, +// async deleteUser(userId) { +// await db.delete(schema.users).where(eq(schema.users.id, userId)); +// }, +// async linkAccount(account) { +// await db.insert(schema.accounts).values(account); +// }, +// async unlinkAccount({ providerAccountId, provider }) { +// await db +// .delete(schema.accounts) +// .where( +// and( +// eq(schema.accounts.provider, provider), +// eq(schema.accounts.providerAccountId, providerAccountId) +// ) +// ); +// }, +// async createSession(session) { +// const [newSession] = await db +// .insert(schema.sessions) +// .values(session) +// .returning(); + +// return newSession; +// }, +// async getSessionAndUser(sessionToken) { +// const results = await db +// .select() +// .from(schema.sessions) +// .leftJoin(schema.users, eq(schema.users.id, schema.sessions.userId)) +// .where(eq(schema.sessions.sessionToken, sessionToken)); + +// if (results.length) { +// return { +// user: results[0].users! as AdapterUser, +// session: results[0].sessions, +// }; +// } + +// return null; +// }, +// async updateSession(session) { +// const [updatedSession] = await db +// .update(schema.sessions) +// .set(session) +// .where(eq(schema.sessions.sessionToken, session.sessionToken)) +// .returning(); + +// return updatedSession; +// }, +// async deleteSession(sessionToken) { +// await db +// .delete(schema.sessions) +// .where(eq(schema.sessions.sessionToken, sessionToken)); +// }, +// async createVerificationToken(token) { +// const [newVerificationToken] = await db +// .insert(schema.verificationTokens) +// .values(token) +// .returning(); + +// return newVerificationToken; +// }, +// async useVerificationToken({ identifier, token }) { +// const [deletedVerificationToken] = await db +// .delete(schema.verificationTokens) +// .where( +// and( +// eq(schema.verificationTokens.identifier, identifier), +// eq(schema.verificationTokens.token, token) +// ) +// ) +// .returning(); + +// return deletedVerificationToken || null; +// }, +// }; +// } diff --git a/package.json b/package.json index 48f3409..640b8e5 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,10 @@ "scripts": { "dev": "prisma generate && next dev", "build": "prisma generate && next build", - "studio": "prisma studio", + "db:mg:create": "drizzle-kit generate:pg --config=drizzle.config.ts", + "db:mg:apply": "ts-node --project tsconfig.migration.json ./lib/db/migrate --config=drizzle.config.ts", + "db:check": "drizzle-kit check:pg --config=drizzle.config.ts", + "db:up": "drizzle-kit up:pg --config=drizzle.config.ts", "start": "next start", "lint": "next lint", "lint:fix": "next lint --fix", @@ -21,11 +24,13 @@ "@radix-ui/react-dropdown-menu": "^2.0.4", "@vercel/analytics": "^1.0.0", "@vercel/kv": "^0.2.1", + "@vercel/postgres": "^0.3.0", "body-scroll-lock": "4.0.0-beta.0", "class-variance-authority": "^0.4.0", "clsx": "^1.2.1", "common-tags": "^1.8.2", "cookie": "^0.5.0", + "drizzle-orm": "^0.26.0", "eventsource-parser": "^1.0.0", "focus-trap-react": "^10.1.1", "framer-motion": "^10.12.4", @@ -45,7 +50,8 @@ "remark-gfm": "^3.0.1", "remark-math": "^5.1.1", "tailwind-merge": "^1.12.0", - "tailwindcss-animate": "^1.0.5" + "tailwindcss-animate": "^1.0.5", + "uuid": "^9.0.0" }, "devDependencies": { "@ianvs/prettier-plugin-sort-imports": "^3.7.1", @@ -60,6 +66,8 @@ "@types/react-syntax-highlighter": "^15.5.6", "@typescript-eslint/parser": "^5.58.0", "autoprefixer": "^10.4.13", + "dotenv": "^16.0.3", + "drizzle-kit": "^0.18.0", "eslint": "^8.31.0", "eslint-config-next": "13.4.3-canary.3", "eslint-config-prettier": "^8.3.0", @@ -69,6 +77,7 @@ "prettier": "^2.7.1", "prisma": "^4.14.0", "tailwindcss": "^3.3.1", + "ts-node": "^10.9.1", "typescript": "^4.9.3" }, "pnpm": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cc1d6af..9ee82ff 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,6 +22,9 @@ dependencies: '@vercel/kv': specifier: ^0.2.1 version: 0.2.1 + '@vercel/postgres': + specifier: ^0.3.0 + version: 0.3.0 body-scroll-lock: specifier: 4.0.0-beta.0 version: 4.0.0-beta.0 @@ -37,6 +40,9 @@ dependencies: cookie: specifier: ^0.5.0 version: 0.5.0 + drizzle-orm: + specifier: ^0.26.0 + version: 0.26.0(@vercel/postgres@0.3.0) eventsource-parser: specifier: ^1.0.0 version: 1.0.0 @@ -97,6 +103,9 @@ dependencies: tailwindcss-animate: specifier: ^1.0.5 version: 1.0.5(tailwindcss@3.3.2) + uuid: + specifier: ^9.0.0 + version: 9.0.0 devDependencies: '@ianvs/prettier-plugin-sort-imports': @@ -135,6 +144,12 @@ devDependencies: autoprefixer: specifier: ^10.4.13 version: 10.4.14(postcss@8.4.23) + dotenv: + specifier: ^16.0.3 + version: 16.0.3 + drizzle-kit: + specifier: ^0.18.0 + version: 0.18.0 eslint: specifier: ^8.31.0 version: 8.40.0 @@ -161,7 +176,10 @@ devDependencies: version: 4.14.0 tailwindcss: specifier: ^3.3.1 - version: 3.3.2 + version: 3.3.2(ts-node@10.9.1) + ts-node: + specifier: ^10.9.1 + version: 10.9.1(@types/node@17.0.45)(typescript@4.9.5) typescript: specifier: ^4.9.3 version: 4.9.5 @@ -388,6 +406,12 @@ packages: '@babel/helper-validator-identifier': 7.19.1 to-fast-properties: 2.0.0 + /@cspotcode/source-map-support@0.8.1: + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + /@emotion/is-prop-valid@0.8.8: resolution: {integrity: sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==} requiresBuild: true @@ -401,6 +425,24 @@ packages: dev: false optional: true + /@esbuild/android-arm@0.15.18: + resolution: {integrity: sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-loong64@0.15.18: + resolution: {integrity: sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@eslint-community/eslint-utils@4.4.0(eslint@8.40.0): resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -532,6 +574,18 @@ packages: '@jridgewell/resolve-uri': 3.1.0 '@jridgewell/sourcemap-codec': 1.4.14 + /@jridgewell/trace-mapping@0.3.9: + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + dependencies: + '@jridgewell/resolve-uri': 3.1.0 + '@jridgewell/sourcemap-codec': 1.4.15 + + /@neondatabase/serverless@0.4.3: + resolution: {integrity: sha512-U8tpuF5f0R5WRsciR7iaJ5S2h54DWa6Z6CEW+J4KgwyvRN3q3qDz0MibdfFXU0WqnRoi/9RSf/2XN4TfeaOCbQ==} + dependencies: + '@types/pg': 8.6.6 + dev: false + /@next-auth/prisma-adapter@1.0.6(@prisma/client@4.14.0)(next-auth@4.22.1): resolution: {integrity: sha512-Z7agwfSZEeEcqKqrnisBun7VndRPshd6vyDsoRU68MXbkui8storkHgvN2hnNDrqr/hSCF9aRn56a1qpihaB4A==} peerDependencies: @@ -1022,9 +1076,21 @@ packages: lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 postcss-selector-parser: 6.0.10 - tailwindcss: 3.3.2 + tailwindcss: 3.3.2(ts-node@10.9.1) dev: true + /@tsconfig/node10@1.0.9: + resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} + + /@tsconfig/node12@1.0.11: + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + + /@tsconfig/node14@1.0.3: + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + + /@tsconfig/node16@1.0.4: + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + /@types/common-tags@1.8.1: resolution: {integrity: sha512-20R/mDpKSPWdJs5TOpz3e7zqbeCNuMCPhV7Yndk9KU2Rbij2r5W4RzwDPkzC+2lzUqXYu9rFzTktCBnDjHuNQg==} dev: true @@ -1070,7 +1136,14 @@ packages: /@types/node@17.0.45: resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==} - dev: true + + /@types/pg@8.6.6: + resolution: {integrity: sha512-O2xNmXebtwVekJDD+02udOncjVcMZQuTEQEMpKJ0ZRf5E7/9JJX3izhKUcUifBkyKpljyUM6BTgy2trmviKlpw==} + dependencies: + '@types/node': 17.0.45 + pg-protocol: 1.6.0 + pg-types: 2.2.0 + dev: false /@types/prop-types@15.7.5: resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} @@ -1184,6 +1257,16 @@ packages: - encoding dev: false + /@vercel/postgres@0.3.0: + resolution: {integrity: sha512-cOC+x6qMnN54B4y0Fh0DV5LJQp2M7puIKbehQBMutY/8/zpzh+oKaQmnZb2QHn489MGOQKyRLJLgHa2P8M085Q==} + engines: {node: '>=14.6'} + dependencies: + '@neondatabase/serverless': 0.4.3 + bufferutil: 4.0.7 + utf-8-validate: 6.0.3 + ws: 8.13.0(bufferutil@4.0.7)(utf-8-validate@6.0.3) + dev: false + /acorn-jsx@5.3.2(acorn@8.8.2): resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -1192,11 +1275,14 @@ packages: acorn: 8.8.2 dev: true + /acorn-walk@8.2.0: + resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} + engines: {node: '>=0.4.0'} + /acorn@8.8.2: resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==} engines: {node: '>=0.4.0'} hasBin: true - dev: true /ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -1235,6 +1321,9 @@ packages: normalize-path: 3.0.0 picomatch: 2.3.1 + /arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + /arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} @@ -1377,6 +1466,12 @@ packages: balanced-match: 1.0.2 concat-map: 0.0.1 + /brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + dependencies: + balanced-match: 1.0.2 + dev: true + /braces@3.0.2: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} engines: {node: '>=8'} @@ -1397,6 +1492,14 @@ packages: resolution: {integrity: sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=} dev: false + /bufferutil@4.0.7: + resolution: {integrity: sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==} + engines: {node: '>=6.14.2'} + requiresBuild: true + dependencies: + node-gyp-build: 4.6.0 + dev: false + /bundle-name@3.0.0: resolution: {integrity: sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==} engines: {node: '>=12'} @@ -1427,6 +1530,11 @@ packages: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} + /camelcase@7.0.1: + resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==} + engines: {node: '>=14.16'} + dev: true + /caniuse-lite@1.0.30001486: resolution: {integrity: sha512-uv7/gXuHi10Whlj0pp5q/tsK/32J2QSqVRKQhs2j8VsDCjgyruAh/eEXHF822VqO9yT6iZKw3nRwZRSPBE9OQg==} @@ -1450,6 +1558,11 @@ packages: supports-color: 7.2.0 dev: true + /chalk@5.2.0: + resolution: {integrity: sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + dev: true + /character-entities-legacy@1.1.4: resolution: {integrity: sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==} dev: false @@ -1491,6 +1604,17 @@ packages: typescript: 4.9.5 dev: false + /cli-color@2.0.3: + resolution: {integrity: sha512-OkoZnxyC4ERN3zLzZaY9Emb7f/MhBOIpePv0Ycok0fJYT+Ouo00UBEIwsVsr0yoow++n5YWlSUgST9GKhNHiRQ==} + engines: {node: '>=0.10'} + dependencies: + d: 1.0.1 + es5-ext: 0.10.62 + es6-iterator: 2.0.3 + memoizee: 0.4.15 + timers-ext: 0.1.7 + dev: true + /client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} dev: false @@ -1536,6 +1660,11 @@ packages: engines: {node: '>= 12'} dev: false + /commander@9.5.0: + resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} + engines: {node: ^12.20.0 || >=14} + dev: true + /common-tags@1.8.2: resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==} engines: {node: '>=4.0.0'} @@ -1552,6 +1681,9 @@ packages: engines: {node: '>= 0.6'} dev: false + /create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + /cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -1569,6 +1701,13 @@ packages: /csstype@3.1.2: resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} + /d@1.0.1: + resolution: {integrity: sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==} + dependencies: + es5-ext: 0.10.62 + type: 1.2.0 + dev: true + /damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} dev: true @@ -1671,11 +1810,21 @@ packages: /didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + /diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + /diff@5.1.0: resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==} engines: {node: '>=0.3.1'} dev: false + /difflib@0.2.4: + resolution: {integrity: sha512-9YVwmMb0wQHQNr5J9m6BSj6fk4pfGITGQOOs+D9Fl+INODWFOfvhIU1hNv6GgR1RBoC/9NJcwu77zShxV0kT7w==} + dependencies: + heap: 0.2.7 + dev: true + /dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -1700,6 +1849,98 @@ packages: esutils: 2.0.3 dev: true + /dotenv@16.0.3: + resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==} + engines: {node: '>=12'} + dev: true + + /dreamopt@0.8.0: + resolution: {integrity: sha512-vyJTp8+mC+G+5dfgsY+r3ckxlz+QMX40VjPQsZc5gxVAxLmi64TBoVkP54A/pRAXMXsbu2GMMBrZPxNv23waMg==} + engines: {node: '>=0.4.0'} + dependencies: + wordwrap: 1.0.0 + dev: true + + /drizzle-kit@0.18.0: + resolution: {integrity: sha512-43oH0XNWJDfMmVtDVnW8OV+tIs3xXDb1MjdsDiv0a7unQAylr0hpGG0HD5uEPWSbt7oxBots+hnYWxo98D0KAg==} + hasBin: true + dependencies: + camelcase: 7.0.1 + chalk: 5.2.0 + commander: 9.5.0 + esbuild: 0.15.18 + esbuild-register: 3.4.2(esbuild@0.15.18) + glob: 8.1.0 + hanji: 0.0.5 + json-diff: 0.9.0 + minimatch: 7.4.6 + zod: 3.21.4 + transitivePeerDependencies: + - supports-color + dev: true + + /drizzle-orm@0.26.0(@vercel/postgres@0.3.0): + resolution: {integrity: sha512-ztjhHehcuG5+lpGYxfT/L5I+yd/Z0dOf0fV3cS2ywBU01wkpxjwl4EJZVT7kVzjYfM8kwMGDghAPRPBCK0vULA==} + peerDependencies: + '@aws-sdk/client-rds-data': '>=3' + '@cloudflare/workers-types': '>=3' + '@libsql/client': '*' + '@neondatabase/serverless': '>=0.1' + '@planetscale/database': '>=1' + '@types/better-sqlite3': '*' + '@types/pg': '*' + '@types/sql.js': '*' + '@vercel/postgres': '*' + better-sqlite3: '>=7' + bun-types: '*' + knex: '*' + kysely: '*' + mysql2: '>=2' + pg: '>=8' + postgres: '>=3' + sql.js: '>=1' + sqlite3: '>=5' + peerDependenciesMeta: + '@aws-sdk/client-rds-data': + optional: true + '@cloudflare/workers-types': + optional: true + '@libsql/client': + optional: true + '@neondatabase/serverless': + optional: true + '@planetscale/database': + optional: true + '@types/better-sqlite3': + optional: true + '@types/pg': + optional: true + '@types/sql.js': + optional: true + '@vercel/postgres': + optional: true + better-sqlite3: + optional: true + bun-types: + optional: true + knex: + optional: true + kysely: + optional: true + mysql2: + optional: true + pg: + optional: true + postgres: + optional: true + sql.js: + optional: true + sqlite3: + optional: true + dependencies: + '@vercel/postgres': 0.3.0 + dev: false + /ecdsa-sig-formatter@1.0.11: resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} dependencies: @@ -1799,6 +2040,261 @@ packages: is-symbol: 1.0.4 dev: true + /es5-ext@0.10.62: + resolution: {integrity: sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==} + engines: {node: '>=0.10'} + requiresBuild: true + dependencies: + es6-iterator: 2.0.3 + es6-symbol: 3.1.3 + next-tick: 1.1.0 + dev: true + + /es6-iterator@2.0.3: + resolution: {integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==} + dependencies: + d: 1.0.1 + es5-ext: 0.10.62 + es6-symbol: 3.1.3 + dev: true + + /es6-symbol@3.1.3: + resolution: {integrity: sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==} + dependencies: + d: 1.0.1 + ext: 1.7.0 + dev: true + + /es6-weak-map@2.0.3: + resolution: {integrity: sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==} + dependencies: + d: 1.0.1 + es5-ext: 0.10.62 + es6-iterator: 2.0.3 + es6-symbol: 3.1.3 + dev: true + + /esbuild-android-64@0.15.18: + resolution: {integrity: sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /esbuild-android-arm64@0.15.18: + resolution: {integrity: sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /esbuild-darwin-64@0.15.18: + resolution: {integrity: sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /esbuild-darwin-arm64@0.15.18: + resolution: {integrity: sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /esbuild-freebsd-64@0.15.18: + resolution: {integrity: sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /esbuild-freebsd-arm64@0.15.18: + resolution: {integrity: sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-32@0.15.18: + resolution: {integrity: sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-64@0.15.18: + resolution: {integrity: sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-arm64@0.15.18: + resolution: {integrity: sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-arm@0.15.18: + resolution: {integrity: sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-mips64le@0.15.18: + resolution: {integrity: sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-ppc64le@0.15.18: + resolution: {integrity: sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-riscv64@0.15.18: + resolution: {integrity: sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-s390x@0.15.18: + resolution: {integrity: sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-netbsd-64@0.15.18: + resolution: {integrity: sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /esbuild-openbsd-64@0.15.18: + resolution: {integrity: sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /esbuild-register@3.4.2(esbuild@0.15.18): + resolution: {integrity: sha512-kG/XyTDyz6+YDuyfB9ZoSIOOmgyFCH+xPRtsCa8W85HLRV5Csp+o3jWVbOSHgSLfyLc5DmP+KFDNwty4mEjC+Q==} + peerDependencies: + esbuild: '>=0.12 <1' + dependencies: + debug: 4.3.4 + esbuild: 0.15.18 + transitivePeerDependencies: + - supports-color + dev: true + + /esbuild-sunos-64@0.15.18: + resolution: {integrity: sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /esbuild-windows-32@0.15.18: + resolution: {integrity: sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /esbuild-windows-64@0.15.18: + resolution: {integrity: sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /esbuild-windows-arm64@0.15.18: + resolution: {integrity: sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /esbuild@0.15.18: + resolution: {integrity: sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/android-arm': 0.15.18 + '@esbuild/linux-loong64': 0.15.18 + esbuild-android-64: 0.15.18 + esbuild-android-arm64: 0.15.18 + esbuild-darwin-64: 0.15.18 + esbuild-darwin-arm64: 0.15.18 + esbuild-freebsd-64: 0.15.18 + esbuild-freebsd-arm64: 0.15.18 + esbuild-linux-32: 0.15.18 + esbuild-linux-64: 0.15.18 + esbuild-linux-arm: 0.15.18 + esbuild-linux-arm64: 0.15.18 + esbuild-linux-mips64le: 0.15.18 + esbuild-linux-ppc64le: 0.15.18 + esbuild-linux-riscv64: 0.15.18 + esbuild-linux-s390x: 0.15.18 + esbuild-netbsd-64: 0.15.18 + esbuild-openbsd-64: 0.15.18 + esbuild-sunos-64: 0.15.18 + esbuild-windows-32: 0.15.18 + esbuild-windows-64: 0.15.18 + esbuild-windows-arm64: 0.15.18 + dev: true + /escalade@3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} engines: {node: '>=6'} @@ -2014,7 +2510,7 @@ packages: dependencies: fast-glob: 3.2.12 postcss: 8.4.23 - tailwindcss: 3.3.2 + tailwindcss: 3.3.2(ts-node@10.9.1) dev: true /eslint-scope@7.2.0: @@ -2112,6 +2608,13 @@ packages: engines: {node: '>=0.10.0'} dev: true + /event-emitter@0.3.5: + resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==} + dependencies: + d: 1.0.1 + es5-ext: 0.10.62 + dev: true + /eventsource-parser@1.0.0: resolution: {integrity: sha512-9jgfSCa3dmEme2ES3mPByGXfgZ87VbP97tng1G2nWwWx6bV2nYxm2AWCrbQjXToSe+yYlqaZNtxffR9IeQr95g==} engines: {node: '>=14.18'} @@ -2147,6 +2650,12 @@ packages: strip-final-newline: 3.0.0 dev: true + /ext@1.7.0: + resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==} + dependencies: + type: 2.7.2 + dev: true + /extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} dev: false @@ -2375,6 +2884,17 @@ packages: path-is-absolute: 1.0.1 dev: true + /glob@8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.6 + once: 1.4.0 + dev: true + /globals@11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} @@ -2438,6 +2958,13 @@ packages: resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} dev: true + /hanji@0.0.5: + resolution: {integrity: sha512-Abxw1Lq+TnYiL4BueXqMau222fPSPMFtya8HdpWsz/xVAhifXou71mPh/kY2+08RgFcVccjG3uZHs6K5HAe3zw==} + dependencies: + lodash.throttle: 4.1.1 + sisteransi: 1.0.5 + dev: true + /has-bigints@1.0.2: resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} dev: true @@ -2498,6 +3025,10 @@ packages: space-separated-tokens: 1.1.5 dev: false + /heap@0.2.7: + resolution: {integrity: sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==} + dev: true + /highlight.js@10.7.3: resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} dev: false @@ -2695,6 +3226,10 @@ packages: engines: {node: '>=12'} dev: false + /is-promise@2.2.2: + resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==} + dev: true + /is-regex@1.1.4: resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} engines: {node: '>= 0.4'} @@ -2820,6 +3355,15 @@ packages: engines: {node: '>=4'} hasBin: true + /json-diff@0.9.0: + resolution: {integrity: sha512-cVnggDrVkAAA3OvFfHpFEhOnmcsUpleEKq4d4O8sQWWSH40MBrWstKigVB1kGrgLWzuom+7rRdaCsnBD6VyObQ==} + hasBin: true + dependencies: + cli-color: 2.0.3 + difflib: 0.2.4 + dreamopt: 0.8.0 + dev: true + /json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} dev: true @@ -2937,6 +3481,10 @@ packages: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true + /lodash.throttle@4.1.1: + resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==} + dev: true + /lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} dev: false @@ -2969,6 +3517,12 @@ packages: dependencies: yallist: 4.0.0 + /lru-queue@0.1.0: + resolution: {integrity: sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==} + dependencies: + es5-ext: 0.10.62 + dev: true + /lucide-react@0.216.0(react@18.2.0): resolution: {integrity: sha512-PGWXs6JXI/08rhfkmj/joji0MAKDCcms44j0LYILO4J36AWqKhgjjaZ6WECRSyZ9TQe5gOrAR0g38O8cKrQngw==} peerDependencies: @@ -2977,6 +3531,9 @@ packages: react: 18.2.0 dev: false + /make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + /markdown-table@3.0.3: resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==} dev: false @@ -3120,6 +3677,19 @@ packages: '@types/mdast': 3.0.11 dev: false + /memoizee@0.4.15: + resolution: {integrity: sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==} + dependencies: + d: 1.0.1 + es5-ext: 0.10.62 + es6-weak-map: 2.0.3 + event-emitter: 0.3.5 + is-promise: 2.2.2 + lru-queue: 0.1.0 + next-tick: 1.1.0 + timers-ext: 0.1.7 + dev: true + /merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} dev: true @@ -3410,6 +3980,20 @@ packages: dependencies: brace-expansion: 1.1.11 + /minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + dependencies: + brace-expansion: 2.0.1 + dev: true + + /minimatch@7.4.6: + resolution: {integrity: sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==} + engines: {node: '>=10'} + dependencies: + brace-expansion: 2.0.1 + dev: true + /minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} dev: true @@ -3484,6 +4068,10 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /next-tick@1.1.0: + resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} + dev: true + /next@13.4.3-canary.3(@babel/core@7.21.8)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-Fix/RAVHJHUpJZsvftaC5Pm8qGP7/NIitOBsKPozSPKIekCUWwKiaoLPY140taMgl9R+oW1nXKwYhAdjM8gSMg==} engines: {node: '>=16.8.0'} @@ -3541,6 +4129,11 @@ packages: whatwg-url: 5.0.0 dev: false + /node-gyp-build@4.6.0: + resolution: {integrity: sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==} + hasBin: true + dev: false + /node-releases@2.0.10: resolution: {integrity: sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==} @@ -3768,6 +4361,26 @@ packages: engines: {node: '>=8'} dev: true + /pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + dev: false + + /pg-protocol@1.6.0: + resolution: {integrity: sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==} + dev: false + + /pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.0 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 + dev: false + /picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} @@ -3803,7 +4416,7 @@ packages: camelcase-css: 2.0.1 postcss: 8.4.23 - /postcss-load-config@4.0.1(postcss@8.4.23): + /postcss-load-config@4.0.1(postcss@8.4.23)(ts-node@10.9.1): resolution: {integrity: sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==} engines: {node: '>= 14'} peerDependencies: @@ -3817,6 +4430,7 @@ packages: dependencies: lilconfig: 2.1.0 postcss: 8.4.23 + ts-node: 10.9.1(@types/node@17.0.45)(typescript@4.9.5) yaml: 2.2.2 /postcss-nested@6.0.1(postcss@8.4.23): @@ -3863,6 +4477,28 @@ packages: picocolors: 1.0.0 source-map-js: 1.0.2 + /postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + dev: false + + /postgres-bytea@1.0.0: + resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==} + engines: {node: '>=0.10.0'} + dev: false + + /postgres-date@1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} + engines: {node: '>=0.10.0'} + dev: false + + /postgres-interval@1.2.0: + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} + engines: {node: '>=0.10.0'} + dependencies: + xtend: 4.0.2 + dev: false + /preact-render-to-string@5.2.3(preact@10.11.3): resolution: {integrity: sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==} peerDependencies: @@ -4266,6 +4902,10 @@ packages: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} dev: true + /sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + dev: true + /slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} @@ -4440,10 +5080,10 @@ packages: peerDependencies: tailwindcss: '>=3.0.0 || insiders' dependencies: - tailwindcss: 3.3.2 + tailwindcss: 3.3.2(ts-node@10.9.1) dev: false - /tailwindcss@3.3.2: + /tailwindcss@3.3.2(ts-node@10.9.1): resolution: {integrity: sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w==} engines: {node: '>=14.0.0'} hasBin: true @@ -4465,7 +5105,7 @@ packages: postcss: 8.4.23 postcss-import: 15.1.0(postcss@8.4.23) postcss-js: 4.0.1(postcss@8.4.23) - postcss-load-config: 4.0.1(postcss@8.4.23) + postcss-load-config: 4.0.1(postcss@8.4.23)(ts-node@10.9.1) postcss-nested: 6.0.1(postcss@8.4.23) postcss-selector-parser: 6.0.12 postcss-value-parser: 4.2.0 @@ -4494,6 +5134,13 @@ packages: dependencies: any-promise: 1.3.0 + /timers-ext@0.1.7: + resolution: {integrity: sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==} + dependencies: + es5-ext: 0.10.62 + next-tick: 1.1.0 + dev: true + /titleize@3.0.0: resolution: {integrity: sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==} engines: {node: '>=12'} @@ -4524,6 +5171,36 @@ packages: /ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + /ts-node@10.9.1(@types/node@17.0.45)(typescript@4.9.5): + resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.9 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 17.0.45 + acorn: 8.8.2 + acorn-walk: 8.2.0 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 4.9.5 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + /tsconfig-paths@3.14.2: resolution: {integrity: sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==} dependencies: @@ -4562,6 +5239,14 @@ packages: engines: {node: '>=10'} dev: true + /type@1.2.0: + resolution: {integrity: sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==} + dev: true + + /type@2.7.2: + resolution: {integrity: sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==} + dev: true + /typed-array-length@1.0.4: resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==} dependencies: @@ -4720,6 +5405,14 @@ packages: tslib: 2.5.0 dev: false + /utf-8-validate@6.0.3: + resolution: {integrity: sha512-uIuGf9TWQ/y+0Lp+KGZCMuJWc3N9BHA+l/UmHd/oUHwJJDeysyTRxNQVkbzsIWfGFbRe3OcgML/i0mvVRPOyDA==} + engines: {node: '>=6.14.2'} + requiresBuild: true + dependencies: + node-gyp-build: 4.6.0 + dev: false + /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -4728,6 +5421,11 @@ packages: hasBin: true dev: false + /uuid@9.0.0: + resolution: {integrity: sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==} + hasBin: true + dev: false + /uvu@0.5.6: resolution: {integrity: sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==} engines: {node: '>=8'} @@ -4739,6 +5437,9 @@ packages: sade: 1.8.1 dev: false + /v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + /vfile-message@3.1.4: resolution: {integrity: sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==} dependencies: @@ -4814,9 +5515,29 @@ packages: engines: {node: '>=0.10.0'} dev: true + /wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + dev: true + /wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + /ws@8.13.0(bufferutil@4.0.7)(utf-8-validate@6.0.3): + resolution: {integrity: sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dependencies: + bufferutil: 4.0.7 + utf-8-validate: 6.0.3 + dev: false + /xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} @@ -4832,6 +5553,10 @@ packages: resolution: {integrity: sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==} engines: {node: '>= 14'} + /yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + /yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -4839,7 +5564,6 @@ packages: /zod@3.21.4: resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==} - dev: false /zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} diff --git a/tsconfig.migration.json b/tsconfig.migration.json new file mode 100644 index 0000000..f06016b --- /dev/null +++ b/tsconfig.migration.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "module": "CommonJS", + "moduleResolution": "Node", + "target": "ES2020", + "jsx": "react", + "strictNullChecks": true, + "strictFunctionTypes": true + }, + "exclude": ["node_modules", "**/node_modules/*"] +} From 030b23985d57dc23097a19b6a8dc71c43fe830ad Mon Sep 17 00:00:00 2001 From: Jared Palmer Date: Fri, 2 Jun 2023 11:15:04 -0400 Subject: [PATCH 02/11] Try to hack auth --- app/api/chat/route.ts | 43 ++ app/api/generate/route.ts | 45 --- app/chat-list.tsx | 2 +- app/chat.tsx | 32 +- app/chat/[id]/page.tsx | 2 +- app/sidebar.tsx | 12 +- app/use-prompt.tsx | 130 ------- auth.ts | 4 +- lib/db/db.ts | 4 - lib/db/index.ts | 38 +- ...s_moondragon.sql => 0000_clammy_freak.sql} | 8 +- lib/db/migrations/0001_colorful_spectrum.sql | 7 - lib/db/migrations/0001_petite_sue_storm.sql | 11 + lib/db/migrations/meta/0000_snapshot.json | 20 +- lib/db/migrations/meta/0001_snapshot.json | 122 +++--- lib/db/migrations/meta/_journal.json | 8 +- lib/db/schema.ts | 367 +++--------------- package.json | 10 +- pnpm-lock.yaml | 138 ++++--- 19 files changed, 330 insertions(+), 673 deletions(-) create mode 100644 app/api/chat/route.ts delete mode 100644 app/api/generate/route.ts delete mode 100644 app/use-prompt.tsx delete mode 100644 lib/db/db.ts rename lib/db/migrations/{0000_flawless_moondragon.sql => 0000_clammy_freak.sql} (93%) delete mode 100644 lib/db/migrations/0001_colorful_spectrum.sql create mode 100644 lib/db/migrations/0001_petite_sue_storm.sql diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts new file mode 100644 index 0000000..306db6e --- /dev/null +++ b/app/api/chat/route.ts @@ -0,0 +1,43 @@ +import { auth } from "@/auth"; +import { chats, db } from "@/lib/db/schema"; +import { openai } from "@/lib/openai"; +import { OpenAIStream, StreamingTextResponse } from "ai-connector"; + +export const runtime = "edge"; + +export const POST = auth(async function POST(req: Request) { + const json = await req.json(); + // @ts-ignore + console.log(req.auth); // todo fix types + + const res = await openai.createChatCompletion({ + model: "gpt-3.5-turbo", + messages: json.messages.map((m: any) => ({ + content: m.content, + role: m.role, + })), + temperature: 0.7, + top_p: 1, + frequency_penalty: 1, + max_tokens: 500, + n: 1, + stream: true, + }); + + const stream = await OpenAIStream(res, { + async onCompletion(completion) { + // @ts-ignore + if (req.auth?.user?.email == null) { + return; + } + const title = json.messages[0].content.substring(0, 20); + await db.insert(chats).values({ + id: crypto.randomUUID(), + title, + userId: (req as any).auth?.user?.email, + }); + }, + }); + + return new StreamingTextResponse(stream); +}); diff --git a/app/api/generate/route.ts b/app/api/generate/route.ts deleted file mode 100644 index 5d73c5a..0000000 --- a/app/api/generate/route.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { auth } from "@/auth"; -import { OpenAIStream, openai } from "@/lib/openai"; -import { prisma } from "@/lib/prisma"; - -export const POST = auth(async function POST(req: Request) { - const json = await req.json(); - // @ts-ignore - console.log(req.auth); // todo fix types - - const res = await openai.createChatCompletion({ - model: "gpt-3.5-turbo", - messages: json.messages, - temperature: 0.7, - top_p: 1, - frequency_penalty: 1, - max_tokens: 500, - n: 1, - stream: true, - }); - - const stream = await OpenAIStream(res); - - let fullResponse = ""; - const decoder = new TextDecoder(); - const saveToPrisma = new TransformStream({ - transform: async (chunk, controller) => { - controller.enqueue(chunk); - fullResponse += decoder.decode(chunk); - }, - flush: async () => { - await prisma.chat.upsert({ - where: { - id: json.id, - }, - create: json, - update: json, - }); - }, - }); - - return new Response(stream.pipeThrough(saveToPrisma), { - status: 200, - headers: { "Content-Type": "text/event-stream" }, - }); -}); diff --git a/app/chat-list.tsx b/app/chat-list.tsx index 22ddbc4..eac0318 100644 --- a/app/chat-list.tsx +++ b/app/chat-list.tsx @@ -1,6 +1,6 @@ "use client"; -import { type Message } from "@prisma/client"; +import { type Message } from "ai-connector"; import { ChatMessage } from "./chat-message"; import { NextChatLogo } from "@/components/ui/nextchat-logo"; diff --git a/app/chat.tsx b/app/chat.tsx index e3634b5..9d69ee5 100644 --- a/app/chat.tsx +++ b/app/chat.tsx @@ -4,39 +4,43 @@ import { type Message } from "@prisma/client"; import { useRouter } from "next/navigation"; import { ChatList } from "./chat-list"; import { Prompt } from "./prompt"; -import { usePrompt } from "./use-prompt"; +import { useChat } from "ai-connector"; export interface ChatProps { // create?: (input: string) => Chat | undefined; - messages?: Message[]; + initialMessages?: Message[]; id?: string; } export function Chat({ id, // create, - messages, + initialMessages, }: ChatProps) { const router = useRouter(); - const { isLoading, messageList, appendUserMessage, reloadLastMessage } = - usePrompt({ - messages, - id, - // onCreate: (id: string) => { - // router.push(`/chat/${id}`); - // }, - }); + const { isLoading, messages, reload, append } = useChat({ + initialMessages: initialMessages as any[], + id, + // onCreate: (id: string) => { + // router.push(`/chat/${id}`); + // }, + }); return (
- +
{ + append({ + content: value, + role: "user", + }); + }} + onRefresh={messages.length ? reload : undefined} isLoading={isLoading} />
diff --git a/app/chat/[id]/page.tsx b/app/chat/[id]/page.tsx index f4e6f06..51331ce 100644 --- a/app/chat/[id]/page.tsx +++ b/app/chat/[id]/page.tsx @@ -46,7 +46,7 @@ export default async function ChatPage({ params }: ChatPageProps) {
- +
); diff --git a/app/sidebar.tsx b/app/sidebar.tsx index d43cd82..ed61d7a 100644 --- a/app/sidebar.tsx +++ b/app/sidebar.tsx @@ -8,6 +8,7 @@ import { Plus } from "lucide-react"; import Link from "next/link"; import { unstable_cache } from "next/cache"; import { SidebarItem } from "./sidebar-item"; +import { db } from "@/lib/db/schema"; export interface SidebarProps { session?: Session; @@ -60,16 +61,7 @@ Sidebar.displayName = "Sidebar"; async function SidebarList({ session }: { session?: Session }) { const chats = await ( await unstable_cache( - () => - prisma.chat.findMany({ - where: { - // This is for debugging, need to add scope to the query later - // userId: session?.user.id, - }, - orderBy: { - updatedAt: "desc", - }, - }), + () => db.query.chats.findMany({}), // @ts-ignore [session?.user?.id || ""], { diff --git a/app/use-prompt.tsx b/app/use-prompt.tsx deleted file mode 100644 index 9bf0a11..0000000 --- a/app/use-prompt.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import { useState, useCallback, useRef, useEffect } from "react"; -import { type Message } from "@prisma/client"; -import { nanoid } from "@/lib/utils"; - -export function usePrompt({ - messages = [], - id, -}: { - messages?: Message[]; - id: string | undefined; -}) { - const [isLoading, setIsLoading] = useState(false); - const [messageList, setMessageList] = useState(messages); - - const isLoadingRef = useRef(isLoading); - const messageListRef = useRef(messageList); - - useEffect(() => { - isLoadingRef.current = isLoading; - }, [isLoading]); - - useEffect(() => { - messageListRef.current = messageList; - }, [messageList]); - - const appendUserMessage = useCallback( - async (content: string | Message) => { - // Prevent multiple requests at once - if (isLoadingRef.current) return; - - const userMsg = - typeof content === "string" - ? ({ id: nanoid(10), role: "user", content } as Message) - : content; - const assMsg = { - id: nanoid(10), - role: "assistant", - content: "", - } as Message; - const messageListSnapshot = messageListRef.current; - - // Reset output - setIsLoading(true); - - try { - // Set user input immediately - setMessageList([...messageListSnapshot, userMsg]); - - // If streaming, we need to use fetchEventSource directly - const response = await fetch(`/api/generate`, { - method: "POST", - body: JSON.stringify({ - id: id || nanoid(10), - messages: [...messageListSnapshot, userMsg].map((m) => ({ - role: m.role, - content: m.content, - })), - }), - headers: { "Content-Type": "application/json" }, - }); - // This data is a ReadableStream - const data = response.body; - if (!data) { - return; - } - - const reader = data.getReader(); - const decoder = new TextDecoder(); - let done = false; - let accumulatedValue = ""; // Variable to accumulate chunks - - while (!done) { - const { value, done: doneReading } = await reader.read(); - done = doneReading; - const chunkValue = decoder.decode(value); - accumulatedValue += chunkValue; // Accumulate the chunk value - - // Check if the accumulated value contains the delimiter - const delimiter = "\n"; - const chunks = accumulatedValue.split(delimiter); - - // Process all chunks except the last one (which may be incomplete) - while (chunks.length > 1) { - const chunkToDispatch = chunks.shift(); // Get the first chunk - if (chunkToDispatch && chunkToDispatch.length > 0) { - const chunk = JSON.parse(chunkToDispatch); - assMsg.content += chunk; - setMessageList([...messageListSnapshot, userMsg, assMsg]); - } - } - - // The last chunk may be incomplete, so keep it in the accumulated value - accumulatedValue = chunks[0]; - } - - // Process any remaining accumulated value after the loop is done - if (accumulatedValue.length > 0) { - assMsg.content += accumulatedValue; - setMessageList([...messageListSnapshot, userMsg, assMsg]); - } - } finally { - setIsLoading(false); - } - }, - [id] - ); - - const reloadLastMessage = useCallback(async () => { - // Prevent multiple requests at once - if (isLoadingRef.current) return; - - const userMsg = messageListRef.current.at(-2); - const assMsg = messageListRef.current.at(-1); - - // Both should exist. - if (!userMsg || !assMsg) return; - - messageListRef.current = messageListRef.current.slice(0, -2); - setMessageList(messageListRef.current); - - await appendUserMessage(userMsg); - }, [appendUserMessage]); - - return { - messageList, - appendUserMessage, - reloadLastMessage, - isLoading, - }; -} diff --git a/auth.ts b/auth.ts index cab5ae5..16f9e09 100644 --- a/auth.ts +++ b/auth.ts @@ -9,14 +9,14 @@ import { sessions, verificationTokens, } from "./lib/db/schema"; -import { DrizzleAdapterPg } from "./lib/db"; +import { PgAdapter } from "./lib/db"; export const { handlers: { GET, POST }, auth, CSRF_experimental, } = NextAuth({ - adapter: DrizzleAdapterPg(db, { + adapter: PgAdapter(db, { accounts, users, sessions, diff --git a/lib/db/db.ts b/lib/db/db.ts deleted file mode 100644 index 066b295..0000000 --- a/lib/db/db.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { drizzle } from "drizzle-orm/vercel-postgres"; -import { sql } from "@vercel/postgres"; - -export const db = drizzle(sql); diff --git a/lib/db/index.ts b/lib/db/index.ts index c96d1c5..60dd07d 100644 --- a/lib/db/index.ts +++ b/lib/db/index.ts @@ -9,15 +9,14 @@ * ## Installation * * ```bash npm2yarn2pnpm - * npm install next-auth drizzle-orm @next-auth/drizzle-adapter + * npm install next-auth drizzle-orm @auth/drizzle-adapter * npm install drizzle-kit --save-dev * ``` * - * @module @next-auth/drizzle-adapter + * @module @auth/drizzle-adapter */ -import type { Adapter } from "@auth/nextjs/adapters"; +import type { Adapter } from "@auth/core/adapters"; import { and, eq } from "drizzle-orm"; -import { v4 as uuid } from "uuid"; import type { DbClient, Schema } from "./schema"; /** @@ -28,7 +27,7 @@ import type { DbClient, Schema } from "./schema"; * ```js title="pages/api/auth/[...nextauth].js" * import NextAuth from "next-auth" * import GoogleProvider from "next-auth/providers/google" - * import { DrizzleAdapter } from "@next-auth/drizzle-adapter" + * import { DrizzleAdapter } from "@auth/drizzle-adapter" * import { db } from "./db-schema" * * export default NextAuth({ @@ -109,7 +108,7 @@ import type { DbClient, Schema } from "./schema"; * ``` * **/ -export function DrizzleAdapterPg( +export function PgAdapter( client: DbClient, { users, sessions, verificationTokens, accounts }: Schema ): Adapter { @@ -117,7 +116,7 @@ export function DrizzleAdapterPg( createUser: async (data) => { return client .insert(users) - .values({ ...data, id: uuid() }) + .values({ ...data, id: crypto.randomUUID() as string }) .returning() .then((res) => res[0]); }, @@ -188,7 +187,7 @@ export function DrizzleAdapterPg( // Drizzle will return `null` for fields that are not defined. // However, the return type is expecting `undefined`. - const account: ReturnType = { + const account = { ...updatedAccount, access_token: updatedAccount.access_token ?? undefined, token_type: updatedAccount.token_type ?? undefined, @@ -202,20 +201,19 @@ export function DrizzleAdapterPg( return account; }, getUserByAccount: async (account) => { - const user = - (await client - .select() - .from(users) - .innerJoin( - accounts, - and( - eq(accounts.providerAccountId, account.providerAccountId), - eq(accounts.provider, account.provider) - ) + const dbAccount = await client + .select() + .from(accounts) + .where( + and( + eq(accounts.providerAccountId, account.providerAccountId), + eq(accounts.provider, account.provider) ) - .then((res) => res[0])) ?? null; + ) + .leftJoin(users, eq(accounts.userId, users.id)) + .then((res) => res[0]); - return user.users; + return dbAccount.users; }, deleteSession: async (sessionToken) => { await client diff --git a/lib/db/migrations/0000_flawless_moondragon.sql b/lib/db/migrations/0000_clammy_freak.sql similarity index 93% rename from lib/db/migrations/0000_flawless_moondragon.sql rename to lib/db/migrations/0000_clammy_freak.sql index e634f49..5aac3c8 100644 --- a/lib/db/migrations/0000_flawless_moondragon.sql +++ b/lib/db/migrations/0000_clammy_freak.sql @@ -15,23 +15,23 @@ CREATE TABLE IF NOT EXISTS "accounts" ( ALTER TABLE "accounts" ADD CONSTRAINT "accounts_provider_providerAccountId" PRIMARY KEY("provider","providerAccountId"); CREATE TABLE IF NOT EXISTS "sessions" ( - "sessionToken" text PRIMARY KEY NOT NULL, "userId" text NOT NULL, - "expires" timestamp NOT NULL + "sessionToken" text PRIMARY KEY NOT NULL, + "expires" integer NOT NULL ); CREATE TABLE IF NOT EXISTS "users" ( "id" text PRIMARY KEY NOT NULL, "name" text, "email" text NOT NULL, - "emailVerified" timestamp, + "emailVerified" integer, "image" text ); CREATE TABLE IF NOT EXISTS "verificationToken" ( "identifier" text NOT NULL, "token" text NOT NULL, - "expires" timestamp NOT NULL + "expires" integer NOT NULL ); --> statement-breakpoint ALTER TABLE "verificationToken" ADD CONSTRAINT "verificationToken_identifier_token" PRIMARY KEY("identifier","token"); diff --git a/lib/db/migrations/0001_colorful_spectrum.sql b/lib/db/migrations/0001_colorful_spectrum.sql deleted file mode 100644 index 5ff3ddb..0000000 --- a/lib/db/migrations/0001_colorful_spectrum.sql +++ /dev/null @@ -1,7 +0,0 @@ -ALTER TABLE "accounts" DROP CONSTRAINT "accounts_provider_providerAccountId"; -ALTER TABLE "verificationToken" DROP CONSTRAINT "verificationToken_identifier_token"; -CREATE UNIQUE INDEX IF NOT EXISTS "Account_provider_providerAccountId_key" ON "accounts" ("provider","providerAccountId"); -CREATE UNIQUE INDEX IF NOT EXISTS "Session_sessionToken_key" ON "sessions" ("sessionToken"); -CREATE UNIQUE INDEX IF NOT EXISTS "User_email_key" ON "users" ("email"); -CREATE UNIQUE INDEX IF NOT EXISTS "VerificationToken_identifier_token_key" ON "verificationToken" ("identifier","token"); -CREATE UNIQUE INDEX IF NOT EXISTS "VerificationToken_token_key" ON "verificationToken" ("token"); \ No newline at end of file diff --git a/lib/db/migrations/0001_petite_sue_storm.sql b/lib/db/migrations/0001_petite_sue_storm.sql new file mode 100644 index 0000000..506efe6 --- /dev/null +++ b/lib/db/migrations/0001_petite_sue_storm.sql @@ -0,0 +1,11 @@ +CREATE TABLE IF NOT EXISTS "chats" ( + "id" text PRIMARY KEY NOT NULL, + "role" text NOT NULL, + "userId" text NOT NULL +); + +DO $$ BEGIN + ALTER TABLE "chats" ADD CONSTRAINT "chats_userId_users_id_fk" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE no action ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; diff --git a/lib/db/migrations/meta/0000_snapshot.json b/lib/db/migrations/meta/0000_snapshot.json index 9bf3d1e..f0e3010 100644 --- a/lib/db/migrations/meta/0000_snapshot.json +++ b/lib/db/migrations/meta/0000_snapshot.json @@ -1,7 +1,7 @@ { "version": "5", "dialect": "pg", - "id": "ea430ef9-8e30-4cb6-9985-d03f43656cf8", + "id": "c6cb6121-2dc1-49be-8bd9-62f1c0c55939", "prevId": "00000000-0000-0000-0000-000000000000", "tables": { "accounts": { @@ -105,21 +105,21 @@ "name": "sessions", "schema": "", "columns": { - "sessionToken": { - "name": "sessionToken", - "type": "text", - "primaryKey": true, - "notNull": true - }, "userId": { "name": "userId", "type": "text", "primaryKey": false, "notNull": true }, + "sessionToken": { + "name": "sessionToken", + "type": "text", + "primaryKey": true, + "notNull": true + }, "expires": { "name": "expires", - "type": "timestamp", + "type": "integer", "primaryKey": false, "notNull": true } @@ -166,7 +166,7 @@ }, "emailVerified": { "name": "emailVerified", - "type": "timestamp", + "type": "integer", "primaryKey": false, "notNull": false }, @@ -199,7 +199,7 @@ }, "expires": { "name": "expires", - "type": "timestamp", + "type": "integer", "primaryKey": false, "notNull": true } diff --git a/lib/db/migrations/meta/0001_snapshot.json b/lib/db/migrations/meta/0001_snapshot.json index 9f8af6f..2042ae3 100644 --- a/lib/db/migrations/meta/0001_snapshot.json +++ b/lib/db/migrations/meta/0001_snapshot.json @@ -1,8 +1,8 @@ { "version": "5", "dialect": "pg", - "id": "34122d24-88e0-430e-862c-2168b0f866cb", - "prevId": "ea430ef9-8e30-4cb6-9985-d03f43656cf8", + "id": "ad52915b-d697-4935-9373-97dca163dd91", + "prevId": "c6cb6121-2dc1-49be-8bd9-62f1c0c55939", "tables": { "accounts": { "name": "accounts", @@ -75,16 +75,7 @@ "notNull": false } }, - "indexes": { - "Account_provider_providerAccountId_key": { - "name": "Account_provider_providerAccountId_key", - "columns": [ - "provider", - "providerAccountId" - ], - "isUnique": true - } - }, + "indexes": {}, "foreignKeys": { "accounts_userId_users_id_fk": { "name": "accounts_userId_users_id_fk", @@ -100,40 +91,81 @@ "onUpdate": "no action" } }, - "compositePrimaryKeys": {} + "compositePrimaryKeys": { + "accounts_provider_providerAccountId": { + "name": "accounts_provider_providerAccountId", + "columns": [ + "provider", + "providerAccountId" + ] + } + } }, - "sessions": { - "name": "sessions", + "chats": { + "name": "chats", "schema": "", "columns": { - "sessionToken": { - "name": "sessionToken", + "id": { + "name": "id", "type": "text", "primaryKey": true, "notNull": true }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, "userId": { "name": "userId", "type": "text", "primaryKey": false, "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "chats_userId_users_id_fk": { + "name": "chats_userId_users_id_fk", + "tableFrom": "chats", + "tableTo": "users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {} + }, + "sessions": { + "name": "sessions", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sessionToken": { + "name": "sessionToken", + "type": "text", + "primaryKey": true, + "notNull": true }, "expires": { "name": "expires", - "type": "timestamp", + "type": "integer", "primaryKey": false, "notNull": true } }, - "indexes": { - "Session_sessionToken_key": { - "name": "Session_sessionToken_key", - "columns": [ - "sessionToken" - ], - "isUnique": true - } - }, + "indexes": {}, "foreignKeys": { "sessions_userId_users_id_fk": { "name": "sessions_userId_users_id_fk", @@ -175,7 +207,7 @@ }, "emailVerified": { "name": "emailVerified", - "type": "timestamp", + "type": "integer", "primaryKey": false, "notNull": false }, @@ -186,15 +218,7 @@ "notNull": false } }, - "indexes": { - "User_email_key": { - "name": "User_email_key", - "columns": [ - "email" - ], - "isUnique": true - } - }, + "indexes": {}, "foreignKeys": {}, "compositePrimaryKeys": {} }, @@ -216,30 +240,22 @@ }, "expires": { "name": "expires", - "type": "timestamp", + "type": "integer", "primaryKey": false, "notNull": true } }, - "indexes": { - "VerificationToken_identifier_token_key": { - "name": "VerificationToken_identifier_token_key", + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "verificationToken_identifier_token": { + "name": "verificationToken_identifier_token", "columns": [ "identifier", "token" - ], - "isUnique": true - }, - "VerificationToken_token_key": { - "name": "VerificationToken_token_key", - "columns": [ - "token" - ], - "isUnique": true + ] } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {} + } } }, "enums": {}, diff --git a/lib/db/migrations/meta/_journal.json b/lib/db/migrations/meta/_journal.json index c023d95..b5d790b 100644 --- a/lib/db/migrations/meta/_journal.json +++ b/lib/db/migrations/meta/_journal.json @@ -5,15 +5,15 @@ { "idx": 0, "version": "5", - "when": 1684771069602, - "tag": "0000_flawless_moondragon", + "when": 1685716228240, + "tag": "0000_clammy_freak", "breakpoints": false }, { "idx": 1, "version": "5", - "when": 1684771628481, - "tag": "0001_colorful_spectrum", + "when": 1685716937211, + "tag": "0001_petite_sue_storm", "breakpoints": false } ] diff --git a/lib/db/schema.ts b/lib/db/schema.ts index 44be2d6..015046d 100644 --- a/lib/db/schema.ts +++ b/lib/db/schema.ts @@ -1,28 +1,23 @@ -import { - integer, - timestamp, - pgTable, - text, - primaryKey, - uniqueIndex, -} from "drizzle-orm/pg-core"; -import { drizzle } from "drizzle-orm/vercel-postgres"; -import { sql } from "@vercel/postgres"; import { ProviderType } from "@auth/nextjs/providers"; +import { sql } from "@vercel/postgres"; +import { relations } from "drizzle-orm"; +import { integer, pgTable, primaryKey, text } from "drizzle-orm/pg-core"; +import { drizzle } from "drizzle-orm/vercel-postgres"; -export const users = pgTable( - "users", - { - id: text("id").notNull().primaryKey(), - name: text("name"), - email: text("email").notNull(), - emailVerified: timestamp("emailVerified", { mode: "date" }), - image: text("image"), - }, - (user) => ({ - emailKey: uniqueIndex("User_email_key").on(user.email), - }) -); +// const connection = createPool({ connectionString: process.env.POSTGRES_URL }); + +export const users = pgTable("users", { + id: text("id").notNull().primaryKey(), + name: text("name"), + email: text("email").notNull(), + emailVerified: integer("emailVerified"), + image: text("image"), +}); + +export const usersRelations = relations(users, ({ many }) => ({ + chats: many(chats), + accounts: many(accounts), +})); export const accounts = pgTable( "accounts", @@ -41,313 +36,63 @@ export const accounts = pgTable( id_token: text("id_token"), session_state: text("session_state"), }, - (table) => { - return { - providerProviderAccountIdKey: uniqueIndex( - "Account_provider_providerAccountId_key" - ).on(table.provider, table.providerAccountId), - }; - } + (account) => ({ + _: primaryKey(account.provider, account.providerAccountId), + }) ); -export const sessions = pgTable( - "sessions", - { - sessionToken: text("sessionToken").notNull().primaryKey(), - userId: text("userId") - .notNull() - .references(() => users.id, { onDelete: "cascade" }), - expires: timestamp("expires", { mode: "date" }).notNull(), - }, - (table) => { - return { - sessionTokenKey: uniqueIndex("Session_sessionToken_key").on( - table.sessionToken - ), - }; - } -); +export const sessions = pgTable("sessions", { + userId: text("userId") + .notNull() + .references(() => users.id, { onDelete: "cascade" }), + sessionToken: text("sessionToken").notNull().primaryKey(), + expires: integer("expires").notNull(), +}); export const verificationTokens = pgTable( "verificationToken", { identifier: text("identifier").notNull(), token: text("token").notNull(), - expires: timestamp("expires", { mode: "date" }).notNull(), + expires: integer("expires").notNull(), }, - (table) => ({ - identifierTokenKey: uniqueIndex( - "VerificationToken_identifier_token_key" - ).on(table.identifier, table.token), - tokenKey: uniqueIndex("VerificationToken_token_key").on(table.token), + (vt) => ({ + _: primaryKey(vt.identifier, vt.token), }) ); -// Connect to Vercel Postgres -export const db = drizzle(sql); - -export type DbClient = typeof db; - export type Schema = { users: typeof users; accounts: typeof accounts; sessions: typeof sessions; verificationTokens: typeof verificationTokens; }; -// import { sql, eq, and } from "drizzle-orm"; -// import { -// PgDatabase, -// QueryResultHKT, -// integer, -// pgTable, -// text, -// timestamp, -// uniqueIndex, -// uuid, -// } from "drizzle-orm/pg-core"; -// import { drizzle } from "drizzle-orm/vercel-postgres"; -// import { sql as c } from "@vercel/postgres"; -// import { Adapter, AdapterUser } from "@auth/nextjs/adapters"; -// export const db = drizzle(c); +export const chats = pgTable("chats", { + id: text("id").notNull().primaryKey(), + title: text("role").notNull(), + userId: text("userId") + .notNull() + .references(() => users.id), +}); -// export const users = pgTable( -// "users", -// { -// id: uuid("id") -// .notNull() -// .primaryKey() -// .default(sql`gen_random_uuid()`), -// name: text("name"), -// email: text("email"), -// emailVerified: timestamp("emailVerified", { precision: 3 }), -// image: text("image"), -// }, -// (table) => { -// return { -// emailKey: uniqueIndex("User_email_key").on(table.email), -// }; -// } -// ); +export const chatsRelations = relations(chats, ({ one }) => ({ + user: one(users, { + fields: [chats.userId], + references: [users.id], + }), +})); -// export const verificationTokens = pgTable( -// "verification_tokens", -// { -// identifier: text("identifier").notNull(), -// token: text("token").notNull(), -// expires: timestamp("expires", { precision: 3 }).notNull(), -// }, -// (table) => { -// return { -// identifierTokenKey: uniqueIndex( -// "VerificationToken_identifier_token_key" -// ).on(table.identifier, table.token), -// tokenKey: uniqueIndex("VerificationToken_token_key").on(table.token), -// }; -// } -// ); +export const db = drizzle(sql, { + schema: { + users, + accounts, + sessions, + verificationTokens, + chats, + chatsRelations, + usersRelations, + }, +}); -// export const sessions = pgTable( -// "sessions", -// { -// id: uuid("id") -// .notNull() -// .primaryKey() -// .default(sql`gen_random_uuid()`), -// sessionToken: text("sessionToken").notNull(), -// userId: uuid("userId") -// .notNull() -// .references(() => users.id, { onDelete: "cascade", onUpdate: "cascade" }), -// expires: timestamp("expires", { precision: 3 }).notNull(), -// }, -// (table) => { -// return { -// sessionTokenKey: uniqueIndex("Session_sessionToken_key").on( -// table.sessionToken -// ), -// }; -// } -// ); - -// export const accounts = pgTable( -// "accounts", -// { -// id: uuid("id") -// .notNull() -// .primaryKey() -// .default(sql`gen_random_uuid()`), -// userId: uuid("userId") -// .notNull() -// .references(() => users.id, { onDelete: "cascade", onUpdate: "cascade" }), -// type: text("type").notNull(), -// provider: text("provider").notNull(), -// providerAccountId: text("providerAccountId").notNull(), -// refresh_token: text("refresh_token"), -// access_token: text("access_token"), -// expires_at: integer("expires_at"), -// token_type: text("token_type"), -// scope: text("scope"), -// id_token: text("id_token"), -// session_state: text("session_state"), -// }, -// (table) => { -// return { -// providerProviderAccountIdKey: uniqueIndex( -// "Account_provider_providerAccountId_key" -// ).on(table.provider, table.providerAccountId), -// }; -// } -// ); - -// type Users = typeof users; -// type Sessions = typeof sessions; -// type Accounts = typeof accounts; -// type VerificationTokens = typeof verificationTokens; - -// export function DrizzleAdapter< -// T extends PgDatabase, -// U extends QueryResultHKT -// >({ -// db, -// schema, -// }: { -// db: T; -// schema: { -// users: Users; -// sessions: Sessions; -// accounts: Accounts; -// verificationTokens: VerificationTokens; -// }; -// }): Adapter { -// return { -// async createUser(user) { -// const [newUser] = await db.insert(schema.users).values(user).returning(); - -// return newUser as AdapterUser; -// }, -// async getUser(id) { -// const [user] = await db -// .select() -// .from(schema.users) -// .where(eq(schema.users.id, id)); - -// return (user as AdapterUser) || null; -// }, -// async getUserByEmail(email) { -// const [user] = await db -// .select() -// .from(schema.users) -// .where(eq(schema.users.email, email)); - -// return (user as AdapterUser) || null; -// }, -// async getUserByAccount({ providerAccountId, provider }) { -// const results = await db -// .select() -// .from(schema.users) -// .leftJoin(schema.accounts, eq(schema.accounts.userId, schema.users.id)) -// .where( -// and( -// eq(schema.accounts.provider, provider), -// eq(schema.accounts.providerAccountId, providerAccountId) -// ) -// ); - -// if (results.length) { -// return results[0].users as AdapterUser; -// } - -// return null; -// }, -// async updateUser(user) { -// const userId = user.id; - -// if (!userId) { -// throw new Error("Cannot update user without id"); -// } - -// const [updatedUser] = await db -// .update(schema.users) -// .set(user) -// .where(eq(schema.users.id, userId)) -// .returning(); - -// return updatedUser as AdapterUser; -// }, -// async deleteUser(userId) { -// await db.delete(schema.users).where(eq(schema.users.id, userId)); -// }, -// async linkAccount(account) { -// await db.insert(schema.accounts).values(account); -// }, -// async unlinkAccount({ providerAccountId, provider }) { -// await db -// .delete(schema.accounts) -// .where( -// and( -// eq(schema.accounts.provider, provider), -// eq(schema.accounts.providerAccountId, providerAccountId) -// ) -// ); -// }, -// async createSession(session) { -// const [newSession] = await db -// .insert(schema.sessions) -// .values(session) -// .returning(); - -// return newSession; -// }, -// async getSessionAndUser(sessionToken) { -// const results = await db -// .select() -// .from(schema.sessions) -// .leftJoin(schema.users, eq(schema.users.id, schema.sessions.userId)) -// .where(eq(schema.sessions.sessionToken, sessionToken)); - -// if (results.length) { -// return { -// user: results[0].users! as AdapterUser, -// session: results[0].sessions, -// }; -// } - -// return null; -// }, -// async updateSession(session) { -// const [updatedSession] = await db -// .update(schema.sessions) -// .set(session) -// .where(eq(schema.sessions.sessionToken, session.sessionToken)) -// .returning(); - -// return updatedSession; -// }, -// async deleteSession(sessionToken) { -// await db -// .delete(schema.sessions) -// .where(eq(schema.sessions.sessionToken, sessionToken)); -// }, -// async createVerificationToken(token) { -// const [newVerificationToken] = await db -// .insert(schema.verificationTokens) -// .values(token) -// .returning(); - -// return newVerificationToken; -// }, -// async useVerificationToken({ identifier, token }) { -// const [deletedVerificationToken] = await db -// .delete(schema.verificationTokens) -// .where( -// and( -// eq(schema.verificationTokens.identifier, identifier), -// eq(schema.verificationTokens.token, token) -// ) -// ) -// .returning(); - -// return deletedVerificationToken || null; -// }, -// }; -// } +export type DbClient = typeof db; diff --git a/package.json b/package.json index 640b8e5..ab8a553 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,8 @@ "version": "0.0.2", "private": true, "scripts": { - "dev": "prisma generate && next dev", - "build": "prisma generate && next build", + "dev": "next dev", + "build": "next build", "db:mg:create": "drizzle-kit generate:pg --config=drizzle.config.ts", "db:mg:apply": "ts-node --project tsconfig.migration.json ./lib/db/migrate --config=drizzle.config.ts", "db:check": "drizzle-kit check:pg --config=drizzle.config.ts", @@ -18,6 +18,7 @@ "format:check": "prettier --check \"**/*.{ts,tsx,mdx}\" --cache" }, "dependencies": { + "@auth/core": "0.0.0-manual.527fff6c", "@auth/nextjs": "0.0.0-manual.030e8328", "@next-auth/prisma-adapter": "1.0.6", "@prisma/client": "^4.14.0", @@ -25,6 +26,7 @@ "@vercel/analytics": "^1.0.0", "@vercel/kv": "^0.2.1", "@vercel/postgres": "^0.3.0", + "ai-connector": "^0.0.5", "body-scroll-lock": "4.0.0-beta.0", "class-variance-authority": "^0.4.0", "clsx": "^1.2.1", @@ -38,7 +40,7 @@ "jsonwebtoken": "^9.0.0", "lucide-react": "0.216.0", "nanoid": "^4.0.2", - "next": "13.4.3-canary.3", + "next": "13.4.5-canary.3", "next-themes": "^0.2.1", "openai-edge": "^0.5.1", "react": "^18.2.0", @@ -69,7 +71,7 @@ "dotenv": "^16.0.3", "drizzle-kit": "^0.18.0", "eslint": "^8.31.0", - "eslint-config-next": "13.4.3-canary.3", + "eslint-config-next": "13.4.5-canary.3", "eslint-config-prettier": "^8.3.0", "eslint-plugin-react": "^7.31.11", "eslint-plugin-tailwindcss": "^3.8.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9ee82ff..0797cd1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,9 +4,12 @@ overrides: '@auth/core': 0.0.0-manual.527fff6c dependencies: + '@auth/core': + specifier: 0.0.0-manual.527fff6c + version: 0.0.0-manual.527fff6c '@auth/nextjs': specifier: 0.0.0-manual.030e8328 - version: 0.0.0-manual.030e8328(next@13.4.3-canary.3)(react@18.2.0) + version: 0.0.0-manual.030e8328(next@13.4.5-canary.3)(react@18.2.0) '@next-auth/prisma-adapter': specifier: 1.0.6 version: 1.0.6(@prisma/client@4.14.0)(next-auth@4.22.1) @@ -25,6 +28,9 @@ dependencies: '@vercel/postgres': specifier: ^0.3.0 version: 0.3.0 + ai-connector: + specifier: ^0.0.5 + version: 0.0.5(react@18.2.0) body-scroll-lock: specifier: 4.0.0-beta.0 version: 4.0.0-beta.0 @@ -65,11 +71,11 @@ dependencies: specifier: ^4.0.2 version: 4.0.2 next: - specifier: 13.4.3-canary.3 - version: 13.4.3-canary.3(@babel/core@7.21.8)(react-dom@18.2.0)(react@18.2.0) + specifier: 13.4.5-canary.3 + version: 13.4.5-canary.3(@babel/core@7.21.8)(react-dom@18.2.0)(react@18.2.0) next-themes: specifier: ^0.2.1 - version: 0.2.1(next@13.4.3-canary.3)(react-dom@18.2.0)(react@18.2.0) + version: 0.2.1(next@13.4.5-canary.3)(react-dom@18.2.0)(react@18.2.0) openai-edge: specifier: ^0.5.1 version: 0.5.1 @@ -154,8 +160,8 @@ devDependencies: specifier: ^8.31.0 version: 8.40.0 eslint-config-next: - specifier: 13.4.3-canary.3 - version: 13.4.3-canary.3(eslint@8.40.0)(typescript@4.9.5) + specifier: 13.4.5-canary.3 + version: 13.4.5-canary.3(eslint@8.40.0)(typescript@4.9.5) eslint-config-prettier: specifier: ^8.3.0 version: 8.8.0(eslint@8.40.0) @@ -213,14 +219,14 @@ packages: preact-render-to-string: 5.2.3(preact@10.11.3) dev: false - /@auth/nextjs@0.0.0-manual.030e8328(next@13.4.3-canary.3)(react@18.2.0): + /@auth/nextjs@0.0.0-manual.030e8328(next@13.4.5-canary.3)(react@18.2.0): resolution: {integrity: sha512-AAtUl9EVCKqLRneDAq6KfUprWmsBRzsBoQ+8ytz/Nfgs8Uax5CWvCJs2CsnYNk04bXSB4L7eyMJmc1zTzZKaHg==} peerDependencies: next: ^13.4.2 react: ^18.2.0 dependencies: '@auth/core': 0.0.0-manual.527fff6c - next: 13.4.3-canary.3(@babel/core@7.21.8)(react-dom@18.2.0)(react@18.2.0) + next: 13.4.5-canary.3(@babel/core@7.21.8)(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 transitivePeerDependencies: - nodemailer @@ -593,21 +599,21 @@ packages: next-auth: ^4 dependencies: '@prisma/client': 4.14.0(prisma@4.14.0) - next-auth: 4.22.1(next@13.4.3-canary.3)(react-dom@18.2.0)(react@18.2.0) + next-auth: 4.22.1(next@13.4.5-canary.3)(react-dom@18.2.0)(react@18.2.0) dev: false - /@next/env@13.4.3-canary.3: - resolution: {integrity: sha512-AlAPQdQ2qf3RJsaaKMXo22l9MLUeqIiDJSJ1LcM9uWiOMIAgsLlQCxcoGcm7rQP9CSw4VPPFSpCR0tvCKmxVwQ==} + /@next/env@13.4.5-canary.3: + resolution: {integrity: sha512-2TrPvCIs8R9DpFpEDmGUOpWkVqbpb4+sGQMcZVP4zDYYg893p0nAbNbBxL996oBl+/vLTzcLIDceP5peHsGPzg==} dev: false - /@next/eslint-plugin-next@13.4.3-canary.3: - resolution: {integrity: sha512-ggD4Z0psyVcLcr0QlGXu+zEdbiYZra8r9LZgL1XuTAtTl4H1h3QgpNB6ENVqJCQibH8h0bBktOrRpHiLwbyRQw==} + /@next/eslint-plugin-next@13.4.5-canary.3: + resolution: {integrity: sha512-hsFfLSo8snWDLBwDT7+i4gEWZjsEzNHlZLT0ox8ftcZo3XBz8FrX11h9yGacageRbiiTotl/5U31Dxrt7MVoBA==} dependencies: glob: 7.1.7 dev: true - /@next/swc-darwin-arm64@13.4.3-canary.3: - resolution: {integrity: sha512-MVmNJTKe50Qm5a6Khju3MpYiTie9ByJVT67rvYxCfZkd48p59PWoosu9tvCtlVuxb5jZxzOAZ/vzDciTs7Vgcw==} + /@next/swc-darwin-arm64@13.4.5-canary.3: + resolution: {integrity: sha512-mUlpzua0RDPLNfNeqi0DzWNCqVzMphNh42BS8PojVO3NPC55+RX9tllQngz+qFAOcLL7g4LgoeE9VWgh31s/WQ==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] @@ -615,8 +621,8 @@ packages: dev: false optional: true - /@next/swc-darwin-x64@13.4.3-canary.3: - resolution: {integrity: sha512-NLrVHpJNqrKF93gZEQSoEq1WAVy0JYq8fry7CvSj2cCjgQxna06LPfkX9R3Iu3eRJJD/idCxbedXjg52oma/sg==} + /@next/swc-darwin-x64@13.4.5-canary.3: + resolution: {integrity: sha512-0bkYuSFh1/0ZedCVsSMtHiSxdfQxF0BO/v5VwRdOLtOZE8WrQdoXsWiVsiFBOTutiZDhats9exFOFgmymoSqSA==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] @@ -624,8 +630,8 @@ packages: dev: false optional: true - /@next/swc-linux-arm64-gnu@13.4.3-canary.3: - resolution: {integrity: sha512-CfrQFtHsbSeN43o/OSMCSUmA610u3gteKhq1SGzzq7TVaT4OnqoHEHfROEmgrpAkIMq65yIlHJM/Aq3/EBkFaw==} + /@next/swc-linux-arm64-gnu@13.4.5-canary.3: + resolution: {integrity: sha512-+PxhM2E98APKNEl1FCwhqU9ttGRoWsZcPPU20qcKFrJ/fKbfaWDnhzlSiZPmmERISZrmd54Uy3maNq9ys+bz6g==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -633,8 +639,8 @@ packages: dev: false optional: true - /@next/swc-linux-arm64-musl@13.4.3-canary.3: - resolution: {integrity: sha512-rRAilp0kqBAWJJuOfaK+GFclricB/lqFspFhk7U6tkYDHyXDZ/MrjTlfOraOJtqBTFtLM1zuzLvPhcK22DzJhA==} + /@next/swc-linux-arm64-musl@13.4.5-canary.3: + resolution: {integrity: sha512-KMRWPxeoZ41146NHpMAGMtD/RkQt/0QOlL13pREnNjeTzQI6P20X+ra5tHkCQoFB/8xseN9RXnuLMyYG3mYoGw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -642,8 +648,8 @@ packages: dev: false optional: true - /@next/swc-linux-x64-gnu@13.4.3-canary.3: - resolution: {integrity: sha512-9weSKDC6eJcDOhKURj/oKa7f9Z9fEZKjnYQBJDX/ojy1LThei/drRNXYrXTY1iay7tcbRhEBvtCiYaEPjvLBUA==} + /@next/swc-linux-x64-gnu@13.4.5-canary.3: + resolution: {integrity: sha512-dwSxDddWMaTAmSPxSWWrwaHHPqLlnNJoc22dTfyQX9Hwecq+Sq2RGSkGicga3z6Z7rX3Nm4MbNduM2GWXwcvgg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -651,8 +657,8 @@ packages: dev: false optional: true - /@next/swc-linux-x64-musl@13.4.3-canary.3: - resolution: {integrity: sha512-yODhVd1qvxFhUja4WrSJhZhyiLD85O5855FxCVxky85qb2+aTjWMYv5DM7sl1fzCag7EwtcI4+HJb4rkBMifRQ==} + /@next/swc-linux-x64-musl@13.4.5-canary.3: + resolution: {integrity: sha512-R2SqJsC6QPNxO3t/U2fMouzLfUNLHP8PfZH/XTIwQ12dxZCwUwBg/cNX5ocBtoci3lCYNcOLNjSuc3z9b/yIeQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -660,8 +666,8 @@ packages: dev: false optional: true - /@next/swc-win32-arm64-msvc@13.4.3-canary.3: - resolution: {integrity: sha512-2SY5f5tndZtObiqS2Uioyou18Ef+fG5yzCj4EAvk1FhuQBoprLaVDyP3wehJACg9U5kYocvLA18wKIt8byhG2g==} + /@next/swc-win32-arm64-msvc@13.4.5-canary.3: + resolution: {integrity: sha512-/spv7kDp2xKUJzdvGFU2gf/N1lXtm/E2p/Fx/XreUyajiaT0f57d2mHfDabW5saq3KL3wPlWZ5zW7iDh2Awm+Q==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] @@ -669,8 +675,8 @@ packages: dev: false optional: true - /@next/swc-win32-ia32-msvc@13.4.3-canary.3: - resolution: {integrity: sha512-eCEPIrSkpIG+k4h9d8h56lVhqpPjjFm/C/P3Vq1AGDSPh9DQ+xfWTleWBeVskKO4RZf1R4aCHLVw12rkQcLw8g==} + /@next/swc-win32-ia32-msvc@13.4.5-canary.3: + resolution: {integrity: sha512-VIlQ+emO306CYbIyEKu8RTAzmMw/z+akEhu2urZPKjj+UMKCLfq1w4u3GJfURfdRiJ5ws7fLXDHVAjon4G+Ygg==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] @@ -678,8 +684,8 @@ packages: dev: false optional: true - /@next/swc-win32-x64-msvc@13.4.3-canary.3: - resolution: {integrity: sha512-n8vAEOOiFgbXleiPTAQjiEgMFCLQaTqPl23fmzf/yKx2RjmWxYM19vn7SoaV20sVPK36KMAorxxYB7H/ny9hKw==} + /@next/swc-win32-x64-msvc@13.4.5-canary.3: + resolution: {integrity: sha512-JSb5haHmRxn2Jysrsw3UJ/mgNu0TD+JJdHyGkdQeGIM4V1Q1G/KgQNCTovWfimd8e1JUoZqSV4aX+y7zEDVkEg==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -1284,6 +1290,18 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + /ai-connector@0.0.5(react@18.2.0): + resolution: {integrity: sha512-byHDAVQt3zlQ61uEBDKlOlY3uX2O+ahNVV1bnnHhnv4fNIlu+NWefxlRUqAxVM80tlCrS/+aXA8AlpkIZsa1tg==} + engines: {node: '>=14.6'} + peerDependencies: + react: ^18.0.0 + dependencies: + eventsource-parser: 1.0.0 + nanoid: 3.3.6 + react: 18.2.0 + swr: 2.1.5(react@18.2.0) + dev: false + /ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} dependencies: @@ -2313,8 +2331,8 @@ packages: engines: {node: '>=12'} dev: false - /eslint-config-next@13.4.3-canary.3(eslint@8.40.0)(typescript@4.9.5): - resolution: {integrity: sha512-vKWwNIUvCRRZLHejVAu/eodOMRmTVYHsTnzFIAQeD7PEIbyJl8fTntPyoE3rPgChWamRYAWR9isyZER4ToUc7Q==} + /eslint-config-next@13.4.5-canary.3(eslint@8.40.0)(typescript@4.9.5): + resolution: {integrity: sha512-F1W76ozmRCPk8sxPXO4tSy3pstBerEfiZ1Aiagr+d2a+l/Mx5CgHWf75CwsQfpiC6BbAee8RrdIjb8kvGlPeww==} peerDependencies: eslint: ^7.23.0 || ^8.0.0 typescript: '>=3.3.1' @@ -2322,7 +2340,7 @@ packages: typescript: optional: true dependencies: - '@next/eslint-plugin-next': 13.4.3-canary.3 + '@next/eslint-plugin-next': 13.4.5-canary.3 '@rushstack/eslint-patch': 1.2.0 '@typescript-eslint/parser': 5.59.5(eslint@8.40.0)(typescript@4.9.5) eslint: 8.40.0 @@ -4031,7 +4049,7 @@ packages: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true - /next-auth@4.22.1(next@13.4.3-canary.3)(react-dom@18.2.0)(react@18.2.0): + /next-auth@4.22.1(next@13.4.5-canary.3)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-NTR3f6W7/AWXKw8GSsgSyQcDW6jkslZLH8AiZa5PQ09w1kR8uHtR9rez/E9gAq/o17+p0JYHE8QjF3RoniiObA==} peerDependencies: next: ^12.2.5 || ^13 @@ -4046,7 +4064,7 @@ packages: '@panva/hkdf': 1.1.1 cookie: 0.5.0 jose: 4.14.4 - next: 13.4.3-canary.3(@babel/core@7.21.8)(react-dom@18.2.0)(react@18.2.0) + next: 13.4.5-canary.3(@babel/core@7.21.8)(react-dom@18.2.0)(react@18.2.0) oauth: 0.9.15 openid-client: 5.4.2 preact: 10.14.1 @@ -4056,14 +4074,14 @@ packages: uuid: 8.3.2 dev: false - /next-themes@0.2.1(next@13.4.3-canary.3)(react-dom@18.2.0)(react@18.2.0): + /next-themes@0.2.1(next@13.4.5-canary.3)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==} peerDependencies: next: '*' react: '*' react-dom: '*' dependencies: - next: 13.4.3-canary.3(@babel/core@7.21.8)(react-dom@18.2.0)(react@18.2.0) + next: 13.4.5-canary.3(@babel/core@7.21.8)(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: false @@ -4072,14 +4090,13 @@ packages: resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} dev: true - /next@13.4.3-canary.3(@babel/core@7.21.8)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-Fix/RAVHJHUpJZsvftaC5Pm8qGP7/NIitOBsKPozSPKIekCUWwKiaoLPY140taMgl9R+oW1nXKwYhAdjM8gSMg==} + /next@13.4.5-canary.3(@babel/core@7.21.8)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-5YLpHWSLjjInYlqxtkZZr7cwYp62wXEm7ik6Wu+4JqWQdbwD/pemuXwErxRIt65ruDQwe9RvE/dzMOuQuNDE+A==} engines: {node: '>=16.8.0'} hasBin: true peerDependencies: '@opentelemetry/api': ^1.1.0 fibers: '>= 3.1.0' - node-sass: ^6.0.0 || ^7.0.0 react: ^18.2.0 react-dom: ^18.2.0 sass: ^1.3.0 @@ -4088,12 +4105,10 @@ packages: optional: true fibers: optional: true - node-sass: - optional: true sass: optional: true dependencies: - '@next/env': 13.4.3-canary.3 + '@next/env': 13.4.5-canary.3 '@swc/helpers': 0.5.1 busboy: 1.6.0 caniuse-lite: 1.0.30001486 @@ -4103,15 +4118,15 @@ packages: styled-jsx: 5.1.1(@babel/core@7.21.8)(react@18.2.0) zod: 3.21.4 optionalDependencies: - '@next/swc-darwin-arm64': 13.4.3-canary.3 - '@next/swc-darwin-x64': 13.4.3-canary.3 - '@next/swc-linux-arm64-gnu': 13.4.3-canary.3 - '@next/swc-linux-arm64-musl': 13.4.3-canary.3 - '@next/swc-linux-x64-gnu': 13.4.3-canary.3 - '@next/swc-linux-x64-musl': 13.4.3-canary.3 - '@next/swc-win32-arm64-msvc': 13.4.3-canary.3 - '@next/swc-win32-ia32-msvc': 13.4.3-canary.3 - '@next/swc-win32-x64-msvc': 13.4.3-canary.3 + '@next/swc-darwin-arm64': 13.4.5-canary.3 + '@next/swc-darwin-x64': 13.4.5-canary.3 + '@next/swc-linux-arm64-gnu': 13.4.5-canary.3 + '@next/swc-linux-arm64-musl': 13.4.5-canary.3 + '@next/swc-linux-x64-gnu': 13.4.5-canary.3 + '@next/swc-linux-x64-musl': 13.4.5-canary.3 + '@next/swc-win32-arm64-msvc': 13.4.5-canary.3 + '@next/swc-win32-ia32-msvc': 13.4.5-canary.3 + '@next/swc-win32-x64-msvc': 13.4.5-canary.3 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros @@ -5059,6 +5074,15 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + /swr@2.1.5(react@18.2.0): + resolution: {integrity: sha512-/OhfZMcEpuz77KavXST5q6XE9nrOBOVcBLWjMT+oAE/kQHyE3PASrevXCtQDZ8aamntOfFkbVJp7Il9tNBQWrw==} + peerDependencies: + react: ^16.11.0 || ^17.0.0 || ^18.0.0 + dependencies: + react: 18.2.0 + use-sync-external-store: 1.2.0(react@18.2.0) + dev: false + /synckit@0.8.5: resolution: {integrity: sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==} engines: {node: ^14.18.0 || >=16.0.0} @@ -5405,6 +5429,14 @@ packages: tslib: 2.5.0 dev: false + /use-sync-external-store@1.2.0(react@18.2.0): + resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + react: 18.2.0 + dev: false + /utf-8-validate@6.0.3: resolution: {integrity: sha512-uIuGf9TWQ/y+0Lp+KGZCMuJWc3N9BHA+l/UmHd/oUHwJJDeysyTRxNQVkbzsIWfGFbRe3OcgML/i0mvVRPOyDA==} engines: {node: '>=6.14.2'} From 17bfd0cac1b9037151f5eb61aaa2bfee4e530c6a Mon Sep 17 00:00:00 2001 From: Jared Palmer Date: Fri, 2 Jun 2023 11:33:17 -0400 Subject: [PATCH 03/11] Alt drizzle --- app/api/chat/route.ts | 20 +- auth.ts | 19 +- lib/db/migrations/0000_clammy_freak.sql | 49 ----- lib/db/migrations/0000_fair_groot.sql | 6 + lib/db/migrations/0001_naive_gunslinger.sql | 1 + lib/db/migrations/0001_petite_sue_storm.sql | 11 - lib/db/migrations/meta/0000_snapshot.json | 208 ++---------------- lib/db/migrations/meta/0001_snapshot.json | 230 +------------------- lib/db/migrations/meta/_journal.json | 8 +- lib/db/schema.ts | 118 ++++------ 10 files changed, 89 insertions(+), 581 deletions(-) delete mode 100644 lib/db/migrations/0000_clammy_freak.sql create mode 100644 lib/db/migrations/0000_fair_groot.sql create mode 100644 lib/db/migrations/0001_naive_gunslinger.sql delete mode 100644 lib/db/migrations/0001_petite_sue_storm.sql diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts index 306db6e..10c8031 100644 --- a/app/api/chat/route.ts +++ b/app/api/chat/route.ts @@ -1,6 +1,7 @@ import { auth } from "@/auth"; import { chats, db } from "@/lib/db/schema"; import { openai } from "@/lib/openai"; +import { nanoid } from "@/lib/utils"; import { OpenAIStream, StreamingTextResponse } from "ai-connector"; export const runtime = "edge"; @@ -31,11 +32,24 @@ export const POST = auth(async function POST(req: Request) { return; } const title = json.messages[0].content.substring(0, 20); - await db.insert(chats).values({ - id: crypto.randomUUID(), + const payload = { + id: json.id ?? crypto.randomUUID(), title, userId: (req as any).auth?.user?.email, - }); + messages: json.messages.concat({ + content: completion, + role: "assistant", + id: nanoid(), + }), + }; + await db + .insert(chats) + .values(payload) + .onConflictDoUpdate({ + target: json.id, + set: payload, + }) + .execute(); }, }); diff --git a/auth.ts b/auth.ts index 16f9e09..a123564 100644 --- a/auth.ts +++ b/auth.ts @@ -2,13 +2,6 @@ import NextAuth from "@auth/nextjs"; import GitHub from "@auth/nextjs/providers/github"; import { NextResponse } from "next/server"; -import { - db, - users, - accounts, - sessions, - verificationTokens, -} from "./lib/db/schema"; import { PgAdapter } from "./lib/db"; export const { @@ -16,12 +9,12 @@ export const { auth, CSRF_experimental, } = NextAuth({ - adapter: PgAdapter(db, { - accounts, - users, - sessions, - verificationTokens, - }), + // adapter: PgAdapter(db, { + // accounts, + // users, + // sessions, + // verificationTokens, + // }), // @ts-ignore providers: [GitHub], session: { strategy: "jwt" }, diff --git a/lib/db/migrations/0000_clammy_freak.sql b/lib/db/migrations/0000_clammy_freak.sql deleted file mode 100644 index 5aac3c8..0000000 --- a/lib/db/migrations/0000_clammy_freak.sql +++ /dev/null @@ -1,49 +0,0 @@ -CREATE TABLE IF NOT EXISTS "accounts" ( - "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 -); ---> statement-breakpoint -ALTER TABLE "accounts" ADD CONSTRAINT "accounts_provider_providerAccountId" PRIMARY KEY("provider","providerAccountId"); - -CREATE TABLE IF NOT EXISTS "sessions" ( - "userId" text NOT NULL, - "sessionToken" text PRIMARY KEY NOT NULL, - "expires" integer NOT NULL -); - -CREATE TABLE IF NOT EXISTS "users" ( - "id" text PRIMARY KEY NOT NULL, - "name" text, - "email" text NOT NULL, - "emailVerified" integer, - "image" text -); - -CREATE TABLE IF NOT EXISTS "verificationToken" ( - "identifier" text NOT NULL, - "token" text NOT NULL, - "expires" integer NOT NULL -); ---> statement-breakpoint -ALTER TABLE "verificationToken" ADD CONSTRAINT "verificationToken_identifier_token" PRIMARY KEY("identifier","token"); - -DO $$ BEGIN - ALTER TABLE "accounts" ADD CONSTRAINT "accounts_userId_users_id_fk" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE cascade ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; - -DO $$ BEGIN - ALTER TABLE "sessions" ADD CONSTRAINT "sessions_userId_users_id_fk" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE cascade ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; diff --git a/lib/db/migrations/0000_fair_groot.sql b/lib/db/migrations/0000_fair_groot.sql new file mode 100644 index 0000000..efc8875 --- /dev/null +++ b/lib/db/migrations/0000_fair_groot.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS "chats" ( + "id" text PRIMARY KEY NOT NULL, + "role" text NOT NULL, + "userId" text NOT NULL, + "messages" json DEFAULT '{}'::json NOT NULL +); diff --git a/lib/db/migrations/0001_naive_gunslinger.sql b/lib/db/migrations/0001_naive_gunslinger.sql new file mode 100644 index 0000000..9a2c74b --- /dev/null +++ b/lib/db/migrations/0001_naive_gunslinger.sql @@ -0,0 +1 @@ +ALTER TABLE "chats" DROP COLUMN IF EXISTS "messages"; \ No newline at end of file diff --git a/lib/db/migrations/0001_petite_sue_storm.sql b/lib/db/migrations/0001_petite_sue_storm.sql deleted file mode 100644 index 506efe6..0000000 --- a/lib/db/migrations/0001_petite_sue_storm.sql +++ /dev/null @@ -1,11 +0,0 @@ -CREATE TABLE IF NOT EXISTS "chats" ( - "id" text PRIMARY KEY NOT NULL, - "role" text NOT NULL, - "userId" text NOT NULL -); - -DO $$ BEGIN - ALTER TABLE "chats" ADD CONSTRAINT "chats_userId_users_id_fk" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE no action ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; diff --git a/lib/db/migrations/meta/0000_snapshot.json b/lib/db/migrations/meta/0000_snapshot.json index f0e3010..96a82fc 100644 --- a/lib/db/migrations/meta/0000_snapshot.json +++ b/lib/db/migrations/meta/0000_snapshot.json @@ -1,149 +1,11 @@ { "version": "5", "dialect": "pg", - "id": "c6cb6121-2dc1-49be-8bd9-62f1c0c55939", + "id": "3bddf21a-46a3-4ee8-8b41-bb79cb80d904", "prevId": "00000000-0000-0000-0000-000000000000", "tables": { - "accounts": { - "name": "accounts", - "schema": "", - "columns": { - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "provider": { - "name": "provider", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "providerAccountId": { - "name": "providerAccountId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "refresh_token": { - "name": "refresh_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "access_token": { - "name": "access_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "token_type": { - "name": "token_type", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "scope": { - "name": "scope", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "id_token": { - "name": "id_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "session_state": { - "name": "session_state", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "accounts_userId_users_id_fk": { - "name": "accounts_userId_users_id_fk", - "tableFrom": "accounts", - "tableTo": "users", - "columnsFrom": [ - "userId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "accounts_provider_providerAccountId": { - "name": "accounts_provider_providerAccountId", - "columns": [ - "provider", - "providerAccountId" - ] - } - } - }, - "sessions": { - "name": "sessions", - "schema": "", - "columns": { - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "sessionToken": { - "name": "sessionToken", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "expires": { - "name": "expires", - "type": "integer", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "sessions_userId_users_id_fk": { - "name": "sessions_userId_users_id_fk", - "tableFrom": "sessions", - "tableTo": "users", - "columnsFrom": [ - "userId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {} - }, - "users": { - "name": "users", + "chats": { + "name": "chats", "schema": "", "columns": { "id": { @@ -152,69 +14,29 @@ "primaryKey": true, "notNull": true }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "email": { - "name": "email", + "role": { + "name": "role", "type": "text", "primaryKey": false, "notNull": true }, - "emailVerified": { - "name": "emailVerified", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "image": { - "name": "image", + "userId": { + "name": "userId", "type": "text", "primaryKey": false, - "notNull": false + "notNull": true + }, + "messages": { + "name": "messages", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'::json" } }, "indexes": {}, "foreignKeys": {}, "compositePrimaryKeys": {} - }, - "verificationToken": { - "name": "verificationToken", - "schema": "", - "columns": { - "identifier": { - "name": "identifier", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "token": { - "name": "token", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires": { - "name": "expires", - "type": "integer", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": { - "verificationToken_identifier_token": { - "name": "verificationToken_identifier_token", - "columns": [ - "identifier", - "token" - ] - } - } } }, "enums": {}, diff --git a/lib/db/migrations/meta/0001_snapshot.json b/lib/db/migrations/meta/0001_snapshot.json index 2042ae3..216b8d3 100644 --- a/lib/db/migrations/meta/0001_snapshot.json +++ b/lib/db/migrations/meta/0001_snapshot.json @@ -1,106 +1,9 @@ { "version": "5", "dialect": "pg", - "id": "ad52915b-d697-4935-9373-97dca163dd91", - "prevId": "c6cb6121-2dc1-49be-8bd9-62f1c0c55939", + "id": "e8eef642-fc25-4737-8e75-5ad1e8c53b84", + "prevId": "3bddf21a-46a3-4ee8-8b41-bb79cb80d904", "tables": { - "accounts": { - "name": "accounts", - "schema": "", - "columns": { - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "provider": { - "name": "provider", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "providerAccountId": { - "name": "providerAccountId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "refresh_token": { - "name": "refresh_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "access_token": { - "name": "access_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "token_type": { - "name": "token_type", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "scope": { - "name": "scope", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "id_token": { - "name": "id_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "session_state": { - "name": "session_state", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "accounts_userId_users_id_fk": { - "name": "accounts_userId_users_id_fk", - "tableFrom": "accounts", - "tableTo": "users", - "columnsFrom": [ - "userId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "accounts_provider_providerAccountId": { - "name": "accounts_provider_providerAccountId", - "columns": [ - "provider", - "providerAccountId" - ] - } - } - }, "chats": { "name": "chats", "schema": "", @@ -125,137 +28,8 @@ } }, "indexes": {}, - "foreignKeys": { - "chats_userId_users_id_fk": { - "name": "chats_userId_users_id_fk", - "tableFrom": "chats", - "tableTo": "users", - "columnsFrom": [ - "userId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {} - }, - "sessions": { - "name": "sessions", - "schema": "", - "columns": { - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "sessionToken": { - "name": "sessionToken", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "expires": { - "name": "expires", - "type": "integer", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "sessions_userId_users_id_fk": { - "name": "sessions_userId_users_id_fk", - "tableFrom": "sessions", - "tableTo": "users", - "columnsFrom": [ - "userId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {} - }, - "users": { - "name": "users", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "emailVerified": { - "name": "emailVerified", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "image": { - "name": "image", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, "foreignKeys": {}, "compositePrimaryKeys": {} - }, - "verificationToken": { - "name": "verificationToken", - "schema": "", - "columns": { - "identifier": { - "name": "identifier", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "token": { - "name": "token", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires": { - "name": "expires", - "type": "integer", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": { - "verificationToken_identifier_token": { - "name": "verificationToken_identifier_token", - "columns": [ - "identifier", - "token" - ] - } - } } }, "enums": {}, diff --git a/lib/db/migrations/meta/_journal.json b/lib/db/migrations/meta/_journal.json index b5d790b..d83e0fb 100644 --- a/lib/db/migrations/meta/_journal.json +++ b/lib/db/migrations/meta/_journal.json @@ -5,15 +5,15 @@ { "idx": 0, "version": "5", - "when": 1685716228240, - "tag": "0000_clammy_freak", + "when": 1685719783900, + "tag": "0000_fair_groot", "breakpoints": false }, { "idx": 1, "version": "5", - "when": 1685716937211, - "tag": "0001_petite_sue_storm", + "when": 1685719963202, + "tag": "0001_naive_gunslinger", "breakpoints": false } ] diff --git a/lib/db/schema.ts b/lib/db/schema.ts index 015046d..7f983f5 100644 --- a/lib/db/schema.ts +++ b/lib/db/schema.ts @@ -1,98 +1,56 @@ -import { ProviderType } from "@auth/nextjs/providers"; import { sql } from "@vercel/postgres"; -import { relations } from "drizzle-orm"; -import { integer, pgTable, primaryKey, text } from "drizzle-orm/pg-core"; +import { pgTable, text } from "drizzle-orm/pg-core"; import { drizzle } from "drizzle-orm/vercel-postgres"; // const connection = createPool({ connectionString: process.env.POSTGRES_URL }); -export const users = pgTable("users", { - id: text("id").notNull().primaryKey(), - name: text("name"), - email: text("email").notNull(), - emailVerified: integer("emailVerified"), - image: text("image"), -}); - -export const usersRelations = relations(users, ({ many }) => ({ - chats: many(chats), - accounts: many(accounts), -})); - -export const accounts = pgTable( - "accounts", - { - userId: text("userId") - .notNull() - .references(() => users.id, { onDelete: "cascade" }), - type: text("type").$type().notNull(), - provider: text("provider").notNull(), - providerAccountId: text("providerAccountId").notNull(), - refresh_token: text("refresh_token"), - access_token: text("access_token"), - expires_at: integer("expires_at"), - token_type: text("token_type"), - scope: text("scope"), - id_token: text("id_token"), - session_state: text("session_state"), - }, - (account) => ({ - _: primaryKey(account.provider, account.providerAccountId), - }) -); - -export const sessions = pgTable("sessions", { - userId: text("userId") - .notNull() - .references(() => users.id, { onDelete: "cascade" }), - sessionToken: text("sessionToken").notNull().primaryKey(), - expires: integer("expires").notNull(), -}); - -export const verificationTokens = pgTable( - "verificationToken", - { - identifier: text("identifier").notNull(), - token: text("token").notNull(), - expires: integer("expires").notNull(), - }, - (vt) => ({ - _: primaryKey(vt.identifier, vt.token), - }) -); - -export type Schema = { - users: typeof users; - accounts: typeof accounts; - sessions: typeof sessions; - verificationTokens: typeof verificationTokens; -}; - export const chats = pgTable("chats", { id: text("id").notNull().primaryKey(), title: text("role").notNull(), - userId: text("userId") - .notNull() - .references(() => users.id), + userId: text("userId").notNull(), + // messages: json("messages").notNull().default({}), + // .references(() => users.id), }); -export const chatsRelations = relations(chats, ({ one }) => ({ - user: one(users, { - fields: [chats.userId], - references: [users.id], - }), -})); +// export const messages = pgTable("messages", { +// id: text("id").notNull().primaryKey(), +// title: text("role").notNull(), +// chatId: text("chatId") +// .notNull() +// .references(() => chats.id), +// }); + +// export const chatsRelations = relations(chats, ({ many }) => ({ +// // user: one(users, { +// // fields: [chats.userId], +// // references: [users.id], +// // }), +// messages: many(messages), +// })); + +// export const messagesRelations = relations(messages, ({ one }) => ({ +// chat: one(chats, { +// fields: [messages.chatId], +// references: [chats.id], +// }), +// })); export const db = drizzle(sql, { schema: { - users, - accounts, - sessions, - verificationTokens, + // users, + // accounts, + // sessions, + // verificationTokens, chats, - chatsRelations, - usersRelations, + // chatsRelations, + // messages, + // messagesRelations, + // usersRelations, }, }); export type DbClient = typeof db; + +export type Schema = { + chats: typeof chats; +}; From c332a1b6f146bdedde35f895dc1484665e1fdf08 Mon Sep 17 00:00:00 2001 From: Jared Palmer Date: Fri, 2 Jun 2023 11:57:44 -0400 Subject: [PATCH 04/11] Try drizzle --- app/api/chat/route.ts | 37 ++++++++------- app/chat.tsx | 3 +- app/chat/[id]/page.tsx | 25 ++++------ app/page.tsx | 4 +- app/sidebar.tsx | 23 +++++---- lib/db/migrate.ts | 1 + lib/db/migrations/0002_striped_purifiers.sql | 1 + lib/db/migrations/meta/0002_snapshot.json | 49 ++++++++++++++++++++ lib/db/migrations/meta/_journal.json | 7 +++ lib/db/schema.ts | 4 +- 10 files changed, 107 insertions(+), 47 deletions(-) create mode 100644 lib/db/migrations/0002_striped_purifiers.sql create mode 100644 lib/db/migrations/meta/0002_snapshot.json diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts index 10c8031..1eb6911 100644 --- a/app/api/chat/route.ts +++ b/app/api/chat/route.ts @@ -10,13 +10,13 @@ export const POST = auth(async function POST(req: Request) { const json = await req.json(); // @ts-ignore console.log(req.auth); // todo fix types - + const messages = json.messages.map((m: any) => ({ + content: m.content, + role: m.role, + })); const res = await openai.createChatCompletion({ model: "gpt-3.5-turbo", - messages: json.messages.map((m: any) => ({ - content: m.content, - role: m.role, - })), + messages, temperature: 0.7, top_p: 1, frequency_penalty: 1, @@ -33,23 +33,26 @@ export const POST = auth(async function POST(req: Request) { } const title = json.messages[0].content.substring(0, 20); const payload = { - id: json.id ?? crypto.randomUUID(), + id: crypto.randomUUID(), title, userId: (req as any).auth?.user?.email, - messages: json.messages.concat({ - content: completion, - role: "assistant", - id: nanoid(), - }), + messages: [ + ...messages, + { + content: completion, + role: "assistant", + }, + ], }; - await db + const chat = await db .insert(chats) .values(payload) - .onConflictDoUpdate({ - target: json.id, - set: payload, - }) - .execute(); + // .onConflictDoUpdate({ + // target: payload.id, + // set: payload, + // }) + .returning(); + console.log(chat); }, }); diff --git a/app/chat.tsx b/app/chat.tsx index 9d69ee5..16d3870 100644 --- a/app/chat.tsx +++ b/app/chat.tsx @@ -1,10 +1,9 @@ "use client"; -import { type Message } from "@prisma/client"; import { useRouter } from "next/navigation"; import { ChatList } from "./chat-list"; import { Prompt } from "./prompt"; -import { useChat } from "ai-connector"; +import { useChat, type Message } from "ai-connector"; export interface ChatProps { // create?: (input: string) => Chat | undefined; diff --git a/app/chat/[id]/page.tsx b/app/chat/[id]/page.tsx index 51331ce..51358d7 100644 --- a/app/chat/[id]/page.tsx +++ b/app/chat/[id]/page.tsx @@ -1,10 +1,11 @@ import { Sidebar } from "@/app/sidebar"; -import { prisma } from "@/lib/prisma"; -import { Chat } from "../../chat"; import { type Metadata } from "next"; import { auth } from "@/auth"; - +import { db, chats } from "@/lib/db/schema"; +import { eq } from "drizzle-orm"; +import { Chat } from "@/app/chat"; +import { Message } from "ai-connector"; export interface ChatPageProps { params: { id: string; @@ -14,10 +15,8 @@ export interface ChatPageProps { export async function generateMetadata({ params, }: ChatPageProps): Promise { - const chat = await prisma.chat.findFirst({ - where: { - id: params.id, - }, + const chat = await db.query.chats.findFirst({ + where: eq(chats.id, params.id), }); return { title: chat?.title.slice(0, 50) ?? "Chat", @@ -30,14 +29,10 @@ export const preferredRegion = "home"; export const dynamic = "force-dynamic"; export default async function ChatPage({ params }: ChatPageProps) { const session = await auth(); - const chat = await prisma.chat.findFirst({ - where: { - id: params.id, - }, - include: { - messages: true, - }, + const chat = await db.query.chats.findFirst({ + where: eq(chats.id, params.id), }); + if (!chat) { throw new Error("Chat not found"); } @@ -46,7 +41,7 @@ export default async function ChatPage({ params }: ChatPageProps) {
- +
); diff --git a/app/page.tsx b/app/page.tsx index 7dec5b3..c366367 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -2,10 +2,8 @@ import { Chat } from "./chat"; import { Sidebar } from "./sidebar"; import { auth } from "@/auth"; -// Prisma does not support Edge without the Data Proxy currently -export const runtime = "nodejs"; // default +export const runtime = "edge"; // default export const preferredRegion = "home"; -export const dynamic = "force-dynamic"; export default async function IndexPage() { const session = await auth(); diff --git a/app/sidebar.tsx b/app/sidebar.tsx index ed61d7a..ebe0963 100644 --- a/app/sidebar.tsx +++ b/app/sidebar.tsx @@ -1,14 +1,14 @@ -import { NextChatLogo } from "@/components/ui/nextchat-logo"; import { Login } from "@/components/ui/login"; +import { NextChatLogo } from "@/components/ui/nextchat-logo"; import { UserMenu } from "@/components/ui/user-menu"; -import { prisma } from "@/lib/prisma"; -import { type Session } from "@auth/nextjs/types"; +import { db, chats } from "@/lib/db/schema"; import { cn } from "@/lib/utils"; +import { type Session } from "@auth/nextjs/types"; +import { eq } from "drizzle-orm"; import { Plus } from "lucide-react"; -import Link from "next/link"; import { unstable_cache } from "next/cache"; +import Link from "next/link"; import { SidebarItem } from "./sidebar-item"; -import { db } from "@/lib/db/schema"; export interface SidebarProps { session?: Session; @@ -59,9 +59,16 @@ export function Sidebar({ session, newChat }: SidebarProps) { Sidebar.displayName = "Sidebar"; async function SidebarList({ session }: { session?: Session }) { - const chats = await ( + const results: any[] = await ( await unstable_cache( - () => db.query.chats.findMany({}), + () => + db.query.chats.findMany({ + columns: { + id: true, + title: true, + }, + where: eq(chats.userId, session?.user?.email || ""), + }), // @ts-ignore [session?.user?.id || ""], { @@ -70,7 +77,7 @@ async function SidebarList({ session }: { session?: Session }) { ) )(); - return chats.map((c) => ( + return results.map((c) => ( )); } diff --git a/lib/db/migrate.ts b/lib/db/migrate.ts index cfbe862..a67bd90 100644 --- a/lib/db/migrate.ts +++ b/lib/db/migrate.ts @@ -4,6 +4,7 @@ import * as dotenv from "dotenv"; dotenv.config(); +// @ts-ignore migrate(db, { migrationsFolder: "lib/db/migrations", }) diff --git a/lib/db/migrations/0002_striped_purifiers.sql b/lib/db/migrations/0002_striped_purifiers.sql new file mode 100644 index 0000000..b9db043 --- /dev/null +++ b/lib/db/migrations/0002_striped_purifiers.sql @@ -0,0 +1 @@ +ALTER TABLE "chats" ADD COLUMN "messages" json DEFAULT '{}'::json NOT NULL; \ No newline at end of file diff --git a/lib/db/migrations/meta/0002_snapshot.json b/lib/db/migrations/meta/0002_snapshot.json new file mode 100644 index 0000000..6f5b1cb --- /dev/null +++ b/lib/db/migrations/meta/0002_snapshot.json @@ -0,0 +1,49 @@ +{ + "version": "5", + "dialect": "pg", + "id": "e3f4035a-a940-4529-b037-1737e178d88b", + "prevId": "e8eef642-fc25-4737-8e75-5ad1e8c53b84", + "tables": { + "chats": { + "name": "chats", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "messages": { + "name": "messages", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'::json" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {} + } + }, + "enums": {}, + "schemas": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + } +} \ No newline at end of file diff --git a/lib/db/migrations/meta/_journal.json b/lib/db/migrations/meta/_journal.json index d83e0fb..e6695c1 100644 --- a/lib/db/migrations/meta/_journal.json +++ b/lib/db/migrations/meta/_journal.json @@ -15,6 +15,13 @@ "when": 1685719963202, "tag": "0001_naive_gunslinger", "breakpoints": false + }, + { + "idx": 2, + "version": "5", + "when": 1685720130302, + "tag": "0002_striped_purifiers", + "breakpoints": false } ] } \ No newline at end of file diff --git a/lib/db/schema.ts b/lib/db/schema.ts index 7f983f5..28fafa9 100644 --- a/lib/db/schema.ts +++ b/lib/db/schema.ts @@ -1,5 +1,5 @@ import { sql } from "@vercel/postgres"; -import { pgTable, text } from "drizzle-orm/pg-core"; +import { json, pgTable, text } from "drizzle-orm/pg-core"; import { drizzle } from "drizzle-orm/vercel-postgres"; // const connection = createPool({ connectionString: process.env.POSTGRES_URL }); @@ -8,7 +8,7 @@ export const chats = pgTable("chats", { id: text("id").notNull().primaryKey(), title: text("role").notNull(), userId: text("userId").notNull(), - // messages: json("messages").notNull().default({}), + messages: json("messages").notNull().default({}), // .references(() => users.id), }); From 0996de27e68fcea1ad905deca3f4fa98b46908db Mon Sep 17 00:00:00 2001 From: Jared Palmer Date: Fri, 2 Jun 2023 12:27:06 -0400 Subject: [PATCH 05/11] Fix edge compat --- components/ui/login.tsx | 6 ++++-- components/ui/user-menu.tsx | 5 +++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/components/ui/login.tsx b/components/ui/login.tsx index 07edb90..b1b32a9 100644 --- a/components/ui/login.tsx +++ b/components/ui/login.tsx @@ -1,11 +1,13 @@ "use client"; -import { signIn } from "@auth/nextjs/client"; + +import { useRouter } from "next/navigation"; export function Login() { + const router = useRouter(); return ( diff --git a/components/ui/user-menu.tsx b/components/ui/user-menu.tsx index fb32743..4cfc263 100644 --- a/components/ui/user-menu.tsx +++ b/components/ui/user-menu.tsx @@ -2,15 +2,16 @@ import { ThemeToggle } from "@/components/theme-toggle"; import { type Session } from "@auth/nextjs/types"; import * as DropdownMenu from "@radix-ui/react-dropdown-menu"; -import { signOut } from "@auth/nextjs/client"; import Image from "next/image"; +import { useRouter } from "next/navigation"; export interface UserMenuProps { session: Session; } export function UserMenu({ session }: UserMenuProps) { + const router = useRouter(); return (
@@ -83,7 +84,7 @@ export function UserMenu({ session }: UserMenuProps) { signOut()} + onClick={() => router.push("/api/auth/signout")} > Log Out From 1343d16c1a5ba862ff86e60134e5bf598689fdbd Mon Sep 17 00:00:00 2001 From: Jared Palmer Date: Fri, 2 Jun 2023 13:28:45 -0400 Subject: [PATCH 06/11] Cleanup prisma --- lib/hooks/use-intersection-observer.ts | 43 -------------- lib/prisma.tsx | 11 ---- prisma/schema.prisma | 78 -------------------------- 3 files changed, 132 deletions(-) delete mode 100644 lib/hooks/use-intersection-observer.ts delete mode 100644 lib/prisma.tsx delete mode 100644 prisma/schema.prisma diff --git a/lib/hooks/use-intersection-observer.ts b/lib/hooks/use-intersection-observer.ts deleted file mode 100644 index 4a8d464..0000000 --- a/lib/hooks/use-intersection-observer.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { RefObject, useEffect, useState } from "react"; - -interface Args extends IntersectionObserverInit { - freezeOnceVisible?: boolean; -} - -function useIntersectionObserver( - elementRef: RefObject, - { - threshold = 0, - root = null, - rootMargin = "0%", - freezeOnceVisible = false, - }: Args -): IntersectionObserverEntry | undefined { - const [entry, setEntry] = useState(); - - const frozen = entry?.isIntersecting && freezeOnceVisible; - - const updateEntry = ([entry]: IntersectionObserverEntry[]): void => { - setEntry(entry); - }; - - useEffect(() => { - const node = elementRef?.current; // DOM Ref - const hasIOSupport = !!window.IntersectionObserver; - - if (!hasIOSupport || frozen || !node) return; - - const observerParams = { threshold, root, rootMargin }; - const observer = new IntersectionObserver(updateEntry, observerParams); - - observer.observe(node); - - return () => observer.disconnect(); - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [threshold, root, rootMargin, frozen]); - - return entry; -} - -export default useIntersectionObserver; diff --git a/lib/prisma.tsx b/lib/prisma.tsx deleted file mode 100644 index b9fdb66..0000000 --- a/lib/prisma.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { PrismaClient } from "@prisma/client"; - -declare global { - var prisma: PrismaClient | undefined; -} - -const prisma = global.prisma || new PrismaClient(); - -if (process.env.NODE_ENV === "development") global.prisma = prisma; - -export { prisma }; diff --git a/prisma/schema.prisma b/prisma/schema.prisma deleted file mode 100644 index ecbfa64..0000000 --- a/prisma/schema.prisma +++ /dev/null @@ -1,78 +0,0 @@ -datasource db { - provider = "postgresql" - url = env("POSTGRES_PRISMA_URL") // uses connection pooling - directUrl = env("POSTGRES_URL_NON_POOLING") // uses a direct connection - shadowDatabaseUrl = env("POSTGRES_URL_NON_POOLING") // used for migrations -} - -generator client { - provider = "prisma-client-js" - previewFeatures = ["jsonProtocol"] -} - -model Chat { - id String @id @default(cuid()) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - title String - messages Message[] - userId String? -} - -model Message { - id String @id @default(cuid()) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - content String - role String - chat Chat @relation(fields: [chatId], references: [id], onDelete: Cascade) - chatId String -} - -model Account { - id String @id @default(cuid()) - userId String - type String - provider String - providerAccountId String - refresh_token String? @db.Text - access_token String? @db.Text - expires_at Int? - token_type String? - scope String? - id_token String? @db.Text - session_state String? - - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - - @@unique([provider, providerAccountId]) - @@index([userId]) -} - -model Session { - id String @id @default(cuid()) - sessionToken String @unique - userId String - expires DateTime - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - - @@index([userId]) -} - -model User { - id String @id @default(cuid()) - name String? - email String? @unique - emailVerified DateTime? - image String? - accounts Account[] - sessions Session[] -} - -model VerificationToken { - identifier String - token String @unique - expires DateTime - - @@unique([identifier, token]) -} From 0846554fa215fc906fcdbab8c7eb06c83c1b30d8 Mon Sep 17 00:00:00 2001 From: Jared Palmer Date: Fri, 2 Jun 2023 13:29:13 -0400 Subject: [PATCH 07/11] Switch to edge --- app/chat/[id]/page.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/chat/[id]/page.tsx b/app/chat/[id]/page.tsx index 51358d7..c7a797c 100644 --- a/app/chat/[id]/page.tsx +++ b/app/chat/[id]/page.tsx @@ -23,10 +23,9 @@ export async function generateMetadata({ }; } -// Prisma does not support Edge without the Data Proxy currently -export const runtime = "nodejs"; // default +export const runtime = "edge"; // default export const preferredRegion = "home"; -export const dynamic = "force-dynamic"; + export default async function ChatPage({ params }: ChatPageProps) { const session = await auth(); const chat = await db.query.chats.findFirst({ From 09ac7639586609f0b3e9c6174eac88dffabdcf38 Mon Sep 17 00:00:00 2001 From: Jared Palmer Date: Fri, 2 Jun 2023 13:29:54 -0400 Subject: [PATCH 08/11] Edge --- app/chat/[id]/page.tsx | 6 +++--- app/page.tsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/chat/[id]/page.tsx b/app/chat/[id]/page.tsx index c7a797c..3229255 100644 --- a/app/chat/[id]/page.tsx +++ b/app/chat/[id]/page.tsx @@ -6,6 +6,9 @@ import { db, chats } from "@/lib/db/schema"; import { eq } from "drizzle-orm"; import { Chat } from "@/app/chat"; import { Message } from "ai-connector"; + +export const runtime = "edge"; +export const preferredRegion = "home"; export interface ChatPageProps { params: { id: string; @@ -23,9 +26,6 @@ export async function generateMetadata({ }; } -export const runtime = "edge"; // default -export const preferredRegion = "home"; - export default async function ChatPage({ params }: ChatPageProps) { const session = await auth(); const chat = await db.query.chats.findFirst({ diff --git a/app/page.tsx b/app/page.tsx index c366367..6212e99 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -2,7 +2,7 @@ import { Chat } from "./chat"; import { Sidebar } from "./sidebar"; import { auth } from "@/auth"; -export const runtime = "edge"; // default +export const runtime = "edge"; export const preferredRegion = "home"; export default async function IndexPage() { From a7af3107b529d971adb72b98233c9f47ddfab12a Mon Sep 17 00:00:00 2001 From: Jared Palmer Date: Fri, 2 Jun 2023 13:33:12 -0400 Subject: [PATCH 09/11] Users can only delete their own chats --- app/actions.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/app/actions.ts b/app/actions.ts index cab6259..6ce379c 100644 --- a/app/actions.ts +++ b/app/actions.ts @@ -1,7 +1,8 @@ "use server"; import { auth } from "@/auth"; -import { prisma } from "@/lib/prisma"; +import { chats, db } from "@/lib/db/schema"; +import { and, eq } from "drizzle-orm"; import { revalidatePath } from "next/cache"; export async function removeChat({ id, path }: { id: string; path: string }) { @@ -12,13 +13,10 @@ export async function removeChat({ id, path }: { id: string; path: string }) { throw new Error("Unauthorized"); } - await prisma.chat.delete({ - where: { - id, - // TODO: Add scoping - // userId, - }, - }); + await db + .delete(chats) + .where(and(eq(chats.id, id), eq(chats.userId, userId))) + .execute(); revalidatePath("/"); revalidatePath("/chat/[id]"); From 01527d450bd988cc9c9ebff11c74bcb8856ad8dc Mon Sep 17 00:00:00 2001 From: Jared Palmer Date: Fri, 2 Jun 2023 14:28:05 -0400 Subject: [PATCH 10/11] Remove auth on delete for now --- app/actions.ts | 21 +++++++++++++++------ app/sidebar-item.tsx | 4 +++- app/sidebar.tsx | 10 ++++++++-- auth.ts | 1 + 4 files changed, 27 insertions(+), 9 deletions(-) diff --git a/app/actions.ts b/app/actions.ts index 6ce379c..b76a647 100644 --- a/app/actions.ts +++ b/app/actions.ts @@ -5,13 +5,22 @@ import { chats, db } from "@/lib/db/schema"; import { and, eq } from "drizzle-orm"; import { revalidatePath } from "next/cache"; -export async function removeChat({ id, path }: { id: string; path: string }) { - const session = await auth(); +export async function removeChat({ + id, + path, + userId, +}: { + id: string; + userId: string; + path: string; +}) { + // @todo next-auth doesn't work in server actions yet + // const session = await auth(); - const userId = session?.user?.email; - if (!userId || !id) { - throw new Error("Unauthorized"); - } + // const userId = session?.user?.email; + // if (!userId || !id) { + // throw new Error("Unauthorized"); + // } await db .delete(chats) diff --git a/app/sidebar-item.tsx b/app/sidebar-item.tsx index d3e1952..09cae84 100644 --- a/app/sidebar-item.tsx +++ b/app/sidebar-item.tsx @@ -11,10 +11,12 @@ export function SidebarItem({ title, href, id, + userId, }: { title: string; href: string; id: string; + userId: string; }) { const pathname = usePathname(); const router = useRouter(); @@ -49,7 +51,7 @@ export function SidebarItem({ onClick={(e) => { e.preventDefault(); startTransition(() => { - removeChat({ id, path: href }).then(() => { + removeChat({ id, userId, path: href }).then(() => { router.push("/"); }); }); diff --git a/app/sidebar.tsx b/app/sidebar.tsx index a2d55f1..0d513b9 100644 --- a/app/sidebar.tsx +++ b/app/sidebar.tsx @@ -98,7 +98,7 @@ async function SidebarList({ session }: { session?: Session }) { where: eq(chats.userId, session?.user?.email || ""), }), // @ts-ignore - [session?.user?.id || ""], + [session?.user?.email ?? ""], { revalidate: 3600, } @@ -106,6 +106,12 @@ async function SidebarList({ session }: { session?: Session }) { )(); return results.map((c) => ( - + )); } diff --git a/auth.ts b/auth.ts index 85f3ab2..a55a7da 100644 --- a/auth.ts +++ b/auth.ts @@ -5,6 +5,7 @@ import { NextResponse } from "next/server"; export const { handlers: { GET, POST }, auth, + CSRF_experimental, } = NextAuth({ // @ts-ignore providers: [GitHub], From 9f4ef62961c18d930babcd67c273c25b263b014c Mon Sep 17 00:00:00 2001 From: Jared Palmer Date: Fri, 2 Jun 2023 14:32:46 -0400 Subject: [PATCH 11/11] Cleanup unused stuff --- app/api/chat/route.ts | 12 +- app/chat-message.tsx | 2 +- lib/db/index.ts | 268 ------------------------- lib/hooks/is-modifier-pressed.tsx | 10 - lib/hooks/use-command-enter-submit.tsx | 4 +- lib/openai.ts | 62 ------ lib/tinykeys.tsx | 209 ------------------- package.json | 1 - pnpm-lock.yaml | 3 - 9 files changed, 13 insertions(+), 558 deletions(-) delete mode 100644 lib/db/index.ts delete mode 100644 lib/hooks/is-modifier-pressed.tsx delete mode 100644 lib/openai.ts delete mode 100644 lib/tinykeys.tsx diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts index fef1de2..07a21f4 100644 --- a/app/api/chat/route.ts +++ b/app/api/chat/route.ts @@ -1,10 +1,20 @@ import { auth } from "@/auth"; import { chats, db } from "@/lib/db/schema"; -import { openai } from "@/lib/openai"; import { OpenAIStream, StreamingTextResponse } from "ai-connector"; +import { Configuration, OpenAIApi } from "openai-edge"; export const runtime = "edge"; +const configuration = new Configuration({ + apiKey: process.env.OPENAI_API_KEY, +}); + +const openai = new OpenAIApi(configuration); + +if (!process.env.OPENAI_API_KEY) { + throw new Error("Missing env var from OpenAI"); +} + export const POST = auth(async function POST(req: Request) { const json = await req.json(); // @ts-ignore diff --git a/app/chat-message.tsx b/app/chat-message.tsx index 7713293..8b84e4a 100644 --- a/app/chat-message.tsx +++ b/app/chat-message.tsx @@ -1,10 +1,10 @@ -import { type Message } from "@prisma/client"; import CodeBlock from "@/components/ui/codeblock"; import { MemoizedReactMarkdown } from "@/components/markdown"; import remarkGfm from "remark-gfm"; import remarkMath from "remark-math"; import { cn } from "@/lib/utils"; import { fontMessage } from "@/lib/fonts"; +import { Message } from "ai-connector"; export interface ChatMessageProps { message: Message; } diff --git a/lib/db/index.ts b/lib/db/index.ts deleted file mode 100644 index 60dd07d..0000000 --- a/lib/db/index.ts +++ /dev/null @@ -1,268 +0,0 @@ -/** - *
- *

Official Drizzle ORM adapter for Auth.js / NextAuth.js.

- * - * - * - *
- * - * ## Installation - * - * ```bash npm2yarn2pnpm - * npm install next-auth drizzle-orm @auth/drizzle-adapter - * npm install drizzle-kit --save-dev - * ``` - * - * @module @auth/drizzle-adapter - */ -import type { Adapter } from "@auth/core/adapters"; -import { and, eq } from "drizzle-orm"; -import type { DbClient, Schema } from "./schema"; - -/** - * ## Setup - * - * Add this adapter to your `pages/api/[...nextauth].js` next-auth configuration object: - * - * ```js title="pages/api/auth/[...nextauth].js" - * import NextAuth from "next-auth" - * import GoogleProvider from "next-auth/providers/google" - * import { DrizzleAdapter } from "@auth/drizzle-adapter" - * import { db } from "./db-schema" - * - * export default NextAuth({ - * adapter: DrizzleAdapter(db), - * providers: [ - * GoogleProvider({ - * clientId: process.env.GOOGLE_CLIENT_ID, - * clientSecret: process.env.GOOGLE_CLIENT_SECRET, - * }), - * ], - * }) - * ``` - * - * ## Advanced usage - * - * ### Create the Drizzle schema from scratch - * - * You'll need to create a database schema that includes the minimal schema for a `next-auth` adapter. - * Be sure to use the Drizzle driver version that you're using for your project. - * - * > This schema is adapted for use in Drizzle and based upon our main [schema](https://authjs.dev/reference/adapters#models) - * - * - * ```json title="db-schema.ts" - * - * import { integer, pgTable, text, primaryKey } from 'drizzle-orm/pg-core'; - * import { drizzle } from 'drizzle-orm/node-postgres'; - * import { migrate } from 'drizzle-orm/node-postgres/migrator'; - * import { Pool } from 'pg' - * import { ProviderType } from 'next-auth/providers'; - * - * export const users = pgTable('users', { - * id: text('id').notNull().primaryKey(), - * name: text('name'), - * email: text("email").notNull(), - * emailVerified: integer("emailVerified"), - * image: text("image"), - * }); - * - * export const accounts = pgTable("accounts", { - * userId: text("userId").notNull().references(() => users.id, { onDelete: "cascade" }), - * type: text("type").$type().notNull(), - * provider: text("provider").notNull(), - * providerAccountId: text("providerAccountId").notNull(), - * refresh_token: text("refresh_token"), - * access_token: text("access_token"), - * expires_at: integer("expires_at"), - * token_type: text("token_type"), - * scope: text("scope"), - * id_token: text("id_token"), - * session_state: text("session_state"), - * }, (account) => ({ - * _: primaryKey(account.provider, account.providerAccountId) - * })) - * - * export const sessions = pgTable("sessions", { - * userId: text("userId").notNull().references(() => users.id, { onDelete: "cascade" }), - * sessionToken: text("sessionToken").notNull().primaryKey(), - * expires: integer("expires").notNull(), - * }) - * - * export const verificationTokens = pgTable("verificationToken", { - * identifier: text("identifier").notNull(), - * token: text("token").notNull(), - * expires: integer("expires").notNull() - * }, (vt) => ({ - * _: primaryKey(vt.identifier, vt.token) - * })) - * - * const pool = new Pool({ - * connectionString: "YOUR_CONNECTION_STRING" - * }); - * - * export const db = drizzle(pool); - * - * migrate(db, { migrationsFolder: "./drizzle" }) - * - * ``` - * - **/ -export function PgAdapter( - client: DbClient, - { users, sessions, verificationTokens, accounts }: Schema -): Adapter { - return { - createUser: async (data) => { - return client - .insert(users) - .values({ ...data, id: crypto.randomUUID() as string }) - .returning() - .then((res) => res[0]); - }, - getUser: async (data) => { - return ( - client - .select() - .from(users) - .where(eq(users.id, data)) - .then((res) => res[0]) ?? null - ); - }, - getUserByEmail: async (data) => { - return ( - client - .select() - .from(users) - .where(eq(users.email, data)) - .then((res) => res[0]) ?? null - ); - }, - createSession: async (data) => { - return client - .insert(sessions) - .values(data) - .returning() - .then((res) => res[0]); - }, - getSessionAndUser: async (data) => { - return ( - client - .select({ - session: sessions, - user: users, - }) - .from(sessions) - .where(eq(sessions.sessionToken, data)) - .innerJoin(users, eq(users.id, sessions.userId)) - .then((res) => res[0]) ?? null - ); - }, - updateUser: async (data) => { - if (!data.id) { - throw new Error("No user id."); - } - - return client - .update(users) - .set(data) - .where(eq(users.id, data.id)) - .returning() - .then((res) => res[0]); - }, - updateSession: async (data) => { - return client - .update(sessions) - .set(data) - .where(eq(sessions.sessionToken, data.sessionToken)) - .returning() - .then((res) => res[0]); - }, - linkAccount: async (rawAccount) => { - const updatedAccount = await client - .insert(accounts) - .values(rawAccount) - .returning() - .then((res) => res[0]); - - // Drizzle will return `null` for fields that are not defined. - // However, the return type is expecting `undefined`. - const account = { - ...updatedAccount, - access_token: updatedAccount.access_token ?? undefined, - token_type: updatedAccount.token_type ?? undefined, - id_token: updatedAccount.id_token ?? undefined, - refresh_token: updatedAccount.refresh_token ?? undefined, - scope: updatedAccount.scope ?? undefined, - expires_at: updatedAccount.expires_at ?? undefined, - session_state: updatedAccount.session_state ?? undefined, - }; - - return account; - }, - getUserByAccount: async (account) => { - const dbAccount = await client - .select() - .from(accounts) - .where( - and( - eq(accounts.providerAccountId, account.providerAccountId), - eq(accounts.provider, account.provider) - ) - ) - .leftJoin(users, eq(accounts.userId, users.id)) - .then((res) => res[0]); - - return dbAccount.users; - }, - deleteSession: async (sessionToken) => { - await client - .delete(sessions) - .where(eq(sessions.sessionToken, sessionToken)); - }, - createVerificationToken: async (token) => { - return client - .insert(verificationTokens) - .values(token) - .returning() - .then((res) => res[0]); - }, - useVerificationToken: async (token) => { - try { - return ( - client - .delete(verificationTokens) - .where( - and( - eq(verificationTokens.identifier, token.identifier), - eq(verificationTokens.token, token.token) - ) - ) - .returning() - .then((res) => res[0]) ?? null - ); - } catch (err) { - throw new Error("No verification token found."); - } - }, - deleteUser: async (id) => { - await client - .delete(users) - .where(eq(users.id, id)) - .returning() - .then((res) => res[0]); - }, - unlinkAccount: async (account) => { - await client - .delete(accounts) - .where( - and( - eq(accounts.providerAccountId, account.providerAccountId), - eq(accounts.provider, account.provider) - ) - ); - - return undefined; - }, - }; -} diff --git a/lib/hooks/is-modifier-pressed.tsx b/lib/hooks/is-modifier-pressed.tsx deleted file mode 100644 index 10e3180..0000000 --- a/lib/hooks/is-modifier-pressed.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { isAppleDevice } from "@/lib/tinykeys"; - -/** - * Uses the Meta key on macOS, and the Ctrl key on other platforms. - */ -export const isModifierPressed = ( - event: KeyboardEvent | React.KeyboardEvent -): boolean => { - return isAppleDevice() ? event.metaKey : event.ctrlKey; -}; diff --git a/lib/hooks/use-command-enter-submit.tsx b/lib/hooks/use-command-enter-submit.tsx index 5c5d75c..bf178c7 100644 --- a/lib/hooks/use-command-enter-submit.tsx +++ b/lib/hooks/use-command-enter-submit.tsx @@ -1,6 +1,5 @@ import type { RefObject } from "react"; import { useRef } from "react"; -import { isModifierPressed } from "./is-modifier-pressed"; export function useCmdEnterSubmit(): { formRef: RefObject; @@ -11,8 +10,7 @@ export function useCmdEnterSubmit(): { const handleKeyDown = ( event: React.KeyboardEvent ): void => { - // Capture `⌘`|Ctrl + Enter - if (isModifierPressed(event) && event.key === "Enter") { + if (event.key === "Enter") { formRef.current?.requestSubmit(); } }; diff --git a/lib/openai.ts b/lib/openai.ts deleted file mode 100644 index a25ea2f..0000000 --- a/lib/openai.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { - createParser, - type ParsedEvent, - type ReconnectInterval, -} from "eventsource-parser"; -import { Configuration, OpenAIApi } from "openai-edge"; - -const configuration = new Configuration({ - apiKey: process.env.OPENAI_API_KEY, -}); - -export const openai = new OpenAIApi(configuration); - -if (!process.env.OPENAI_API_KEY) { - throw new Error("Missing env var from OpenAI"); -} - -export async function OpenAIStream(res: Response) { - const encoder = new TextEncoder(); - const decoder = new TextDecoder(); - - let counter = 0; - - const stream = new ReadableStream({ - async start(controller) { - function onParse(event: ParsedEvent | ReconnectInterval) { - if (event.type === "event") { - const data = event.data; - if (data === "[DONE]") { - controller.close(); - return; - } - try { - const json = JSON.parse(data) as { - choices: any; - }; - const text = - json.choices[0]?.delta?.content ?? json.choices[0]?.text ?? ""; - - if (counter < 2 && (text.match(/\n/) || []).length) { - return; - } - - const queue = encoder.encode(JSON.stringify(text) + "\n"); - controller.enqueue(queue); - counter++; - } catch (e) { - controller.error(e); - } - } - } - - const parser = createParser(onParse); - - for await (const chunk of res.body as any) { - parser.feed(decoder.decode(chunk)); - } - }, - }); - - return stream; -} diff --git a/lib/tinykeys.tsx b/lib/tinykeys.tsx deleted file mode 100644 index 820d8cb..0000000 --- a/lib/tinykeys.tsx +++ /dev/null @@ -1,209 +0,0 @@ -// forked from https://github.com/jamiebuilds/tinykeys -// to fix navigator not being defined in SSR context -// import { type ModifierKey } from "react"; - -type ModifierKey = any; -/* - -MIT License - -Copyright (c) 2020 Jamie Kyle - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - -*/ - -type KeyBindingPress = [string[], string]; - -/** - * A map of keybinding strings to event handlers. - */ -export interface KeyBindingMap { - [keybinding: string]: (event: React.KeyboardEvent) => void; -} - -export interface Options { - ignoreFocus?: boolean; -} - -/** - * These are the modifier keys that change the meaning of keybindings. - * - * Note: Ignoring "AltGraph" because it is covered by the others. - */ -let KEYBINDING_MODIFIER_KEYS = ["Shift", "Meta", "Alt", "Control"]; - -/** - * Keybinding sequences should timeout if individual key presses are more than - * 1s apart. - */ -let TIMEOUT = 1000; - -/** - * When focus is on these elements, ignore the keydown event. - */ -let inputs = ["select", "textarea", "input"]; - -export const isAppleDevice = (): boolean => { - return /Mac|iPod|iPhone|iPad/.test(navigator.platform); -}; - -/** - * Parses a "Key Binding String" into its parts - * - * grammar = `` - * = ` ...` - * = `` or `+` - * = `++...` - */ -function parse(str: string): KeyBindingPress[] { - const modifierKey = (isAppleDevice() ? "Meta" : "Control") as ModifierKey; - return str - .trim() - .split(" ") - .map((press) => { - let mods = press.split("+"); - - let key = mods.pop()!; - mods = mods.map((mod) => (mod === "$mod" ? modifierKey : mod)); - return [mods, key]; - }); -} - -/** - * This tells us if a series of events matches a key binding sequence either - * partially or exactly. - */ -function match(event: React.KeyboardEvent, press: KeyBindingPress): boolean { - // prettier-ignore - return !( - // Allow either the `event.key` or the `event.code` - // MDN event.key: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key - // MDN event.code: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code - ( - press[1].toUpperCase() !== event.key.toUpperCase() && - press[1] !== event.code - ) || - - // Ensure all the modifiers in the keybinding are pressed. - press[0].find((mod) => { - return !event.getModifierState(mod as ModifierKey) - }) || - - // KEYBINDING_MODIFIER_KEYS (Shift/Control/etc) change the meaning of a - // keybinding. So if they are pressed but aren't part of this keybinding, - // then we don't have a match. - KEYBINDING_MODIFIER_KEYS.find(mod => { - return !press[0].includes(mod) && event.getModifierState(mod as ModifierKey) - }) - ) -} - -/** - * Subscribes to keybindings. - * - * Returns an unsubscribe method. - * - * @example - * ```js - * import keybindings from "../src/keybindings" - * - * keybindings(window, { - * "Shift+d": () => { - * alert("The 'Shift' and 'd' keys were pressed at the same time") - * }, - * "y e e t": () => { - * alert("The keys 'y', 'e', 'e', and 't' were pressed in order") - * }, - * "$mod+d": () => { - * alert("Either 'Control+d' or 'Meta+d' were pressed") - * }, - * }) - * ``` - */ -export default function keybindings( - target: Window | HTMLElement, - keyBindingMap: KeyBindingMap, - options: Options = {} -): () => void { - const keyBindings = Object.keys(keyBindingMap).map((key) => { - return [parse(key), keyBindingMap[key]] as const; - }); - - const possibleMatches = new Map(); - - let timer: any = null; - - const onKeyDown = (event: React.KeyboardEvent): void => { - // Ignore modifier keydown events - // Note: This works because: - // - non-modifiers will always return false - // - if the current keypress is a modifier then it will return true when we check its state - // MDN: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/getModifierState - if ( - event.getModifierState && - event.getModifierState(event.key as ModifierKey) - ) { - return; - } - - // Ignore event when a focusable item is focused - if (options.ignoreFocus) { - if (document.activeElement) { - if ( - inputs.indexOf(document.activeElement.tagName.toLowerCase()) !== -1 || - (document.activeElement as HTMLElement).contentEditable === "true" - ) { - return; - } - } - } - - keyBindings.forEach((keyBinding) => { - let sequence = keyBinding[0]; - - let callback = keyBinding[1]; - - let prev = possibleMatches.get(sequence); - - let remainingExpectedPresses = prev ? prev : sequence; - - let currentExpectedPress = remainingExpectedPresses[0]; - - let matches = match(event, currentExpectedPress); - - if (!matches) { - possibleMatches.delete(sequence); - } else if (remainingExpectedPresses.length > 1) { - possibleMatches.set(sequence, remainingExpectedPresses.slice(1)); - } else { - possibleMatches.delete(sequence); - callback(event); - } - }); - - clearTimeout(timer); - timer = setTimeout(possibleMatches.clear.bind(possibleMatches), TIMEOUT); - }; - - target.addEventListener("keydown", onKeyDown as any); - return (): void => { - target.removeEventListener("keydown", onKeyDown as any); - }; -} diff --git a/package.json b/package.json index 52b1db2..f571296 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,6 @@ "common-tags": "^1.8.2", "cookie": "^0.5.0", "drizzle-orm": "^0.26.0", - "eventsource-parser": "^1.0.0", "focus-trap-react": "^10.1.1", "framer-motion": "^10.12.4", "jose": "^4.14.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 02191d7..463b48e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -43,9 +43,6 @@ dependencies: drizzle-orm: specifier: ^0.26.0 version: 0.26.0(@vercel/postgres@0.3.0) - eventsource-parser: - specifier: ^1.0.0 - version: 1.0.0 focus-trap-react: specifier: ^10.1.1 version: 10.1.1(prop-types@15.8.1)(react-dom@18.2.0)(react@18.2.0)