diff --git a/apps/dokploy/components/layouts/side.tsx b/apps/dokploy/components/layouts/side.tsx
index d1d4ae273..0d423b038 100644
--- a/apps/dokploy/components/layouts/side.tsx
+++ b/apps/dokploy/components/layouts/side.tsx
@@ -26,6 +26,7 @@ import {
PieChart,
Server,
ShieldCheck,
+ Star,
Trash2,
User,
Users,
@@ -505,6 +506,8 @@ function SidebarLogo() {
} = api.organization.all.useQuery();
const { mutateAsync: deleteOrganization, isLoading: isRemoving } =
api.organization.delete.useMutation();
+ const { mutateAsync: setDefaultOrganization, isLoading: isSettingDefault } =
+ api.organization.setDefault.useMutation();
const { isMobile } = useSidebar();
const { data: activeOrganization } = authClient.useActiveOrganization();
const _utils = api.useUtils();
@@ -605,7 +608,11 @@ function SidebarLogo() {
}}
className="w-full gap-2 p-2"
>
-
{org.name}
+
{org.ownerId === session?.user?.id && (
+
{
- const memberResult = await db.query.organization.findMany({
+ // Get all memberships for the user with organization info
+ // Query memberships first to get the isDefault value correctly
+ const memberships = await db
+ .select({
+ organizationId: member.organizationId,
+ isDefault: member.isDefault,
+ createdAt: member.createdAt,
+ })
+ .from(member)
+ .where(eq(member.userId, ctx.user.id));
+
+ // If no default is set, set the oldest organization as default
+ const hasDefault = memberships.some((m) => m.isDefault === true);
+ if (!hasDefault && memberships.length > 0) {
+ // Find the oldest membership (first created)
+ const oldestMembership = memberships.reduce((oldest, current) =>
+ current.createdAt < oldest.createdAt ? current : oldest,
+ );
+
+ // Set it as default
+ await db
+ .update(member)
+ .set({ isDefault: true })
+ .where(
+ and(
+ eq(member.organizationId, oldestMembership.organizationId),
+ eq(member.userId, ctx.user.id),
+ ),
+ );
+
+ // Update the memberships array
+ const updatedMemberships = memberships.map((m) =>
+ m.organizationId === oldestMembership.organizationId
+ ? { ...m, isDefault: true }
+ : m,
+ );
+
+ // Get all organizations for the user
+ const organizations = await db.query.organization.findMany({
+ where: (organization) =>
+ exists(
+ db
+ .select()
+ .from(member)
+ .where(
+ and(
+ eq(member.organizationId, organization.id),
+ eq(member.userId, ctx.user.id),
+ ),
+ ),
+ ),
+ });
+
+ // Create a map of organizationId to isDefault
+ const defaultMap = new Map(
+ updatedMemberships.map((m) => [m.organizationId, Boolean(m.isDefault)]),
+ );
+
+ // Map organizations with their isDefault flag
+ return organizations.map((org) => ({
+ ...org,
+ isDefault: defaultMap.get(org.id) ?? false,
+ }));
+ }
+
+ // Get all organizations for the user
+ const organizations = await db.query.organization.findMany({
where: (organization) =>
exists(
db
@@ -64,7 +138,17 @@ export const organizationRouter = createTRPCRouter({
),
),
});
- return memberResult;
+
+ // Create a map of organizationId to isDefault
+ const defaultMap = new Map(
+ memberships.map((m) => [m.organizationId, Boolean(m.isDefault)]),
+ );
+
+ // Map organizations with their isDefault flag
+ return organizations.map((org) => ({
+ ...org,
+ isDefault: defaultMap.get(org.id) ?? false,
+ }));
}),
one: protectedProcedure
.input(
@@ -184,4 +268,45 @@ export const organizationRouter = createTRPCRouter({
.delete(invitation)
.where(eq(invitation.id, input.invitationId));
}),
+ setDefault: protectedProcedure
+ .input(
+ z.object({
+ organizationId: z.string(),
+ }),
+ )
+ .mutation(async ({ ctx, input }) => {
+ // Verify user is a member of this organization
+ const userMember = await db.query.member.findFirst({
+ where: and(
+ eq(member.organizationId, input.organizationId),
+ eq(member.userId, ctx.user.id),
+ ),
+ });
+
+ if (!userMember) {
+ throw new TRPCError({
+ code: "FORBIDDEN",
+ message: "You are not a member of this organization",
+ });
+ }
+
+ // First, unset all defaults for this user
+ await db
+ .update(member)
+ .set({ isDefault: false })
+ .where(eq(member.userId, ctx.user.id));
+
+ // Then set this organization as default
+ await db
+ .update(member)
+ .set({ isDefault: true })
+ .where(
+ and(
+ eq(member.organizationId, input.organizationId),
+ eq(member.userId, ctx.user.id),
+ ),
+ );
+
+ return { success: true };
+ }),
});
diff --git a/packages/server/src/db/schema/account.ts b/packages/server/src/db/schema/account.ts
index f3d70e680..d995364dc 100644
--- a/packages/server/src/db/schema/account.ts
+++ b/packages/server/src/db/schema/account.ts
@@ -94,6 +94,7 @@ export const member = pgTable("member", {
role: text("role").notNull().$type<"owner" | "member" | "admin">(),
createdAt: timestamp("created_at").notNull(),
teamId: text("team_id"),
+ isDefault: boolean("is_default").notNull().default(false),
// Permissions
canCreateProjects: boolean("canCreateProjects").notNull().default(false),
canAccessToSSHKeys: boolean("canAccessToSSHKeys").notNull().default(false),
diff --git a/packages/server/src/lib/auth.ts b/packages/server/src/lib/auth.ts
index 739a666f7..16ce5ed39 100644
--- a/packages/server/src/lib/auth.ts
+++ b/packages/server/src/lib/auth.ts
@@ -165,6 +165,7 @@ const { handler, api } = betterAuth({
organizationId: organization?.id || "",
role: "owner",
createdAt: new Date(),
+ isDefault: true, // Mark first organization as default
});
});
}
@@ -174,14 +175,28 @@ const { handler, api } = betterAuth({
session: {
create: {
before: async (session) => {
- const member = await db.query.member.findFirst({
- where: eq(schema.member.userId, session.userId),
- orderBy: desc(schema.member.createdAt),
+ // First try to find the default organization for this user
+ let member = await db.query.member.findFirst({
+ where: and(
+ eq(schema.member.userId, session.userId),
+ eq(schema.member.isDefault, true),
+ ),
with: {
organization: true,
},
});
+ // If no default is set, fallback to the most recently created organization
+ if (!member) {
+ member = await db.query.member.findFirst({
+ where: eq(schema.member.userId, session.userId),
+ orderBy: desc(schema.member.createdAt),
+ with: {
+ organization: true,
+ },
+ });
+ }
+
return {
data: {
...session,