Skip to content

Commit 5938306

Browse files
CROSSLINK-213 Add items endpoint and populate items on SAM message (#421)
* CROSSLINK-213 Add items endpoint and populate items on SAM message * CROSSLINK-213 Item unifying and processing * CROSSLINK-213 Item unifying and processing * CROSSLINK-213 Improve code stability
1 parent fce687d commit 5938306

12 files changed

Lines changed: 622 additions & 40 deletions

File tree

broker/common/common.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,13 @@ import (
44
"fmt"
55
"reflect"
66
"strings"
7+
8+
"github.com/indexdata/crosslink/iso18626"
79
)
810

11+
const MULTIPLE_ITEMS = "#MultipleItems#"
12+
const MULTIPLE_ITEMS_END = "#MultipleItemsEnd#"
13+
914
func StructToMap(obj interface{}) (map[string]interface{}, error) {
1015
result := make(map[string]interface{})
1116
val := reflect.ValueOf(obj)
@@ -37,3 +42,63 @@ func StructToMap(obj interface{}) (map[string]interface{}, error) {
3742

3843
return result, nil
3944
}
45+
46+
func SamHasItems(sam iso18626.SupplyingAgencyMessage) bool {
47+
return strings.Contains(sam.MessageInfo.Note, MULTIPLE_ITEMS) && strings.Contains(sam.MessageInfo.Note, MULTIPLE_ITEMS_END)
48+
}
49+
50+
func GetItemParams(note string) ([][]string, int, int) {
51+
startIdx := strings.Index(note, MULTIPLE_ITEMS)
52+
endIdx := strings.Index(note, MULTIPLE_ITEMS_END)
53+
54+
// Validate indices to avoid panics if markers are missing or misordered.
55+
if startIdx < 0 || endIdx < 0 || endIdx <= startIdx {
56+
return nil, startIdx, endIdx
57+
}
58+
59+
content := note[startIdx+len(MULTIPLE_ITEMS) : endIdx]
60+
content = strings.TrimSpace(content)
61+
var result [][]string
62+
for _, f := range strings.Split(content, "\n") {
63+
result = append(result, UnpackItemsNote(f))
64+
}
65+
return result, startIdx, endIdx
66+
}
67+
68+
func PackItemsNote(fields []string) string {
69+
escaped := make([]string, len(fields))
70+
for i, f := range fields {
71+
// Escape backslashes first, then the separator
72+
temp := strings.ReplaceAll(f, "\\", "\\\\")
73+
escaped[i] = strings.ReplaceAll(temp, "|", "\\|")
74+
}
75+
return strings.Join(escaped, "|")
76+
}
77+
78+
func UnpackItemsNote(input string) []string {
79+
var result []string
80+
var current strings.Builder
81+
escaped := false
82+
83+
for i := 0; i < len(input); i++ {
84+
char := input[i]
85+
86+
if escaped {
87+
current.WriteByte(char)
88+
escaped = false
89+
continue
90+
}
91+
92+
switch char {
93+
case '\\':
94+
escaped = true
95+
case '|':
96+
result = append(result, current.String())
97+
current.Reset()
98+
default:
99+
current.WriteByte(char)
100+
}
101+
}
102+
result = append(result, current.String())
103+
return result
104+
}

broker/common/common_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package common
33
import (
44
"reflect"
55
"testing"
6+
7+
"github.com/stretchr/testify/assert"
68
)
79

810
type User struct {
@@ -68,3 +70,26 @@ func TestStructToMap(t *testing.T) {
6870
})
6971
}
7072
}
73+
74+
func TestGetItemParams(t *testing.T) {
75+
// Just ID
76+
note := MULTIPLE_ITEMS + "\n1\n" + MULTIPLE_ITEMS_END
77+
result, startIdx, endIdx := GetItemParams(note)
78+
assert.Equal(t, 0, startIdx)
79+
assert.Equal(t, 18, endIdx)
80+
assert.Equal(t, [][]string{{"1"}}, result)
81+
82+
// All params
83+
note = MULTIPLE_ITEMS + "\n1|2\\||3\\\\\n" + MULTIPLE_ITEMS_END
84+
result, startIdx, endIdx = GetItemParams(note)
85+
assert.Equal(t, 0, startIdx)
86+
assert.Equal(t, 26, endIdx)
87+
assert.Equal(t, [][]string{{"1", "2|", "3\\"}}, result)
88+
89+
// Incorrect tag order
90+
note = MULTIPLE_ITEMS_END + "\n1\n" + MULTIPLE_ITEMS
91+
result, startIdx, endIdx = GetItemParams(note)
92+
assert.Equal(t, 21, startIdx)
93+
assert.Equal(t, 0, endIdx)
94+
assert.Nil(t, result)
95+
}

