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
37 changes: 35 additions & 2 deletions broker/descriptors/ModuleDescriptor-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,24 @@
"permissionsRequired" : [
"broker.state_model.item.get"
]
},
{
"methods": [
"GET"
],
"pathPattern": "/broker/patron_requests/{id}/items",
"permissionsRequired": [
"broker.patron_requests.item.items.get"
]
},
{
"methods": [
"GET"
],
"pathPattern": "/broker/patron_requests/{id}/notifications",
"permissionsRequired": [
"broker.patron_requests.item.notifications.get"
]
}
]
}
Expand Down Expand Up @@ -230,6 +248,16 @@
"displayName": "Broker - read patron request events",
"permissionName": "broker.patron_requests.item.events.get"
},
{
"description": "List items for a patron request",
"displayName": "Broker - read patron request items",
"permissionName": "broker.patron_requests.item.items.get"
},
{
"description": "List notifications for a patron request",
"displayName": "Broker - read patron request notifications",
"permissionName": "broker.patron_requests.item.notifications.get"
},
{
"description": "Read-only access to patron requests",
"displayName": "Broker - patron requests: read",
Expand All @@ -239,7 +267,10 @@
"broker.patron_requests.get",
"broker.patron_requests.item.get",
"broker.patron_requests.item.actions.get",
"broker.patron_requests.item.events.get"
"broker.patron_requests.item.events.get",
"broker.patron_requests.item.actions.get",
"broker.patron_requests.item.items.get",
"broker.patron_requests.item.notifications.get"
]
},
{
Expand Down Expand Up @@ -289,7 +320,9 @@
"broker.patron_requests.item.actions.get",
"broker.patron_requests.item.events.get",
"broker.patron_requests.item.action.post",
"broker.state_model.item.get"
"broker.state_model.item.get",
"broker.patron_requests.item.items.get",
"broker.patron_requests.item.notifications.get"
]
}
]
Expand Down
91 changes: 91 additions & 0 deletions broker/oapi/open-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,54 @@ components:
- barcode
- createdAt

PrNotification:
type: object
title: Notification
description: Patron request notification
properties:
id:
type: string
description: Notification id
fromSymbol:
type: string
description: Symbol of notification sender
toSymbol:
type: string
description: Symbol of notification receiver
side:
type: string
description: Patron request side
note:
type: string
description: Note of notification
cost:
type: number
format: double
description: Cost amount
currency:
type: string
description: Currency symbol
condition:
type: string
description: Condition of notification
receipt:
type: string
description: Receipt of notification
createdAt:
type: string
format: date-time
description: Notification creation date time
acknowledgedAt:
type: string
format: date-time
description: Notification acknowledged at date time
required:
- id
- fromSymbol
- toSymbol
- side
- createdAt

paths:
/:
get:
Expand Down Expand Up @@ -1348,6 +1396,49 @@ paths:
schema:
$ref: '#/components/schemas/Error'

/patron_requests/{id}/notifications:
get:
summary: Retrieve patron request related notifications
parameters:
- in: path
name: id
schema:
type: string
required: true
description: ID of the patron request
- $ref: '#/components/parameters/Tenant'
- $ref: '#/components/parameters/Side'
- $ref: '#/components/parameters/Symbol'
tags:
- patron-requests-api
responses:
'200':
description: Successful retrieval of patron request notifications
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/PrNotification'
'400':
description: Bad Request. Invalid query parameters.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
description: Not Found. Patron request not found.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'500':
description: Internal Server Error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'

/sse/events:
get:
summary: Subscribe to real-time notifications. Notification is send out every time when ISO18626 message is sent out
Expand Down
70 changes: 70 additions & 0 deletions broker/patron_request/api/api-handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,41 @@ func (a *PatronRequestApiHandler) GetPatronRequestsIdItems(w http.ResponseWriter
writeJsonResponse(w, responseItems)
}

func (a *PatronRequestApiHandler) GetPatronRequestsIdNotifications(w http.ResponseWriter, r *http.Request, id string, params proapi.GetPatronRequestsIdNotificationsParams) {
symbol, err := api.GetSymbolForRequest(r, a.tenant, params.XOkapiTenant, params.Symbol)
logParams := map[string]string{"method": "GetPatronRequestsIdNotifications", "id": id, "symbol": symbol}

if params.Side != nil {
logParams["side"] = *params.Side
}
ctx := common.CreateExtCtxWithArgs(context.Background(), &common.LoggerArgs{Other: logParams})

if err != nil {
addBadRequestError(ctx, w, err)
return
}
pr := a.getPatronRequestById(w, ctx, id, params.Side, symbol)
if pr == nil {
return
}
list, err := a.prRepo.GetNotificationsByPrId(ctx, pr.ID)
if err != nil {
addInternalError(ctx, w, err)
return
}

var responseList []proapi.PrNotification
for _, n := range list {
apiN, inErr := toApiNotification(n)
if inErr != nil {
addInternalError(ctx, w, inErr)
return
}
responseList = append(responseList, apiN)
}
writeJsonResponse(w, responseList)
}

func writeJsonResponse(w http.ResponseWriter, resp any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
Expand Down Expand Up @@ -588,3 +623,38 @@ func toApiItem(item pr_db.Item) proapi.PrItem {
CreatedAt: item.CreatedAt.Time,
}
}

