feat: backfill entities with new organization id

This commit is contained in:
Nicolas Meienberger 2026-01-17 15:02:35 +01:00
parent 4425f22f3a
commit 0fafe4114b
9 changed files with 3317 additions and 9 deletions

View file

@ -0,0 +1,5 @@
DROP INDEX `volumes_table_name_unique`;--> statement-breakpoint
ALTER TABLE `volumes_table` ADD `organization_id` text REFERENCES organization(id);--> statement-breakpoint
ALTER TABLE `backup_schedules_table` ADD `organization_id` text REFERENCES organization(id);--> statement-breakpoint
ALTER TABLE `notification_destinations_table` ADD `organization_id` text REFERENCES organization(id);--> statement-breakpoint
ALTER TABLE `repositories_table` ADD `organization_id` text REFERENCES organization(id);

View file

@ -0,0 +1,22 @@
-- Backfill organization_id for volumes, backups, notifications and repositories
-- Uses the first organization found in the database
-- Update volumes_table
UPDATE volumes_table
SET organization_id = (SELECT id FROM organization ORDER BY created_at ASC LIMIT 1)
WHERE organization_id IS NULL;
-- Update backup_schedules_table
UPDATE backup_schedules_table
SET organization_id = (SELECT id FROM organization ORDER BY created_at ASC LIMIT 1)
WHERE organization_id IS NULL;
-- Update notification_destinations_table
UPDATE notification_destinations_table
SET organization_id = (SELECT id FROM organization ORDER BY created_at ASC LIMIT 1)
WHERE organization_id IS NULL;
-- Update repositories_table
UPDATE repositories_table
SET organization_id = (SELECT id FROM organization ORDER BY created_at ASC LIMIT 1)
WHERE organization_id IS NULL;

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -267,6 +267,20 @@
"when": 1768654184173,
"tag": "0037_create-default-member",
"breakpoints": true
},
{
"idx": 38,
"version": "6",
"when": 1768658258128,
"tag": "0038_shallow_pride",
"breakpoints": true
},
{
"idx": 39,
"version": "6",
"when": 1768658359845,
"tag": "0039_backfill-entities-org-id",
"breakpoints": true
}
]
}

View file

@ -169,7 +169,7 @@ export const invitation = sqliteTable(
export const volumesTable = sqliteTable("volumes_table", {
id: int().primaryKey({ autoIncrement: true }),
shortId: text("short_id").notNull().unique(),
name: text().notNull().unique(),
name: text().notNull(),
type: text().$type<BackendType>().notNull(),
status: text().$type<BackendStatus>().notNull().default("unmounted"),
lastError: text("last_error"),
@ -185,6 +185,7 @@ export const volumesTable = sqliteTable("volumes_table", {
.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").references(() => organization.id, { onDelete: "cascade" }),
});
export type Volume = typeof volumesTable.$inferSelect;
export type VolumeInsert = typeof volumesTable.$inferInsert;
@ -214,6 +215,7 @@ export const repositoriesTable = sqliteTable("repositories_table", {
updatedAt: int("updated_at", { mode: "number" })
.notNull()
.default(sql`(unixepoch() * 1000)`),
organizationId: text("organization_id").references(() => organization.id, { onDelete: "cascade" }),
});
export type Repository = typeof repositoriesTable.$inferSelect;
export type RepositoryInsert = typeof repositoriesTable.$inferInsert;
@ -257,6 +259,7 @@ export const backupSchedulesTable = sqliteTable("backup_schedules_table", {
updatedAt: int("updated_at", { mode: "number" })
.notNull()
.default(sql`(unixepoch() * 1000)`),
organizationId: text("organization_id").references(() => organization.id, { onDelete: "cascade" }),
});
export type BackupScheduleInsert = typeof backupSchedulesTable.$inferInsert;
@ -277,6 +280,7 @@ export const notificationDestinationsTable = sqliteTable("notification_destinati
updatedAt: int("updated_at", { mode: "number" })
.notNull()
.default(sql`(unixepoch() * 1000)`),
organizationId: text("organization_id").references(() => organization.id, { onDelete: "cascade" }),
});
export type NotificationDestination = typeof notificationDestinationsTable.$inferSelect;
@ -373,6 +377,10 @@ export const sessionRelations = relations(sessionsTable, ({ one }) => ({
fields: [sessionsTable.userId],
references: [usersTable.id],
}),
activeOrganization: one(organization, {
fields: [sessionsTable.activeOrganizationId],
references: [organization.id],
}),
}));
export const accountRelations = relations(account, ({ one }) => ({

View file

@ -8,6 +8,7 @@ declare module "hono" {
username: string;
hasDownloadedResticPassword: boolean;
};
organizationId: string;
}
}
@ -16,17 +17,19 @@ declare module "hono" {
* Verifies the session cookie and attaches user to context
*/
export const requireAuth = createMiddleware(async (c, next) => {
const session = await auth.api.getSession({
const sess = await auth.api.getSession({
headers: c.req.raw.headers,
});
const { user } = session ?? {};
const { user, session } = sess ?? {};
const { activeOrganizationId } = session ?? {};
if (!user) {
if (!user || !session || !activeOrganizationId) {
return c.json<unknown>({ message: "Invalid or expired session" }, 401);
}
c.set("user", user);
c.set("organizationId", activeOrganizationId);
await next();
});

View file

@ -29,13 +29,13 @@ import { requireAuth } from "../auth/auth.middleware";
export const volumeController = new Hono()
.use(requireAuth)
.get("/", listVolumesDto, async (c) => {
const volumes = await volumeService.listVolumes();
const volumes = await volumeService.listVolumes(c.get("organizationId"));
return c.json<ListVolumesDto>(volumes, 200);
})
.post("/", createVolumeDto, validator("json", createVolumeBody), async (c) => {
const body = c.req.valid("json");
const res = await volumeService.createVolume(body.name, body.config);
const res = await volumeService.createVolume(body.name, body.config, c.get("organizationId"));
const response = {
...res.volume,

View file

@ -42,13 +42,15 @@ async function encryptSensitiveFields(config: BackendConfig): Promise<BackendCon
}
}
const listVolumes = async () => {
const volumes = await db.query.volumesTable.findMany({});
const listVolumes = async (organizationId: string) => {
const volumes = await db.query.volumesTable.findMany({
where: eq(volumesTable.organizationId, organizationId),
});
return volumes;
};
const createVolume = async (name: string, backendConfig: BackendConfig) => {
const createVolume = async (name: string, backendConfig: BackendConfig, organizationId: string) => {
const slug = slugify(name, { lower: true, strict: true });
const existing = await db.query.volumesTable.findFirst({
@ -69,6 +71,7 @@ const createVolume = async (name: string, backendConfig: BackendConfig) => {
name: slug,
config: encryptedConfig,
type: backendConfig.backend,
organizationId,
})
.returning();
@ -252,6 +255,7 @@ const testConnection = async (backendConfig: BackendConfig) => {
status: "unmounted" as const,
lastError: null,
autoRemount: true,
organizationId: "test-org",
};
const backend = createVolumeBackend(mockVolume);