Skip to content
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

Implemented case snoozes. #887

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
69 changes: 69 additions & 0 deletions api/handle_cases.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"mime/multipart"
"net/http"
"time"

"github.com/cockroachdb/errors"
"github.com/gin-gonic/gin"
Expand Down Expand Up @@ -155,6 +156,74 @@ func handlePatchCase(uc usecases.Usecases) func(c *gin.Context) {
}
}

type CaseSnoozeParams struct {
Until time.Time `json:"until"`
}

func handleSnoozeCase(uc usecases.Usecases) func(c *gin.Context) {
return func(c *gin.Context) {
ctx := c.Request.Context()
creds, _ := utils.CredentialsFromCtx(ctx)

userId := creds.ActorIdentity.UserId
caseId := c.Param("case_id")

var params CaseSnoozeParams

if err := c.ShouldBindBodyWithJSON(&params); err != nil {
presentError(ctx, c, err)
return
}

if params.Until.Before(time.Now()) {
presentError(ctx, c, errors.Wrap(models.BadParameterError,
"a case cannot only be snoozed until a future date"))
return
}

uc := usecasesWithCreds(ctx, uc)
caseUsecase := uc.NewCaseUseCase()

req := models.CaseSnoozeRequest{
UserId: userId,
CaseId: caseId,
Until: params.Until,
}

if err := caseUsecase.Snooze(ctx, req); err != nil {
presentError(ctx, c, err)
return
}

c.Status(http.StatusNoContent)
}
}

func handleUnsnoozeCase(uc usecases.Usecases) func(c *gin.Context) {
return func(c *gin.Context) {
ctx := c.Request.Context()
creds, _ := utils.CredentialsFromCtx(ctx)

userId := creds.ActorIdentity.UserId
caseId := c.Param("case_id")

uc := usecasesWithCreds(ctx, uc)
caseUsecase := uc.NewCaseUseCase()

req := models.CaseSnoozeRequest{
UserId: userId,
CaseId: caseId,
}

if err := caseUsecase.Unsnooze(ctx, req); err != nil {
presentError(ctx, c, err)
return
}

c.Status(http.StatusNoContent)
}
}

