mirror of
https://github.com/sourcebot-dev/sourcebot.git
synced 2025-12-11 20:05:25 +00:00
feat(ee): Add REST API to get users and delete a user (#578)
* add get users and delete user endpoints * changelog * changelog typo * update license * add tags to changelog
This commit is contained in:
parent
5b1caae854
commit
3ff88da33b
4 changed files with 176 additions and 1 deletions
|
|
@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
- Added support for GitHub Apps for service auth. [#570](https://github.com/sourcebot-dev/sourcebot/pull/570)
|
- Added support for GitHub Apps for service auth. [#570](https://github.com/sourcebot-dev/sourcebot/pull/570)
|
||||||
- Added prometheus metrics for repo index manager. [#571](https://github.com/sourcebot-dev/sourcebot/pull/571)
|
- Added prometheus metrics for repo index manager. [#571](https://github.com/sourcebot-dev/sourcebot/pull/571)
|
||||||
- Added experimental environment variable to disable API key creation for non-admin users. [#577](https://github.com/sourcebot-dev/sourcebot/pull/577)
|
- Added experimental environment variable to disable API key creation for non-admin users. [#577](https://github.com/sourcebot-dev/sourcebot/pull/577)
|
||||||
|
- [Experimental][Sourcebot EE] Added REST API to get users and delete a user. [#578](https://github.com/sourcebot-dev/sourcebot/pull/578)
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Fixed "dubious ownership" errors when cloning / fetching repos. [#553](https://github.com/sourcebot-dev/sourcebot/pull/553)
|
- Fixed "dubious ownership" errors when cloning / fetching repos. [#553](https://github.com/sourcebot-dev/sourcebot/pull/553)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ Copyright (c) 2025 Taqla Inc.
|
||||||
|
|
||||||
Portions of this software are licensed as follows:
|
Portions of this software are licensed as follows:
|
||||||
|
|
||||||
- All content that resides under the "ee/", "packages/web/src/ee/", "packages/backend/src/ee/", and "packages/shared/src/ee/" directories of this repository, if these directories exist, is licensed under the license defined in "ee/LICENSE".
|
- All content located within any folder or subfolder named “ee” in this repository is licensed under the terms specified in “ee/LICENSE”,
|
||||||
- All third party components incorporated into the Sourcebot Software are licensed under the original license provided by the owner of the applicable component.
|
- All third party components incorporated into the Sourcebot Software are licensed under the original license provided by the owner of the applicable component.
|
||||||
- Content outside of the above mentioned directories or restrictions above is available under the "Functional Source License" as defined below.
|
- Content outside of the above mentioned directories or restrictions above is available under the "Functional Source License" as defined below.
|
||||||
|
|
||||||
|
|
|
||||||
93
packages/web/src/app/api/(server)/ee/user/route.ts
Normal file
93
packages/web/src/app/api/(server)/ee/user/route.ts
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
'use server';
|
||||||
|
|
||||||
|
import { withAuthV2, withMinimumOrgRole } from "@/withAuthV2";
|
||||||
|
import { OrgRole } from "@sourcebot/db";
|
||||||
|
import { isServiceError } from "@/lib/utils";
|
||||||
|
import { serviceErrorResponse, missingQueryParam, notFound } from "@/lib/serviceError";
|
||||||
|
import { createLogger } from "@sourcebot/logger";
|
||||||
|
import { NextRequest } from "next/server";
|
||||||
|
import { StatusCodes } from "http-status-codes";
|
||||||
|
import { ErrorCode } from "@/lib/errorCodes";
|
||||||
|
import { getAuditService } from "@/ee/features/audit/factory";
|
||||||
|
|
||||||
|
const logger = createLogger('ee-user-api');
|
||||||
|
const auditService = getAuditService();
|
||||||
|
|
||||||
|
export const DELETE = async (request: NextRequest) => {
|
||||||
|
const url = new URL(request.url);
|
||||||
|
const userId = url.searchParams.get('userId');
|
||||||
|
|
||||||
|
if (!userId) {
|
||||||
|
return serviceErrorResponse(missingQueryParam('userId'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await withAuthV2(async ({ org, role, user: currentUser, prisma }) => {
|
||||||
|
return withMinimumOrgRole(role, OrgRole.OWNER, async () => {
|
||||||
|
try {
|
||||||
|
if (currentUser.id === userId) {
|
||||||
|
return {
|
||||||
|
statusCode: StatusCodes.BAD_REQUEST,
|
||||||
|
errorCode: ErrorCode.INVALID_REQUEST_BODY,
|
||||||
|
message: 'Cannot delete your own user account',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetUser = await prisma.user.findUnique({
|
||||||
|
where: {
|
||||||
|
id: userId,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
email: true,
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!targetUser) {
|
||||||
|
return notFound('User not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
await auditService.createAudit({
|
||||||
|
action: "user.delete",
|
||||||
|
actor: {
|
||||||
|
id: currentUser.id,
|
||||||
|
type: "user"
|
||||||
|
},
|
||||||
|
target: {
|
||||||
|
id: userId,
|
||||||
|
type: "user"
|
||||||
|
},
|
||||||
|
orgId: org.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Delete the user (cascade will handle all related records)
|
||||||
|
await prisma.user.delete({
|
||||||
|
where: {
|
||||||
|
id: userId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info('User deleted successfully', {
|
||||||
|
deletedUserId: userId,
|
||||||
|
deletedByUserId: currentUser.id,
|
||||||
|
orgId: org.id
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: 'User deleted successfully'
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Error deleting user', { error, userId });
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isServiceError(result)) {
|
||||||
|
return serviceErrorResponse(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response.json(result, { status: StatusCodes.OK });
|
||||||
|
};
|
||||||
|
|
||||||
81
packages/web/src/app/api/(server)/ee/users/route.ts
Normal file
81
packages/web/src/app/api/(server)/ee/users/route.ts
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
'use server';
|
||||||
|
|
||||||
|
import { withAuthV2, withMinimumOrgRole } from "@/withAuthV2";
|
||||||
|
import { OrgRole } from "@sourcebot/db";
|
||||||
|
import { isServiceError } from "@/lib/utils";
|
||||||
|
import { serviceErrorResponse } from "@/lib/serviceError";
|
||||||
|
import { createLogger } from "@sourcebot/logger";
|
||||||
|
import { getAuditService } from "@/ee/features/audit/factory";
|
||||||
|
|
||||||
|
const logger = createLogger('ee-users-api');
|
||||||
|
const auditService = getAuditService();
|
||||||
|
|
||||||
|
export const GET = async () => {
|
||||||
|
const result = await withAuthV2(async ({ prisma, org, role, user }) => {
|
||||||
|
return withMinimumOrgRole(role, OrgRole.OWNER, async () => {
|
||||||
|
try {
|
||||||
|
const memberships = await prisma.userToOrg.findMany({
|
||||||
|
where: {
|
||||||
|
orgId: org.id,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
user: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const usersWithActivity = await Promise.all(
|
||||||
|
memberships.map(async (membership) => {
|
||||||
|
const lastActivity = await prisma.audit.findFirst({
|
||||||
|
where: {
|
||||||
|
actorId: membership.user.id,
|
||||||
|
actorType: 'user',
|
||||||
|
orgId: org.id,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
timestamp: 'desc',
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
timestamp: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: membership.user.id,
|
||||||
|
name: membership.user.name,
|
||||||
|
email: membership.user.email,
|
||||||
|
role: membership.role,
|
||||||
|
createdAt: membership.user.createdAt,
|
||||||
|
lastActivityAt: lastActivity?.timestamp ?? null,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
await auditService.createAudit({
|
||||||
|
action: "user.list",
|
||||||
|
actor: {
|
||||||
|
id: user.id,
|
||||||
|
type: "user"
|
||||||
|
},
|
||||||
|
target: {
|
||||||
|
id: org.id.toString(),
|
||||||
|
type: "org"
|
||||||
|
},
|
||||||
|
orgId: org.id
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info('Fetched users list', { count: usersWithActivity.length });
|
||||||
|
return usersWithActivity;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Error fetching users', { error });
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isServiceError(result)) {
|
||||||
|
return serviceErrorResponse(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response.json(result);
|
||||||
|
};
|
||||||
|
|
||||||
Loading…
Reference in a new issue