feat(db): add support for multiple users and organizations
This commit is contained in:
parent
36b17d73eb
commit
4425f22f3a
13 changed files with 6547 additions and 84 deletions
|
|
@ -1,8 +1,14 @@
|
|||
import { createAuthClient } from "better-auth/react";
|
||||
import { twoFactorClient, usernameClient } from "better-auth/client/plugins";
|
||||
import { twoFactorClient, usernameClient, adminClient, organizationClient } from "better-auth/client/plugins";
|
||||
import { inferAdditionalFields } from "better-auth/client/plugins";
|
||||
import type { auth } from "~/lib/auth";
|
||||
|
||||
export const authClient = createAuthClient({
|
||||
plugins: [inferAdditionalFields<typeof auth>(), usernameClient(), twoFactorClient()],
|
||||
plugins: [
|
||||
inferAdditionalFields<typeof auth>(),
|
||||
usernameClient(),
|
||||
adminClient(),
|
||||
organizationClient(),
|
||||
twoFactorClient(),
|
||||
],
|
||||
});
|
||||
|
|
|
|||
44
app/drizzle/0034_slippery_mongu.sql
Normal file
44
app/drizzle/0034_slippery_mongu.sql
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
CREATE TABLE `invitation` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`organization_id` text NOT NULL,
|
||||
`email` text NOT NULL,
|
||||
`role` text,
|
||||
`status` text DEFAULT 'pending' NOT NULL,
|
||||
`expires_at` integer NOT NULL,
|
||||
`created_at` integer NOT NULL,
|
||||
`inviter_id` text NOT NULL,
|
||||
FOREIGN KEY (`organization_id`) REFERENCES `organization`(`id`) ON UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY (`inviter_id`) REFERENCES `users_table`(`id`) ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX `invitation_organizationId_idx` ON `invitation` (`organization_id`);--> statement-breakpoint
|
||||
CREATE INDEX `invitation_email_idx` ON `invitation` (`email`);--> statement-breakpoint
|
||||
CREATE TABLE `member` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`organization_id` text NOT NULL,
|
||||
`user_id` text NOT NULL,
|
||||
`role` text DEFAULT 'member' NOT NULL,
|
||||
`created_at` integer NOT NULL,
|
||||
FOREIGN KEY (`organization_id`) REFERENCES `organization`(`id`) ON UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY (`user_id`) REFERENCES `users_table`(`id`) ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX `member_organizationId_idx` ON `member` (`organization_id`);--> statement-breakpoint
|
||||
CREATE INDEX `member_userId_idx` ON `member` (`user_id`);--> statement-breakpoint
|
||||
CREATE TABLE `organization` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`name` text NOT NULL,
|
||||
`slug` text NOT NULL,
|
||||
`logo` text,
|
||||
`created_at` integer NOT NULL,
|
||||
`metadata` text
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `organization_slug_unique` ON `organization` (`slug`);--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `organization_slug_uidx` ON `organization` (`slug`);--> statement-breakpoint
|
||||
ALTER TABLE `sessions_table` ADD `impersonated_by` text;--> statement-breakpoint
|
||||
ALTER TABLE `sessions_table` ADD `active_organization_id` text;--> statement-breakpoint
|
||||
ALTER TABLE `users_table` ADD `role` text;--> statement-breakpoint
|
||||
ALTER TABLE `users_table` ADD `banned` integer DEFAULT false;--> statement-breakpoint
|
||||
ALTER TABLE `users_table` ADD `ban_reason` text;--> statement-breakpoint
|
||||
ALTER TABLE `users_table` ADD `ban_expires` integer;
|
||||
1
app/drizzle/0035_default-admin-role.sql
Normal file
1
app/drizzle/0035_default-admin-role.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
UPDATE users_table SET role = 'admin' WHERE role IS NULL OR role = '';
|
||||
10
app/drizzle/0036_create-default-org.sql
Normal file
10
app/drizzle/0036_create-default-org.sql
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
-- Custom SQL migration file, put your code below! --
|
||||
INSERT INTO organization (id, name, slug, created_at)
|
||||
SELECT
|
||||
'default-org-' || u.id as id,
|
||||
u.name || '''s Workspace' as name,
|
||||
lower(replace(u.name, ' ', '-')) || '-' || lower(hex(randomblob(2))) as slug,
|
||||
strftime('%s', 'now') * 1000 as created_at
|
||||
FROM users_table u
|
||||
LEFT JOIN member m ON u.id = m.user_id
|
||||
WHERE m.user_id IS NULL;
|
||||
11
app/drizzle/0037_create-default-member.sql
Normal file
11
app/drizzle/0037_create-default-member.sql
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
-- Custom SQL migration file, put your code below! --
|
||||
INSERT INTO member (id, organization_id, user_id, role, created_at)
|
||||
SELECT
|
||||
'default-mem-' || u.id as id,
|
||||
'default-org-' || u.id as organization_id,
|
||||
u.id as user_id,
|
||||
'owner' as role,
|
||||
strftime('%s', 'now') * 1000 as created_at
|
||||
FROM users_table u
|
||||
LEFT JOIN member m ON u.id = m.user_id
|
||||
WHERE m.user_id IS NULL;
|
||||
1550
app/drizzle/meta/0034_snapshot.json
Normal file
1550
app/drizzle/meta/0034_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1550
app/drizzle/meta/0035_snapshot.json
Normal file
1550
app/drizzle/meta/0035_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1550
app/drizzle/meta/0036_snapshot.json
Normal file
1550
app/drizzle/meta/0036_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1550
app/drizzle/meta/0037_snapshot.json
Normal file
1550
app/drizzle/meta/0037_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -239,6 +239,34 @@
|
|||
"when": 1768497767122,
|
||||
"tag": "0033_chunky_tyrannus",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 34,
|
||||
"version": "6",
|
||||
"when": 1768653352902,
|
||||
"tag": "0034_slippery_mongu",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 35,
|
||||
"version": "6",
|
||||
"when": 1768653442143,
|
||||
"tag": "0035_default-admin-role",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 36,
|
||||
"version": "6",
|
||||
"when": 1768653580581,
|
||||
"tag": "0036_create-default-org",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 37,
|
||||
"version": "6",
|
||||
"when": 1768654184173,
|
||||
"tag": "0037_create-default-member",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -32,6 +32,7 @@ export const convertLegacyUserOnFirstLogin = async (ctx: AuthMiddlewareContext)
|
|||
name: legacyUser.name,
|
||||
hasDownloadedResticPassword: legacyUser.hasDownloadedResticPassword,
|
||||
emailVerified: false,
|
||||
role: "admin", // In legacy system, the only user is an admin
|
||||
});
|
||||
|
||||
await tx.insert(account).values({
|
||||
|
|
|
|||
|
|
@ -6,12 +6,13 @@ import {
|
|||
type MiddlewareOptions,
|
||||
} from "better-auth";
|
||||
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
||||
import { createAuthMiddleware, twoFactor, username } from "better-auth/plugins";
|
||||
import { admin, createAuthMiddleware, twoFactor, username, organization } from "better-auth/plugins";
|
||||
import { convertLegacyUserOnFirstLogin } from "./auth-middlewares/convert-legacy-user";
|
||||
import { cryptoUtils } from "~/server/utils/crypto";
|
||||
import { db } from "~/server/db/db";
|
||||
import { ensureOnlyOneUser } from "./auth-middlewares/only-one-user";
|
||||
import { member, organization as organizationTable } from "~/server/db/schema";
|
||||
import { config } from "~/server/core/config";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
export type AuthMiddlewareContext = MiddlewareContext<MiddlewareOptions, AuthContext<BetterAuthOptions>>;
|
||||
|
||||
|
|
@ -21,13 +22,71 @@ const createBetterAuth = (secret: string) =>
|
|||
trustedOrigins: config.trustedOrigins ?? ["*"],
|
||||
hooks: {
|
||||
before: createAuthMiddleware(async (ctx) => {
|
||||
await ensureOnlyOneUser(ctx);
|
||||
// await ensureOnlyOneUser(ctx);
|
||||
await convertLegacyUserOnFirstLogin(ctx);
|
||||
}),
|
||||
},
|
||||
database: drizzleAdapter(db, {
|
||||
provider: "sqlite",
|
||||
}),
|
||||
databaseHooks: {
|
||||
user: {
|
||||
create: {
|
||||
before: async (user) => {
|
||||
const anyUser = await db.query.usersTable.findFirst();
|
||||
const isFirstUser = !anyUser;
|
||||
|
||||
if (isFirstUser) {
|
||||
user.role = "admin";
|
||||
}
|
||||
|
||||
return { data: user };
|
||||
},
|
||||
after: async (user) => {
|
||||
const slug = user.email.split("@")[0] + "-" + Math.random().toString(36).slice(-4);
|
||||
|
||||
await db.transaction(async (tx) => {
|
||||
const orgId = Bun.randomUUIDv7();
|
||||
|
||||
await tx.insert(organizationTable).values({
|
||||
name: `${user.name}'s Workspace`,
|
||||
slug: slug,
|
||||
id: orgId,
|
||||
createdAt: new Date(),
|
||||
});
|
||||
|
||||
await tx.insert(member).values({
|
||||
id: Bun.randomUUIDv7(),
|
||||
userId: user.id,
|
||||
role: "owner",
|
||||
organizationId: orgId,
|
||||
createdAt: new Date(),
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
session: {
|
||||
create: {
|
||||
before: async (session) => {
|
||||
const orgMembership = await db.query.member.findFirst({
|
||||
where: eq(member.userId, session.userId),
|
||||
});
|
||||
|
||||
if (!orgMembership) {
|
||||
throw new Error("User does not belong to any organization");
|
||||
}
|
||||
|
||||
return {
|
||||
data: {
|
||||
...session,
|
||||
activeOrganizationId: orgMembership?.organizationId,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
emailAndPassword: {
|
||||
enabled: true,
|
||||
},
|
||||
|
|
@ -50,6 +109,12 @@ const createBetterAuth = (secret: string) =>
|
|||
},
|
||||
plugins: [
|
||||
username(),
|
||||
admin({
|
||||
defaultRole: "user",
|
||||
}),
|
||||
organization({
|
||||
allowUserToCreateOrganization: false,
|
||||
}),
|
||||
twoFactor({
|
||||
backupCodeOptions: {
|
||||
storeBackupCodes: "encrypted",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { relations, sql } from "drizzle-orm";
|
||||
import { index, int, integer, sqliteTable, text, real, primaryKey, unique } from "drizzle-orm/sqlite-core";
|
||||
import { index, int, integer, sqliteTable, text, real, primaryKey, unique, uniqueIndex } from "drizzle-orm/sqlite-core";
|
||||
import type {
|
||||
CompressionMode,
|
||||
RepositoryBackend,
|
||||
|
|
@ -10,32 +10,6 @@ import type {
|
|||
import type { BackendStatus, BackendType, volumeConfigSchema } from "~/schemas/volumes";
|
||||
import type { NotificationType, notificationConfigSchema } from "~/schemas/notifications";
|
||||
|
||||
/**
|
||||
* Volumes Table
|
||||
*/
|
||||
export const volumesTable = sqliteTable("volumes_table", {
|
||||
id: int().primaryKey({ autoIncrement: true }),
|
||||
shortId: text("short_id").notNull().unique(),
|
||||
name: text().notNull().unique(),
|
||||
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),
|
||||
});
|
||||
export type Volume = typeof volumesTable.$inferSelect;
|
||||
export type VolumeInsert = typeof volumesTable.$inferInsert;
|
||||
|
||||
/**
|
||||
* Users Table
|
||||
*/
|
||||
|
|
@ -57,6 +31,10 @@ export const usersTable = sqliteTable("users_table", {
|
|||
image: text("image"),
|
||||
displayUsername: text("display_username"),
|
||||
twoFactorEnabled: integer("two_factor_enabled", { mode: "boolean" }).notNull().default(false),
|
||||
role: text("role"),
|
||||
banned: integer("banned", { mode: "boolean" }).default(false),
|
||||
banReason: text("ban_reason"),
|
||||
banExpires: integer("ban_expires", { mode: "timestamp_ms" }),
|
||||
});
|
||||
|
||||
export type User = typeof usersTable.$inferSelect;
|
||||
|
|
@ -78,6 +56,8 @@ export const sessionsTable = sqliteTable(
|
|||
.default(sql`(unixepoch() * 1000)`),
|
||||
ipAddress: text("ip_address"),
|
||||
userAgent: text("user_agent"),
|
||||
impersonatedBy: text("impersonated_by"),
|
||||
activeOrganizationId: text("active_organization_id"),
|
||||
},
|
||||
(table) => [index("sessionsTable_userId_idx").on(table.userId)],
|
||||
);
|
||||
|
|
@ -132,25 +112,82 @@ export const verification = sqliteTable(
|
|||
(table) => [index("verification_identifier_idx").on(table.identifier)],
|
||||
);
|
||||
|
||||
export const userRelations = relations(usersTable, ({ many }) => ({
|
||||
sessions: many(sessionsTable),
|
||||
accounts: many(account),
|
||||
twoFactors: many(twoFactor),
|
||||
}));
|
||||
export const organization = sqliteTable(
|
||||
"organization",
|
||||
{
|
||||
id: text("id").primaryKey(),
|
||||
name: text("name").notNull(),
|
||||
slug: text("slug").notNull().unique(),
|
||||
logo: text("logo"),
|
||||
createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull(),
|
||||
metadata: text("metadata"),
|
||||
},
|
||||
(table) => [uniqueIndex("organization_slug_uidx").on(table.slug)],
|
||||
);
|
||||
|
||||
export const sessionRelations = relations(sessionsTable, ({ one }) => ({
|
||||
user: one(usersTable, {
|
||||
fields: [sessionsTable.userId],
|
||||
references: [usersTable.id],
|
||||
}),
|
||||
}));
|
||||
export const member = sqliteTable(
|
||||
"member",
|
||||
{
|
||||
id: text("id").primaryKey(),
|
||||
organizationId: text("organization_id")
|
||||
.notNull()
|
||||
.references(() => organization.id, { onDelete: "cascade" }),
|
||||
userId: text("user_id")
|
||||
.notNull()
|
||||
.references(() => usersTable.id, { onDelete: "cascade" }),
|
||||
role: text("role").default("member").notNull(),
|
||||
createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull(),
|
||||
},
|
||||
(table) => [index("member_organizationId_idx").on(table.organizationId), index("member_userId_idx").on(table.userId)],
|
||||
);
|
||||
|
||||
export const accountRelations = relations(account, ({ one }) => ({
|
||||
user: one(usersTable, {
|
||||
fields: [account.userId],
|
||||
references: [usersTable.id],
|
||||
}),
|
||||
}));
|
||||
export const invitation = sqliteTable(
|
||||
"invitation",
|
||||
{
|
||||
id: text("id").primaryKey(),
|
||||
organizationId: text("organization_id")
|
||||
.notNull()
|
||||
.references(() => organization.id, { onDelete: "cascade" }),
|
||||
email: text("email").notNull(),
|
||||
role: text("role"),
|
||||
status: text("status").default("pending").notNull(),
|
||||
expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(),
|
||||
createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull(),
|
||||
inviterId: text("inviter_id")
|
||||
.notNull()
|
||||
.references(() => usersTable.id, { onDelete: "cascade" }),
|
||||
},
|
||||
(table) => [
|
||||
index("invitation_organizationId_idx").on(table.organizationId),
|
||||
index("invitation_email_idx").on(table.email),
|
||||
],
|
||||
);
|
||||
|
||||
/**
|
||||
* Volumes Table
|
||||
*/
|
||||
export const volumesTable = sqliteTable("volumes_table", {
|
||||
id: int().primaryKey({ autoIncrement: true }),
|
||||
shortId: text("short_id").notNull().unique(),
|
||||
name: text().notNull().unique(),
|
||||
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),
|
||||
});
|
||||
export type Volume = typeof volumesTable.$inferSelect;
|
||||
export type VolumeInsert = typeof volumesTable.$inferInsert;
|
||||
|
||||
/**
|
||||
* Repositories Table
|
||||
|
|
@ -223,18 +260,6 @@ export const backupSchedulesTable = sqliteTable("backup_schedules_table", {
|
|||
});
|
||||
export type BackupScheduleInsert = typeof backupSchedulesTable.$inferInsert;
|
||||
|
||||
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 type BackupSchedule = typeof backupSchedulesTable.$inferSelect;
|
||||
|
||||
/**
|
||||
|
|
@ -253,9 +278,6 @@ export const notificationDestinationsTable = sqliteTable("notification_destinati
|
|||
.notNull()
|
||||
.default(sql`(unixepoch() * 1000)`),
|
||||
});
|
||||
export const notificationDestinationRelations = relations(notificationDestinationsTable, ({ many }) => ({
|
||||
schedules: many(backupScheduleNotificationsTable),
|
||||
}));
|
||||
export type NotificationDestination = typeof notificationDestinationsTable.$inferSelect;
|
||||
|
||||
/**
|
||||
|
|
@ -280,16 +302,6 @@ export const backupScheduleNotificationsTable = sqliteTable(
|
|||
},
|
||||
(table) => [primaryKey({ columns: [table.scheduleId, table.destinationId] })],
|
||||
);
|
||||
export const backupScheduleNotificationRelations = relations(backupScheduleNotificationsTable, ({ one }) => ({
|
||||
schedule: one(backupSchedulesTable, {
|
||||
fields: [backupScheduleNotificationsTable.scheduleId],
|
||||
references: [backupSchedulesTable.id],
|
||||
}),
|
||||
destination: one(notificationDestinationsTable, {
|
||||
fields: [backupScheduleNotificationsTable.destinationId],
|
||||
references: [notificationDestinationsTable.id],
|
||||
}),
|
||||
}));
|
||||
export type BackupScheduleNotification = typeof backupScheduleNotificationsTable.$inferSelect;
|
||||
|
||||
/**
|
||||
|
|
@ -317,16 +329,6 @@ export const backupScheduleMirrorsTable = sqliteTable(
|
|||
(table) => [unique().on(table.scheduleId, table.repositoryId)],
|
||||
);
|
||||
|
||||
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 type BackupScheduleMirror = typeof backupScheduleMirrorsTable.$inferSelect;
|
||||
|
||||
/**
|
||||
|
|
@ -357,3 +359,98 @@ 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],
|
||||
}),
|
||||
}));
|
||||
|
||||
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],
|
||||
}),
|
||||
}));
|
||||
|
|
|
|||
Loading…
Reference in a new issue