func handlePostCaseDecisions(uc usecases.Usecases) func(c *gin.Context) {
return func(c *gin.Context) {
ctx := c.Request.Context()
Expand Down
2 changes: 2 additions & 0 deletions api/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,8 @@ func addRoutes(r *gin.Engine, conf Configuration, uc usecases.Usecases, auth Aut
router.GET("/cases", tom, handleListCases(uc))
router.POST("/cases", tom, handlePostCase(uc))
router.GET("/cases/:case_id", tom, handleGetCase(uc))
router.POST("/cases/:case_id/snooze", tom, handleSnoozeCase(uc))
router.DELETE("/cases/:case_id/snooze", tom, handleUnsnoozeCase(uc))
router.PATCH("/cases/:case_id", tom, handlePatchCase(uc))
router.POST("/cases/:case_id/decisions", tom, handlePostCaseDecisions(uc))
router.POST("/cases/:case_id/comments", tom, handlePostCaseComment(uc))
Expand Down
20 changes: 14 additions & 6 deletions dto/case_dto.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type APICase struct {
Status string `json:"status"`
Tags []APICaseTag `json:"tags"`
Files []APICaseFile `json:"files"`
SnoozedUntil *time.Time `json:"snoozed_until,omitempty"`
}

type APICaseWithDecisions struct {
Expand All @@ -26,7 +27,7 @@ type APICaseWithDecisions struct {
}

func AdaptCaseDto(c models.Case) APICase {
return APICase{
dto := APICase{
Id: c.Id,
Contributors: pure_utils.Map(c.Contributors, NewAPICaseContributor),
CreatedAt: c.CreatedAt,
Expand All @@ -38,6 +39,12 @@ func AdaptCaseDto(c models.Case) APICase {
Tags: pure_utils.Map(c.Tags, NewAPICaseTag),
Files: pure_utils.Map(c.Files, NewAPICaseFile),
}

if c.SnoozedUntil != nil && c.SnoozedUntil.After(time.Now()) {
dto.SnoozedUntil = c.SnoozedUntil
}

return dto
}

type CastListPage struct {
Expand Down Expand Up @@ -86,11 +93,12 @@ type CreateCaseCommentBody struct {
}

type CaseFilters struct {
EndDate time.Time `form:"end_date"`
InboxIds []string `form:"inbox_id[]"`
StartDate time.Time `form:"start_date"`
Statuses []string `form:"status[]"`
Name string `form:"name"`
EndDate time.Time `form:"end_date"`
InboxIds []string `form:"inbox_id[]"`
StartDate time.Time `form:"start_date"`
Statuses []string `form:"status[]"`
Name string `form:"name"`
IncludeSnoozed bool `form:"include_snoozed"`
}

type ReviewCaseDecisionsBody struct {
Expand Down
12 changes: 12 additions & 0 deletions models/case.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type Case struct {
Status CaseStatus
Tags []CaseTag
Files []CaseFile
SnoozedUntil *time.Time
}

func (c Case) GetMetadata() CaseMetadata {
Expand All @@ -29,6 +30,10 @@ func (c Case) GetMetadata() CaseMetadata {
}
}

func (c Case) IsSnoozed() bool {
return c.SnoozedUntil != nil && c.SnoozedUntil.After(time.Now())
}

func (c CaseStatus) IsFinalized() bool {
return c == CaseDiscarded || c == CaseResolved
}
Expand Down Expand Up @@ -76,6 +81,7 @@ type CaseFilters struct {
EndDate time.Time
Statuses []CaseStatus
InboxIds []string
IncludeSnoozed bool
}

type CaseListPage struct {
Expand Down Expand Up @@ -109,3 +115,9 @@ type ReviewCaseDecisionsBody struct {
ReviewStatus string
UserId string
}

type CaseSnoozeRequest struct {
UserId UserId
CaseId string
Until time.Time
}
2 changes: 2 additions & 0 deletions models/case_event.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ const (
SanctionCheckReviewed CaseEventType = "sanction_check_reviewed"
DecisionAdded CaseEventType = "decision_added"
DecisionReviewed CaseEventType = "decision_reviewed"
CaseSnoozed CaseEventType = "case_snoozed"
CaseUnsnoozed CaseEventType = "case_unsnoozed"
)

type CaseEventResourceType string
Expand Down
33 changes: 33 additions & 0 deletions repositories/case_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"strings"
"time"

"github.com/Masterminds/squirrel"
"github.com/cockroachdb/errors"
Expand Down Expand Up @@ -145,6 +146,32 @@ func (repo *MarbleDbRepository) UpdateCase(ctx context.Context, exec Executor, u
return err
}

func (repo *MarbleDbRepository) SnoozeCase(ctx context.Context, exec Executor, snoozeRequest models.CaseSnoozeRequest) error {
if err := validateMarbleDbExecutor(exec); err != nil {
return err
}

sql := NewQueryBuilder().
Update(dbmodels.TABLE_CASES).
Set("snoozed_until", snoozeRequest.Until).
Where(squirrel.Eq{"id": snoozeRequest.CaseId})

return ExecBuilder(ctx, exec, sql)
}

func (repo *MarbleDbRepository) UnsnoozeCase(ctx context.Context, exec Executor, caseId string) error {
if err := validateMarbleDbExecutor(exec); err != nil {
return err
}

sql := NewQueryBuilder().
Update(dbmodels.TABLE_CASES).
Set("snoozed_until", nil).
Where(squirrel.Eq{"id": caseId})

return ExecBuilder(ctx, exec, sql)
}

func (repo *MarbleDbRepository) CreateCaseTag(ctx context.Context, exec Executor, caseId, tagId string) error {
if err := validateMarbleDbExecutor(exec); err != nil {
return err
Expand Down Expand Up @@ -247,6 +274,12 @@ func applyCaseFilters(query squirrel.SelectBuilder, filters models.CaseFilters)
if filters.Name != "" {
query = query.Where("c.name % ?", filters.Name)
}
if !filters.IncludeSnoozed {
query = query.Where(squirrel.Or{
squirrel.Eq{"snoozed_until": nil},
squirrel.LtOrEq{"snoozed_until": time.Now()},
})
}
return query
}

Expand Down
6 changes: 5 additions & 1 deletion repositories/dbmodels/db_case.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package dbmodels

import (
"time"

"github.com/checkmarble/marble-backend/models"
"github.com/jackc/pgx/v5/pgtype"
)
Expand All @@ -12,6 +14,7 @@ type DBCase struct {
Name pgtype.Text `db:"name"`
OrganizationId pgtype.Text `db:"org_id"`
Status pgtype.Text `db:"status"`
SnoozedUntil *time.Time `db:"snoozed_until"`
}

type DBCaseWithContributorsAndTags struct {
Expand All @@ -28,7 +31,7 @@ type DBPaginatedCases struct {

const TABLE_CASES = "cases"

var SelectCaseColumn = []string{"id", "created_at", "inbox_id", "name", "org_id", "status"}
var SelectCaseColumn = []string{"id", "created_at", "inbox_id", "name", "org_id", "status", "snoozed_until"}

func AdaptCase(db DBCase) (models.Case, error) {
return models.Case{
Expand All @@ -38,6 +41,7 @@ func AdaptCase(db DBCase) (models.Case, error) {
Name: db.Name.String,
OrganizationId: db.OrganizationId.String,
Status: models.CaseStatus(db.Status.String),
SnoozedUntil: db.SnoozedUntil,
}, nil
}

Expand Down
9 changes: 9 additions & 0 deletions repositories/migrations/20250305145800_add_case_snoozes.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-- +goose Up

alter table cases
add column snoozed_until timestamp with time zone null;

-- +goose Down

alter table cases
drop column snoozed_until;
Loading
Loading