Skip to content

Commit a95a5a3

Browse files
committed
Fix project re-registration fails after deletion
1 parent 52ed598 commit a95a5a3

2 files changed

Lines changed: 99 additions & 23 deletions

File tree

icp_server/modules/storage/project_repository.bal

Lines changed: 77 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ public isolated function createProject(types:ProjectInput project, types:UserCon
5757
// Check for duplicate handler within the same org
5858
sql:ParameterizedQuery handlerCheckQuery = `SELECT COUNT(*) as cnt FROM projects WHERE org_id = ${project.orgId} AND handler = ${handler}`;
5959
stream<record {|int cnt;|}, sql:Error?> handlerCheckStream = dbClient->query(handlerCheckQuery);
60-
record {|int cnt;|}[] handlerCheckResult = check from record {|int cnt;|} r in handlerCheckStream select r;
60+
record {|int cnt;|}[] handlerCheckResult = check from record {|int cnt;|} r in handlerCheckStream
61+
select r;
6162
if handlerCheckResult.length() > 0 && handlerCheckResult[0].cnt > 0 {
6263
return error(string `Project handler '${handler}' is already taken in this organization`);
6364
}
@@ -98,8 +99,8 @@ public isolated function createProject(types:ProjectInput project, types:UserCon
9899

99100
// 1. Create project admin group
100101
string adminGroupId = uuid:createType1AsString();
101-
string groupName = string `${project.name} Admins`;
102-
string groupDescription = string `Admin group for project: ${project.name}`;
102+
string groupName = string `${handler} Admins`;
103+
string groupDescription = string `Admin group for project: ${handler}`;
103104

104105
sql:ExecutionResult|sql:Error groupResult = dbClient->execute(`
105106
INSERT INTO user_groups (group_id, group_name, org_uuid, description)
@@ -154,10 +155,18 @@ public isolated function createProject(types:ProjectInput project, types:UserCon
154155
// RBAC setup errors arrive as plain errors with their own messages via `fail`.
155156
if e is sql:Error {
156157
match classifySqlError(e) {
157-
DUPLICATE_KEY => { return error("A project with this name or handler already exists in this organization", e); }
158-
VALUE_TOO_LONG => { return error("The provided value exceeds the maximum allowed length", e); }
159-
FOREIGN_KEY_VIOLATION => { return error("Cannot complete the operation due to a dependency constraint", e); }
160-
_ => { return error("An unexpected error occurred. Please contact your administrator.", e); }
158+
DUPLICATE_KEY => {
159+
return error("A project with this name or handler already exists in this organization", e);
160+
}
161+
VALUE_TOO_LONG => {
162+
return error("The provided value exceeds the maximum allowed length", e);
163+
}
164+
FOREIGN_KEY_VIOLATION => {
165+
return error("Cannot complete the operation due to a dependency constraint", e);
166+
}
167+
_ => {
168+
return error("An unexpected error occurred. Please contact your administrator.", e);
169+
}
161170
}
162171
}
163172
return e;
@@ -336,9 +345,15 @@ public isolated function updateProjectWithInput(types:ProjectUpdateInput project
336345
log:printError(string `Failed to update project ${project.id}`, 'error = e);
337346
if e is sql:Error {
338347
match classifySqlError(e) {
339-
DUPLICATE_KEY => { return error("A project with this name already exists in this organization", e); }
340-
VALUE_TOO_LONG => { return error("The provided value exceeds the maximum allowed length", e); }
341-
_ => { return error("An unexpected error occurred. Please contact your administrator.", e); }
348+
DUPLICATE_KEY => {
349+
return error("A project with this name already exists in this organization", e);
350+
}
351+
VALUE_TOO_LONG => {
352+
return error("The provided value exceeds the maximum allowed length", e);
353+
}
354+
_ => {
355+
return error("An unexpected error occurred. Please contact your administrator.", e);
356+
}
342357
}
343358
}
344359
return error("An unexpected error occurred while updating the project. Please contact your administrator.", e);
@@ -347,17 +362,60 @@ public isolated function updateProjectWithInput(types:ProjectUpdateInput project
347362
return ();
348363
}
349364

350-
// Delete a project by ID (this will cascade delete all components, runtimes, roles, and user role assignments)
365+
// Delete a project by ID and clean up the auto-created project admin group.
351366
public isolated function deleteProject(string projectId) returns error? {
352-
sql:ParameterizedQuery deleteQuery = `DELETE FROM projects WHERE project_id = ${projectId}`;
353-
var result = dbClient->execute(deleteQuery);
354-
if result is sql:Error {
355-
log:printError(string `Failed to delete project ${projectId}`, 'error = result);
356-
match classifySqlError(result) {
357-
FOREIGN_KEY_VIOLATION => { return error("Cannot delete project because it has dependent resources", result); }
358-
_ => { return error("An unexpected error occurred. Please contact your administrator.", result); }
367+
do {
368+
transaction {
369+
record {|string name; string handler; int org_id;|}|sql:Error projectRecord = dbClient->queryRow(
370+
`SELECT name, handler, org_id FROM projects WHERE project_id = ${projectId}`
371+
);
372+
if projectRecord is sql:NoRowsError {
373+
fail error(string `Project with ID ${projectId} not found`);
374+
}
375+
if projectRecord is sql:Error {
376+
fail projectRecord;
377+
}
378+
379+
string projectAdminRoleId = check getProjectAdminRoleId();
380+
string handlerBasedAdminGroup = string `${projectRecord.handler} Admins`;
381+
382+
sql:ExecutionResult|sql:Error groupDeleteResult = dbClient->execute(`
383+
DELETE FROM user_groups
384+
WHERE group_name = ${handlerBasedAdminGroup}
385+
AND org_uuid = ${projectRecord.org_id}
386+
AND group_id IN (
387+
SELECT group_id FROM group_role_mapping
388+
WHERE project_uuid = ${projectId} AND role_id = ${projectAdminRoleId}
389+
)
390+
`);
391+
if groupDeleteResult is sql:Error {
392+
fail groupDeleteResult;
393+
}
394+
395+
sql:ExecutionResult|sql:Error projectDeleteResult = dbClient->execute(
396+
`DELETE FROM projects WHERE project_id = ${projectId}`
397+
);
398+
if projectDeleteResult is sql:Error {
399+
fail projectDeleteResult;
400+
}
401+
402+
check commit;
359403
}
404+
} on fail error e {
405+
log:printError(string `Failed to delete project ${projectId}`, 'error = e);
406+
if e is sql:Error {
407+
match classifySqlError(e) {
408+
FOREIGN_KEY_VIOLATION => {
409+
return error("Cannot delete project because it has dependent resources", e);
410+
}
411+
_ => {
412+
return error("An unexpected error occurred. Please contact your administrator.", e);
413+
}
414+
}
415+
}
416+
return e;
360417
}
418+
361419
log:printInfo(string `Successfully deleted project ${projectId}`);
362420
return ();
363421
}
@@ -424,4 +482,4 @@ public isolated function checkProjectHandlerAvailability(int orgId, string proje
424482
alternateHandlerCandidate: alternateCandidate
425483
};
426484
}
427-
485+

icp_server/modules/storage/secret_repository.bal

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -301,8 +301,22 @@ public isolated function resolveOrCreateProject(string handler, string? createdB
301301
VALUES (${adminGroupId}, ${groupName}, ${DEFAULT_ORG_ID}, ${string `Admin group for project: ${handler}`})
302302
`);
303303
if groupRes is sql:Error {
304-
log:printError(string `resolveOrCreateProject: failed to create admin group for handler=${handler}`, 'error = groupRes);
305-
fail error("Failed to set up project admin group", groupRes);
304+
if classifySqlError(groupRes) == DUPLICATE_KEY {
305+
record {|string group_id;|}|sql:Error existingGroup = dbClient->queryRow(`
306+
SELECT group_id FROM user_groups
307+
WHERE group_name = ${groupName} AND org_uuid = ${DEFAULT_ORG_ID}
308+
`);
309+
if existingGroup is record {|string group_id;|} {
310+
adminGroupId = existingGroup.group_id;
311+
log:printWarn(string `resolveOrCreateProject: reusing existing admin group for handler=${handler}`, groupId = adminGroupId);
312+
} else {
313+
log:printError(string `resolveOrCreateProject: duplicate admin group but lookup failed for handler=${handler}`, 'error = existingGroup);
314+
fail error("Failed to resolve duplicate project admin group", existingGroup);
315+
}
316+
} else {
317+
log:printError(string `resolveOrCreateProject: failed to create admin group for handler=${handler}`, 'error = groupRes);
318+
fail error("Failed to set up project admin group", groupRes);
319+
}
306320
}
307321

308322
string roleId = check getProjectAdminRoleId();
@@ -322,8 +336,12 @@ public isolated function resolveOrCreateProject(string handler, string? createdB
322336
VALUES (${adminGroupId}, ${createdBy})
323337
`);
324338
if userRes is sql:Error {
325-
log:printError(string `resolveOrCreateProject: failed to add creator to admin group for handler=${handler}`, 'error = userRes);
326-
fail error("Failed to add user to project admin group", userRes);
339+
if classifySqlError(userRes) == DUPLICATE_KEY {
340+
log:printWarn(string `resolveOrCreateProject: creator already belongs to admin group for handler=${handler}`, groupId = adminGroupId, userId = createdBy);
341+
} else {
342+
log:printError(string `resolveOrCreateProject: failed to add creator to admin group for handler=${handler}`, 'error = userRes);
343+
fail error("Failed to add user to project admin group", userRes);
344+
}
327345
}
328346
}
329347

0 commit comments

Comments
 (0)