feat(ai): update AI settings to use organization-based access control

- Refactor AI-related schemas and services to use organizationId instead of adminId
- Update AI router to check organization-level permissions
- Modify AI settings creation and retrieval to work with organization context
- Adjust server-side props and access checks for AI settings
This commit is contained in:
Mauricio Siu
2025-03-02 00:54:46 -06:00
parent 747c2137c9
commit f78cda9cce
10 changed files with 5326 additions and 64 deletions

View File

@@ -0,0 +1,12 @@
CREATE TABLE "ai" (
"aiId" text PRIMARY KEY NOT NULL,
"name" text NOT NULL,
"apiUrl" text NOT NULL,
"apiKey" text NOT NULL,
"model" text NOT NULL,
"isEnabled" boolean DEFAULT true NOT NULL,
"organizationId" text NOT NULL,
"createdAt" text NOT NULL
);
--> statement-breakpoint
ALTER TABLE "ai" ADD CONSTRAINT "ai_organizationId_organization_id_fk" FOREIGN KEY ("organizationId") REFERENCES "public"."organization"("id") ON DELETE cascade ON UPDATE no action;

File diff suppressed because it is too large Load Diff

View File

@@ -477,6 +477,13 @@
"when": 1740892043121,
"tag": "0067_condemned_sugar_man",
"breakpoints": true
},
{
"idx": 68,
"version": "7",
"when": 1740897756774,
"tag": "0068_complex_rhino",
"breakpoints": true
}
]
}

View File

@@ -143,7 +143,8 @@
"xterm-addon-fit": "^0.8.0",
"zod": "^3.23.4",
"zod-form-data": "^2.0.2",
"@faker-js/faker": "^8.4.1"
"@faker-js/faker": "^8.4.1",
"@tailwindcss/typography": "0.5.16"
},
"devDependencies": {
"@types/qrcode": "^1.5.5",

View File

@@ -25,7 +25,7 @@ export async function getServerSideProps(
ctx: GetServerSidePropsContext<{ serviceId: string }>,
) {
const { req, res } = ctx;
const { user, session } = await validateRequest(req, res);
const { user, session } = await validateRequest(req);
const locale = getLocale(req.cookies);
const helpers = createServerSideHelpers({
@@ -34,22 +34,17 @@ export async function getServerSideProps(
req: req as any,
res: res as any,
db: null as any,
session: session,
user: user,
session: session as any,
user: user as any,
},
transformer: superjson,
});
await helpers.settings.isCloud.prefetch();
await helpers.auth.get.prefetch();
if (user?.rol === "user") {
await helpers.user.byAuthId.prefetch({
authId: user.authId,
});
}
await helpers.user.get.prefetch();
if (!user) {
if (!user || user.role === "member") {
return {
redirect: {
permanent: true,

View File

@@ -15,7 +15,7 @@ import { createDomain } from "@dokploy/server/index";
import {
deleteAiSettings,
getAiSettingById,
getAiSettingsByAdminId,
getAiSettingsByOrganizationId,
saveAiSettings,
suggestVariants,
} from "@dokploy/server/services/ai";
@@ -33,7 +33,7 @@ export const aiRouter = createTRPCRouter({
.input(z.object({ aiId: z.string() }))
.query(async ({ ctx, input }) => {
const aiSetting = await getAiSettingById(input.aiId);
if (aiSetting.adminId !== ctx.user.adminId) {
if (aiSetting.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You don't have access to this AI configuration",
@@ -42,24 +42,26 @@ export const aiRouter = createTRPCRouter({
return aiSetting;
}),
create: adminProcedure.input(apiCreateAi).mutation(async ({ ctx, input }) => {
return await saveAiSettings(ctx.user.adminId, input);
return await saveAiSettings(ctx.session.activeOrganizationId, input);
}),
update: protectedProcedure
.input(apiUpdateAi)
.mutation(async ({ ctx, input }) => {
return await saveAiSettings(ctx.user.adminId, input);
return await saveAiSettings(ctx.session.activeOrganizationId, input);
}),
getAll: adminProcedure.query(async ({ ctx }) => {
return await getAiSettingsByAdminId(ctx.user.adminId);
return await getAiSettingsByOrganizationId(
ctx.session.activeOrganizationId,
);
}),
get: protectedProcedure
.input(z.object({ aiId: z.string() }))
.query(async ({ ctx, input }) => {
const aiSetting = await getAiSettingById(input.aiId);
if (aiSetting.adminId !== ctx.user.authId) {
if (aiSetting.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You don't have access to this AI configuration",
@@ -72,7 +74,7 @@ export const aiRouter = createTRPCRouter({
.input(z.object({ aiId: z.string() }))
.mutation(async ({ ctx, input }) => {
const aiSetting = await getAiSettingById(input.aiId);
if (aiSetting.adminId !== ctx.user.adminId) {
if (aiSetting.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You don't have access to this AI configuration",
@@ -93,7 +95,7 @@ export const aiRouter = createTRPCRouter({
try {
return await suggestVariants({
...input,
adminId: ctx.user.adminId,
organizationId: ctx.session.activeOrganizationId,
});
} catch (error) {
throw new TRPCError({
@@ -105,8 +107,12 @@ export const aiRouter = createTRPCRouter({
deploy: protectedProcedure
.input(deploySuggestionSchema)
.mutation(async ({ ctx, input }) => {
if (ctx.user.rol === "user") {
await checkServiceAccess(ctx.user.adminId, input.projectId, "create");
if (ctx.user.rol === "member") {
await checkServiceAccess(
ctx.session.activeOrganizationId,
input.projectId,
"create",
);
}
if (IS_CLOUD && !input.serverId) {
@@ -143,8 +149,12 @@ export const aiRouter = createTRPCRouter({
}
}
if (ctx.user.rol === "user") {
await addNewService(ctx.user.authId, compose.composeId);
if (ctx.user.rol === "member") {
await addNewService(
ctx.session.activeOrganizationId,
ctx.user.ownerId,
compose.composeId,
);
}
return null;