Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,22 @@ const organizationSchema = z.object({
message: "Organization name is required",
}),
logo: z.string().optional(),
description: z.string().max(280).optional(),
});

const getOrganizationDescription = (metadata?: string | null) => {
if (!metadata) {
return "";
}

try {
const parsed = JSON.parse(metadata);
return typeof parsed?.description === "string" ? parsed.description : "";
} catch {
return "";
}
};

type OrganizationFormValues = z.infer<typeof organizationSchema>;

interface Props {
Expand Down Expand Up @@ -60,6 +74,7 @@ export function AddOrganization({ organizationId }: Props) {
defaultValues: {
name: "",
logo: "",
description: "",
},
});

Expand All @@ -68,6 +83,7 @@ export function AddOrganization({ organizationId }: Props) {
form.reset({
name: organization.name,
logo: organization.logo || "",
description: getOrganizationDescription(organization.metadata),
});
}
}, [organization, form]);
Expand All @@ -76,6 +92,7 @@ export function AddOrganization({ organizationId }: Props) {
await mutateAsync({
name: values.name,
logo: values.logo,
description: values.description,
organizationId: organizationId ?? "",
})
.then(() => {
Expand Down Expand Up @@ -129,7 +146,7 @@ export function AddOrganization({ organizationId }: Props) {
</DialogTitle>
<DialogDescription>
{organizationId
? "Update the organization name and logo"
? "Update the organization name, logo, and description"
: "Create a new organization to manage your projects."}
</DialogDescription>
</DialogHeader>
Expand Down Expand Up @@ -173,6 +190,24 @@ export function AddOrganization({ organizationId }: Props) {
</FormItem>
)}
/>
<FormField
control={form.control}
name="description"
render={({ field }) => (
<FormItem className="gap-4">
<FormLabel className="text-right">Description</FormLabel>
<FormControl>
<Input
placeholder="What this organization is for"
{...field}
value={field.value || ""}
className="col-span-3"
/>
</FormControl>
<FormMessage className="col-span-3 col-start-2" />
</FormItem>
)}
/>
<DialogFooter>
<Button type="submit" isLoading={isPending}>
{organizationId ? "Update organization" : "Create organization"}
Expand Down
50 changes: 49 additions & 1 deletion apps/dokploy/server/api/routers/organization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,52 @@ import {
user,
} from "@/server/db/schema";
import { createTRPCRouter, protectedProcedure, withPermission } from "../trpc";

const parseOrganizationMetadata = (metadata: string | null) => {
if (!metadata) {
return {} as Record<string, unknown>;
}

try {
const parsed = JSON.parse(metadata);
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
? (parsed as Record<string, unknown>)
: ({} as Record<string, unknown>);
} catch {
return {} as Record<string, unknown>;
}
};

const stringifyOrganizationMetadata = (
metadata: Record<string, unknown>,
description?: string,
) => {
const nextMetadata = { ...metadata };
const normalizedDescription = description?.trim();

if (normalizedDescription) {
nextMetadata.description = normalizedDescription;
} else {
delete nextMetadata.description;
}

return Object.keys(nextMetadata).length > 0
? JSON.stringify(nextMetadata)
: null;
};

export const organizationRouter = createTRPCRouter({
create: protectedProcedure
.input(
z.object({
name: z.string(),
logo: z.string().optional(),
description: z.string().max(280).optional(),
}),
)
.mutation(async ({ ctx, input }) => {
const normalizedDescription = input.description?.trim();

if (ctx.user.role !== "owner" && ctx.user.role !== "admin" && !IS_CLOUD) {
throw new TRPCError({
code: "FORBIDDEN",
Expand All @@ -31,7 +68,9 @@ export const organizationRouter = createTRPCRouter({
const result = await db
.insert(organization)
.values({
...input,
name: input.name,
logo: input.logo,
metadata: stringifyOrganizationMetadata({}, input.description),
slug: nanoid(),
createdAt: new Date(),
ownerId: ctx.user.id,
Expand Down Expand Up @@ -62,6 +101,9 @@ export const organizationRouter = createTRPCRouter({
resourceType: "organization",
resourceId: result.id,
resourceName: result.name,
metadata: normalizedDescription
? { description: normalizedDescription }
: undefined,
});
return result;
}),
Expand Down Expand Up @@ -119,6 +161,7 @@ export const organizationRouter = createTRPCRouter({
organizationId: z.string(),
name: z.string(),
logo: z.string().optional(),
description: z.string().max(280).optional(),
}),
)
.mutation(async ({ ctx, input }) => {
Expand Down Expand Up @@ -166,6 +209,10 @@ export const organizationRouter = createTRPCRouter({
.set({
name: input.name,
logo: input.logo,
metadata: stringifyOrganizationMetadata(
parseOrganizationMetadata(org.metadata),
input.description,
),
})
.where(eq(organization.id, input.organizationId))
.returning();
Expand All @@ -174,6 +221,7 @@ export const organizationRouter = createTRPCRouter({
resourceType: "organization",
resourceId: input.organizationId,
resourceName: input.name,
metadata: { description: input.description?.trim() || null },
});
return result[0];
}),
Expand Down
Loading