refactor: add organizations system

This commit is contained in:
Mauricio Siu
2025-02-09 20:53:06 -06:00
parent fafc238e70
commit 8bd72a8a34
47 changed files with 359 additions and 171 deletions

View File

@@ -1,9 +1,9 @@
import {
boolean,
integer,
pgTable,
text,
integer,
timestamp,
boolean,
} from "drizzle-orm/pg-core";
export const user = pgTable("user", {
@@ -14,10 +14,6 @@ export const user = pgTable("user", {
image: text("image"),
createdAt: timestamp("created_at").notNull(),
updatedAt: timestamp("updated_at").notNull(),
role: text("role"),
banned: boolean("banned"),
banReason: text("ban_reason"),
banExpires: timestamp("ban_expires"),
});
export const session = pgTable("session", {
@@ -31,32 +27,67 @@ export const session = pgTable("session", {
userId: text("user_id")
.notNull()
.references(() => user.id),
impersonatedBy: text("impersonated_by"),
activeOrganizationId: text("active_organization_id"),
});
// export const account = pgTable("account", {
// id: text("id").primaryKey(),
// accountId: text("account_id").notNull(),
// providerId: text("provider_id").notNull(),
// userId: text("user_id")
// .notNull()
// .references(() => user.id),
// 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(),
// updatedAt: timestamp("updated_at").notNull(),
// });
export const account = pgTable("account", {
id: text("id").primaryKey(),
accountId: text("account_id").notNull(),
providerId: text("provider_id").notNull(),
userId: text("user_id")
.notNull()
.references(() => user.id),
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(),
updatedAt: timestamp("updated_at").notNull(),
});
// export const verification = pgTable("verification", {
// id: text("id").primaryKey(),
// identifier: text("identifier").notNull(),
// value: text("value").notNull(),
// expiresAt: timestamp("expires_at").notNull(),
// createdAt: timestamp("created_at"),
// updatedAt: timestamp("updated_at"),
// });
export const verification = pgTable("verification", {
id: text("id").primaryKey(),
identifier: text("identifier").notNull(),
value: text("value").notNull(),
expiresAt: timestamp("expires_at").notNull(),
createdAt: timestamp("created_at"),
updatedAt: timestamp("updated_at"),
});
export const organization = pgTable("organization", {
id: text("id").primaryKey(),
name: text("name").notNull(),
slug: text("slug").unique(),
logo: text("logo"),
createdAt: timestamp("created_at").notNull(),
metadata: text("metadata"),
});
export const member = pgTable("member", {
id: text("id").primaryKey(),
organizationId: text("organization_id")
.notNull()
.references(() => organization.id),
userId: text("user_id")
.notNull()
.references(() => user.id),
role: text("role").notNull(),
createdAt: timestamp("created_at").notNull(),
});
export const invitation = pgTable("invitation", {
id: text("id").primaryKey(),
organizationId: text("organization_id")
.notNull()
.references(() => organization.id),
email: text("email").notNull(),
role: text("role"),
status: text("status").notNull(),
expiresAt: timestamp("expires_at").notNull(),
inviterId: text("inviter_id")
.notNull()
.references(() => user.id),
});

View File

@@ -6,9 +6,9 @@ import { TimeSpan } from "lucia";
import { Lucia } from "lucia/dist/core.js";
import type { Session, User } from "lucia/dist/core.js";
import { db } from "../db";
import { type DatabaseUser, auth, sessionTable } from "../db/schema";
import { type DatabaseUser, auth, session } from "../db/schema";
export const adapter = new DrizzlePostgreSQLAdapter(db, sessionTable, auth);
export const adapter = new DrizzlePostgreSQLAdapter(db, session, auth);
export const lucia = new Lucia(adapter, {
sessionCookie: {
@@ -46,6 +46,7 @@ export async function validateRequest(
req: IncomingMessage,
res: ServerResponse,
): ReturnValidateToken {
console.log(session);
const sessionId = lucia.readSessionCookie(req.headers.cookie ?? "");
if (!sessionId) {

View File

@@ -32,3 +32,38 @@ export const verification = pgTable("verification", {
createdAt: timestamp("created_at"),
updatedAt: timestamp("updated_at"),
});
export const organization = pgTable("organization", {
id: text("id").primaryKey(),
name: text("name").notNull(),
slug: text("slug").unique(),
logo: text("logo"),
createdAt: timestamp("created_at").notNull(),
metadata: text("metadata"),
});
export const member = pgTable("member", {
id: text("id").primaryKey(),
organizationId: text("organization_id")
.notNull()
.references(() => organization.id),
userId: text("user_id")
.notNull()
.references(() => user.id),
role: text("role").notNull(),
createdAt: timestamp("created_at").notNull(),
});
export const invitation = pgTable("invitation", {
id: text("id").primaryKey(),
organizationId: text("organization_id")
.notNull()
.references(() => organization.id),
email: text("email").notNull(),
role: text("role"),
status: text("status").notNull(),
expiresAt: timestamp("expires_at").notNull(),
inviterId: text("inviter_id")
.notNull()
.references(() => user.id),
});

View File

@@ -2,7 +2,7 @@ import { pgTable, text, timestamp } from "drizzle-orm/pg-core";
import { user } from "./user";
// OLD TABLE
export const sessionTable = pgTable("session", {
export const session = pgTable("session", {
id: text("id").primaryKey(),
expiresAt: timestamp("expires_at").notNull(),
token: text("token").notNull().unique(),
@@ -14,4 +14,5 @@ export const sessionTable = pgTable("session", {
.notNull()
.references(() => user.id),
impersonatedBy: text("impersonated_by"),
activeOrganizationId: text("active_organization_id"),
});

View File

@@ -24,11 +24,16 @@ export const user = pgTable("user", {
isRegistered: boolean("isRegistered").notNull().default(false),
expirationDate: timestamp("expirationDate", {
precision: 3,
mode: "string",
}).notNull(),
createdAt: text("createdAt")
mode: "date",
})
.notNull()
.$defaultFn(() => new Date().toISOString()),
.$defaultFn(() => new Date()),
createdAt: timestamp("createdAt", {
precision: 3,
mode: "date",
})
.notNull()
.$defaultFn(() => new Date()),
canCreateProjects: boolean("canCreateProjects").notNull().default(false),
canAccessToSSHKeys: boolean("canAccessToSSHKeys").notNull().default(false),
canCreateServices: boolean("canCreateServices").notNull().default(false),

View File

@@ -1,8 +1,10 @@
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { admin } from "better-auth/plugins";
import { admin, createAuthMiddleware, organization } from "better-auth/plugins";
import { db } from "../db";
import * as schema from "../db/schema";
import type { IncomingMessage } from "node:http";
import { eq } from "drizzle-orm";
export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "pg",
@@ -11,5 +13,38 @@ export const auth = betterAuth({
emailAndPassword: {
enabled: true,
},
plugins: [admin()],
hooks: {
after: createAuthMiddleware(async (ctx) => {
if (ctx.path.startsWith("/sign-up")) {
const newSession = ctx.context.newSession;
await db
.update(schema.user)
.set({
role: "admin",
})
.where(eq(schema.user.id, newSession?.user?.id || ""));
}
}),
},
user: {
additionalFields: {},
},
plugins: [organization()],
});
export const validateRequest = async (request: IncomingMessage) => {
const session = await auth.api.getSession({
headers: new Headers({
cookie: request.headers.cookie || "",
}),
});
if (!session?.session || !session.user) {
return {
session: null,
user: null,
};
}
return session;
};

View File

@@ -94,8 +94,8 @@ export const updateAdminById = async (
};
export const isAdminPresent = async () => {
const admin = await db.query.users.findFirst({
where: eq(users.role, "admin"),
const admin = await db.query.user.findFirst({
where: eq(user.role, "admin"),
});
if (!admin) {
return false;

View File

@@ -100,10 +100,11 @@ export const findAuthByEmail = async (email: string) => {
};
export const findAuthById = async (authId: string) => {
const result = await db.query.auth.findFirst({
where: eq(auth.id, authId),
const result = await db.query.user.findFirst({
where: eq(user.id, authId),
columns: {
password: false,
createdAt: false,
updatedAt: false,
},
});
if (!result) {

View File

@@ -20,18 +20,16 @@ export const findUserById = async (userId: string) => {
export const findUserByAuthId = async (authId: string) => {
const userR = await db.query.user.findFirst({
where: eq(user.authId, authId),
with: {
auth: true,
},
where: eq(user.id, authId),
with: {},
});
if (!user) {
if (!userR) {
throw new TRPCError({
code: "NOT_FOUND",
message: "User not found",
});
}
return user;
return userR;
};
export const findUsers = async (adminId: string) => {