fix: mysql returning failing on update

This commit is contained in:
Bereket Engida
2024-11-16 00:34:57 +03:00
parent 1dc5544dd0
commit f9a30c357d
6 changed files with 107 additions and 40 deletions

View File

@@ -13,6 +13,8 @@ import { reactInvitationEmail } from "./email/invitation";
import { LibsqlDialect } from "@libsql/kysely-libsql";
import { reactResetPasswordEmail } from "./email/rest-password";
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 to = process.env.TEST_EMAIL || "";
@@ -22,11 +24,15 @@ const libsql = new LibsqlDialect({
authToken: process.env.TURSO_AUTH_TOKEN || "",
});
const mysql = new MysqlDialect(
createPool("mysql://user:password@localhost:3306/better_auth"),
);
export const auth = betterAuth({
appName: "Better Auth Demo",
database: {
dialect: libsql,
type: "sqlite",
dialect: mysql,
type: "mysql",
},
session: {
cookieCache: {

View File

@@ -64,11 +64,12 @@
"kysely": "^0.27.4",
"lucide-react": "^0.439.0",
"mini-svg-data-uri": "^1.4.4",
"mysql2": "^3.11.0",
"next": "15.0.0-rc.1",
"next-themes": "^0.3.0",
"prisma": "^5.19.1",
"react-day-picker": "8.10.1",
"react": "^18.3.1",
"react-day-picker": "8.10.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.53.0",
"react-qr-code": "^2.0.15",

View File

@@ -19,6 +19,23 @@ interface KyselyAdapterConfig {
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 = (
db: Kysely<any>,
options: BetterAuthOptions,
@@ -43,7 +60,10 @@ const createTransform = (
if (f.type === "boolean" && type === "sqlite") {
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;
@@ -62,6 +82,10 @@ const createTransform = (
return value;
}
function getModelName(model: string) {
return schema[model].tableName;
}
const shouldGenerateId = config?.generateId !== false;
return {
transformInput(data: Record<string, any>, model: string) {
@@ -194,25 +218,25 @@ const createTransform = (
| InsertQueryBuilder<any, any, any>
| UpdateQueryBuilder<any, string, string, any>,
model: string,
where: Where[],
) {
let res: any;
if (config?.type !== "mysql") {
res = await builder.returningAll().executeTakeFirst();
} 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();
const primaryKey = "id";
const insertedId = values[primaryKey];
const field = values.id ? "id" : where[0].field ? where[0].field : "id";
const value = values[field] || where[0].value;
res = await db
.selectFrom(this.getModelName(model))
.selectFrom(getModelName(model))
.selectAll()
.where(primaryKey, "=", insertedId)
.where(getField(model, field), "=", value)
.executeTakeFirst();
}
return res;
},
getModelName(model: string) {
return schema[model].tableName;
},
getModelName,
getField,
};
};
@@ -235,7 +259,7 @@ export const kyselyAdapter =
const transformed = transformInput(values, model);
const builder = db.insertInto(getModelName(model)).values(transformed);
return transformOutput(
await withReturning(transformed, builder, model),
await withReturning(transformed, builder, model, []),
model,
select,
);
@@ -257,7 +281,7 @@ export const kyselyAdapter =
async findMany(data) {
const { model, where, limit, offset, sortBy } = data;
const { and, or } = convertWhereClause(model, where);
let query = db.selectFrom(getModelName(model)).selectAll();
let query = db.selectFrom(getModelName(model));
if (and) {
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.limit(limit || 100);
if (offset) query = query.offset(offset);
if (offset) {
query = query.offset(offset);
}
if (sortBy) {
query = query.orderBy(
getField(model, sortBy.field),
@@ -288,7 +314,7 @@ export const kyselyAdapter =
query = query.where((eb) => eb.or(or.map((expr) => expr(eb))));
}
return transformOutput(
await withReturning(transformedData, query, model),
await withReturning(transformedData, query, model, where),
model,
);
},

View File

@@ -1,42 +1,71 @@
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 { getMigrations } from "../../../db/get-migration";
import path from "path";
import Database from "better-sqlite3";
import { kyselyAdapter } from "..";
import { Kysely, SqliteDialect } from "kysely";
import { Kysely, MysqlDialect, SqliteDialect } from "kysely";
import type { BetterAuthOptions } from "../../../types";
import { createPool } from "mysql2/promise";
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 db = new Kysely({
const mysql = createPool("mysql://user:password@localhost:3306/better_auth");
const sqliteKy = new Kysely({
dialect: new SqliteDialect({
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({
adapter,
adapter: mysqlAdapter,
});
const sqliteAdapter = kyselyAdapter(sqliteKy, {
type: "sqlite",
})(sqliteOptions);
await runAdapterTest({
adapter: sqliteAdapter,
});
});

View File

@@ -59,7 +59,9 @@ export async function generateState(
export async function parseState(c: GenericEndpointContext) {
const state = c.query.state || c.body.state;
console.log("finding verification value", state);
const data = await c.context.internalAdapter.findVerificationValue(state);
console.log("data", data);
if (!data) {
logger.error("State Mismatch. Verification not found", {
state,

3
pnpm-lock.yaml generated
View File

@@ -203,6 +203,9 @@ importers:
mini-svg-data-uri:
specifier: ^1.4.4
version: 1.4.4
mysql2:
specifier: ^3.11.0
version: 3.11.3
next:
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)