broker/oapi/open-api.yaml

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -709,6 +709,35 @@ components:
709709
required:
710710
- name
711711

712+
PrItem:
713+
type: object
714+
title: Item
715+
description: Patron request item
716+
properties:
717+
id:
718+
type: string
719+
description: Item system id
720+
barcode:
721+
type: string
722+
description: Item barcode
723+
title:
724+
type: string
725+
description: Item title
726+
callNumber:
727+
type: string
728+
description: Item call number
729+
itemId:
730+
type: string
731+
description: Item item id
732+
createdAt:
733+
type: string
734+
format: date-time
735+
description: Item creation date time
736+
required:
737+
- id
738+
- barcode
739+
- createdAt
740+
712741
paths:
713742
/:
714743
get:
@@ -1276,6 +1305,49 @@ paths:
12761305
schema:
12771306
$ref: '#/components/schemas/Error'
12781307

1308+
/patron_requests/{id}/items:
1309+
get:
1310+
summary: Retrieve patron request related items
1311+
parameters:
1312+
- in: path
1313+
name: id
1314+
schema:
1315+
type: string
1316+
required: true
1317+
description: ID of the patron request
1318+
- $ref: '#/components/parameters/Tenant'
1319+
- $ref: '#/components/parameters/Side'
1320+
- $ref: '#/components/parameters/Symbol'
1321+
tags:
1322+
- patron-requests-api
1323+
responses:
1324+
'200':
1325+
description: Successful retrieval of patron request items
1326+
content:
1327+
application/json:
1328+
schema:
1329+
type: array
1330+
items:
1331+
$ref: '#/components/schemas/PrItem'
1332+
'400':
1333+
description: Bad Request. Invalid query parameters.
1334+
content:
1335+
application/json:
1336+
schema:
1337+
$ref: '#/components/schemas/Error'
1338+
'404':
1339+
description: Not Found. Patron request not found.
1340+
content:
1341+
application/json:
1342+
schema:
1343+
$ref: '#/components/schemas/Error'
1344+
'500':
1345+
description: Internal Server Error
1346+
content:
1347+
application/json:
1348+
schema:
1349+
$ref: '#/components/schemas/Error'
1350+
12791351
/sse/events:
12801352
get:
12811353
summary: Subscribe to real-time notifications. Notification is send out every time when ISO18626 message is sent out

broker/patron_request/api/api-handler.go

