Cleanup unused stuff

This commit is contained in:
Jared Palmer 2023-06-02 14:32:46 -04:00
parent 01527d450b
commit 9f4ef62961
9 changed files with 13 additions and 558 deletions

View file

@ -1,268 +0,0 @@
/**
* <div style={{display: "flex", justifyContent: "space-between", alignItems: "center", padding: 16}}>
* <p style={{fontWeight: "normal"}}>Official <a href="https://github.com/drizzle-team/drizzle-orm">Drizzle ORM</a> adapter for Auth.js / NextAuth.js.</p>
* <a href="https://github.com/drizzle-team/drizzle-orm">
* <img style={{display: "block"}} src="https://pbs.twimg.com/profile_images/1598308842391179266/CtXrfLnk_400x400.jpg" width="38" />
* </a>
* </div>
*
* ## 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<ProviderType>().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;
},
};
}

View file

@ -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;
};

View file

@ -1,6 +1,5 @@
import type { RefObject } from "react";
import { useRef } from "react";
import { isModifierPressed } from "./is-modifier-pressed";
export function useCmdEnterSubmit(): {
formRef: RefObject<HTMLFormElement>;
@ -11,8 +10,7 @@ export function useCmdEnterSubmit(): {
const handleKeyDown = (
event: React.KeyboardEvent<HTMLTextAreaElement>
): void => {
// Capture `⌘`|Ctrl + Enter
if (isModifierPressed(event) && event.key === "Enter") {
if (event.key === "Enter") {
formRef.current?.requestSubmit();
}
};

View file

@ -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;
}

View file

@ -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 = `<sequence>`
* <sequence> = `<press> <press> <press> ...`
* <press> = `<key>` or `<mods>+<key>`
* <mods> = `<mod>+<mod>+...`
*/
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<KeyBindingPress[], KeyBindingPress[]>();
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);
};
}