migration

This commit is contained in:
Théo LAGACHE
2025-05-16 15:54:17 +02:00
parent 01019fe1a3
commit 6f2523e524
285 changed files with 15492 additions and 46090 deletions
+13
View File
@@ -0,0 +1,13 @@
import "dotenv/config";
import { drizzle } from "drizzle-orm/node-postgres";
import dotenv from "dotenv";
import { env } from "@/env.mjs";
import * as schema from "./schema";
dotenv.config({
path: ".env",
});
export const db = drizzle(env.DATABASE_URL!, {
schema,
});
+174
View File
@@ -0,0 +1,174 @@
CREATE TYPE "public"."dbms_status" AS ENUM('active', 'inactive');--> statement-breakpoint
CREATE TYPE "public"."status" AS ENUM('waiting', 'ongoing', 'failed', 'success');--> statement-breakpoint
CREATE TYPE "public"."type_storage" AS ENUM('local', 's3');--> statement-breakpoint
CREATE TABLE "settings" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"storage" "type_storage" DEFAULT 'local' NOT NULL,
"name" varchar(255) NOT NULL,
"s3_endpoint_url" varchar(255),
"s3_access_key_id" varchar(255),
"s3_secret_access_key" varchar(255),
"s3_bucket_name" varchar(255),
"smtp_password" varchar(255),
"smtp_from" varchar(255),
"smtp_host" varchar(255),
"smtp_port" varchar(255),
"smtp_user" varchar(255),
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp,
CONSTRAINT "settings_name_unique" UNIQUE("name")
);
--> statement-breakpoint
CREATE TABLE "account" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"account_id" text NOT NULL,
"provider_id" text NOT NULL,
"user_id" uuid NOT NULL,
"access_token" text,
"refresh_token" text,
"id_token" text,
"access_token_expires_at" timestamp,
"refresh_token_expires_at" timestamp,
"scope" text,
"password" text,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp
);
--> statement-breakpoint
CREATE TABLE "session" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"expires_at" timestamp NOT NULL,
"token" text NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp,
"ip_address" text,
"user_agent" text,
"user_id" uuid NOT NULL,
"impersonated_by" text,
CONSTRAINT "session_token_unique" UNIQUE("token")
);
--> statement-breakpoint
CREATE TABLE "user" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"name" text NOT NULL,
"email" text NOT NULL,
"email_verified" boolean NOT NULL,
"image" text,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp,
"role" text,
"banned" boolean,
"ban_reason" text,
"ban_expires" timestamp,
"deleted_at" timestamp,
CONSTRAINT "user_email_unique" UNIQUE("email")
);
--> statement-breakpoint
CREATE TABLE "verification" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"identifier" text NOT NULL,
"value" text NOT NULL,
"expires_at" timestamp NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp
);
--> statement-breakpoint
CREATE TABLE "invitation" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"organization_id" uuid NOT NULL,
"email" text NOT NULL,
"role" text,
"status" text NOT NULL,
"expires_at" timestamp NOT NULL,
"inviter_id" uuid NOT NULL
);
--> statement-breakpoint
CREATE TABLE "member" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"organization_id" uuid NOT NULL,
"user_id" uuid NOT NULL,
"role" text NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp
);
--> statement-breakpoint
CREATE TABLE "organization" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"name" text NOT NULL,
"slug" text NOT NULL,
"logo" text,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp,
"metadata" text,
CONSTRAINT "organization_slug_unique" UNIQUE("slug")
);
--> statement-breakpoint
CREATE TABLE "projects" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"slug" text NOT NULL,
"name" text NOT NULL,
"is_archived" boolean DEFAULT false,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp,
"organization_id" uuid NOT NULL,
CONSTRAINT "projects_slug_unique" UNIQUE("slug")
);
--> statement-breakpoint
CREATE TABLE "agents" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"slug" text NOT NULL,
"name" text NOT NULL,
"description" text NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp,
"last_contact" timestamp,
CONSTRAINT "agents_slug_unique" UNIQUE("slug")
);
--> statement-breakpoint
CREATE TABLE "backups" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"status" "status" DEFAULT 'waiting' NOT NULL,
"file" text,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp,
"database_id" uuid NOT NULL
);
--> statement-breakpoint
CREATE TABLE "databases" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"agent_database_id" uuid DEFAULT gen_random_uuid() NOT NULL,
"name" text NOT NULL,
"dbms" "dbms_status" NOT NULL,
"description" text NOT NULL,
"backup_policy" text,
"is_waiting_for_backup" boolean DEFAULT false NOT NULL,
"backup_to_restore" text,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp,
"agent_id" uuid NOT NULL,
"last_contact" timestamp,
"project_id" uuid NOT NULL
);
--> statement-breakpoint
CREATE TABLE "restorations" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"status" "status" DEFAULT 'waiting' NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp,
"backup_id" uuid NOT NULL,
"database_id" uuid
);
--> statement-breakpoint
ALTER TABLE "account" ADD CONSTRAINT "account_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "session" ADD CONSTRAINT "session_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "invitation" ADD CONSTRAINT "invitation_organization_id_organization_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organization"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "invitation" ADD CONSTRAINT "invitation_inviter_id_user_id_fk" FOREIGN KEY ("inviter_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "member" ADD CONSTRAINT "member_organization_id_organization_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organization"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "member" ADD CONSTRAINT "member_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "projects" ADD CONSTRAINT "projects_organization_id_organization_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organization"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "backups" ADD CONSTRAINT "backups_database_id_databases_id_fk" FOREIGN KEY ("database_id") REFERENCES "public"."databases"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "databases" ADD CONSTRAINT "databases_agent_id_agents_id_fk" FOREIGN KEY ("agent_id") REFERENCES "public"."agents"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "databases" ADD CONSTRAINT "databases_project_id_projects_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "restorations" ADD CONSTRAINT "restorations_backup_id_backups_id_fk" FOREIGN KEY ("backup_id") REFERENCES "public"."backups"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "restorations" ADD CONSTRAINT "restorations_database_id_databases_id_fk" FOREIGN KEY ("database_id") REFERENCES "public"."databases"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE UNIQUE INDEX "database_id_status_unique" ON "backups" USING btree ("database_id","status");
+1
View File
@@ -0,0 +1 @@
ALTER TABLE "session" ADD COLUMN "active_organization_id" text;
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+20
View File
@@ -0,0 +1,20 @@
{
"version": "7",
"dialect": "postgresql",
"entries": [
{
"idx": 0,
"version": "7",
"when": 1744888916262,
"tag": "0000_round_king_cobra",
"breakpoints": true
},
{
"idx": 1,
"version": "7",
"when": 1747319063637,
"tag": "0001_small_anthem",
"breakpoints": true
}
]
}
+7
View File
@@ -0,0 +1,7 @@
export * from "./schema/00_setting";
export { user, session, userRelations } from "./schema/01_user";
export type { Organization } from "./schema/02_organization";
export { organization, member as organizationMember, invitation as organizationInvitation } from "./schema/02_organization";
export * from "./schema/03_project";
export * from "./schema/04_agent";
export * from "./schema/05_database";
+24
View File
@@ -0,0 +1,24 @@
import { pgTable, timestamp, uuid, varchar } from "drizzle-orm/pg-core";
import { typeStorageEnum } from "./types";
import { createSelectSchema } from "drizzle-zod";
import { z } from "zod";
export const setting = pgTable("settings", {
id: uuid("id").primaryKey().defaultRandom(),
storage: typeStorageEnum("storage").default("local").notNull(),
name: varchar("name", { length: 255 }).unique().notNull(),
s3EndPointUrl: varchar("s3_endpoint_url", { length: 255 }),
s3AccessKeyId: varchar("s3_access_key_id", { length: 255 }),
s3SecretAccessKey: varchar("s3_secret_access_key", { length: 255 }),
S3BucketName: varchar("s3_bucket_name", { length: 255 }),
smtpPassword: varchar("smtp_password", { length: 255 }),
smtpFrom: varchar("smtp_from", { length: 255 }),
smtpHost: varchar("smtp_host", { length: 255 }),
smtpPort: varchar("smtp_port", { length: 255 }),
smtpUser: varchar("smtp_user", { length: 255 }),
createdAt: timestamp("created_at").notNull().defaultNow(),
updatedAt: timestamp("updated_at"),
});
export const settingSchema = createSelectSchema(setting);
export type Setting = z.infer<typeof settingSchema>;
+94
View File
@@ -0,0 +1,94 @@
import { relations } from "drizzle-orm";
import { boolean, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
import { createSelectSchema } from "drizzle-zod";
import { z } from "zod";
import { project } from "./03_project";
import { member, invitation, organization } from "./02_organization";
export const user = pgTable("user", {
id: uuid("id").defaultRandom().primaryKey(),
name: text("name").notNull(),
email: text("email").notNull().unique(),
emailVerified: boolean("email_verified").notNull(),
image: text("image"),
createdAt: timestamp("created_at").notNull().defaultNow(),
updatedAt: timestamp("updated_at"),
role: text("role"),
banned: boolean("banned"),
banReason: text("ban_reason"),
banExpires: timestamp("ban_expires"),
deletedAt: timestamp("deleted_at"),
});
export const session = pgTable("session", {
id: uuid("id").defaultRandom().primaryKey(),
expiresAt: timestamp("expires_at").notNull(),
token: text("token").notNull().unique(),
createdAt: timestamp("created_at").notNull().defaultNow(),
updatedAt: timestamp("updated_at"),
ipAddress: text("ip_address"),
userAgent: text("user_agent"),
userId: uuid("user_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
impersonatedBy: text("impersonated_by"), //id or name ????
activeOrganizationId: text("active_organization_id"),
});
export const account = pgTable("account", {
id: uuid("id").defaultRandom().primaryKey(),
accountId: text("account_id").notNull(),
providerId: text("provider_id").notNull(),
userId: uuid("user_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
accessToken: text("access_token"),
refreshToken: text("refresh_token"),
idToken: text("id_token"),
accessTokenExpiresAt: timestamp("access_token_expires_at"),
refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
scope: text("scope"),
password: text("password"),
createdAt: timestamp("created_at").notNull().defaultNow(),
updatedAt: timestamp("updated_at"),
});
export const verification = pgTable("verification", {
id: uuid("id").defaultRandom().primaryKey(),
identifier: text("identifier").notNull(),
value: text("value").notNull(),
expiresAt: timestamp("expires_at").notNull(),
createdAt: timestamp("created_at").notNull().defaultNow(),
updatedAt: timestamp("updated_at"),
});
export const userRelations = relations(user, ({ many }) => ({
sessions: many(session),
accounts: many(account),
memberships: many(member),
invitations: many(invitation),
}));
export const sessionRelations = relations(session, ({ one }) => ({
user: one(user, {
fields: [session.userId],
references: [user.id],
}),
}));
export const accountRelations = relations(account, ({ one }) => ({
user: one(user, {
fields: [account.userId],
references: [user.id],
}),
}));
export const projectRelations = relations(project, ({ one }) => ({
organization: one(organization, {
fields: [project.organizationId],
references: [organization.id],
}),
}));
export const userSchema = createSelectSchema(user);
export type User = z.infer<typeof userSchema>;
+80
View File
@@ -0,0 +1,80 @@
import { pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
import { user } from "./01_user";
import { relations } from "drizzle-orm";
import { project } from "./03_project";
import { createSelectSchema } from "drizzle-zod";
import { z } from "zod";
export const organization = pgTable("organization", {
id: uuid("id").defaultRandom().primaryKey(),
name: text("name").notNull(),
slug: text("slug").unique().notNull(),
logo: text("logo"),
createdAt: timestamp("created_at").notNull().defaultNow(),
updatedAt: timestamp("updated_at"),
metadata: text("metadata"),
});
export const member = pgTable("member", {
id: uuid("id").defaultRandom().primaryKey(),
organizationId: uuid("organization_id")
.notNull()
.references(() => organization.id, { onDelete: "cascade" }),
userId: uuid("user_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
role: text("role").notNull(),
createdAt: timestamp("created_at").notNull().defaultNow(),
updatedAt: timestamp("updated_at"),
});
export const invitation = pgTable("invitation", {
id: uuid("id").defaultRandom().primaryKey(),
organizationId: uuid("organization_id")
.notNull()
.references(() => organization.id, { onDelete: "cascade" }),
email: text("email").notNull(),
role: text("role"),
status: text("status").notNull(),
expiresAt: timestamp("expires_at").notNull(),
inviterId: uuid("inviter_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
});
export const organizationRelations = relations(organization, ({ many }) => ({
members: many(member),
invitations: many(invitation),
projects: many(project),
}));
export const memberRelations = relations(member, ({ one }) => ({
user: one(user, {
fields: [member.userId],
references: [user.id],
}),
organization: one(organization, {
fields: [member.organizationId],
references: [organization.id],
}),
}));
export const invitationRelations = relations(invitation, ({ one }) => ({
organization: one(organization, {
fields: [invitation.organizationId],
references: [organization.id],
}),
inviter: one(user, {
fields: [invitation.inviterId],
references: [user.id],
}),
}));
export const organizationSchema = createSelectSchema(organization);
export type Organization = z.infer<typeof organizationSchema>;
export const organizationMemberSchema = createSelectSchema(member);
export type OrganizationMember = z.infer<typeof organizationMemberSchema>;
export const organizationInvitationSchema = createSelectSchema(invitation);
export type OrganizationInvitation = z.infer<typeof organizationInvitationSchema>;
+34
View File
@@ -0,0 +1,34 @@
import { pgTable, text, boolean, uuid, timestamp } from "drizzle-orm/pg-core";
import { relations } from "drizzle-orm";
import { Organization, organization } from "./02_organization";
import { createSelectSchema } from "drizzle-zod";
import { z } from "zod";
import { Database, database } from "./05_database";
export const project = pgTable("projects", {
id: uuid("id").primaryKey().defaultRandom(),
slug: text("slug").notNull().unique(),
name: text("name").notNull().notNull(),
isArchived: boolean("is_archived").default(false),
createdAt: timestamp("created_at").notNull().defaultNow(),
updatedAt: timestamp("updated_at"),
organizationId: uuid("organization_id")
.notNull()
.references(() => organization.id),
});
export const projectRelations = relations(project, ({ one, many }) => ({
organization: one(organization, {
fields: [project.organizationId],
references: [organization.id],
}),
databases: many(database),
}));
export const projectSchema = createSelectSchema(project);
export type Project = z.infer<typeof projectSchema>;
export type ProjectWith = Project & {
databases: Database[];
organization: Organization;
};
+16
View File
@@ -0,0 +1,16 @@
import { pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
import { createSelectSchema } from "drizzle-zod";
import { z } from "zod";
export const agent = pgTable("agents", {
id: uuid("id").primaryKey().defaultRandom(),
slug: text("slug").notNull().unique(),
name: text("name").notNull().notNull(),
description: text("description").notNull(),
createdAt: timestamp("created_at").notNull().defaultNow(),
updatedAt: timestamp("updated_at"),
lastContact: timestamp("last_contact"),
});
export const agentSchema = createSelectSchema(agent);
export type Agent = z.infer<typeof agentSchema>;
+87
View File
@@ -0,0 +1,87 @@
import { pgTable, text, boolean, timestamp, uuid, integer, pgEnum, uniqueIndex } from "drizzle-orm/pg-core";
import { Agent, agent } from "./04_agent";
import { Project, project } from "./03_project";
import { relations } from "drizzle-orm";
import { dbmsEnum, statusEnum } from "./types";
import { createSelectSchema } from "drizzle-zod";
import { z } from "zod";
export const database = pgTable("databases", {
id: uuid("id").primaryKey().defaultRandom(),
agentDatabaseId: uuid("agent_database_id").notNull().defaultRandom(),
name: text("name").notNull(),
dbms: dbmsEnum("dbms").notNull(),
description: text("description").notNull(),
backupPolicy: text("backup_policy"),
isWaitingForBackup: boolean("is_waiting_for_backup").default(false).notNull(),
backupToRestore: text("backup_to_restore"),
createdAt: timestamp("created_at").notNull().defaultNow(),
updatedAt: timestamp("updated_at"),
agentId: uuid("agent_id")
.notNull()
.references(() => agent.id, { onDelete: "cascade" }),
lastContact: timestamp("last_contact"),
projectId: uuid("project_id")
.references(() => project.id)
.notNull(),
});
export const backup = pgTable(
"backups",
{
id: uuid("id").primaryKey().defaultRandom(),
status: statusEnum("status").default("waiting").notNull(),
file: text("file"),
createdAt: timestamp("created_at").notNull().defaultNow(),
updatedAt: timestamp("updated_at"),
databaseId: uuid("database_id")
.notNull()
.references(() => database.id, { onDelete: "cascade" }),
},
(table) => [uniqueIndex("database_id_status_unique").on(table.databaseId, table.status)]
);
export const restoration = pgTable("restorations", {
id: uuid("id").primaryKey().defaultRandom(),
status: statusEnum("status").default("waiting").notNull(),
createdAt: timestamp("created_at").notNull().defaultNow(),
updatedAt: timestamp("updated_at"),
backupId: uuid("backup_id")
.notNull()
.references(() => backup.id, { onDelete: "cascade" }),
databaseId: uuid("database_id").references(() => database.id, { onDelete: "cascade" }),
});
export const databaseRelations = relations(database, ({ one, many }) => ({
agent: one(agent, { fields: [database.agentId], references: [agent.id] }),
project: one(project, { fields: [database.projectId], references: [project.id] }),
backups: many(backup),
restorations: many(restoration),
}));
export const backupRelations = relations(backup, ({ one, many }) => ({
database: one(database, { fields: [backup.databaseId], references: [database.id] }),
restorations: many(restoration),
}));
export const restorationRelations = relations(restoration, ({ one }) => ({
backup: one(backup, { fields: [restoration.backupId], references: [backup.id] }),
database: one(database, { fields: [restoration.databaseId], references: [database.id] }),
}));
export const databaseSchema = createSelectSchema(database);
export type Database = z.infer<typeof databaseSchema>;
export const backupSchema = createSelectSchema(backup);
export type Backup = z.infer<typeof backupSchema>;
export const restorationSchema = createSelectSchema(restoration);
export type Restoration = z.infer<typeof restorationSchema>;
export type DatabaseWith = Database & {
agent: Agent;
project: Project;
backups: Backup[];
restorations: Restoration[];
};
+16
View File
@@ -0,0 +1,16 @@
import { pgEnum } from "drizzle-orm/pg-core";
import { createSelectSchema } from "drizzle-zod";
import { z } from "zod";
export const dbmsEnum = pgEnum("dbms_status", ["active", "inactive"]);
export const statusEnum = pgEnum("status", ["waiting", "ongoing", "failed", "success"]);
export const typeStorageEnum = pgEnum("type_storage", ["local", "s3"]);
export const dbmsEnumSchema = createSelectSchema(dbmsEnum);
export type EDbmsSchema = z.infer<typeof dbmsEnumSchema>;
export const statusEnumSchema = createSelectSchema(statusEnum);
export type EStatusSchema = z.infer<typeof statusEnumSchema>;
export const typeStorageEnumSchema = createSelectSchema(typeStorageEnum);
export type ETypeStorageSchema = z.infer<typeof typeStorageEnumSchema>;