import * as faroe_user_server from "@faroe/user-server";
import * as drizzle from "drizzle-orm";
import * as mysql from "drizzle-orm/mysql-core";
const userTable = mysql.mysqlTable("user", {
// TODO: update column type, set collation to be case sensitive
id: mysql.varchar("id").primaryKey(),
// TODO: set collation to be case sensitive
emailAddress: mysql.varchar("email_address", { length: 100 }).unique().notNull(),
passwordHash: mysql.varbinary("password_hash", { length: 255 }).notNull(),
passwordSalt: mysql.varbinary("password_salt", { length: 255 }).notNull(),
passwordHashAlgorithmId: mysql.varchar("password_hash_algorithm_id", { length: 127 }).notNull(),
disabled: mysql.boolean("disabled").notNull().default(false),
emailAddressCounter: mysql.int("email_address_counter").notNull().default(0),
passwordHashCounter: mysql.int("password_hash_counter").notNull().default(0),
disabledCounter: mysql.int("disabled_counter").notNull().default(0),
sessionsCounter: mysql.int("sessions_counter").notNull().default(0)
});
const actions: faroe_user_server.Actions = {
async createUserAction(_actionInvocationId, emailAddress, passwordHash, passwordHashAlgorithmId, passwordSalt) {
const id = generateId(); // TODO
try {
await db.insert(userTable).values({
id,
emailAddress,
passwordHash,
passwordSalt,
passwordHashAlgorithmId,
});
} catch (error) {
// TODO
if (error is uniqueConstraintFailedError) {
throw new faroe_user_server.ActionError("email_address_already_used");
}
throw new faroe_user_server.ActionError("unexpected_error");
}
const user: faroe_user_server.User = {
id,
emailAddress,
passwordHash,
passwordHashAlgorithmId,
passwordSalt,
disabled: false,
displayName: emailAddress,
emailAddressCounter: 0,
passwordHashCounter: 0,
disabledCounter: 0,
sessionsCounter: 0,
};
return user;
},
async getUserAction(_actionInvocationId, userId) {
let storedUsers: []drizzle.InferModel<typeof userTable>
try {
storedUsers = await db
.select()
.from(userTable)
.where(drizzle.eq(userTable.id, userId))
} catch {
throw new faroe_user_server.ActionError("unexpected_error");
}
if (storedUsers.length === 0) {
throw new faroe_user_server.ActionError("user_not_found");
}
const user: faroe_user_server.User = {
id: storedUsers[0].id,
emailAddress: storedUsers[0].emailAddress,
passwordHash: storedUsers[0].passwordHash,
passwordHashAlgorithmId: storedUsers[0].passwordHashAlgorithmId,
passwordSalt: storedUsers[0].passwordSalt,
disabled: storedUsers[0].disabled,
displayName: storedUsers[0].emailAddress,
emailAddressCounter: storedUsers[0].emailAddressCounter,
passwordHashCounter: storedUsers[0].passwordHashCounter,
disabledCounter: storedUsers[0].disabledCounter,
sessionsCounter: storedUsers[0].sessionsCounter,
};
return user;
},
async getUserByEmailAddressAction(_actionInvocationId, emailAddress) {
let storedUsers: []drizzle.InferModel<typeof userTable>
try {
storedUsers = await db
.select()
.from(userTable)
.where(drizzle.eq(userTable.emailAddress, emailAddress))
} catch {
throw new faroe_user_server.ActionError("unexpected_error");
}
if (storedUsers.length === 0) {
throw new faroe_user_server.ActionError("user_not_found");
}
const user: faroe_user_server.User = {
id: storedUsers[0].id,
emailAddress: storedUsers[0].emailAddress,
passwordHash: storedUsers[0].passwordHash,
passwordHashAlgorithmId: storedUsers[0].passwordHashAlgorithmId,
passwordSalt: storedUsers[0].passwordSalt,
disabled: storedUsers[0].disabled,
displayName: storedUsers[0].emailAddress,
emailAddressCounter: storedUsers[0].emailAddressCounter,
passwordHashCounter: storedUsers[0].passwordHashCounter,
disabledCounter: storedUsers[0].disabledCounter,
sessionsCounter: storedUsers[0].sessionsCounter,
};
return user;
},
async updateUserEmailAddressAction(_actionInvocationId, userId, emailAddress, userEmailAddressCounter) {
let result: { changes: number };
try {
result = await db
.update(userTable)
.set({
emailAddress,
emailAddressCounter: drizzle.sql`${userTable.emailAddressCounter} + 1`,
})
.where(
drizzle.and(
drizzle.eq(userTable.id, userId),
drizzle.eq(userTable.emailAddressCounter, userEmailAddressCounter)
)
)
} catch {
throw new faroe_user_server.ActionError("unexpected_error");
}
if (result.changes === 0) {
throw new faroe_user_server.ActionError("user_not_found");
}
},
async updateUserPasswordHashAction(_actionInvocationId, userId, passwordHash, passwordHashAlgorithmId, passwordSalt, userPasswordHashCounter) {
let result: { changes: number };
try {
result = await db
.update(userTable)
.set({
passwordHash,
passwordHashAlgorithmId,
passwordSalt,
passwordHashCounter: drizzle.sql`${userTable.passwordHashCounter} + 1`,
})
.where(
drizzle.and(
drizzle.eq(userTable.id, userId),
drizzle.eq(userTable.passwordHashCounter, userPasswordHashCounter)
)
)
} catch (error) {
throw new faroe_user_server.ActionError("unexpected_error");
}
if (result.changes === 0) {
throw new faroe_user_server.UserNotFoundError();
}
},
async incrementUserSessionsCounterAction(_actionInvocationId, userId, userSessionsCounter) {
let result: { changes: number };
try {
result = await db
.update(userTable)
.set({
sessionsCounter: drizzle.sql`${userTable.sessionsCounter} + 1`,
})
.where(
drizzle.and(
drizzle.eq(userTable.id, userId),
drizzle.eq(userTable.sessionsCounter, userSessionsCounter)
)
)
} catch {
throw new faroe_user_server.ActionError("unexpected_error");
}
if (result.changes === 0) {
throw new faroe_user_server.ActionError("user_not_found");
}
},
async deleteUserAction(_actionInvocationId, userId) {
let result: { changes: number };
try {
result = await db
.delete(userTable)
.where(drizzle.eq(userTable.id, userId))
} catch (error) {
throw new faroe_user_server.ActionError("unexpected_error");
}
if (result.changes === 0) {
throw new faroe_user_server.ActionError("user_not_found");
}
},
};