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 { 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 { inferAdditionalFields } from "better-auth/client/plugins";
|
||||||
import type { auth } from "~/lib/auth";
|
import type { auth } from "~/lib/auth";
|
||||||
|
|
||||||
export const authClient = createAuthClient({
|
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,
|
"when": 1768497767122,
|
||||||
"tag": "0033_chunky_tyrannus",
|
"tag": "0033_chunky_tyrannus",
|
||||||
"breakpoints": true
|
"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,
|
name: legacyUser.name,
|
||||||
hasDownloadedResticPassword: legacyUser.hasDownloadedResticPassword,
|
hasDownloadedResticPassword: legacyUser.hasDownloadedResticPassword,
|
||||||
emailVerified: false,
|
emailVerified: false,
|
||||||
|
role: "admin", // In legacy system, the only user is an admin
|
||||||
});
|
});
|
||||||
|
|
||||||
await tx.insert(account).values({
|
await tx.insert(account).values({
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,13 @@ import {
|
||||||
type MiddlewareOptions,
|
type MiddlewareOptions,
|
||||||
} from "better-auth";
|
} from "better-auth";
|
||||||
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
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 { convertLegacyUserOnFirstLogin } from "./auth-middlewares/convert-legacy-user";
|
||||||
import { cryptoUtils } from "~/server/utils/crypto";
|
import { cryptoUtils } from "~/server/utils/crypto";
|
||||||
import { db } from "~/server/db/db";
|
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 { config } from "~/server/core/config";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
|
||||||
export type AuthMiddlewareContext = MiddlewareContext<MiddlewareOptions, AuthContext<BetterAuthOptions>>;
|
export type AuthMiddlewareContext = MiddlewareContext<MiddlewareOptions, AuthContext<BetterAuthOptions>>;
|
||||||
|
|
||||||
|
|
@ -21,13 +22,71 @@ const createBetterAuth = (secret: string) =>
|
||||||
trustedOrigins: config.trustedOrigins ?? ["*"],
|
trustedOrigins: config.trustedOrigins ?? ["*"],
|
||||||
hooks: {
|
hooks: {
|
||||||
before: createAuthMiddleware(async (ctx) => {
|
before: createAuthMiddleware(async (ctx) => {
|
||||||
await ensureOnlyOneUser(ctx);
|
// await ensureOnlyOneUser(ctx);
|
||||||
await convertLegacyUserOnFirstLogin(ctx);
|
await convertLegacyUserOnFirstLogin(ctx);
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
database: drizzleAdapter(db, {
|
database: drizzleAdapter(db, {
|
||||||
provider: "sqlite",
|
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: {
|
emailAndPassword: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
},
|
},
|
||||||
|
|
@ -50,6 +109,12 @@ const createBetterAuth = (secret: string) =>
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
username(),
|
username(),
|
||||||
|
admin({
|
||||||
|
defaultRole: "user",
|
||||||
|
}),
|
||||||
|
organization({
|
||||||
|
allowUserToCreateOrganization: false,
|
||||||
|
}),
|
||||||
twoFactor({
|
twoFactor({
|
||||||
backupCodeOptions: {
|
backupCodeOptions: {
|
||||||
storeBackupCodes: "encrypted",
|
storeBackupCodes: "encrypted",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { relations, sql } from "drizzle-orm";
|
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 {
|
import type {
|
||||||
CompressionMode,
|
CompressionMode,
|
||||||
RepositoryBackend,
|
RepositoryBackend,
|
||||||
|
|
@ -10,32 +10,6 @@ import type {
|
||||||
import type { BackendStatus, BackendType, volumeConfigSchema } from "~/schemas/volumes";
|
import type { BackendStatus, BackendType, volumeConfigSchema } from "~/schemas/volumes";
|
||||||
import type { NotificationType, notificationConfigSchema } from "~/schemas/notifications";
|
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
|
* Users Table
|
||||||
*/
|
*/
|
||||||
|
|
@ -57,6 +31,10 @@ export const usersTable = sqliteTable("users_table", {
|
||||||
image: text("image"),
|
image: text("image"),
|
||||||
displayUsername: text("display_username"),
|
displayUsername: text("display_username"),
|
||||||
twoFactorEnabled: integer("two_factor_enabled", { mode: "boolean" }).notNull().default(false),
|
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;
|
export type User = typeof usersTable.$inferSelect;
|
||||||
|
|
@ -78,6 +56,8 @@ export const sessionsTable = sqliteTable(
|
||||||
.default(sql`(unixepoch() * 1000)`),
|
.default(sql`(unixepoch() * 1000)`),
|
||||||
ipAddress: text("ip_address"),
|
ipAddress: text("ip_address"),
|
||||||
userAgent: text("user_agent"),
|
userAgent: text("user_agent"),
|
||||||
|
impersonatedBy: text("impersonated_by"),
|
||||||
|
activeOrganizationId: text("active_organization_id"),
|
||||||
},
|
},
|
||||||
(table) => [index("sessionsTable_userId_idx").on(table.userId)],
|
(table) => [index("sessionsTable_userId_idx").on(table.userId)],
|
||||||
);
|
);
|
||||||
|
|
@ -132,25 +112,82 @@ export const verification = sqliteTable(
|
||||||
(table) => [index("verification_identifier_idx").on(table.identifier)],
|
(table) => [index("verification_identifier_idx").on(table.identifier)],
|
||||||
);
|
);
|
||||||
|
|
||||||
export const userRelations = relations(usersTable, ({ many }) => ({
|
export const organization = sqliteTable(
|
||||||
sessions: many(sessionsTable),
|
"organization",
|
||||||
accounts: many(account),
|
{
|
||||||
twoFactors: many(twoFactor),
|
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 }) => ({
|
export const member = sqliteTable(
|
||||||
user: one(usersTable, {
|
"member",
|
||||||
fields: [sessionsTable.userId],
|
{
|
||||||
references: [usersTable.id],
|
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 }) => ({
|
export const invitation = sqliteTable(
|
||||||
user: one(usersTable, {
|
"invitation",
|
||||||
fields: [account.userId],
|
{
|
||||||
references: [usersTable.id],
|
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
|
* Repositories Table
|
||||||
|
|
@ -223,18 +260,6 @@ export const backupSchedulesTable = sqliteTable("backup_schedules_table", {
|
||||||
});
|
});
|
||||||
export type BackupScheduleInsert = typeof backupSchedulesTable.$inferInsert;
|
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;
|
export type BackupSchedule = typeof backupSchedulesTable.$inferSelect;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -253,9 +278,6 @@ export const notificationDestinationsTable = sqliteTable("notification_destinati
|
||||||
.notNull()
|
.notNull()
|
||||||
.default(sql`(unixepoch() * 1000)`),
|
.default(sql`(unixepoch() * 1000)`),
|
||||||
});
|
});
|
||||||
export const notificationDestinationRelations = relations(notificationDestinationsTable, ({ many }) => ({
|
|
||||||
schedules: many(backupScheduleNotificationsTable),
|
|
||||||
}));
|
|
||||||
export type NotificationDestination = typeof notificationDestinationsTable.$inferSelect;
|
export type NotificationDestination = typeof notificationDestinationsTable.$inferSelect;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -280,16 +302,6 @@ export const backupScheduleNotificationsTable = sqliteTable(
|
||||||
},
|
},
|
||||||
(table) => [primaryKey({ columns: [table.scheduleId, table.destinationId] })],
|
(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;
|
export type BackupScheduleNotification = typeof backupScheduleNotificationsTable.$inferSelect;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -317,16 +329,6 @@ export const backupScheduleMirrorsTable = sqliteTable(
|
||||||
(table) => [unique().on(table.scheduleId, table.repositoryId)],
|
(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;
|
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)],
|
(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