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
2 changes: 2 additions & 0 deletions apps/csm-portal/backend/cmd/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ func main() {
mux.HandleFunc("GET /cases/{case_id}/attachments/{attachment_id}/content", caseHandler.GetCaseAttachmentContent)
mux.HandleFunc("POST /cases/search", caseHandler.SearchCases)
mux.HandleFunc("POST /service-requests/search", caseHandler.SearchServiceRequests)
mux.HandleFunc("POST /security-report-analyses/search", caseHandler.SearchSecurityReportAnalyses)
mux.HandleFunc("POST /engagements/search", caseHandler.SearchEngagements)
mux.HandleFunc("GET /updates/product-update-levels", updatesHandler.GetProductUpdateLevels)
mux.HandleFunc("POST /updates/levels/search", updatesHandler.SearchUpdatesBetweenUpdateLevels)
mux.HandleFunc("GET /users/me", usersHandler.GetMe)
Expand Down
12 changes: 12 additions & 0 deletions apps/csm-portal/backend/internal/entity/entity.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,18 @@ func (c *Client) SearchServiceRequests(ctx context.Context, body []byte) ([]byte
return c.do(ctx, http.MethodPost, "/service-requests/search", body)
}

// SearchSecurityReportAnalyses calls POST /security-report-analyses/search on the entity service.
// Response is returned as raw JSON; typed response structs are deferred.
func (c *Client) SearchSecurityReportAnalyses(ctx context.Context, body []byte) ([]byte, error) {
return c.do(ctx, http.MethodPost, "/security-report-analyses/search", body)
}

// SearchEngagements calls POST /engagements/search on the entity service.
// Response is returned as raw JSON; typed response structs are deferred.
func (c *Client) SearchEngagements(ctx context.Context, body []byte) ([]byte, error) {
return c.do(ctx, http.MethodPost, "/engagements/search", body)
}

// GetCase calls GET /cases/{id} on the entity service.
// Response is returned as raw JSON; typed response structs are deferred.
func (c *Client) GetCase(ctx context.Context, caseID string) ([]byte, error) {
Expand Down
70 changes: 70 additions & 0 deletions apps/csm-portal/backend/internal/handler/cases.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ type entityCaseClient interface {
SearchCaseComments(ctx context.Context, caseID string, body []byte) ([]byte, error)
SearchCases(ctx context.Context, body []byte) ([]byte, error)
SearchServiceRequests(ctx context.Context, body []byte) ([]byte, error)
SearchSecurityReportAnalyses(ctx context.Context, body []byte) ([]byte, error)
SearchEngagements(ctx context.Context, body []byte) ([]byte, error)
GetCase(ctx context.Context, caseID string) ([]byte, error)
CreateCaseAttachment(ctx context.Context, caseID string, body []byte) ([]byte, error)
SearchCaseAttachments(ctx context.Context, caseID string, body []byte) ([]byte, error)
Expand Down Expand Up @@ -327,6 +329,74 @@ func (h *CaseHandler) SearchServiceRequests(w http.ResponseWriter, r *http.Reque
writeJSON(w, http.StatusOK, result)
}

// SearchSecurityReportAnalyses handles POST /security-report-analyses/search.
func (h *CaseHandler) SearchSecurityReportAnalyses(w http.ResponseWriter, r *http.Request) {
user := middleware.UserInfoFromContext(r.Context())
if user == nil {
writeError(w, http.StatusUnauthorized, ErrMsgUnauthorized)
return
}

r.Body = http.MaxBytesReader(w, r.Body, maxRequestBodyBytes)
body, err := io.ReadAll(r.Body)
if err != nil {
if _, ok := err.(*http.MaxBytesError); ok {
writeError(w, http.StatusRequestEntityTooLarge, ErrMsgTooLarge)
return
}
writeError(w, http.StatusBadRequest, errMsgReadBody)
return
}

if !json.Valid(body) {
writeError(w, http.StatusBadRequest, ErrMsgBadRequest)
return
}

result, err := h.entity.SearchSecurityReportAnalyses(r.Context(), body)
if err != nil {
slog.ErrorContext(r.Context(), "entity SearchSecurityReportAnalyses failed", "userID", user.UserID, "err", err)
mapUpstreamError(w, err, "Failed to search security report analyses.")
return
}

writeJSON(w, http.StatusOK, result)
}

// SearchEngagements handles POST /engagements/search.
func (h *CaseHandler) SearchEngagements(w http.ResponseWriter, r *http.Request) {
user := middleware.UserInfoFromContext(r.Context())
if user == nil {
writeError(w, http.StatusUnauthorized, ErrMsgUnauthorized)
return
}

r.Body = http.MaxBytesReader(w, r.Body, maxRequestBodyBytes)
body, err := io.ReadAll(r.Body)
if err != nil {
if _, ok := err.(*http.MaxBytesError); ok {
writeError(w, http.StatusRequestEntityTooLarge, ErrMsgTooLarge)
return
}
writeError(w, http.StatusBadRequest, errMsgReadBody)
return
}

if !json.Valid(body) {
writeError(w, http.StatusBadRequest, ErrMsgBadRequest)
return
}

result, err := h.entity.SearchEngagements(r.Context(), body)
if err != nil {
slog.ErrorContext(r.Context(), "entity SearchEngagements failed", "userID", user.UserID, "err", err)
mapUpstreamError(w, err, "Failed to search engagements.")
return
}

writeJSON(w, http.StatusOK, result)
}

// CreateCaseAttachment handles POST /cases/{id}/attachments.
func (h *CaseHandler) CreateCaseAttachment(w http.ResponseWriter, r *http.Request) {
user := middleware.UserInfoFromContext(r.Context())
Expand Down
20 changes: 18 additions & 2 deletions apps/csm-portal/backend/internal/handler/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,10 @@ type mockEntityCaseClient struct {
createCaseCommentFn func(ctx context.Context, caseID string, body []byte) ([]byte, error)
searchCaseCommentsFn func(ctx context.Context, caseID string, body []byte) ([]byte, error)
searchCasesFn func(ctx context.Context, body []byte) ([]byte, error)
searchServiceRequestsFn func(ctx context.Context, body []byte) ([]byte, error)
getCaseFn func(ctx context.Context, caseID string) ([]byte, error)
searchServiceRequestsFn func(ctx context.Context, body []byte) ([]byte, error)
searchSecurityReportAnalysesFn func(ctx context.Context, body []byte) ([]byte, error)
searchEngagementsFn func(ctx context.Context, body []byte) ([]byte, error)
getCaseFn func(ctx context.Context, caseID string) ([]byte, error)
createCaseAttachmentFn func(ctx context.Context, caseID string, body []byte) ([]byte, error)
searchCaseAttachmentsFn func(ctx context.Context, caseID string, body []byte) ([]byte, error)
getCaseAttachmentContentFn func(ctx context.Context, caseID, attachmentID string) ([]byte, string, error)
Expand Down Expand Up @@ -139,6 +141,20 @@ func (m *mockEntityCaseClient) SearchServiceRequests(ctx context.Context, body [
return []byte(`{}`), nil
}

func (m *mockEntityCaseClient) SearchSecurityReportAnalyses(ctx context.Context, body []byte) ([]byte, error) {
if m.searchSecurityReportAnalysesFn != nil {
return m.searchSecurityReportAnalysesFn(ctx, body)
}
return []byte(`{}`), nil
}

func (m *mockEntityCaseClient) SearchEngagements(ctx context.Context, body []byte) ([]byte, error) {
if m.searchEngagementsFn != nil {
return m.searchEngagementsFn(ctx, body)
}
return []byte(`{}`), nil
}

func (m *mockEntityCaseClient) GetCase(ctx context.Context, caseID string) ([]byte, error) {
if m.getCaseFn != nil {
return m.getCaseFn(ctx, caseID)
Expand Down
Loading