mirror of
https://github.com/LukeHagar/better-auth.git
synced 2025-12-09 12:27:43 +00:00
fix: mysql returning failing on update
This commit is contained in:
@@ -13,6 +13,8 @@ import { reactInvitationEmail } from "./email/invitation";
|
|||||||
import { LibsqlDialect } from "@libsql/kysely-libsql";
|
import { LibsqlDialect } from "@libsql/kysely-libsql";
|
||||||
import { reactResetPasswordEmail } from "./email/rest-password";
|
import { reactResetPasswordEmail } from "./email/rest-password";
|
||||||
import { resend } from "./email/resend";
|
import { resend } from "./email/resend";
|
||||||
|
import { MysqlDialect } from "kysely";
|
||||||
|
import { createPool } from "mysql2/promise";
|
||||||
|
|
||||||
const from = process.env.BETTER_AUTH_EMAIL || "delivered@resend.dev";
|
const from = process.env.BETTER_AUTH_EMAIL || "delivered@resend.dev";
|
||||||
const to = process.env.TEST_EMAIL || "";
|
const to = process.env.TEST_EMAIL || "";
|
||||||
@@ -22,11 +24,15 @@ const libsql = new LibsqlDialect({
|
|||||||
authToken: process.env.TURSO_AUTH_TOKEN || "",
|
authToken: process.env.TURSO_AUTH_TOKEN || "",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const mysql = new MysqlDialect(
|
||||||
|
createPool("mysql://user:password@localhost:3306/better_auth"),
|
||||||
|
);
|
||||||
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
appName: "Better Auth Demo",
|
appName: "Better Auth Demo",
|
||||||
database: {
|
database: {
|
||||||
dialect: libsql,
|
dialect: mysql,
|
||||||
type: "sqlite",
|
type: "mysql",
|
||||||
},
|
},
|
||||||
session: {
|
session: {
|
||||||
cookieCache: {
|
cookieCache: {
|
||||||
|
|||||||
@@ -64,11 +64,12 @@
|
|||||||
"kysely": "^0.27.4",
|
"kysely": "^0.27.4",
|
||||||
"lucide-react": "^0.439.0",
|
"lucide-react": "^0.439.0",
|
||||||
"mini-svg-data-uri": "^1.4.4",
|
"mini-svg-data-uri": "^1.4.4",
|
||||||
|
"mysql2": "^3.11.0",
|
||||||
"next": "15.0.0-rc.1",
|
"next": "15.0.0-rc.1",
|
||||||
"next-themes": "^0.3.0",
|
"next-themes": "^0.3.0",
|
||||||
"prisma": "^5.19.1",
|
"prisma": "^5.19.1",
|
||||||
"react-day-picker": "8.10.1",
|
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
|
"react-day-picker": "8.10.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-hook-form": "^7.53.0",
|
"react-hook-form": "^7.53.0",
|
||||||
"react-qr-code": "^2.0.15",
|
"react-qr-code": "^2.0.15",
|
||||||
|
|||||||
@@ -19,6 +19,23 @@ interface KyselyAdapterConfig {
|
|||||||
generateId?: ((size?: number) => string) | false;
|
generateId?: ((size?: number) => string) | false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatDateForMySQL(date: Date): string {
|
||||||
|
const pad = (n: number) => (n < 10 ? "0" + n : n);
|
||||||
|
return (
|
||||||
|
date.getFullYear() +
|
||||||
|
"-" +
|
||||||
|
pad(date.getMonth() + 1) +
|
||||||
|
"-" +
|
||||||
|
pad(date.getDate()) +
|
||||||
|
" " +
|
||||||
|
pad(date.getHours()) +
|
||||||
|
":" +
|
||||||
|
pad(date.getMinutes()) +
|
||||||
|
":" +
|
||||||
|
pad(date.getSeconds())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const createTransform = (
|
const createTransform = (
|
||||||
db: Kysely<any>,
|
db: Kysely<any>,
|
||||||
options: BetterAuthOptions,
|
options: BetterAuthOptions,
|
||||||
@@ -43,7 +60,10 @@ const createTransform = (
|
|||||||
if (f.type === "boolean" && type === "sqlite") {
|
if (f.type === "boolean" && type === "sqlite") {
|
||||||
return value ? 1 : 0;
|
return value ? 1 : 0;
|
||||||
}
|
}
|
||||||
if (f.type === "date" && value) {
|
if (f.type === "date" && value && value instanceof Date) {
|
||||||
|
if (type === "mysql") {
|
||||||
|
return formatDateForMySQL(value);
|
||||||
|
}
|
||||||
return value.toISOString();
|
return value.toISOString();
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
@@ -62,6 +82,10 @@ const createTransform = (
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getModelName(model: string) {
|
||||||
|
return schema[model].tableName;
|
||||||
|
}
|
||||||
|
|
||||||
const shouldGenerateId = config?.generateId !== false;
|
const shouldGenerateId = config?.generateId !== false;
|
||||||
return {
|
return {
|
||||||
transformInput(data: Record<string, any>, model: string) {
|
transformInput(data: Record<string, any>, model: string) {
|
||||||
@@ -194,25 +218,25 @@ const createTransform = (
|
|||||||
| InsertQueryBuilder<any, any, any>
|
| InsertQueryBuilder<any, any, any>
|
||||||
| UpdateQueryBuilder<any, string, string, any>,
|
| UpdateQueryBuilder<any, string, string, any>,
|
||||||
model: string,
|
model: string,
|
||||||
|
where: Where[],
|
||||||
) {
|
) {
|
||||||
let res: any;
|
let res: any;
|
||||||
if (config?.type !== "mysql") {
|
if (config?.type !== "mysql") {
|
||||||
res = await builder.returningAll().executeTakeFirst();
|
res = await builder.returningAll().executeTakeFirst();
|
||||||
} else {
|
} else {
|
||||||
|
//this isn't good, but kysely doesn't support returning in mysql and it doesn't return the inserted id. Change this if there is a better way.
|
||||||
await builder.execute();
|
await builder.execute();
|
||||||
const primaryKey = "id";
|
const field = values.id ? "id" : where[0].field ? where[0].field : "id";
|
||||||
const insertedId = values[primaryKey];
|
const value = values[field] || where[0].value;
|
||||||
res = await db
|
res = await db
|
||||||
.selectFrom(this.getModelName(model))
|
.selectFrom(getModelName(model))
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.where(primaryKey, "=", insertedId)
|
.where(getField(model, field), "=", value)
|
||||||
.executeTakeFirst();
|
.executeTakeFirst();
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
getModelName(model: string) {
|
getModelName,
|
||||||
return schema[model].tableName;
|
|
||||||
},
|
|
||||||
getField,
|
getField,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -235,7 +259,7 @@ export const kyselyAdapter =
|
|||||||
const transformed = transformInput(values, model);
|
const transformed = transformInput(values, model);
|
||||||
const builder = db.insertInto(getModelName(model)).values(transformed);
|
const builder = db.insertInto(getModelName(model)).values(transformed);
|
||||||
return transformOutput(
|
return transformOutput(
|
||||||
await withReturning(transformed, builder, model),
|
await withReturning(transformed, builder, model, []),
|
||||||
model,
|
model,
|
||||||
select,
|
select,
|
||||||
);
|
);
|
||||||
@@ -257,7 +281,7 @@ export const kyselyAdapter =
|
|||||||
async findMany(data) {
|
async findMany(data) {
|
||||||
const { model, where, limit, offset, sortBy } = data;
|
const { model, where, limit, offset, sortBy } = data;
|
||||||
const { and, or } = convertWhereClause(model, where);
|
const { and, or } = convertWhereClause(model, where);
|
||||||
let query = db.selectFrom(getModelName(model)).selectAll();
|
let query = db.selectFrom(getModelName(model));
|
||||||
if (and) {
|
if (and) {
|
||||||
query = query.where((eb) => eb.and(and.map((expr) => expr(eb))));
|
query = query.where((eb) => eb.and(and.map((expr) => expr(eb))));
|
||||||
}
|
}
|
||||||
@@ -265,7 +289,9 @@ export const kyselyAdapter =
|
|||||||
query = query.where((eb) => eb.or(or.map((expr) => expr(eb))));
|
query = query.where((eb) => eb.or(or.map((expr) => expr(eb))));
|
||||||
}
|
}
|
||||||
query = query.limit(limit || 100);
|
query = query.limit(limit || 100);
|
||||||
if (offset) query = query.offset(offset);
|
if (offset) {
|
||||||
|
query = query.offset(offset);
|
||||||
|
}
|
||||||
if (sortBy) {
|
if (sortBy) {
|
||||||
query = query.orderBy(
|
query = query.orderBy(
|
||||||
getField(model, sortBy.field),
|
getField(model, sortBy.field),
|
||||||
@@ -288,7 +314,7 @@ export const kyselyAdapter =
|
|||||||
query = query.where((eb) => eb.or(or.map((expr) => expr(eb))));
|
query = query.where((eb) => eb.or(or.map((expr) => expr(eb))));
|
||||||
}
|
}
|
||||||
return transformOutput(
|
return transformOutput(
|
||||||
await withReturning(transformedData, query, model),
|
await withReturning(transformedData, query, model, where),
|
||||||
model,
|
model,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,42 +1,71 @@
|
|||||||
import fs from "fs/promises";
|
import fs from "fs/promises";
|
||||||
import { afterAll, beforeEach, describe, expect, it } from "vitest";
|
import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
import { runAdapterTest } from "../../test";
|
import { runAdapterTest } from "../../test";
|
||||||
import { getMigrations } from "../../../db/get-migration";
|
import { getMigrations } from "../../../db/get-migration";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import Database from "better-sqlite3";
|
import Database from "better-sqlite3";
|
||||||
import { kyselyAdapter } from "..";
|
import { kyselyAdapter } from "..";
|
||||||
import { Kysely, SqliteDialect } from "kysely";
|
import { Kysely, MysqlDialect, SqliteDialect } from "kysely";
|
||||||
import type { BetterAuthOptions } from "../../../types";
|
import type { BetterAuthOptions } from "../../../types";
|
||||||
|
import { createPool } from "mysql2/promise";
|
||||||
|
|
||||||
describe("adapter test", async () => {
|
describe("adapter test", async () => {
|
||||||
const database = new Database(path.join(__dirname, "test.db"));
|
|
||||||
const opts = {
|
|
||||||
database,
|
|
||||||
user: {
|
|
||||||
fields: {
|
|
||||||
email: "email_address",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
session: {
|
|
||||||
modelName: "sessions",
|
|
||||||
},
|
|
||||||
} satisfies BetterAuthOptions;
|
|
||||||
beforeEach(async () => {
|
|
||||||
const { runMigrations } = await getMigrations(opts);
|
|
||||||
await runMigrations();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await fs.unlink(path.join(__dirname, "test.db"));
|
|
||||||
});
|
|
||||||
const sqlite = new Database(path.join(__dirname, "test.db"));
|
const sqlite = new Database(path.join(__dirname, "test.db"));
|
||||||
const db = new Kysely({
|
const mysql = createPool("mysql://user:password@localhost:3306/better_auth");
|
||||||
|
const sqliteKy = new Kysely({
|
||||||
dialect: new SqliteDialect({
|
dialect: new SqliteDialect({
|
||||||
database: sqlite,
|
database: sqlite,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
const adapter = kyselyAdapter(db)(opts);
|
const mysqlKy = new Kysely({
|
||||||
|
dialect: new MysqlDialect(mysql),
|
||||||
|
});
|
||||||
|
const opts = (database: BetterAuthOptions["database"]) =>
|
||||||
|
({
|
||||||
|
database: database,
|
||||||
|
user: {
|
||||||
|
fields: {
|
||||||
|
email: "email_address",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
session: {
|
||||||
|
modelName: "sessions",
|
||||||
|
},
|
||||||
|
}) satisfies BetterAuthOptions;
|
||||||
|
const mysqlOptions = opts({
|
||||||
|
db: mysqlKy,
|
||||||
|
type: "mysql",
|
||||||
|
});
|
||||||
|
const sqliteOptions = opts({
|
||||||
|
db: sqliteKy,
|
||||||
|
type: "sqlite",
|
||||||
|
});
|
||||||
|
beforeAll(async () => {
|
||||||
|
const { runMigrations } = await getMigrations(mysqlOptions);
|
||||||
|
await runMigrations();
|
||||||
|
const { runMigrations: runMigrationsSqlite } =
|
||||||
|
await getMigrations(sqliteOptions);
|
||||||
|
await runMigrationsSqlite();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await mysql.query("DROP DATABASE IF EXISTS better_auth");
|
||||||
|
await mysql.query("CREATE DATABASE better_auth");
|
||||||
|
await mysql.end();
|
||||||
|
await fs.unlink(path.join(__dirname, "test.db"));
|
||||||
|
});
|
||||||
|
|
||||||
|
const mysqlAdapter = kyselyAdapter(mysqlKy, {
|
||||||
|
type: "mysql",
|
||||||
|
})(mysqlOptions);
|
||||||
await runAdapterTest({
|
await runAdapterTest({
|
||||||
adapter,
|
adapter: mysqlAdapter,
|
||||||
|
});
|
||||||
|
|
||||||
|
const sqliteAdapter = kyselyAdapter(sqliteKy, {
|
||||||
|
type: "sqlite",
|
||||||
|
})(sqliteOptions);
|
||||||
|
await runAdapterTest({
|
||||||
|
adapter: sqliteAdapter,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -59,7 +59,9 @@ export async function generateState(
|
|||||||
|
|
||||||
export async function parseState(c: GenericEndpointContext) {
|
export async function parseState(c: GenericEndpointContext) {
|
||||||
const state = c.query.state || c.body.state;
|
const state = c.query.state || c.body.state;
|
||||||
|
console.log("finding verification value", state);
|
||||||
const data = await c.context.internalAdapter.findVerificationValue(state);
|
const data = await c.context.internalAdapter.findVerificationValue(state);
|
||||||
|
console.log("data", data);
|
||||||
if (!data) {
|
if (!data) {
|
||||||
logger.error("State Mismatch. Verification not found", {
|
logger.error("State Mismatch. Verification not found", {
|
||||||
state,
|
state,
|
||||||
|
|||||||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@@ -203,6 +203,9 @@ importers:
|
|||||||
mini-svg-data-uri:
|
mini-svg-data-uri:
|
||||||
specifier: ^1.4.4
|
specifier: ^1.4.4
|
||||||
version: 1.4.4
|
version: 1.4.4
|
||||||
|
mysql2:
|
||||||
|
specifier: ^3.11.0
|
||||||
|
version: 3.11.3
|
||||||
next:
|
next:
|
||||||
specifier: 15.0.0-rc.1
|
specifier: 15.0.0-rc.1
|
||||||
version: 15.0.0-rc.1(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
version: 15.0.0-rc.1(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
|||||||
Reference in New Issue
Block a user