Lines changed: 55 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -247,13 +247,8 @@ func (a *PatronRequestApiHandler) DeletePatronRequestsId(w http.ResponseWriter,
247247
addBadRequestError(ctx, w, err)
248248
return
249249
}
250-
pr, err := a.getPatronRequestById(ctx, id, params.Side, symbol)
251-
if err != nil {
252-
addInternalError(ctx, w, err)
253-
return
254-
}
250+
pr := a.getPatronRequestById(w, ctx, id, params.Side, symbol)
255251
if pr == nil {
256-
addNotFoundError(w)
257252
return
258253
}
259254
err = a.prRepo.WithTxFunc(ctx, func(repo pr_db.PrRepo) error {
@@ -266,18 +261,21 @@ func (a *PatronRequestApiHandler) DeletePatronRequestsId(w http.ResponseWriter,
266261
w.WriteHeader(http.StatusNoContent)
267262
}
268263

269-
func (a *PatronRequestApiHandler) getPatronRequestById(ctx common.ExtendedContext, id string, side *string, symbol string) (*pr_db.PatronRequest, error) {
264+
func (a *PatronRequestApiHandler) getPatronRequestById(w http.ResponseWriter, ctx common.ExtendedContext, id string, side *string, symbol string) *pr_db.PatronRequest {
270265
pr, err := a.prRepo.GetPatronRequestById(ctx, id)
271266
if err != nil {
272267
if errors.Is(err, pgx.ErrNoRows) {
273-
return nil, nil
268+
addNotFoundError(w)
269+
return nil
274270
}
275-
return nil, err
271+
addInternalError(ctx, w, err)
272+
return nil
276273
}
277274
if isOwner(pr, symbol) && (!isSideParamValid(side) || string(pr.Side) == *side) {
278-
return &pr, nil
275+
return &pr
279276
}
280-
return nil, nil
277+
addNotFoundError(w)
278+
return nil
281279
}
282280

283281
func isSideParamValid(side *string) bool {
@@ -301,13 +299,8 @@ func (a *PatronRequestApiHandler) GetPatronRequestsId(w http.ResponseWriter, r *
301299
addBadRequestError(ctx, w, err)
302300
return
303301
}
304-
pr, err := a.getPatronRequestById(ctx, id, params.Side, symbol)
305-
if err != nil {
306-
addInternalError(ctx, w, err)
307-
return
308-
}
302+
pr := a.getPatronRequestById(w, ctx, id, params.Side, symbol)
309303
if pr == nil {
310-
addNotFoundError(w)
311304
return
312305
}
313306
var illRequest iso18626.Request
@@ -331,13 +324,8 @@ func (a *PatronRequestApiHandler) GetPatronRequestsIdActions(w http.ResponseWrit
331324
addBadRequestError(ctx, w, err)
332325
return
333326
}
334-
pr, err := a.getPatronRequestById(ctx, id, params.Side, symbol)
335-
if err != nil {
336-
addInternalError(ctx, w, err)
337-
return
338-
}
327+
pr := a.getPatronRequestById(w, ctx, id, params.Side, symbol)
339328
if pr == nil {
340-
addNotFoundError(w)
341329
return
342330
}
343331
actionMapping, err := a.actionMappingService.GetActionMapping(*pr)
@@ -361,13 +349,8 @@ func (a *PatronRequestApiHandler) PostPatronRequestsIdAction(w http.ResponseWrit
361349
addBadRequestError(ctx, w, err)
362350
return
363351
}
364-
pr, err := a.getPatronRequestById(ctx, id, params.Side, symbol)
365-
if err != nil {
366-
addInternalError(ctx, w, err)
367-
return
368-
}
352+
pr := a.getPatronRequestById(w, ctx, id, params.Side, symbol)
369353
if pr == nil {
370-
addNotFoundError(w)
371354
return
372355
}
373356
var action proapi.ExecuteAction
@@ -422,6 +405,7 @@ func (a *PatronRequestApiHandler) PostPatronRequestsIdAction(w http.ResponseWrit
422405
func (a *PatronRequestApiHandler) GetPatronRequestsIdEvents(w http.ResponseWriter, r *http.Request, id string, params proapi.GetPatronRequestsIdEventsParams) {
423406
symbol, err := api.GetSymbolForRequest(r, a.tenant, params.XOkapiTenant, params.Symbol)
424407
logParams := map[string]string{"method": "GetPatronRequestsIdEvents", "id": id, "symbol": symbol}
408+
425409
if params.Side != nil {
426410
logParams["side"] = *params.Side
427411
}
@@ -431,24 +415,49 @@ func (a *PatronRequestApiHandler) GetPatronRequestsIdEvents(w http.ResponseWrite
431415
addBadRequestError(ctx, w, err)
432416
return
433417
}
434-
pr, err := a.getPatronRequestById(ctx, id, params.Side, symbol)
418+
pr := a.getPatronRequestById(w, ctx, id, params.Side, symbol)
419+
if pr == nil {
420+
return
421+
}
422+
eventsList, err := a.eventRepo.GetPatronRequestEvents(ctx, pr.ID)
435423
if err != nil {
436424
addInternalError(ctx, w, err)
437425
return
438426
}
427+
428+
var responseItems []oapi.Event
429+
for _, event := range eventsList {
430+
responseItems = append(responseItems, api.ToApiEvent(event, "", &event.PatronRequestID))
431+
}
432+
writeJsonResponse(w, responseItems)
433+
}
434+
435+
func (a *PatronRequestApiHandler) GetPatronRequestsIdItems(w http.ResponseWriter, r *http.Request, id string, params proapi.GetPatronRequestsIdItemsParams) {
436+
symbol, err := api.GetSymbolForRequest(r, a.tenant, params.XOkapiTenant, params.Symbol)
437+
logParams := map[string]string{"method": "GetPatronRequestsIdItems", "id": id, "symbol": symbol}
438+
439+
if params.Side != nil {
440+
logParams["side"] = *params.Side
441+
}
442+
ctx := common.CreateExtCtxWithArgs(context.Background(), &common.LoggerArgs{Other: logParams})
443+
444+
if err != nil {
445+
addBadRequestError(ctx, w, err)
446+
return
447+
}
448+
pr := a.getPatronRequestById(w, ctx, id, params.Side, symbol)
439449
if pr == nil {
440-
addNotFoundError(w)
441450
return
442451
}
443-
events, err := a.eventRepo.GetPatronRequestEvents(ctx, pr.ID)
452+
itemsList, err := a.prRepo.GetItemsByPrId(ctx, pr.ID)
444453
if err != nil {
445454
addInternalError(ctx, w, err)
446455
return
447456
}
448457

449-
var responseItems []oapi.Event
450-
for _, event := range events {
451-
responseItems = append(responseItems, api.ToApiEvent(event, "", &event.PatronRequestID))
458+
var responseItems []proapi.PrItem
459+
for _, item := range itemsList {
460+
responseItems = append(responseItems, toApiItem(item))
452461
}
453462
writeJsonResponse(w, responseItems)
454463
}
@@ -568,3 +577,14 @@ func getDbText(value *string) pgtype.Text {
568577
String: *value,
569578
}
570579
}
580+
581+
func toApiItem(item pr_db.Item) proapi.PrItem {
582+
return proapi.PrItem{
583+
Id: item.ID,
584+
Barcode: item.Barcode,
585+
CallNumber: toString(item.CallNumber),
586+
ItemId: toString(item.ItemID),
587+
Title: toString(item.Title),
588+
CreatedAt: item.CreatedAt.Time,
589+
}
590+
}

broker/patron_request/db/prrepo.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ type PrRepo interface {
1919
GetNextHrid(ctx common.ExtendedContext, prefix string) (string, error)
2020
SaveItem(ctx common.ExtendedContext, params SaveItemParams) (Item, error)
2121
GetItemById(ctx common.ExtendedContext, id string) (Item, error)
22-
GetItemByPrId(ctx common.ExtendedContext, prId string) ([]Item, error)
22+
GetItemsByPrId(ctx common.ExtendedContext, prId string) ([]Item, error)
2323
SaveNotification(ctx common.ExtendedContext, params SaveNotificationParams) (Notification, error)
2424
GetNotificationById(ctx common.ExtendedContext, id string) (Notification, error)
2525
GetNotificationsByPrId(ctx common.ExtendedContext, prId string) ([]Notification, error)
@@ -111,7 +111,7 @@ func (r *PgPrRepo) GetItemById(ctx common.ExtendedContext, id string) (Item, err
111111
return row.Item, err
112112
}
113113

114-
func (r *PgPrRepo) GetItemByPrId(ctx common.ExtendedContext, prId string) ([]Item, error) {
114+
func (r *PgPrRepo) GetItemsByPrId(ctx common.ExtendedContext, prId string) ([]Item, error) {
115115
rows, err := r.queries.GetItemsByPrId(ctx, r.GetConnOrTx(), prId)
116116
var list []Item
117117
for _, row := range rows {

0 commit comments

Comments
 (0)