refactor: convert all findMany to new structure

This commit is contained in:
Nicolas Meienberger 2026-02-01 15:30:36 +01:00
parent 74714f8e74
commit 1e04c763f0
27 changed files with 304 additions and 269 deletions

View file

@ -1,5 +1,5 @@
import { hashPassword } from "better-auth/crypto";
import { and, eq, ne } from "drizzle-orm";
import { eq } from "drizzle-orm";
import { db } from "~/server/db/db";
import { account, usersTable, member, organization } from "~/server/db/schema";
import type { AuthMiddlewareContext } from "../auth";
@ -14,7 +14,9 @@ export const convertLegacyUserOnFirstLogin = async (ctx: AuthMiddlewareContext)
}
const legacyUser = await db.query.usersTable.findFirst({
where: and(eq(usersTable.username, body.username.trim().toLowerCase()), ne(usersTable.passwordHash, "")),
where: {
AND: [{ username: body.username.trim().toLowerCase() }, { passwordHash: { NOT: "" } }],
},
});
if (legacyUser) {
@ -26,7 +28,7 @@ export const convertLegacyUserOnFirstLogin = async (ctx: AuthMiddlewareContext)
const accountId = crypto.randomUUID();
const oldMembership = await tx.query.member.findFirst({
where: eq(member.userId, legacyUser.id),
where: { userId: legacyUser.id },
with: {
organization: true,
},

View file

@ -1,8 +1,6 @@
import { db } from "~/server/db/db";
import type { AuthMiddlewareContext } from "../auth";
import { logger } from "~/server/utils/logger";
import { eq } from "drizzle-orm";
import { appMetadataTable } from "~/server/db/schema";
import { ForbiddenError } from "http-errors-enhanced";
import { REGISTRATION_ENABLED_KEY } from "~/client/lib/constants";
@ -15,7 +13,7 @@ export const ensureOnlyOneUser = async (ctx: AuthMiddlewareContext) => {
}
const result = await db.query.appMetadataTable.findFirst({
where: eq(appMetadataTable.key, REGISTRATION_ENABLED_KEY),
where: { key: REGISTRATION_ENABLED_KEY },
});
if (result?.value !== "true" && existingUser) {

View file

@ -98,7 +98,7 @@ const createBetterAuth = (secret: string) => {
create: {
before: async (session) => {
const orgMembership = await db.query.member.findFirst({
where: eq(member.userId, session.userId),
where: { userId: session.userId },
});
if (!orgMembership) {

View file

@ -15,7 +15,7 @@ const listOrganizations = () => {
const getUserCurrentOrganization = async (userId: string) => {
const membership = await db.query.member.findFirst({
where: eq(member.userId, userId),
where: { userId },
with: {
organization: true,
},
@ -36,9 +36,7 @@ const assignUserToOrganization = async (userId: string, organizationId: string)
throw new Error("Organization not found");
}
const existingMembership = await db.query.member.findFirst({
where: eq(member.userId, userId),
});
const existingMembership = await db.query.member.findFirst({ where: { userId } });
await db.transaction(async (tx) => {
if (existingMembership) {

View file

@ -1,4 +1,5 @@
import { Database } from "bun:sqlite";
import { relations } from "./relations";
import path from "node:path";
import { drizzle } from "drizzle-orm/bun-sqlite";
import { migrate } from "drizzle-orm/bun-sqlite/migrator";
@ -13,7 +14,7 @@ import type * as schemaTypes from "./schema";
* to isolate the db initialization code from the rest of the server code.
*/
let _sqlite: Database | undefined;
let _db: ReturnType<typeof drizzle<typeof schemaTypes>> | undefined;
let _db: ReturnType<typeof initDb> | undefined;
let _schema: typeof schemaTypes | undefined;
/**
@ -35,7 +36,7 @@ const initDb = () => {
}
_sqlite = new Database(DATABASE_URL);
return drizzle({ client: _sqlite, schema: _schema });
return drizzle({ client: _sqlite, relations, schema: _schema });
};
/**
@ -51,7 +52,7 @@ export const db = new Proxy(
return Reflect.get(_db, prop, receiver);
},
},
) as ReturnType<typeof drizzle<typeof schemaTypes>>;
) as ReturnType<typeof initDb>;
export const runDbMigrations = () => {
let migrationsFolder: string;

141
app/server/db/relations.ts Normal file
View file

@ -0,0 +1,141 @@
import { defineRelations } from "drizzle-orm";
import * as schema from "./schema";
export const relations = defineRelations(schema, (r) => ({
repositoriesTable: {
backupSchedulesTablesViaBackupScheduleMirrorsTable: r.many.backupSchedulesTable({
from: r.repositoriesTable.id.through(r.backupScheduleMirrorsTable.repositoryId),
to: r.backupSchedulesTable.id.through(r.backupScheduleMirrorsTable.scheduleId),
alias: "repositoriesTable_id_backupSchedulesTable_id_via_backupScheduleMirrorsTable",
}),
backupSchedules: r.many.backupSchedulesTable({
alias: "backupSchedulesTable_repositoryId_repositoriesTable_id",
}),
organization: r.one.organization({
from: r.repositoriesTable.organizationId,
to: r.organization.id,
}),
},
backupScheduleMirrorsTable: {
repository: r.one.repositoriesTable({
from: r.backupScheduleMirrorsTable.repositoryId,
to: r.repositoriesTable.id,
optional: false,
}),
backupSchedule: r.one.backupSchedulesTable({
from: r.backupScheduleMirrorsTable.scheduleId,
to: r.backupSchedulesTable.id,
optional: false,
}),
},
backupSchedulesTable: {
mirrors: r.many.repositoriesTable({
alias: "repositoriesTable_id_backupSchedulesTable_id_via_backupScheduleMirrorsTable",
}),
notificationDestinationsTables: r.many.notificationDestinationsTable(),
organization: r.one.organization({
from: r.backupSchedulesTable.organizationId,
to: r.organization.id,
optional: false,
}),
repository: r.one.repositoriesTable({
from: r.backupSchedulesTable.repositoryId,
to: r.repositoriesTable.id,
alias: "backupSchedulesTable_repositoryId_repositoriesTable_id",
optional: false,
}),
volume: r.one.volumesTable({
from: r.backupSchedulesTable.volumeId,
to: r.volumesTable.id,
optional: false,
}),
},
notificationDestinationsTable: {
backupSchedules: r.many.backupSchedulesTable({
from: r.notificationDestinationsTable.id.through(r.backupScheduleNotificationsTable.destinationId),
to: r.backupSchedulesTable.id.through(r.backupScheduleNotificationsTable.scheduleId),
}),
organization: r.one.organization({
from: r.notificationDestinationsTable.organizationId,
to: r.organization.id,
}),
},
account: {
user: r.one.usersTable({
from: r.account.userId,
to: r.usersTable.id,
}),
},
usersTable: {
accounts: r.many.account(),
sessions: r.many.sessionsTable(),
members: r.many.member(),
twoFactors: r.many.twoFactor(),
organizations: r.many.organization({
from: r.usersTable.id.through(r.member.userId),
to: r.organization.id.through(r.member.organizationId),
alias: "usersTable_id_organization_id_via_member",
}),
},
sessionsTable: {
user: r.one.usersTable({
from: r.sessionsTable.userId,
to: r.usersTable.id,
}),
},
twoFactor: {
usersTable: r.one.usersTable({
from: r.twoFactor.userId,
to: r.usersTable.id,
}),
},
organization: {
users: r.many.usersTable({
alias: "usersTable_id_organization_id_via_member",
}),
backupSchedules: r.many.backupSchedulesTable(),
notificationDestinations: r.many.notificationDestinationsTable(),
repositories: r.many.repositoriesTable(),
volumes: r.many.volumesTable(),
members: r.many.member(),
invitations: r.many.invitation(),
},
volumesTable: {
backupSchedules: r.many.backupSchedulesTable(),
organization: r.one.organization({
from: r.volumesTable.organizationId,
to: r.organization.id,
}),
},
member: {
user: r.one.usersTable({
from: r.member.userId,
to: r.usersTable.id,
optional: false,
}),
organization: r.one.organization({
from: r.member.organizationId,
to: r.organization.id,
optional: false,
}),
},
invitation: {
organization: r.one.organization({
from: r.invitation.organizationId,
to: r.organization.id,
optional: false,
}),
},
backupScheduleNotificationsTable: {
destination: r.one.notificationDestinationsTable({
from: r.backupScheduleNotificationsTable.destinationId,
to: r.notificationDestinationsTable.id,
optional: false,
}),
backupSchedule: r.one.backupSchedulesTable({
from: r.backupScheduleNotificationsTable.scheduleId,
to: r.backupSchedulesTable.id,
optional: false,
}),
},
}));

View file

@ -1,4 +1,4 @@
import { relations, sql } from "drizzle-orm";
import { sql } from "drizzle-orm";
import { index, int, integer, sqliteTable, text, real, primaryKey, unique, uniqueIndex } from "drizzle-orm/sqlite-core";
import type {
CompressionMode,
@ -175,29 +175,33 @@ export const invitation = sqliteTable(
/**
* Volumes Table
*/
export const volumesTable = sqliteTable("volumes_table", {
id: int().primaryKey({ autoIncrement: true }),
shortId: text("short_id").notNull().unique(),
name: text().notNull(),
type: text().$type<BackendType>().notNull(),
status: text().$type<BackendStatus>().notNull().default("unmounted"),
lastError: text("last_error"),
lastHealthCheck: integer("last_health_check", { mode: "number" })
.notNull()
.default(sql`(unixepoch() * 1000)`),
createdAt: integer("created_at", { mode: "number" })
.notNull()
.default(sql`(unixepoch() * 1000)`),
updatedAt: integer("updated_at", { mode: "number" })
.notNull()
.$onUpdate(() => Date.now())
.default(sql`(unixepoch() * 1000)`),
config: text("config", { mode: "json" }).$type<typeof volumeConfigSchema.inferOut>().notNull(),
autoRemount: int("auto_remount", { mode: "boolean" }).notNull().default(true),
organizationId: text("organization_id")
.notNull()
.references(() => organization.id, { onDelete: "cascade" }),
}, (table) => [unique().on(table.name, table.organizationId)]);
export const volumesTable = sqliteTable(
"volumes_table",
{
id: int().primaryKey({ autoIncrement: true }),
shortId: text("short_id").notNull().unique(),
name: text().notNull(),
type: text().$type<BackendType>().notNull(),
status: text().$type<BackendStatus>().notNull().default("unmounted"),
lastError: text("last_error"),
lastHealthCheck: integer("last_health_check", { mode: "number" })
.notNull()
.default(sql`(unixepoch() * 1000)`),
createdAt: integer("created_at", { mode: "number" })
.notNull()
.default(sql`(unixepoch() * 1000)`),
updatedAt: integer("updated_at", { mode: "number" })
.notNull()
.$onUpdate(() => Date.now())
.default(sql`(unixepoch() * 1000)`),
config: text("config", { mode: "json" }).$type<typeof volumeConfigSchema.inferOut>().notNull(),
autoRemount: int("auto_remount", { mode: "boolean" }).notNull().default(true),
organizationId: text("organization_id")
.notNull()
.references(() => organization.id, { onDelete: "cascade" }),
},
(table) => [unique().on(table.name, table.organizationId)],
);
export type Volume = typeof volumesTable.$inferSelect;
export type VolumeInsert = typeof volumesTable.$inferInsert;
@ -381,102 +385,3 @@ export const twoFactor = sqliteTable(
},
(table) => [index("twoFactor_secret_idx").on(table.secret), index("twoFactor_userId_idx").on(table.userId)],
);
export const userRelations = relations(usersTable, ({ many }) => ({
sessions: many(sessionsTable),
accounts: many(account),
members: many(member),
invitations: many(invitation),
twoFactors: many(twoFactor),
}));
export const sessionRelations = relations(sessionsTable, ({ one }) => ({
user: one(usersTable, {
fields: [sessionsTable.userId],
references: [usersTable.id],
}),
activeOrganization: one(organization, {
fields: [sessionsTable.activeOrganizationId],
references: [organization.id],
}),
}));
export const accountRelations = relations(account, ({ one }) => ({
user: one(usersTable, {
fields: [account.userId],
references: [usersTable.id],
}),
}));
export const organizationRelations = relations(organization, ({ many }) => ({
members: many(member),
invitations: many(invitation),
}));
export const memberRelations = relations(member, ({ one }) => ({
organization: one(organization, {
fields: [member.organizationId],
references: [organization.id],
}),
usersTable: one(usersTable, {
fields: [member.userId],
references: [usersTable.id],
}),
}));
export const invitationRelations = relations(invitation, ({ one }) => ({
organization: one(organization, {
fields: [invitation.organizationId],
references: [organization.id],
}),
usersTable: one(usersTable, {
fields: [invitation.inviterId],
references: [usersTable.id],
}),
}));
export const twoFactorRelations = relations(twoFactor, ({ one }) => ({
usersTable: one(usersTable, {
fields: [twoFactor.userId],
references: [usersTable.id],
}),
}));
export const backupScheduleMirrorRelations = relations(backupScheduleMirrorsTable, ({ one }) => ({
schedule: one(backupSchedulesTable, {
fields: [backupScheduleMirrorsTable.scheduleId],
references: [backupSchedulesTable.id],
}),
repository: one(repositoriesTable, {
fields: [backupScheduleMirrorsTable.repositoryId],
references: [repositoriesTable.id],
}),
}));
export const backupScheduleRelations = relations(backupSchedulesTable, ({ one, many }) => ({
volume: one(volumesTable, {
fields: [backupSchedulesTable.volumeId],
references: [volumesTable.id],
}),
repository: one(repositoriesTable, {
fields: [backupSchedulesTable.repositoryId],
references: [repositoriesTable.id],
}),
notifications: many(backupScheduleNotificationsTable),
mirrors: many(backupScheduleMirrorsTable),
}));
export const notificationDestinationRelations = relations(notificationDestinationsTable, ({ many }) => ({
schedules: many(backupScheduleNotificationsTable),
}));
export const backupScheduleNotificationRelations = relations(backupScheduleNotificationsTable, ({ one }) => ({
schedule: one(backupSchedulesTable, {
fields: [backupScheduleNotificationsTable.scheduleId],
references: [backupSchedulesTable.id],
}),
destination: one(notificationDestinationsTable, {
fields: [backupScheduleNotificationsTable.destinationId],
references: [notificationDestinationsTable.id],
}),
}));

View file

@ -2,8 +2,6 @@ import { Job } from "../core/scheduler";
import { volumeService } from "../modules/volumes/volume.service";
import { logger } from "../utils/logger";
import { db } from "../db/db";
import { eq } from "drizzle-orm";
import { volumesTable } from "../db/schema";
import { withContext } from "../core/request-context";
export class VolumeAutoRemountJob extends Job {
@ -11,7 +9,7 @@ export class VolumeAutoRemountJob extends Job {
logger.debug("Running auto-remount for all errored volumes...");
const volumes = await db.query.volumesTable.findMany({
where: eq(volumesTable.status, "error"),
where: { status: "error" },
});
for (const volume of volumes) {

View file

@ -2,8 +2,6 @@ import { Job } from "../core/scheduler";
import { volumeService } from "../modules/volumes/volume.service";
import { logger } from "../utils/logger";
import { db } from "../db/db";
import { eq, or } from "drizzle-orm";
import { volumesTable } from "../db/schema";
import { withContext } from "../core/request-context";
export class VolumeHealthCheckJob extends Job {
@ -11,7 +9,9 @@ export class VolumeHealthCheckJob extends Job {
logger.debug("Running health check for all volumes...");
const volumes = await db.query.volumesTable.findMany({
where: or(eq(volumesTable.status, "mounted"), eq(volumesTable.status, "error")),
where: {
OR: [{ status: "mounted" }, { status: "error" }],
},
});
for (const volume of volumes) {

View file

@ -2,8 +2,6 @@ import { Job } from "../core/scheduler";
import { repositoriesService } from "../modules/repositories/repositories.service";
import { logger } from "../utils/logger";
import { db } from "../db/db";
import { eq, or } from "drizzle-orm";
import { repositoriesTable } from "../db/schema";
import { withContext } from "../core/request-context";
export class RepositoryHealthCheckJob extends Job {
@ -11,7 +9,7 @@ export class RepositoryHealthCheckJob extends Job {
logger.debug("Running health check for all repositories...");
const repositories = await db.query.repositoriesTable.findMany({
where: or(eq(repositoriesTable.status, "healthy"), eq(repositoriesTable.status, "error")),
where: { OR: [{ status: "healthy" }, { status: "error" }] },
});
for (const repository of repositories) {

View file

@ -1,8 +1,6 @@
import { createMiddleware } from "hono/factory";
import { auth } from "~/lib/auth";
import { db } from "~/server/db/db";
import { member } from "~/server/db/schema";
import { eq, and } from "drizzle-orm";
import { withContext } from "~/server/core/request-context";
declare module "hono" {
@ -34,7 +32,9 @@ export const requireAuth = createMiddleware(async (c, next) => {
}
const membership = await db.query.member.findFirst({
where: and(eq(member.userId, user.id), eq(member.organizationId, activeOrganizationId)),
where: {
AND: [{ userId: user.id }, { organizationId: activeOrganizationId }],
},
});
if (!membership) {
@ -58,7 +58,9 @@ export const requireOrgAdmin = createMiddleware(async (c, next) => {
const organizationId = c.get("organizationId");
const membership = await db.query.member.findFirst({
where: and(eq(member.userId, user.id), eq(member.organizationId, organizationId)),
where: {
AND: [{ userId: user.id }, { organizationId: organizationId }],
},
});
if (!membership || (membership.role !== "owner" && membership.role !== "admin")) {

View file

@ -24,7 +24,9 @@ export class AuthService {
*/
async getUserDeletionImpact(userId: string): Promise<UserDeletionImpactDto> {
const userMemberships = await db.query.member.findMany({
where: and(eq(member.userId, userId), eq(member.role, "owner")),
where: {
AND: [{ userId: userId }, { role: "owner" }],
},
});
const impacts: UserDeletionImpactDto["organizations"] = [];
@ -43,7 +45,7 @@ export class AuthService {
if (otherOwners[0].count === 0) {
const org = await db.query.organization.findFirst({
where: eq(organization.id, membership.organizationId),
where: { id: membership.organizationId },
});
if (org) {

View file

@ -1,7 +1,5 @@
import { verifyPassword } from "better-auth/crypto";
import { eq } from "drizzle-orm";
import { db } from "~/server/db/db";
import { account } from "~/server/db/schema";
type PasswordVerificationBody = {
userId: string;
@ -10,7 +8,7 @@ type PasswordVerificationBody = {
export const verifyUserPassword = async ({ password, userId }: PasswordVerificationBody) => {
const userAccount = await db.query.account.findFirst({
where: eq(account.userId, userId),
where: { userId },
});
if (!userAccount || !userAccount.password) {

View file

@ -1,9 +1,9 @@
import { and, asc, eq, isNull, ne, or } from "drizzle-orm";
import { and, eq } from "drizzle-orm";
import cron from "node-cron";
import { CronExpressionParser } from "cron-parser";
import { NotFoundError, BadRequestError, ConflictError } from "http-errors-enhanced";
import { db } from "../../db/db";
import { backupSchedulesTable, backupScheduleMirrorsTable, repositoriesTable, volumesTable } from "../../db/schema";
import { backupSchedulesTable, backupScheduleMirrorsTable, repositoriesTable } from "../../db/schema";
import { restic } from "../../utils/restic";
import { logger } from "../../utils/logger";
import { cache } from "../../utils/cache";
@ -60,12 +60,9 @@ const processPattern = (pattern: string, volumePath: string) => {
const listSchedules = async () => {
const organizationId = getOrganizationId();
const schedules = await db.query.backupSchedulesTable.findMany({
where: eq(backupSchedulesTable.organizationId, organizationId),
with: {
volume: true,
repository: true,
},
orderBy: [asc(backupSchedulesTable.sortOrder), asc(backupSchedulesTable.id)],
where: { organizationId },
with: { volume: true, repository: true },
orderBy: { sortOrder: "asc", id: "asc" },
});
return schedules;
};
@ -73,11 +70,8 @@ const listSchedules = async () => {
const getSchedule = async (scheduleId: number) => {
const organizationId = getOrganizationId();
const schedule = await db.query.backupSchedulesTable.findFirst({
where: and(eq(backupSchedulesTable.id, scheduleId), eq(backupSchedulesTable.organizationId, organizationId)),
with: {
volume: true,
repository: true,
},
where: { AND: [{ id: scheduleId }, { organizationId }] },
with: { volume: true, repository: true },
});
if (!schedule) {
@ -94,7 +88,9 @@ const createSchedule = async (data: CreateBackupScheduleBody) => {
}
const existingName = await db.query.backupSchedulesTable.findFirst({
where: and(eq(backupSchedulesTable.name, data.name), eq(backupSchedulesTable.organizationId, organizationId)),
where: {
AND: [{ name: data.name }, { organizationId }],
},
});
if (existingName) {
@ -102,7 +98,9 @@ const createSchedule = async (data: CreateBackupScheduleBody) => {
}
const volume = await db.query.volumesTable.findFirst({
where: and(eq(volumesTable.id, data.volumeId), eq(volumesTable.organizationId, organizationId)),
where: {
AND: [{ id: data.volumeId }, { organizationId }],
},
});
if (!volume) {
@ -110,7 +108,9 @@ const createSchedule = async (data: CreateBackupScheduleBody) => {
}
const repository = await db.query.repositoriesTable.findFirst({
where: and(eq(repositoriesTable.id, data.repositoryId), eq(repositoriesTable.organizationId, organizationId)),
where: {
AND: [{ id: data.repositoryId }, { organizationId }],
},
});
if (!repository) {
@ -148,7 +148,9 @@ const createSchedule = async (data: CreateBackupScheduleBody) => {
const updateSchedule = async (scheduleId: number, data: UpdateBackupScheduleBody) => {
const organizationId = getOrganizationId();
const schedule = await db.query.backupSchedulesTable.findFirst({
where: and(eq(backupSchedulesTable.id, scheduleId), eq(backupSchedulesTable.organizationId, organizationId)),
where: {
AND: [{ id: scheduleId }, { organizationId }],
},
});
if (!schedule) {
@ -161,11 +163,9 @@ const updateSchedule = async (scheduleId: number, data: UpdateBackupScheduleBody
if (data.name) {
const existingName = await db.query.backupSchedulesTable.findFirst({
where: and(
eq(backupSchedulesTable.name, data.name),
ne(backupSchedulesTable.id, scheduleId),
eq(backupSchedulesTable.organizationId, organizationId),
),
where: {
AND: [{ name: data.name }, { NOT: { id: scheduleId } }, { organizationId }],
},
});
if (existingName) {
@ -174,7 +174,9 @@ const updateSchedule = async (scheduleId: number, data: UpdateBackupScheduleBody
}
const repository = await db.query.repositoriesTable.findFirst({
where: and(eq(repositoriesTable.id, data.repositoryId), eq(repositoriesTable.organizationId, organizationId)),
where: {
AND: [{ id: data.repositoryId }, { organizationId }],
},
});
if (!repository) {
@ -200,7 +202,9 @@ const updateSchedule = async (scheduleId: number, data: UpdateBackupScheduleBody
const deleteSchedule = async (scheduleId: number) => {
const organizationId = getOrganizationId();
const schedule = await db.query.backupSchedulesTable.findFirst({
where: and(eq(backupSchedulesTable.id, scheduleId), eq(backupSchedulesTable.organizationId, organizationId)),
where: {
AND: [{ id: scheduleId }, { organizationId }],
},
});
if (!schedule) {
@ -215,7 +219,7 @@ const deleteSchedule = async (scheduleId: number) => {
const executeBackup = async (scheduleId: number, manual = false) => {
const organizationId = getOrganizationId();
const schedule = await db.query.backupSchedulesTable.findFirst({
where: and(eq(backupSchedulesTable.id, scheduleId), eq(backupSchedulesTable.organizationId, organizationId)),
where: { AND: [{ id: scheduleId }, { organizationId }] },
});
if (!schedule) {
@ -233,7 +237,7 @@ const executeBackup = async (scheduleId: number, manual = false) => {
}
const volume = await db.query.volumesTable.findFirst({
where: and(eq(volumesTable.id, schedule.volumeId), eq(volumesTable.organizationId, organizationId)),
where: { AND: [{ id: schedule.volumeId }, { organizationId }] },
});
if (!volume) {
@ -241,7 +245,7 @@ const executeBackup = async (scheduleId: number, manual = false) => {
}
const repository = await db.query.repositoriesTable.findFirst({
where: and(eq(repositoriesTable.id, schedule.repositoryId), eq(repositoriesTable.organizationId, organizationId)),
where: { AND: [{ id: schedule.repositoryId }, { organizationId }] },
});
if (!repository) {
@ -436,11 +440,9 @@ const getSchedulesToExecute = async () => {
const organizationId = getOrganizationId();
const now = Date.now();
const schedules = await db.query.backupSchedulesTable.findMany({
where: and(
eq(backupSchedulesTable.enabled, true),
or(ne(backupSchedulesTable.lastBackupStatus, "in_progress"), isNull(backupSchedulesTable.lastBackupStatus)),
eq(backupSchedulesTable.organizationId, organizationId),
),
where: {
AND: [{ enabled: true }, { lastBackupStatus: { NOT: "in_progress" } }, { organizationId }],
},
});
const schedulesToRun: number[] = [];
@ -457,7 +459,7 @@ const getSchedulesToExecute = async () => {
const getScheduleForVolume = async (volumeId: number) => {
const organizationId = getOrganizationId();
const schedule = await db.query.backupSchedulesTable.findFirst({
where: and(eq(backupSchedulesTable.volumeId, volumeId), eq(backupSchedulesTable.organizationId, organizationId)),
where: { AND: [{ volumeId }, { organizationId }] },
with: { volume: true, repository: true },
});
@ -467,7 +469,7 @@ const getScheduleForVolume = async (volumeId: number) => {
const stopBackup = async (scheduleId: number) => {
const organizationId = getOrganizationId();
const schedule = await db.query.backupSchedulesTable.findFirst({
where: and(eq(backupSchedulesTable.id, scheduleId), eq(backupSchedulesTable.organizationId, organizationId)),
where: { AND: [{ id: scheduleId }, { organizationId }] },
});
if (!schedule) {
@ -498,7 +500,9 @@ const stopBackup = async (scheduleId: number) => {
const runForget = async (scheduleId: number, repositoryId?: string) => {
const organizationId = getOrganizationId();
const schedule = await db.query.backupSchedulesTable.findFirst({
where: and(eq(backupSchedulesTable.id, scheduleId), eq(backupSchedulesTable.organizationId, organizationId)),
where: {
AND: [{ id: scheduleId }, { organizationId }],
},
});
if (!schedule) {
@ -510,10 +514,9 @@ const runForget = async (scheduleId: number, repositoryId?: string) => {
}
const repository = await db.query.repositoriesTable.findFirst({
where: and(
eq(repositoriesTable.id, repositoryId ?? schedule.repositoryId),
eq(repositoriesTable.organizationId, organizationId),
),
where: {
AND: [{ id: repositoryId ?? schedule.repositoryId }, { organizationId }],
},
});
if (!repository) {
@ -535,7 +538,9 @@ const runForget = async (scheduleId: number, repositoryId?: string) => {
const getMirrors = async (scheduleId: number) => {
const organizationId = getOrganizationId();
const schedule = await db.query.backupSchedulesTable.findFirst({
where: and(eq(backupSchedulesTable.id, scheduleId), eq(backupSchedulesTable.organizationId, organizationId)),
where: {
AND: [{ id: scheduleId }, { organizationId }],
},
});
if (!schedule) {
@ -543,7 +548,9 @@ const getMirrors = async (scheduleId: number) => {
}
const mirrors = await db.query.backupScheduleMirrorsTable.findMany({
where: eq(backupScheduleMirrorsTable.scheduleId, scheduleId),
where: {
scheduleId,
},
with: { repository: true },
});
@ -553,7 +560,7 @@ const getMirrors = async (scheduleId: number) => {
const updateMirrors = async (scheduleId: number, data: UpdateScheduleMirrorsBody) => {
const organizationId = getOrganizationId();
const schedule = await db.query.backupSchedulesTable.findFirst({
where: and(eq(backupSchedulesTable.id, scheduleId), eq(backupSchedulesTable.organizationId, organizationId)),
where: { AND: [{ id: scheduleId }, { organizationId }] },
with: { repository: true },
});
@ -567,7 +574,7 @@ const updateMirrors = async (scheduleId: number, data: UpdateScheduleMirrorsBody
}
const repo = await db.query.repositoriesTable.findFirst({
where: and(eq(repositoriesTable.id, mirror.repositoryId), eq(repositoriesTable.organizationId, organizationId)),
where: { AND: [{ id: mirror.repositoryId }, { organizationId }] },
});
if (!repo) {
@ -584,7 +591,7 @@ const updateMirrors = async (scheduleId: number, data: UpdateScheduleMirrorsBody
}
const existingMirrors = await db.query.backupScheduleMirrorsTable.findMany({
where: eq(backupScheduleMirrorsTable.scheduleId, scheduleId),
where: { scheduleId },
});
const existingMirrorsMap = new Map(
@ -622,7 +629,7 @@ const copyToMirrors = async (
) => {
const organizationId = getOrganizationId();
const schedule = await db.query.backupSchedulesTable.findFirst({
where: and(eq(backupSchedulesTable.id, scheduleId), eq(backupSchedulesTable.organizationId, organizationId)),
where: { AND: [{ id: scheduleId }, { organizationId }] },
});
if (!schedule) {
@ -630,7 +637,7 @@ const copyToMirrors = async (
}
const mirrors = await db.query.backupScheduleMirrorsTable.findMany({
where: eq(backupScheduleMirrorsTable.scheduleId, scheduleId),
where: { scheduleId },
with: { repository: true },
});
@ -712,7 +719,7 @@ const copyToMirrors = async (
const getMirrorCompatibility = async (scheduleId: number) => {
const organizationId = getOrganizationId();
const schedule = await db.query.backupSchedulesTable.findFirst({
where: and(eq(backupSchedulesTable.id, scheduleId), eq(backupSchedulesTable.organizationId, organizationId)),
where: { AND: [{ id: scheduleId }, { organizationId }] },
with: { repository: true },
});
@ -720,9 +727,7 @@ const getMirrorCompatibility = async (scheduleId: number) => {
throw new NotFoundError("Backup schedule not found");
}
const allRepositories = await db.query.repositoriesTable.findMany({
where: eq(repositoriesTable.organizationId, organizationId),
});
const allRepositories = await db.query.repositoriesTable.findMany({ where: { organizationId } });
const repos = allRepositories.filter((repo) => repo.id !== schedule.repositoryId);
const compatibility = await Promise.all(
@ -740,7 +745,7 @@ const reorderSchedules = async (scheduleIds: number[]) => {
}
const existingSchedules = await db.query.backupSchedulesTable.findMany({
where: eq(backupSchedulesTable.organizationId, organizationId),
where: { organizationId },
columns: { id: true },
});
const existingIds = new Set(existingSchedules.map((s) => s.id));

View file

@ -3,7 +3,6 @@ import { v00001 } from "./migrations/00001-retag-snapshots";
import { v00002 } from "./migrations/00002-isolate-restic-passwords";
import { v00003 } from "./migrations/00003-assign-organization";
import { sql } from "drizzle-orm";
import { eq } from "drizzle-orm";
import { appMetadataTable, usersTable } from "../../db/schema";
import { db } from "../../db/db";
@ -26,9 +25,7 @@ const recordMigrationCheckpoint = async (version: string): Promise<void> => {
const hasMigrationCheckpoint = async (id: string): Promise<boolean> => {
const key = `${MIGRATION_KEY_PREFIX}${id}`;
const result = await db.query.appMetadataTable.findFirst({
where: eq(appMetadataTable.key, key),
});
const result = await db.query.appMetadataTable.findFirst({ where: { key } });
return result !== undefined;
};

View file

@ -1,6 +1,6 @@
import { eq } from "drizzle-orm";
import { db } from "../../../db/db";
import { backupScheduleMirrorsTable, repositoriesTable, type Repository } from "../../../db/schema";
import { backupScheduleMirrorsTable, type Repository } from "../../../db/schema";
import { logger } from "../../../utils/logger";
import { toMessage } from "~/server/utils/errors";
import { exec } from "~/server/utils/spawn";
@ -42,7 +42,7 @@ const execute = async () => {
const newTag = schedule.shortId;
const repository = await db.query.repositoriesTable.findFirst({
where: eq(repositoriesTable.id, schedule.repositoryId),
where: { id: schedule.repositoryId },
});
if (!repository) {
@ -63,7 +63,7 @@ const execute = async () => {
for (const mirror of mirrors) {
const mirrorRepo = await db.query.repositoriesTable.findFirst({
where: eq(repositoriesTable.id, mirror.repositoryId),
where: { id: mirror.repositoryId },
});
if (!mirrorRepo) {

View file

@ -1,6 +1,5 @@
import { eq } from "drizzle-orm";
import { db } from "../../../db/db";
import { member, usersTable } from "../../../db/schema";
import { member } from "../../../db/schema";
import { logger } from "../../../utils/logger";
import { toMessage } from "~/server/utils/errors";
@ -8,7 +7,9 @@ const execute = async () => {
const errors: Array<{ name: string; error: string }> = [];
try {
const allUsers = await db.query.usersTable.findMany({ where: eq(usersTable.role, "admin") });
const allUsers = await db.query.usersTable.findMany({
where: { role: "admin" },
});
const allOrganizations = await db.query.organization.findMany({});
if (allUsers.length === 0) {
@ -35,7 +36,7 @@ const execute = async () => {
const org = allOrganizations[0];
const existingMembers = await db.query.member.findMany({
where: (memberTable, { eq }) => eq(memberTable.userId, user.id),
where: { userId: user.id },
});
if (existingMembers.length > 0) {

View file

@ -1,7 +1,5 @@
import { Scheduler } from "../../core/scheduler";
import { eq, or } from "drizzle-orm";
import { db } from "../../db/db";
import { volumesTable } from "../../db/schema";
import { logger } from "../../utils/logger";
import { createVolumeBackend } from "../backends/backend";
@ -9,7 +7,7 @@ export const shutdown = async () => {
await Scheduler.stop();
const volumes = await db.query.volumesTable.findMany({
where: or(eq(volumesTable.status, "mounted")),
where: { status: "mounted" },
});
for (const volume of volumes) {

View file

@ -1,7 +1,7 @@
import { Scheduler } from "../../core/scheduler";
import { and, eq, or } from "drizzle-orm";
import { eq } from "drizzle-orm";
import { db } from "../../db/db";
import { backupSchedulesTable, volumesTable } from "../../db/schema";
import { backupSchedulesTable } from "../../db/schema";
import { logger } from "../../utils/logger";
import { volumeService } from "../volumes/volume.service";
import { CleanupDanglingMountsJob } from "../../jobs/cleanup-dangling";
@ -62,10 +62,14 @@ export const startup = async () => {
await ensureLatestConfigurationSchema();
const volumes = await db.query.volumesTable.findMany({
where: or(
eq(volumesTable.status, "mounted"),
and(eq(volumesTable.autoRemount, true), eq(volumesTable.status, "error")),
),
where: {
OR: [
{ status: "mounted" },
{
AND: [{ autoRemount: true }, { status: "error" }],
},
],
},
});
for (const volume of volumes) {

View file

@ -1,10 +1,9 @@
import { eq, and, inArray } from "drizzle-orm";
import { eq, and } from "drizzle-orm";
import { BadRequestError, ConflictError, InternalServerError, NotFoundError } from "http-errors-enhanced";
import { db } from "../../db/db";
import {
notificationDestinationsTable,
backupScheduleNotificationsTable,
backupSchedulesTable,
type NotificationDestination,
} from "../../db/schema";
import { cryptoUtils } from "../../utils/crypto";
@ -19,8 +18,8 @@ import { getOrganizationId } from "~/server/core/request-context";
const listDestinations = async () => {
const organizationId = getOrganizationId();
const destinations = await db.query.notificationDestinationsTable.findMany({
where: eq(notificationDestinationsTable.organizationId, organizationId),
orderBy: (destinations, { asc }) => [asc(destinations.name)],
where: { organizationId },
orderBy: { name: "asc" },
});
return destinations;
};
@ -28,10 +27,7 @@ const listDestinations = async () => {
const getDestination = async (id: number) => {
const organizationId = getOrganizationId();
const destination = await db.query.notificationDestinationsTable.findFirst({
where: and(
eq(notificationDestinationsTable.id, id),
eq(notificationDestinationsTable.organizationId, organizationId),
),
where: { AND: [{ id }, { organizationId }] },
});
if (!destination) {
@ -256,7 +252,7 @@ const testDestination = async (id: number) => {
const getScheduleNotifications = async (scheduleId: number) => {
const organizationId = getOrganizationId();
const schedule = await db.query.backupSchedulesTable.findFirst({
where: and(eq(backupSchedulesTable.id, scheduleId), eq(backupSchedulesTable.organizationId, organizationId)),
where: { AND: [{ id: scheduleId }, { organizationId }] },
});
if (!schedule) {
@ -264,7 +260,7 @@ const getScheduleNotifications = async (scheduleId: number) => {
}
const assignments = await db.query.backupScheduleNotificationsTable.findMany({
where: eq(backupScheduleNotificationsTable.scheduleId, scheduleId),
where: { scheduleId },
with: {
destination: true,
},
@ -285,7 +281,7 @@ const updateScheduleNotifications = async (
) => {
const organizationId = getOrganizationId();
const schedule = await db.query.backupSchedulesTable.findFirst({
where: and(eq(backupSchedulesTable.id, scheduleId), eq(backupSchedulesTable.organizationId, organizationId)),
where: { AND: [{ id: scheduleId }, { organizationId }] },
});
if (!schedule) {
@ -295,10 +291,9 @@ const updateScheduleNotifications = async (
const destinationIds = [...new Set(assignments.map((a) => a.destinationId))];
if (destinationIds.length > 0) {
const destinations = await db.query.notificationDestinationsTable.findMany({
where: and(
inArray(notificationDestinationsTable.id, destinationIds),
eq(notificationDestinationsTable.organizationId, organizationId),
),
where: {
AND: [{ id: { in: destinationIds } }, { organizationId }],
},
});
if (destinations.length !== destinationIds.length) {
@ -338,7 +333,7 @@ const sendBackupNotification = async (
const organizationId = getOrganizationId();
const assignments = await db.query.backupScheduleNotificationsTable.findMany({
where: eq(backupScheduleNotificationsTable.scheduleId, scheduleId),
where: { scheduleId },
with: {
destination: true,
},

View file

@ -1,5 +1,5 @@
import crypto from "node:crypto";
import { and, eq, or } from "drizzle-orm";
import { and, eq } from "drizzle-orm";
import { ConflictError, InternalServerError, NotFoundError } from "http-errors-enhanced";
import { db } from "../../db/db";
import { repositoriesTable } from "../../db/schema";
@ -26,18 +26,15 @@ const runningDoctors = new Map<string, AbortController>();
const findRepository = async (idOrShortId: string) => {
const organizationId = getOrganizationId();
return await db.query.repositoriesTable.findFirst({
where: and(
or(eq(repositoriesTable.id, idOrShortId), eq(repositoriesTable.shortId, idOrShortId)),
eq(repositoriesTable.organizationId, organizationId),
),
where: {
AND: [{ OR: [{ id: idOrShortId }, { shortId: idOrShortId }] }, { organizationId }],
},
});
};
const listRepositories = async () => {
const organizationId = getOrganizationId();
const repositories = await db.query.repositoriesTable.findMany({
where: eq(repositoriesTable.organizationId, organizationId),
});
const repositories = await db.query.repositoriesTable.findMany({ where: { organizationId } });
return repositories;
};

View file

@ -15,7 +15,7 @@ import {
import { systemService } from "./system.service";
import { requireAuth, requireOrgAdmin } from "../auth/auth.middleware";
import { db } from "../../db/db";
import { organization, usersTable } from "../../db/schema";
import { usersTable } from "../../db/schema";
import { eq } from "drizzle-orm";
import { verifyUserPassword } from "../auth/helpers";
import { cryptoUtils } from "../../utils/crypto";
@ -78,9 +78,7 @@ export const systemController = new Hono()
}
try {
const org = await db.query.organization.findFirst({
where: eq(organization.id, organizationId),
});
const org = await db.query.organization.findFirst({ where: { id: organizationId } });
if (!org?.metadata?.resticPassword) {
return c.json({ message: "Organization Restic password not found" }, 404);

View file

@ -6,7 +6,6 @@ import { cache } from "../../utils/cache";
import { logger } from "~/server/utils/logger";
import { db } from "../../db/db";
import { appMetadataTable } from "../../db/schema";
import { eq } from "drizzle-orm";
import { REGISTRATION_ENABLED_KEY } from "~/client/lib/constants";
const CACHE_TTL = 60 * 60;
@ -91,7 +90,7 @@ const getUpdates = async (): Promise<UpdateInfoDto> => {
const isRegistrationEnabled = async () => {
const result = await db.query.appMetadataTable.findFirst({
where: eq(appMetadataTable.key, REGISTRATION_ENABLED_KEY),
where: { key: REGISTRATION_ENABLED_KEY },
});
return result?.value === "true";

View file

@ -46,7 +46,7 @@ async function encryptSensitiveFields(config: BackendConfig): Promise<BackendCon
const listVolumes = async () => {
const organizationId = getOrganizationId();
const volumes = await db.query.volumesTable.findMany({
where: eq(volumesTable.organizationId, organizationId),
where: { organizationId: organizationId },
});
return volumes;
@ -56,10 +56,12 @@ const findVolume = async (idOrShortId: string | number) => {
const organizationId = getOrganizationId();
const isNumeric = typeof idOrShortId === "number" || /^\d+$/.test(String(idOrShortId));
return await db.query.volumesTable.findFirst({
where: and(
isNumeric ? eq(volumesTable.id, Number(idOrShortId)) : eq(volumesTable.shortId, idOrShortId),
eq(volumesTable.organizationId, organizationId),
),
where: {
AND: [
isNumeric ? { id: Number(idOrShortId) } : { shortId: String(idOrShortId) },
{ organizationId: organizationId },
],
},
});
};

View file

@ -4,7 +4,6 @@ import path from "node:path";
import os from "node:os";
import { throttle } from "es-toolkit";
import { type } from "arktype";
import { eq } from "drizzle-orm";
import { REPOSITORY_BASE, RESTIC_PASS_FILE, DEFAULT_EXCLUDES, RESTIC_CACHE_DIR } from "../core/constants";
import { config as appConfig } from "../core/config";
import { logger } from "./logger";
@ -14,7 +13,6 @@ import { safeSpawn, exec } from "./spawn";
import type { CompressionMode, RepositoryConfig, OverwriteMode, BandwidthLimit } from "~/schemas/restic";
import { ResticError } from "./errors";
import { db } from "../db/db";
import { organization } from "../db/schema";
const backupOutputSchema = type({
message_type: "'summary'",
@ -110,9 +108,7 @@ export const buildEnv = async (config: RepositoryConfig, organizationId: string)
await fs.writeFile(passwordFilePath, decryptedPassword, { mode: 0o600 });
env.RESTIC_PASSWORD_FILE = passwordFilePath;
} else {
const org = await db.query.organization.findFirst({
where: eq(organization.id, organizationId),
});
const org = await db.query.organization.findFirst({ where: { id: organizationId } });
if (!org) {
throw new Error(`Organization ${organizationId} not found`);

View file

@ -19,7 +19,7 @@ export const createTestOrganization = async (overrides: Partial<typeof organizat
};
const existing = await db.query.organization.findFirst({
where: (o, { eq }) => eq(o.id, org.id ?? TEST_ORG_ID),
where: { id: org.id },
});
if (existing) {

View file

@ -2,11 +2,11 @@ import { createClient } from "@libsql/client";
import { drizzle } from "drizzle-orm/libsql";
import path from "node:path";
import { DATABASE_URL } from "~/server/core/constants";
import * as schema from "~/server/db/schema";
import { relations } from "~/server/db/relations";
const sqlite = createClient({ url: `file:${path.join(process.cwd(), "playwright", DATABASE_URL)}` });
export const db = drizzle({ client: sqlite, schema: schema });
export const db = drizzle({ client: sqlite, relations: relations });
export const resetDatabase = async () => {
const cursor = await sqlite.execute("SELECT name FROM sqlite_master WHERE type='table'");