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 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)
|
||||
- [Experimental][Sourcebot EE] Added REST API to get users and delete a user. [#578](https://github.com/sourcebot-dev/sourcebot/pull/578)
|
||||
|
||||
### Fixed
|
||||
- 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:
|
||||
|
||||
- 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.
|
||||
- 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