feat(git-provider): enhance sharing and permissions management

- Added functionality to toggle sharing of Git providers with the organization.
- Introduced a new column "sharedWithOrganization" in the git_provider table to track sharing status.
- Updated user permissions to include accessedGitProviders, allowing for more granular access control.
- Enhanced API routes to support fetching accessible Git providers based on user roles and permissions.
- Implemented UI components for managing Git provider sharing and permissions in the dashboard.
This commit is contained in:
Mauricio Siu
2026-04-03 14:29:48 -06:00
parent 86ba597d67
commit 06b18aca08
16 changed files with 8664 additions and 69 deletions

View File

@@ -1,6 +1,7 @@
import {
createBitbucket,
findBitbucketById,
getAccessibleGitProviderIds,
getBitbucketBranches,
getBitbucketRepositories,
testBitbucketConnection,
@@ -54,6 +55,8 @@ export const bitbucketRouter = createTRPCRouter({
return await findBitbucketById(input.bitbucketId);
}),
bitbucketProviders: protectedProcedure.query(async ({ ctx }) => {
const accessibleIds = await getAccessibleGitProviderIds(ctx.session);
let result = await db.query.bitbucket.findMany({
with: {
gitProvider: true,
@@ -67,7 +70,7 @@ export const bitbucketRouter = createTRPCRouter({
return (
provider.gitProvider.organizationId ===
ctx.session.activeOrganizationId &&
provider.gitProvider.userId === ctx.session.userId
accessibleIds.has(provider.gitProvider.gitProviderId)
);
});
return result;

View File

@@ -1,18 +1,34 @@
import { findGitProviderById, removeGitProvider } from "@dokploy/server";
import {
findGitProviderById,
getAccessibleGitProviderIds,
removeGitProvider,
updateGitProvider,
} from "@dokploy/server";
import { db } from "@dokploy/server/db";
import { hasValidLicense } from "@dokploy/server/services/proprietary/license-key";
import { TRPCError } from "@trpc/server";
import { and, desc, eq } from "drizzle-orm";
import { desc, eq, inArray } from "drizzle-orm";
import { audit } from "@/server/api/utils/audit";
import {
createTRPCRouter,
protectedProcedure,
withPermission,
} from "@/server/api/trpc";
import { apiRemoveGitProvider, gitProvider } from "@/server/db/schema";
import {
apiRemoveGitProvider,
apiToggleShareGitProvider,
gitProvider,
} from "@/server/db/schema";
export const gitProviderRouter = createTRPCRouter({
getAll: protectedProcedure.query(async ({ ctx }) => {
return await db.query.gitProvider.findMany({
const accessibleIds = await getAccessibleGitProviderIds(ctx.session);
if (accessibleIds.size === 0) {
return [];
}
const results = await db.query.gitProvider.findMany({
with: {
gitlab: true,
bitbucket: true,
@@ -20,12 +36,67 @@ export const gitProviderRouter = createTRPCRouter({
gitea: true,
},
orderBy: desc(gitProvider.createdAt),
where: and(
eq(gitProvider.userId, ctx.session.userId),
eq(gitProvider.organizationId, ctx.session.activeOrganizationId),
),
where: inArray(gitProvider.gitProviderId, [...accessibleIds]),
});
return results.map((r) => ({
...r,
isOwner: r.userId === ctx.session.userId,
}));
}),
toggleShare: protectedProcedure
.input(apiToggleShareGitProvider)
.mutation(async ({ input, ctx }) => {
const provider = await findGitProviderById(input.gitProviderId);
if (provider.userId !== ctx.session.userId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "Only the owner can share this provider",
});
}
await audit(ctx, {
action: "update",
resourceType: "gitProvider",
resourceId: provider.gitProviderId,
resourceName: provider.name ?? provider.gitProviderId,
});
return await updateGitProvider(input.gitProviderId, {
sharedWithOrganization: input.sharedWithOrganization,
});
}),
allForPermissions: withPermission("member", "update")
.use(async ({ ctx, next }) => {
const licensed = await hasValidLicense(
ctx.session.activeOrganizationId,
);
if (!licensed) {
throw new TRPCError({
code: "FORBIDDEN",
message: "Valid enterprise license required",
});
}
return next();
})
.query(async ({ ctx }) => {
return await db.query.gitProvider.findMany({
columns: {
gitProviderId: true,
name: true,
providerType: true,
},
orderBy: desc(gitProvider.createdAt),
where: eq(
gitProvider.organizationId,
ctx.session.activeOrganizationId,
),
});
}),
remove: withPermission("gitProviders", "delete")
.input(apiRemoveGitProvider)
.mutation(async ({ input, ctx }) => {

View File

@@ -1,6 +1,7 @@
import {
createGitea,
findGiteaById,
getAccessibleGitProviderIds,
getGiteaBranches,
getGiteaRepositories,
haveGiteaRequirements,
@@ -57,6 +58,8 @@ export const giteaRouter = createTRPCRouter({
}),
giteaProviders: protectedProcedure.query(async ({ ctx }) => {
const accessibleIds = await getAccessibleGitProviderIds(ctx.session);
let result = await db.query.gitea.findMany({
with: {
gitProvider: true,
@@ -67,7 +70,7 @@ export const giteaRouter = createTRPCRouter({
(provider) =>
provider.gitProvider.organizationId ===
ctx.session.activeOrganizationId &&
provider.gitProvider.userId === ctx.session.userId,
accessibleIds.has(provider.gitProvider.gitProviderId),
);
const filtered = result

View File

@@ -1,5 +1,6 @@
import {
findGithubById,
getAccessibleGitProviderIds,
getGithubBranches,
getGithubRepositories,
haveGithubRequirements,
@@ -35,6 +36,8 @@ export const githubRouter = createTRPCRouter({
return await getGithubBranches(input);
}),
githubProviders: protectedProcedure.query(async ({ ctx }) => {
const accessibleIds = await getAccessibleGitProviderIds(ctx.session);
let result = await db.query.github.findMany({
with: {
gitProvider: true,
@@ -45,7 +48,7 @@ export const githubRouter = createTRPCRouter({
(provider) =>
provider.gitProvider.organizationId ===
ctx.session.activeOrganizationId &&
provider.gitProvider.userId === ctx.session.userId,
accessibleIds.has(provider.gitProvider.gitProviderId),
);
const filtered = result

View File

@@ -1,6 +1,7 @@
import {
createGitlab,
findGitlabById,
getAccessibleGitProviderIds,
getGitlabBranches,
getGitlabRepositories,
haveGitlabRequirements,
@@ -54,6 +55,8 @@ export const gitlabRouter = createTRPCRouter({
return await findGitlabById(input.gitlabId);
}),
gitlabProviders: protectedProcedure.query(async ({ ctx }) => {
const accessibleIds = await getAccessibleGitProviderIds(ctx.session);
let result = await db.query.gitlab.findMany({
with: {
gitProvider: true,
@@ -64,7 +67,7 @@ export const gitlabRouter = createTRPCRouter({
return (
provider.gitProvider.organizationId ===
ctx.session.activeOrganizationId &&
provider.gitProvider.userId === ctx.session.userId
accessibleIds.has(provider.gitProvider.gitProviderId)
);
});
const filtered = result

View File

@@ -143,7 +143,12 @@ export const licenseKeyRouter = createTRPCRouter({
});
}
await deactivateLicenseKey(currentUser.licenseKey);
try {
await deactivateLicenseKey(currentUser.licenseKey);
} catch (_) {
// Always clean up locally even if the license server is unreachable
}
await db
.update(user)
.set({

View File

@@ -13,6 +13,7 @@ import {
updateUser,
} from "@dokploy/server";
import { db } from "@dokploy/server/db";
import { hasValidLicense } from "@dokploy/server/services/proprietary/license-key";
import {
account,
apiAssignPermissions,
@@ -344,12 +345,19 @@ export const userRouter = createTRPCRouter({
});
}
const { id, ...rest } = input;
const { id, accessedGitProviders, ...rest } = input;
const licensed = await hasValidLicense(
ctx.session?.activeOrganizationId || "",
);
await db
.update(member)
.set({
...rest,
...(licensed && accessedGitProviders !== undefined
? { accessedGitProviders }
: {}),
})
.where(
and(