Skip to content

Account Management API: Added v2 API endpoint to remove collaborators… #7751

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 11, 2024
Merged
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 @@ -3,14 +3,16 @@ import { z } from "../../../framework";
import { FailedAPIOperationSchema, OkAPIOperationSchema } from "../../common";

import { ProjectIdSchema } from "../common";
import { AccountIdSchema } from "../../accounts/common";

// OpenAPI spec
//
export const RemoveProjectCollaboratorInputSchema = z
.object({
project_id: ProjectIdSchema,
account_id: AccountIdSchema,
})
.describe("Remove a collaborator from a project.");
.describe("Remove a collaborator from an existing project.");

export const RemoveProjectCollaboratorOutputSchema = z.union([
FailedAPIOperationSchema,
Expand Down
4 changes: 2 additions & 2 deletions src/packages/next/pages/api/v2/projects/collaborators/add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ async function handle(req, res) {
}

export default apiRoute({
add: apiRouteOperation({
addProjectCollaborator: apiRouteOperation({
method: "POST",
openApiOperation: {
tags: ["Projects", "Administrators"],
tags: ["Projects", "Admin"],
},
})
.input({
Expand Down
61 changes: 61 additions & 0 deletions src/packages/next/pages/api/v2/projects/collaborators/remove.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
API endpoint to remove a user from an existing project.

Permissions checks are performed by the underlying API call and are NOT
executed at this stage.

*/
import { db } from "@cocalc/database";
import { remove_collaborators_from_projects } from "@cocalc/server/projects/collab";

import getAccountId from "lib/account/get-account";
import getParams from "lib/api/get-params";
import { apiRoute, apiRouteOperation } from "lib/api";
import { OkStatus } from "lib/api/status";
import {
RemoveProjectCollaboratorInputSchema,
RemoveProjectCollaboratorOutputSchema,
} from "lib/api/schema/projects/collaborators/remove";

async function handle(req, res) {
const { project_id, account_id } = getParams(req);
const client_account_id = await getAccountId(req);

try {
if (!client_account_id) {
throw Error("must be signed in");
}

await remove_collaborators_from_projects(
db(),
client_account_id,
[account_id],
[project_id],
);

res.json(OkStatus);
} catch (err) {
res.json({ error: err.message });
}
}

export default apiRoute({
removeProjectCollaborator: apiRouteOperation({
method: "POST",
openApiOperation: {
tags: ["Projects", "Admin"],
},
})
.input({
contentType: "application/json",
body: RemoveProjectCollaboratorInputSchema,
})
.outputs([
{
status: 200,
contentType: "application/json",
body: RemoveProjectCollaboratorOutputSchema,
},
])
.handler(handle),
});
2 changes: 1 addition & 1 deletion src/packages/next/pages/api/v2/shopping/cart/add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ async function add(req): Promise<number | undefined> {
}

export default apiRoute({
add: apiRouteOperation({
addCartItem: apiRouteOperation({
method: "POST",
openApiOperation: {
tags: ["Shopping"],
Expand Down
38 changes: 38 additions & 0 deletions src/packages/server/projects/collab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,44 @@ export async function add_collaborators_to_projects(
}
}

export async function remove_collaborators_from_projects(
db: PostgreSQL,
account_id: string,
accounts: string[],
projects: string[], // can be empty strings if tokens specified (since they determine project_id)
): Promise<void> {
try {
// Ensure user is allowed to modify project(s)
//
await verify_write_access_to_projects(db, account_id, projects);
} catch (err) {
// Users can always remove themselves from a project.
//
if (accounts.length == 1 && account_id == accounts[0]) {
await verify_course_access_to_project(db, account_id, projects[0]);
} else {
throw err;
}
}

/* Right now this function is called from outside typescript
(e.g., api from user), so we have to do extra type checking.
Also, the input is uuid's, which typescript can't check. */
verify_types(account_id, accounts, projects);

// Remove users from projects
//
for (const i in projects) {
const project_id: string = projects[i];
const account_id: string = accounts[i];

await callback2(db.remove_user_from_project, {
project_id,
account_id,
});
}
}

// This is only meant to be used here in support of
// add_collaborators_to_projects -- do not export it.
async function verify_write_access_to_projects(
Expand Down