Cleanup unused stuff
This commit is contained in:
parent
01527d450b
commit
9f4ef62961
9 changed files with 13 additions and 558 deletions
268
lib/db/index.ts
268
lib/db/index.ts
|
|
@ -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;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
209
lib/tinykeys.tsx
209
lib/tinykeys.tsx
|
|
@ -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);
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue