Skip to content
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
2 changes: 1 addition & 1 deletion broker/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ func Init(ctx context.Context) (Context, error) {

sseBroker := api.NewSseBroker(appCtx, common.NewTenant(TENANT_TO_SYMBOL))

AddDefaultHandlers(eventBus, iso18626Client, supplierLocator, workflowManager, iso18626Handler, prActionService, prApiHandler, sseBroker)
AddDefaultHandlers(eventBus, iso18626Client, supplierLocator, workflowManager, iso18626Handler, *prActionService, prApiHandler, sseBroker)
err = StartEventBus(ctx, eventBus)
if err != nil {
return Context{}, err
Expand Down
6 changes: 4 additions & 2 deletions broker/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ require (
github.com/indexdata/crosslink/httpclient v0.0.0
github.com/indexdata/crosslink/illmock v0.0.0
github.com/indexdata/crosslink/iso18626 v0.0.0
github.com/indexdata/crosslink/marcxml v0.0.0
github.com/indexdata/crosslink/sru v0.0.0
)

replace (
Expand All @@ -29,6 +27,7 @@ require (
github.com/jackc/pgx/v5 v5.8.0
github.com/lib/pq v1.10.9
github.com/oapi-codegen/oapi-codegen/v2 v2.5.1
github.com/oapi-codegen/runtime v1.1.2
github.com/stretchr/testify v1.11.1
github.com/testcontainers/testcontainers-go v0.40.0
github.com/testcontainers/testcontainers-go/modules/postgres v0.40.0
Expand All @@ -38,6 +37,7 @@ require (
dario.cat/mergo v1.0.2 // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
Expand All @@ -59,6 +59,8 @@ require (
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-openapi/jsonpointer v0.21.1 // indirect
github.com/go-openapi/swag v0.23.1 // indirect
github.com/indexdata/crosslink v0.0.0-20260121111122-3096ccb3a8ea // indirect
github.com/indexdata/crosslink/marcxml v0.0.0-20260122210555-37edcda908a8 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
Expand Down
10 changes: 10 additions & 0 deletions broker/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEK
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
Expand Down Expand Up @@ -74,6 +78,8 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0/go.mod h1:igFoXX2ELCW06bol23DW
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/indexdata/cql-go v1.0.1-0.20250722084932-84f3837d6030 h1:RP9TZZTGoBo2Jx04FEWaRmNhCX/Fk8VR04tX0FMRzik=
github.com/indexdata/cql-go v1.0.1-0.20250722084932-84f3837d6030/go.mod h1:zmSHcE8JyK94EWZrV7VyjLr2QfRoj+EeEOttl9wm64U=
github.com/indexdata/crosslink v0.0.0-20260121111122-3096ccb3a8ea h1:xYeIBOOcz3gdVSCqGt2llJUQ9KXvXSSXtc8vhFVZxd4=
github.com/indexdata/crosslink v0.0.0-20260121111122-3096ccb3a8ea/go.mod h1:r0ibawAkcS/t9mThAE9T2riuuU+jd9B7qUKOkBDIoGk=
github.com/indexdata/go-utils v0.0.0-20250210100229-d30dbd51df72 h1:/ssOlpKK1EOeWMCOUrxCOCZP+0Z1LD58Xg1nrU01TAQ=
github.com/indexdata/go-utils v0.0.0-20250210100229-d30dbd51df72/go.mod h1:0sW6Szxv8GNU3LBtK6mgBKDEUnlovPfghiG9xi+i0R8=
github.com/indexdata/xsd2goxsl v1.3.0 h1:LZGBORHnO6olHBtvc6hQEefymdRuiM77FgtW4pCek7g=
Expand All @@ -90,6 +96,7 @@ github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
Expand Down Expand Up @@ -133,6 +140,8 @@ github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=
github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc=
github.com/oapi-codegen/oapi-codegen/v2 v2.5.1 h1:5vHNY1uuPBRBWqB2Dp0G7YB03phxLQZupZTIZaeorjc=
github.com/oapi-codegen/oapi-codegen/v2 v2.5.1/go.mod h1:ro0npU1BWkcGpCgGD9QwPp44l5OIZ94tB3eabnT7DjQ=
github.com/oapi-codegen/runtime v1.1.2 h1:P2+CubHq8fO4Q6fV1tqDBZHCwpVpvPg7oKiYzQgXIyI=
github.com/oapi-codegen/runtime v1.1.2/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg=
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY=
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw=
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c=
Expand Down Expand Up @@ -169,6 +178,7 @@ github.com/speakeasy-api/jsonpath v0.6.2 h1:Mys71yd6u8kuowNCR0gCVPlVAHCmKtoGXYoA
github.com/speakeasy-api/jsonpath v0.6.2/go.mod h1:ymb2iSkyOycmzKwbEAYPJV/yi2rSmvBCLZJcyD+VVWw=
github.com/speakeasy-api/openapi-overlay v0.10.2 h1:VOdQ03eGKeiHnpb1boZCGm7x8Haj6gST0P3SGTX95GU=
github.com/speakeasy-api/openapi-overlay v0.10.2/go.mod h1:n0iOU7AqKpNFfEt6tq7qYITC4f0yzVVdFw0S7hukemg=
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
Expand Down
16 changes: 16 additions & 0 deletions broker/patron_request/api/api-handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,22 @@ func NewApiHandler(prRepo pr_db.PrRepo, eventBus events.EventBus, tenant common.
}
}

func (a *PatronRequestApiHandler) GetStateModelModelsModel(w http.ResponseWriter, r *http.Request, model string, params proapi.GetStateModelModelsModelParams) {
/*
logParams := map[string]string{"method": "GetStateModelModelsReturnables"}
ctx := common.CreateExtCtxWithArgs(context.Background(), &common.LoggerArgs{
Other: logParams,
})
*/
stateModel := a.actionMappingService.SMService.GetStateModel(model) //Need to come up with a controlled vocab

if stateModel == nil {
addNotFoundError(w)
return
}
writeJsonResponse(w, *stateModel)
}

func (a *PatronRequestApiHandler) GetPatronRequests(w http.ResponseWriter, r *http.Request) {
logParams := map[string]string{"method": "GetPatronRequests"}
ctx := common.CreateExtCtxWithArgs(context.Background(), &common.LoggerArgs{
Expand Down
125 changes: 125 additions & 0 deletions broker/patron_request/oapi/open-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,106 @@ components:
pattern: '^[_a-z][_a-z0-9]*$'

schemas:
StateModel:
type: object
description: ReShare state model definition
additionalProperties: false
properties:
type:
type: string
const: StateModel
name:
type: string
description: Name of the state model
desc:
type: string
description: Description of the state model
version:
type: string
description: Version of the state model in SemVer
states:
type: array
description: A list of all allowed states
items:
$ref: '#/components/schemas/State'

State:
title: State
type: object
description: Definition of a particular state
properties:
name:
type: string
description: Name of the state, in capital letters with underscores
display:
type: string
description: Human-readable state name
desc:
type: string
description: Description of the state, e.g., what situation is modelled by the state
side:
type: string
description: Indicates which side of the request the state belongs to
enum:
- REQUESTER
- SUPPLIER
actions:
type: array
description: List of all actions that may be performed on the request when in this state
items:
$ref: '#/components/schemas/Action'
events:
type: array
description: List of all events that may be triggered to the request when in this state
items:
$ref: '#/components/schemas/Event'
terminal:
type: boolean
description: Indicates if the state is terminal (meaning no actions or events are allowed in this state)
oneOf:
- anyOf:
- required:
- actions
- required:
- events
- required:
- terminal
required:
- name
- side

Action:
type: object
title: Action
additionalProperties: false
properties:
name:
type: string
description: Description of the action
transitions:
type: array
description: List of all possible state transitions after performing the action. When no transitions are defined, the action is considered to be non-state-changing
items:
type: string
required:
- name

Event:
type: object
title: Event
additionalProperties: false
properties:
name:
type: string
description: Description of the event
transitions:
type: array
description: List of all possible state transitions after performing the event. When no transitions are defined, the event is considered to be non-state-changing
items:
type: string
required:
- name

PatronRequest:
type: object
properties:
Expand Down Expand Up @@ -110,6 +210,31 @@ components:
- actionResult

paths:
/state_model/models/{model}:
get:
summary: Retrieve a state model by name
parameters:
- in: path
name: model
schema:
type: string
required: true
description: The name of the statemodel to retrieve
- $ref: '#/components/parameters/Tenant'
responses:
'200':
description: Successful retrieval of state model
content:
application/json:
schema:
$ref: '#/components/schemas/StateModel'
'500':
description: Internal Server Error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'

/patron_requests:
get:
summary: Retrieve patron requests
Expand Down
4 changes: 2 additions & 2 deletions broker/patron_request/service/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ type PatronRequestActionService struct {
actionMappingService ActionMappingService
}

func CreatePatronRequestActionService(prRepo pr_db.PrRepo, eventBus events.EventBus, iso18626Handler handler.Iso18626HandlerInterface, lmsCreator lms.LmsCreator) PatronRequestActionService {
return PatronRequestActionService{
func CreatePatronRequestActionService(prRepo pr_db.PrRepo, eventBus events.EventBus, iso18626Handler handler.Iso18626HandlerInterface, lmsCreator lms.LmsCreator) *PatronRequestActionService {
return &PatronRequestActionService{
prRepo: prRepo,
eventBus: eventBus,
iso18626Handler: iso18626Handler,
Expand Down
82 changes: 56 additions & 26 deletions broker/patron_request/service/action_mapping.go
Original file line number Diff line number Diff line change
@@ -1,64 +1,94 @@
package prservice

import (
pr_db "github.com/indexdata/crosslink/broker/patron_request/db"
"slices"

pr_db "github.com/indexdata/crosslink/broker/patron_request/db"
proapi "github.com/indexdata/crosslink/broker/patron_request/oapi"
)

type ActionMapping interface {
IsActionAvailable(pr pr_db.PatronRequest, action pr_db.PatronRequestAction) bool
GetActionsForPatronRequest(pr pr_db.PatronRequest) []pr_db.PatronRequestAction
GetBorrowerActionsMap() map[pr_db.PatronRequestState][]pr_db.PatronRequestAction
GetLenderActionsMap() map[pr_db.PatronRequestState][]pr_db.PatronRequestAction
}

var returnableBorrowerStateActionMapping = map[pr_db.PatronRequestState][]pr_db.PatronRequestAction{
BorrowerStateNew: {BorrowerActionValidate},
BorrowerStateValidated: {BorrowerActionSendRequest},
BorrowerStateSupplierLocated: {BorrowerActionCancelRequest},
BorrowerStateConditionPending: {BorrowerActionAcceptCondition, BorrowerActionRejectCondition},
BorrowerStateWillSupply: {BorrowerActionCancelRequest},
BorrowerStateShipped: {BorrowerActionReceive},
BorrowerStateReceived: {BorrowerActionCheckOut},
BorrowerStateCheckedOut: {BorrowerActionCheckIn},
BorrowerStateCheckedIn: {BorrowerActionShipReturn},
}
var returnableLenderStateActionMapping = map[pr_db.PatronRequestState][]pr_db.PatronRequestAction{
LenderStateNew: {LenderActionValidate},
LenderStateValidated: {LenderActionWillSupply, LenderActionCannotSupply, LenderActionAddCondition},
LenderStateWillSupply: {LenderActionAddCondition, LenderActionCannotSupply, LenderActionShip},
LenderStateConditionPending: {LenderActionCannotSupply},
LenderStateConditionAccepted: {LenderActionShip, LenderActionCannotSupply},
LenderStateShippedReturn: {LenderActionMarkReceived},
LenderStateCancelRequested: {LenderActionMarkCancelled, LenderActionWillSupply},
type ActionMappingService struct {
SMService StateModelService
}

type ActionMappingService struct {
func (r *ActionMappingService) NewActionMappingService() *ActionMappingService {
return &ActionMappingService{
SMService: StateModelService{},
}
}

func (r *ActionMappingService) GetActionMapping(pr pr_db.PatronRequest) ActionMapping {
return new(ReturnableActionMapping)
//At a future point, we will check the PatronRequest loan type to decide what kind of mapping service to return
return NewReturnableActionMapping(r.SMService.GetStateModel("returnable"))
}

type ReturnableActionMapping struct {
borrowerStateActionMapping map[pr_db.PatronRequestState][]pr_db.PatronRequestAction
lenderStateActionMapping map[pr_db.PatronRequestState][]pr_db.PatronRequestAction
}

/* Constructor function to initialize the mappings for the returnables */
func NewReturnableActionMapping(stateModel *proapi.StateModel) *ReturnableActionMapping {
Copy link
Contributor

@jakub-id jakub-id Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need a concrete action mapping since the mapping is loaded from the file so let's rename to ActionMappingService.

r := new(ReturnableActionMapping)

borrowerMap := make(map[pr_db.PatronRequestState][]pr_db.PatronRequestAction)
lenderMap := make(map[pr_db.PatronRequestState][]pr_db.PatronRequestAction)

for _, state := range *stateModel.States {
if state.Actions != nil {
nameList := make([]pr_db.PatronRequestAction, 0)
for _, action := range *state.Actions {
nameList = append(nameList, pr_db.PatronRequestAction(action.Name))
}
if state.Side == proapi.REQUESTER {
borrowerMap[pr_db.PatronRequestState(state.Name)] = nameList
} else {
lenderMap[pr_db.PatronRequestState(state.Name)] = nameList
}
}
}

r.borrowerStateActionMapping = borrowerMap
r.lenderStateActionMapping = lenderMap

return r
}

func (r *ReturnableActionMapping) GetBorrowerActionsMap() map[pr_db.PatronRequestState][]pr_db.PatronRequestAction {
return r.borrowerStateActionMapping
}

func (r *ReturnableActionMapping) GetLenderActionsMap() map[pr_db.PatronRequestState][]pr_db.PatronRequestAction {
return r.lenderStateActionMapping
}

func (r *ReturnableActionMapping) IsActionAvailable(pr pr_db.PatronRequest, action pr_db.PatronRequestAction) bool {
if pr.Side == SideBorrowing {
return isActionAvailable(pr.State, action, returnableBorrowerStateActionMapping)
return isActionAvailable(pr.State, action, r.borrowerStateActionMapping)
} else {
return isActionAvailable(pr.State, action, returnableLenderStateActionMapping)
return isActionAvailable(pr.State, action, r.lenderStateActionMapping)
}
}

func (r *ReturnableActionMapping) GetActionsForPatronRequest(pr pr_db.PatronRequest) []pr_db.PatronRequestAction {
if pr.Side == SideBorrowing {
return getActionsByStateFromMapping(pr.State, returnableBorrowerStateActionMapping)
return getActionsByStateFromMapping(pr.State, r.borrowerStateActionMapping)
} else {
return getActionsByStateFromMapping(pr.State, returnableLenderStateActionMapping)
return getActionsByStateFromMapping(pr.State, r.lenderStateActionMapping)
}
}

func isActionAvailable(state pr_db.PatronRequestState, action pr_db.PatronRequestAction, actionMapping map[pr_db.PatronRequestState][]pr_db.PatronRequestAction) bool {
return slices.Contains(getActionsByStateFromMapping(state, actionMapping), action)
}

func getActionsByStateFromMapping(state pr_db.PatronRequestState, actionMapping map[pr_db.PatronRequestState][]pr_db.PatronRequestAction) []pr_db.PatronRequestAction {
if actions, ok := actionMapping[state]; ok {
return actions
Expand Down
Loading