diff --git a/apps/customer-portal/backend/Dependencies.toml b/apps/customer-portal/backend/Dependencies.toml index 16f0d06cf3..d838f29c48 100644 --- a/apps/customer-portal/backend/Dependencies.toml +++ b/apps/customer-portal/backend/Dependencies.toml @@ -348,7 +348,6 @@ modules = [ {org = "wso2", packageName = "customer_portal", moduleName = "customer_portal"}, {org = "wso2", packageName = "customer_portal", moduleName = "customer_portal.authorization"}, {org = "wso2", packageName = "customer_portal", moduleName = "customer_portal.entity"}, - {org = "wso2", packageName = "customer_portal", moduleName = "customer_portal.scim"}, - {org = "wso2", packageName = "customer_portal", moduleName = "customer_portal.types"} + {org = "wso2", packageName = "customer_portal", moduleName = "customer_portal.scim"} ] diff --git a/apps/customer-portal/backend/constants.bal b/apps/customer-portal/backend/constants.bal index b66e0d39ae..7c8a3fbdd3 100644 --- a/apps/customer-portal/backend/constants.bal +++ b/apps/customer-portal/backend/constants.bal @@ -23,7 +23,7 @@ const ERR_MSG_FETCHING_PROJECT_DETAILS = "Error retrieving project details!"; const ERR_MSG_PROJECT_ID_EMPTY = "Project ID cannot be empty!"; const ERR_MSG_CASE_ID_EMPTY = "Case ID cannot be empty!"; const ERR_LIMIT_OFFSET_INVALID = "Limit must be between 1 and 50. Offset must be a non-negative integer!"; -const ERR_MSG_UNAUTHORIZED_ACCESS = "You're not authorized to access this service. Please sign in again." + +const ERR_MSG_UNAUTHORIZED_ACCESS = "You're not authorized to access this service. Please sign in again. " + "If the issue continues, contact support."; // Default Pagination Values diff --git a/apps/customer-portal/backend/modules/entity/entity.bal b/apps/customer-portal/backend/modules/entity/entity.bal index c9fa461c8f..6f9d1890f5 100644 --- a/apps/customer-portal/backend/modules/entity/entity.bal +++ b/apps/customer-portal/backend/modules/entity/entity.bal @@ -88,6 +88,15 @@ public isolated function getCase(string idToken, string caseId) returns CaseResp return csEntityClient->/cases/[caseId].get(generateHeaders(idToken)); } +# Create a new case. +# +# + idToken - ID token for authorization +# + payload - Case creation payload +# + return - Case creation response or error +public isolated function createCase(string idToken, CaseCreatePayload payload) returns CaseCreateResponse|error { + return csEntityClient->/cases.post(payload, generateHeaders(idToken)); +} + # Search cases of a project. # # + idToken - ID token for authorization diff --git a/apps/customer-portal/backend/modules/entity/types.bal b/apps/customer-portal/backend/modules/entity/types.bal index aed948d953..0e2fab5ff9 100644 --- a/apps/customer-portal/backend/modules/entity/types.bal +++ b/apps/customer-portal/backend/modules/entity/types.bal @@ -116,6 +116,53 @@ public type Subscription record {| json...; |}; +# Payload for creating a case. +public type CaseCreatePayload record {| + # Project ID + @constraint:String {minLength: 1} + string projectId; + # Deployment ID + @constraint:String {minLength: 1} + string deploymentId; + # Product ID + @constraint:String {minLength: 1} + string productId; + # Case title + @constraint:String {minLength: 1, maxLength: 500} + string title; + # Case description + @constraint:String {minLength: 1, maxLength: 65000} + string description; + # Issue type ID + int issueTypeKey; + # Severity key + int severityKey; +|}; + +# Response from creating a case. +public type CaseCreateResponse record {| + # Success message + string message; + # Created case details + CreatedCase case; +|}; + +# Created case details. +public type CreatedCase record {| + # System ID of the created case + string id; + # WSO2 internal ID of the case + string internalId; + # Case number + string number; + # User who created the case + string createdBy; + # Created date and time + string createdOn; + # Status + ChoiceListItem state; +|}; + # Base case. public type Case record {| # Case ID @@ -134,10 +181,12 @@ public type Case record {| ReferenceTableItem? assignedEngineer; # Associated project ReferenceTableItem? project; - # Type of the case - ReferenceTableItem? 'type; # Deployment information ReferenceTableItem? deployment; + # Deployed product information + ReferenceTableItem? deployedProduct; + # issue type of the case + ChoiceListItem? issueType; # Status information ChoiceListItem? state; # Severity information diff --git a/apps/customer-portal/backend/service.bal b/apps/customer-portal/backend/service.bal index 65e9210524..e0ff1b3d66 100644 --- a/apps/customer-portal/backend/service.bal +++ b/apps/customer-portal/backend/service.bal @@ -634,6 +634,57 @@ service http:InterceptableService / on new http:Listener(9090) { return caseResponse; } + # Create a new case. + # + # + payload - Case creation payload + # + return - Success message or error response + resource function post cases(http:RequestContext ctx, entity:CaseCreatePayload payload) + returns http:Created|http:BadRequest|http:Unauthorized|http:Forbidden|http:InternalServerError { + + authorization:UserInfoPayload|error userInfo = ctx.getWithType(authorization:HEADER_USER_INFO); + if userInfo is error { + return { + body: { + message: ERR_MSG_USER_INFO_HEADER_NOT_FOUND + } + }; + } + + entity:CaseCreateResponse|error createdCaseResponse = entity:createCase(userInfo.idToken, payload); + if createdCaseResponse is error { + if getStatusCode(createdCaseResponse) == http:STATUS_UNAUTHORIZED { + log:printWarn(string `User: ${userInfo.userId} is not authorized to access the customer portal!`); + return { + body: { + message: ERR_MSG_UNAUTHORIZED_ACCESS + } + }; + } + + if getStatusCode(createdCaseResponse) == http:STATUS_FORBIDDEN { + log:printWarn(string `User: ${userInfo.userId} is forbidden to create a case for project: ${ + payload.projectId}!`); + return { + body: { + message: "You're not authorized to create a case for the selected project. " + + "Please check your access permissions or contact support." + } + }; + } + + string customError = "Failed to create a new case."; + log:printError(customError, createdCaseResponse); + return { + body: { + message: customError + } + }; + } + return { + body: createdCaseResponse.case + }; + } + # Search cases for a specific project with filters and pagination. # # + id - ID of the project diff --git a/apps/customer-portal/backend/types.bal b/apps/customer-portal/backend/types.bal index a83d5b1c62..e5d292e01f 100644 --- a/apps/customer-portal/backend/types.bal +++ b/apps/customer-portal/backend/types.bal @@ -67,8 +67,10 @@ public type Case record {| ReferenceItem? assignedEngineer; # Associated project ReferenceItem? project; - # Type of the case - ReferenceItem? 'type; + # Deployed product information + ReferenceItem? deployedProduct; + # issueType of the case + ReferenceItem? issueType; # Deployment ReferenceItem? deployment; # Severity of the case diff --git a/apps/customer-portal/backend/utils.bal b/apps/customer-portal/backend/utils.bal index 39dc94ed3e..4e16ca2f2a 100644 --- a/apps/customer-portal/backend/utils.bal +++ b/apps/customer-portal/backend/utils.bal @@ -40,7 +40,8 @@ public isolated function searchCases(string idToken, string projectId, CaseSearc entity:CaseSearchResponse casesResponse = check entity:searchCases(idToken, searchPayload); Case[] cases = from entity:Case case in casesResponse.cases let entity:ReferenceTableItem? project = case.project - let entity:ReferenceTableItem? 'type = case.'type + let entity:ReferenceTableItem? deployedProduct = case.deployedProduct + let entity:ChoiceListItem? issueType = case.issueType let entity:ReferenceTableItem? deployment = case.deployment let entity:ReferenceTableItem? assignedEngineer = case.assignedEngineer let entity:ChoiceListItem? severity = case.severity @@ -53,7 +54,8 @@ public isolated function searchCases(string idToken, string projectId, CaseSearc createdOn: case.createdOn, description: case.description, project: project != () ? {id: project.id, label: project.name} : (), - 'type: 'type != () ? {id: 'type.id, label: 'type.name} : (), + deployedProduct: deployedProduct != () ? {id: deployedProduct.id, label: deployedProduct.name} : (), + issueType: issueType != () ? {id: issueType.id.toString(), label: issueType.label} : (), deployment: deployment != () ? {id: deployment.id, label: deployment.name} : (), assignedEngineer: assignedEngineer != () ? {id: assignedEngineer.id, label: assignedEngineer.name} : (), severity: severity != () ? {id: severity.id.toString(), label: severity.label} : (),