func toApiNotification(notification pr_db.Notification) (proapi.PrNotification, error) {
var ackAt *time.Time
if notification.AcknowledgedAt.Valid {
t := notification.AcknowledgedAt.Time
ackAt = &t
}
var cost *float64
if notification.Cost.Valid {
f, err := notification.Cost.Float64Value()
if err != nil {
return proapi.PrNotification{}, err
}
val := f.Float64
cost = &val
}
var receipt *string
if notification.Receipt != "" {
r := string(notification.Receipt)
receipt = &r
}
return proapi.PrNotification{
Id: notification.ID,
FromSymbol: notification.FromSymbol,
ToSymbol: notification.ToSymbol,
Side: string(notification.Side),
Note: toString(notification.Note),
Cost: cost,
Currency: toString(notification.Currency),
Condition: toString(notification.Condition),
Receipt: receipt,
CreatedAt: notification.CreatedAt.Time,
AcknowledgedAt: ackAt,
}, nil
}
40 changes: 40 additions & 0 deletions broker/patron_request/api/api-handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,42 @@ func TestGetPatronRequestsIdEventsErrorGettingEvents(t *testing.T) {
assert.Contains(t, rr.Body.String(), "DB error")
}

func TestGetPatronRequestsIdNotificationsNoSymbol(t *testing.T) {
handler := NewPrApiHandler(new(PrRepoError), mockEventBus, mockEventRepo, common.NewTenant(""), 10)
req, _ := http.NewRequest("GET", "/", nil)
rr := httptest.NewRecorder()
handler.GetPatronRequestsIdNotifications(rr, req, "3", proapi.GetPatronRequestsIdNotificationsParams{Side: &proapiBorrowingSide})
assert.Equal(t, http.StatusBadRequest, rr.Code)
assert.Contains(t, rr.Body.String(), "symbol must be specified")
}

func TestGetPatronRequestsIdNotificationsDbError(t *testing.T) {
handler := NewPrApiHandler(new(PrRepoError), mockEventBus, mockEventRepo, common.NewTenant(""), 10)
req, _ := http.NewRequest("GET", "/", nil)
rr := httptest.NewRecorder()
handler.GetPatronRequestsIdNotifications(rr, req, "1", proapi.GetPatronRequestsIdNotificationsParams{Symbol: &symbol, Side: &proapiBorrowingSide})
assert.Equal(t, http.StatusInternalServerError, rr.Code)
assert.Contains(t, rr.Body.String(), "DB error")
}

func TestGetPatronRequestsIdNotificationsNotFoundBecauseOfSide(t *testing.T) {
handler := NewPrApiHandler(new(PrRepoError), mockEventBus, mockEventRepo, common.NewTenant(""), 10)
req, _ := http.NewRequest("GET", "/", nil)
rr := httptest.NewRecorder()
handler.GetPatronRequestsIdNotifications(rr, req, "3", proapi.GetPatronRequestsIdNotificationsParams{Symbol: &symbol, Side: &proapiLendingSide})
assert.Equal(t, http.StatusNotFound, rr.Code)
assert.Contains(t, rr.Body.String(), "not found")
}

func TestGetPatronRequestsIdNotificationsErrorGettingEvents(t *testing.T) {
handler := NewPrApiHandler(new(PrRepoError), mockEventBus, new(mocks.MockEventRepositoryError), common.NewTenant(""), 10)
req, _ := http.NewRequest("GET", "/", nil)
rr := httptest.NewRecorder()
handler.GetPatronRequestsIdNotifications(rr, req, "3", proapi.GetPatronRequestsIdNotificationsParams{Symbol: &symbol, Side: &proapiBorrowingSide})
assert.Equal(t, http.StatusInternalServerError, rr.Code)
assert.Contains(t, rr.Body.String(), "DB error")
}

func TestToDbPatronRequest(t *testing.T) {
handler := NewPrApiHandler(new(PrRepoError), mockEventBus, mockEventRepo, common.NewTenant(""), 10)
ctx := common.CreateExtCtxWithArgs(context.Background(), &common.LoggerArgs{})
Expand Down Expand Up @@ -366,6 +402,10 @@ func (r *PrRepoError) GetNextHrid(ctx common.ExtendedContext, prefix string) (st
return strings.ToUpper(prefix) + "-" + strconv.FormatInt(r.counter, 10), nil
}

func (r *PrRepoError) GetNotificationsByPrId(ctx common.ExtendedContext, prId string) ([]pr_db.Notification, error) {
return []pr_db.Notification{}, errors.New("DB error")
}

type MockEventBus struct {
mock.Mock
events.EventBus
Expand Down
16 changes: 14 additions & 2 deletions broker/patron_request/service/action_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -705,8 +705,9 @@ func (m *MockEventBus) CreateNotice(id string, eventName events.EventName, data
type MockPrRepo struct {
mock.Mock
pr_db.PgPrRepo
savedPr pr_db.PatronRequest
savedItems []pr_db.Item
savedPr pr_db.PatronRequest
savedItems []pr_db.Item
savedNotifications []pr_db.Notification
}

func (r *MockPrRepo) GetPatronRequestById(ctx common.ExtendedContext, id string) (pr_db.PatronRequest, error) {
Expand Down Expand Up @@ -743,6 +744,17 @@ func (r *MockPrRepo) SaveItem(ctx common.ExtendedContext, params pr_db.SaveItemP
return pr_db.Item(params), nil
}

func (r *MockPrRepo) SaveNotification(ctx common.ExtendedContext, params pr_db.SaveNotificationParams) (pr_db.Notification, error) {
if r.savedNotifications == nil {
r.savedNotifications = []pr_db.Notification{}
}
r.savedNotifications = append(r.savedNotifications, pr_db.Notification(params))
if params.PrID == "error" {
return pr_db.Notification{}, errors.New("db error")
}
return pr_db.Notification(params), nil
}

type MockIso18626Handler struct {
mock.Mock
handler.Iso18626Handler
Expand Down
Loading