Skip to content
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
145 changes: 96 additions & 49 deletions entity-service/internal/domain/entity.go
Original file line number Diff line number Diff line change
Expand Up @@ -799,24 +799,24 @@ type CaseView struct {

// SearchCasesFilters holds all optional filter criteria for a case search.
type SearchCasesFilters struct {
Types []string `json:"types"`
SearchQuery string `json:"searchQuery"`
ProjectIDs []string `json:"projectIds"`
DeploymentIDs []string `json:"deploymentIds"`
States []CaseState `json:"states"`
Severities []CaseSeverity `json:"severities"`
IssueTypes []CaseIssueType `json:"issueTypes"`
EngagementTypes []EngagementType `json:"engagementTypes"`
ClosedStartDate *time.Time `json:"closedStartDate"`
ClosedEndDate *time.Time `json:"closedEndDate"`
StartCreatedDate *time.Time `json:"startCreatedDate"`
EndCreatedDate *time.Time `json:"endCreatedDate"`
StartUpdatedDate *time.Time `json:"startUpdatedDate"`
EndUpdatedDate *time.Time `json:"endUpdatedDate"`
CreatedBy []string `json:"createdBy"`
CreatedByMe bool `json:"createdByMe"`
WorkStates []CaseWorkState `json:"workStates"`
AssignedUserIDs []string `json:"assignedUserIds"`
Types []string `json:"types"`
SearchQuery string `json:"searchQuery"`
ProjectIDs []string `json:"projectIds"`
DeploymentIDs []string `json:"deploymentIds"`
States []CaseState `json:"states"`
Severities []CaseSeverity `json:"severities"`
IssueTypes []CaseIssueType `json:"issueTypes"`
EngagementTypes []EngagementType `json:"engagementTypes"`
ClosedStartDate *time.Time `json:"closedStartDate"`
ClosedEndDate *time.Time `json:"closedEndDate"`
StartCreatedDate *time.Time `json:"startCreatedDate"`
EndCreatedDate *time.Time `json:"endCreatedDate"`
StartUpdatedDate *time.Time `json:"startUpdatedDate"`
EndUpdatedDate *time.Time `json:"endUpdatedDate"`
CreatedBy []string `json:"createdBy"`
CreatedByMe bool `json:"createdByMe"`
WorkStates []CaseWorkState `json:"workStates"`
AssignedUserIDs []string `json:"assignedUserIds"`
}

// SearchCasesRequest is the input for a case search operation.
Expand Down Expand Up @@ -864,8 +864,6 @@ type SearchCasesResponse struct {
Limit int `json:"limit"`
}



// UpdateCaseRequest is the input for PATCH /cases/{id}.
// Exactly one of State, Severity, WorkState, WatchList, or AssigneeEmail must be provided.
// WatchList and AssigneeEmail are only supported for the ServiceNow data source.
Expand Down Expand Up @@ -939,25 +937,25 @@ type CaseAttachment struct {
// For type "service_request": catalogId, catalogItemId, and variables are required.
// For type "security_report_analysis": subject, description, and at least one attachment are required.
type CreateCaseRequest struct {
CreatedBy string `json:"-"`
Type string `json:"type"`
ProjectID string `json:"projectId"`
DeploymentID string `json:"deploymentId"`
DeployedProductID string `json:"deployedProductId"`
Subject string `json:"subject"`
Description string `json:"description"`
Severity CaseSeverity `json:"severity"`
IssueType CaseIssueType `json:"issueType"`
CreatedBy string `json:"-"`
Type string `json:"type"`
ProjectID string `json:"projectId"`
DeploymentID string `json:"deploymentId"`
DeployedProductID string `json:"deployedProductId"`
Subject string `json:"subject"`
Description string `json:"description"`
Severity CaseSeverity `json:"severity"`
IssueType CaseIssueType `json:"issueType"`
// For service_request type
CatalogID string `json:"catalogId"`
CatalogItemID string `json:"catalogItemId"`
Variables []Variable `json:"variables"`
CatalogID string `json:"catalogId"`
CatalogItemID string `json:"catalogItemId"`
Variables []Variable `json:"variables"`
// Optional fields
RelatedCaseID string `json:"relatedCaseId"`
ConversationID string `json:"conversationId"`
WatchList []string `json:"watchList"`
RelatedCaseID string `json:"relatedCaseId"`
ConversationID string `json:"conversationId"`
WatchList []string `json:"watchList"`
// For security_report_analysis type
Attachments []CaseAttachment `json:"attachments"`
Attachments []CaseAttachment `json:"attachments"`
}

// CommentType classifies the type of a case comment.
Expand Down Expand Up @@ -1012,9 +1010,9 @@ type CaseCommentDetail struct {
// SearchCaseCommentsRequest is the input for listing comments on a case.
// CaseID is populated from the URL path parameter and is not part of the JSON body.
type SearchCaseCommentsRequest struct {
CaseID string `json:"-"`
CaseID string `json:"-"`
Filters *CommentFilters `json:"filters"`
Pagination Pagination `json:"pagination"`
Pagination Pagination `json:"pagination"`
}

// CommentFilters holds optional filter criteria for searching case comments.
Expand All @@ -1038,13 +1036,13 @@ type Attachment struct {
ReferenceID string `json:"referenceId"`
ReferenceType ReferenceType `json:"referenceType"`
Name string `json:"name"`
Type string `json:"type"`
SizeBytes int `json:"sizeBytes"`
Description *string `json:"description"`
CreatedBy string `json:"createdBy"`
CreatedOn time.Time `json:"createdOn"`
DownloadURL *string `json:"downloadUrl"`
PreviewURL *string `json:"previewUrl"`
Type string `json:"type"`
SizeBytes int `json:"sizeBytes"`
Description *string `json:"description"`
CreatedBy string `json:"createdBy"`
CreatedOn time.Time `json:"createdOn"`
DownloadURL *string `json:"downloadUrl"`
PreviewURL *string `json:"previewUrl"`
}

// CreateAttachmentRequest is the input for POST /attachments.
Expand Down Expand Up @@ -1284,10 +1282,14 @@ const (

// SearchTimeCardsFilters holds optional filter criteria for POST /time-cards/search.
type SearchTimeCardsFilters struct {
ProjectIDs []string `json:"projectIds,omitempty"`
StartDate *string `json:"startDate,omitempty"`
EndDate *string `json:"endDate,omitempty"`
States []TimeCardState `json:"states,omitempty"`
ProjectIDs []string `json:"projectIds,omitempty"`
CaseID *string `json:"caseId,omitempty"`
UserID *string `json:"userId,omitempty"`
ApproverID *string `json:"approverId,omitempty"` // eligible approver (SN approver_list)
ApprovedByID *string `json:"approvedById,omitempty"` // who actually approved (SN approved_by)
StartDate *string `json:"startDate,omitempty"`
EndDate *string `json:"endDate,omitempty"`
States []TimeCardState `json:"states,omitempty"`
}

// SearchTimeCardsRequest is the request body for POST /time-cards/search.
Expand Down Expand Up @@ -1330,6 +1332,51 @@ type SearchTimeCardsResponse struct {
Offset int `json:"offset"`
}

// CreateTimeCardRequest is the request body for POST /time-cards. The submitter
// is taken from the authenticated session, never from the payload; the card is
// created in the "submitted" state. approverIds populate the eligible-approver list.
type CreateTimeCardRequest struct {
CaseID string `json:"caseId"`
ProjectID string `json:"projectId"`
Date string `json:"date"` // YYYY-MM-DD
ApproverIDs []string `json:"approverIds"`
IsBillable bool `json:"isBillable"`
IssueComplexity *string `json:"issueComplexity,omitempty"`
WorkLogComment *string `json:"workLogComment,omitempty"`
TimeAnalyzing int `json:"timeAnalyzing"`
TimeSettingUp int `json:"timeSettingUp"`
TimeReproducingDebugging int `json:"timeReproducingDebugging"`
TimeProvidingSolution int `json:"timeProvidingSolution"`
TimePatching int `json:"timePatching"`
}

// UpdateTimeCardRequest is the request body for PATCH /time-cards/{id}. ID is
// injected from the path. It carries EITHER editable fields (submitter, while the
// card is submitted) OR a state transition: State="approved", or State="rejected"
// with LeadComment. SN enforces authorization (submitter for edits, an eligible
// approver in approver_list for transitions).
type UpdateTimeCardRequest struct {
ID string `json:"-"`
State *TimeCardState `json:"state,omitempty"`
LeadComment *string `json:"leadComment,omitempty"`
Date *string `json:"date,omitempty"`
ApproverIDs []string `json:"approverIds,omitempty"`
IsBillable *bool `json:"isBillable,omitempty"`
IssueComplexity *string `json:"issueComplexity,omitempty"`
WorkLogComment *string `json:"workLogComment,omitempty"`
TimeAnalyzing *int `json:"timeAnalyzing,omitempty"`
TimeSettingUp *int `json:"timeSettingUp,omitempty"`
TimeReproducingDebugging *int `json:"timeReproducingDebugging,omitempty"`
TimeProvidingSolution *int `json:"timeProvidingSolution,omitempty"`
TimePatching *int `json:"timePatching,omitempty"`
}

// TimeCardMutationResponse is returned by create and update.
type TimeCardMutationResponse struct {
Message string `json:"message,omitempty"`
TimeCard *TimeCardView `json:"timeCard,omitempty"`
}

// ChangeRequest is the full change request detail returned by GET /change-requests/{id}.
// It extends SearchChangeRequestView with additional fields.
type ChangeRequest struct {
Expand Down
38 changes: 38 additions & 0 deletions entity-service/internal/handler/time_card_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"encoding/json"
"net/http"

"github.com/wso2-open-operations/cs-tools/entity-service/internal/apierror"
"github.com/wso2-open-operations/cs-tools/entity-service/internal/domain"
"github.com/wso2-open-operations/cs-tools/entity-service/internal/service"
)
Expand Down Expand Up @@ -48,3 +49,40 @@ func (h *TimeCardHandler) SearchTimeCards(w http.ResponseWriter, r *http.Request
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(resp)
}

// CreateTimeCard handles POST /time-cards.
func (h *TimeCardHandler) CreateTimeCard(w http.ResponseWriter, r *http.Request) {
var req domain.CreateTimeCardRequest
if !decodeRequest(w, r, &req) {
return
}
resp, err := h.svc.CreateTimeCard(r.Context(), req)
if err != nil {
writeServiceError(w, r, err)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
_ = json.NewEncoder(w).Encode(resp)
}

// UpdateTimeCard handles PATCH /time-cards/{id}.
func (h *TimeCardHandler) UpdateTimeCard(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
if id == "" {
apierror.WriteJSON(w, http.StatusBadRequest, "time card ID is required")
return
}
var req domain.UpdateTimeCardRequest
if !decodeRequest(w, r, &req) {
return
}
req.ID = id
resp, err := h.svc.UpdateTimeCard(r.Context(), req)
if err != nil {
writeServiceError(w, r, err)
return
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(resp)
}
4 changes: 3 additions & 1 deletion entity-service/internal/server/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,8 @@ func NewRouter(db *pgxpool.Pool, cfg *config.Config) http.Handler {

if timeCardHandler != nil {
mux.HandleFunc("POST /time-cards/search", timeCardHandler.SearchTimeCards)
mux.HandleFunc("POST /time-cards", timeCardHandler.CreateTimeCard)
mux.HandleFunc("PATCH /time-cards/{id}", timeCardHandler.UpdateTimeCard)
}

if catalogHandler != nil {
Expand All @@ -192,7 +194,7 @@ func NewRouter(db *pgxpool.Pool, cfg *config.Config) http.Handler {

if productVulnerabilityHandler != nil {
mux.HandleFunc("POST /products/vulnerabilities/search", productVulnerabilityHandler.SearchProductVulnerabilities)
mux.HandleFunc("GET /products/vulnerabilities/{id}", productVulnerabilityHandler.GetProductVulnerability)
mux.HandleFunc("GET /products/vulnerabilities/{id}", productVulnerabilityHandler.GetProductVulnerability)
}

return middleware.CorrelationID(
Expand Down
8 changes: 6 additions & 2 deletions entity-service/internal/service/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,8 +202,13 @@ type ChangeRequestService interface {
// TimeCardService defines the operations available on the time-cards entity.
type TimeCardService interface {
// SearchTimeCards returns a paginated list of time cards filtered by optional
// project IDs, date range, and states.
// project IDs, case, user, approver, date range, and states.
SearchTimeCards(ctx context.Context, req domain.SearchTimeCardsRequest) (domain.SearchTimeCardsResponse, error)
// CreateTimeCard logs a new time card against a case in the submitted state.
CreateTimeCard(ctx context.Context, req domain.CreateTimeCardRequest) (domain.TimeCardMutationResponse, error)
// UpdateTimeCard edits an editable (submitted) time card, or transitions its
// state (approve/reject) when req.State is set. SN enforces authorization.
UpdateTimeCard(ctx context.Context, req domain.UpdateTimeCardRequest) (domain.TimeCardMutationResponse, error)
}

// ProductVulnerabilityService defines the operations available on product vulnerabilities.
Expand All @@ -217,5 +222,4 @@ type ProductVulnerabilityService interface {
// GetProductVulnerability returns the detail of a single vulnerability by its UUID.
// A NotFoundError is returned if the vulnerability does not exist.
GetProductVulnerability(ctx context.Context, id string) (domain.ProductVulnerabilityView, error)

}
Loading