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
65 changes: 65 additions & 0 deletions broker/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@ import (
"fmt"
"reflect"
"strings"

"github.com/indexdata/crosslink/iso18626"
)

const MULTIPLE_ITEMS = "#MultipleItems#"
const MULTIPLE_ITEMS_END = "#MultipleItemsEnd#"

func StructToMap(obj interface{}) (map[string]interface{}, error) {
result := make(map[string]interface{})
val := reflect.ValueOf(obj)
Expand Down Expand Up @@ -37,3 +42,63 @@ func StructToMap(obj interface{}) (map[string]interface{}, error) {

return result, nil
}

func SamHasItems(sam iso18626.SupplyingAgencyMessage) bool {
return strings.Contains(sam.MessageInfo.Note, MULTIPLE_ITEMS) && strings.Contains(sam.MessageInfo.Note, MULTIPLE_ITEMS_END)
}

func GetItemParams(note string) ([][]string, int, int) {
startIdx := strings.Index(note, MULTIPLE_ITEMS)
endIdx := strings.Index(note, MULTIPLE_ITEMS_END)

// Validate indices to avoid panics if markers are missing or misordered.
if startIdx < 0 || endIdx < 0 || endIdx <= startIdx {
return nil, startIdx, endIdx
}

content := note[startIdx+len(MULTIPLE_ITEMS) : endIdx]
content = strings.TrimSpace(content)
var result [][]string
for _, f := range strings.Split(content, "\n") {
result = append(result, UnpackItemsNote(f))
}
return result, startIdx, endIdx
}

func PackItemsNote(fields []string) string {
escaped := make([]string, len(fields))
for i, f := range fields {
// Escape backslashes first, then the separator
temp := strings.ReplaceAll(f, "\\", "\\\\")
escaped[i] = strings.ReplaceAll(temp, "|", "\\|")
}
return strings.Join(escaped, "|")
}

func UnpackItemsNote(input string) []string {
var result []string
var current strings.Builder
escaped := false

for i := 0; i < len(input); i++ {
char := input[i]

if escaped {
current.WriteByte(char)
escaped = false
continue
}

switch char {
case '\\':
escaped = true
case '|':
result = append(result, current.String())
current.Reset()
default:
current.WriteByte(char)
}
}
result = append(result, current.String())
return result
}
25 changes: 25 additions & 0 deletions broker/common/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package common
import (
"reflect"
"testing"

"github.com/stretchr/testify/assert"
)

type User struct {
Expand Down Expand Up @@ -68,3 +70,26 @@ func TestStructToMap(t *testing.T) {
})
}
}

func TestGetItemParams(t *testing.T) {
// Just ID
note := MULTIPLE_ITEMS + "\n1\n" + MULTIPLE_ITEMS_END
result, startIdx, endIdx := GetItemParams(note)
assert.Equal(t, 0, startIdx)
assert.Equal(t, 18, endIdx)
assert.Equal(t, [][]string{{"1"}}, result)

// All params
note = MULTIPLE_ITEMS + "\n1|2\\||3\\\\\n" + MULTIPLE_ITEMS_END
result, startIdx, endIdx = GetItemParams(note)
assert.Equal(t, 0, startIdx)
assert.Equal(t, 26, endIdx)
assert.Equal(t, [][]string{{"1", "2|", "3\\"}}, result)

// Incorrect tag order
note = MULTIPLE_ITEMS_END + "\n1\n" + MULTIPLE_ITEMS
result, startIdx, endIdx = GetItemParams(note)
assert.Equal(t, 21, startIdx)
assert.Equal(t, 0, endIdx)
assert.Nil(t, result)
}
72 changes: 72 additions & 0 deletions broker/oapi/open-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -709,6 +709,35 @@ components:
required:
- name

PrItem:
type: object
title: Item
description: Patron request item
properties:
id:
type: string
description: Item system id
barcode:
type: string
description: Item barcode
title:
type: string
description: Item title
callNumber:
type: string
description: Item call number
itemId:
type: string
description: Item item id
createdAt:
type: string
format: date-time
description: Item creation date time
required:
- id
- barcode
- createdAt

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

/patron_requests/{id}/items:
get:
summary: Retrieve patron request related items
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 items
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/PrItem'
'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
90 changes: 55 additions & 35 deletions broker/patron_request/api/api-handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,13 +247,8 @@ func (a *PatronRequestApiHandler) DeletePatronRequestsId(w http.ResponseWriter,
addBadRequestError(ctx, w, err)
return
}
pr, err := a.getPatronRequestById(ctx, id, params.Side, symbol)
if err != nil {
addInternalError(ctx, w, err)
return
}
pr := a.getPatronRequestById(w, ctx, id, params.Side, symbol)
if pr == nil {
addNotFoundError(w)
return
}
err = a.prRepo.WithTxFunc(ctx, func(repo pr_db.PrRepo) error {
Expand All @@ -266,18 +261,21 @@ func (a *PatronRequestApiHandler) DeletePatronRequestsId(w http.ResponseWriter,
w.WriteHeader(http.StatusNoContent)
}

func (a *PatronRequestApiHandler) getPatronRequestById(ctx common.ExtendedContext, id string, side *string, symbol string) (*pr_db.PatronRequest, error) {
func (a *PatronRequestApiHandler) getPatronRequestById(w http.ResponseWriter, ctx common.ExtendedContext, id string, side *string, symbol string) *pr_db.PatronRequest {
pr, err := a.prRepo.GetPatronRequestById(ctx, id)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, nil
addNotFoundError(w)
return nil
}
return nil, err
addInternalError(ctx, w, err)
return nil
}
if isOwner(pr, symbol) && (!isSideParamValid(side) || string(pr.Side) == *side) {
return &pr, nil
return &pr
}
return nil, nil
addNotFoundError(w)
return nil
}

func isSideParamValid(side *string) bool {
Expand All @@ -301,13 +299,8 @@ func (a *PatronRequestApiHandler) GetPatronRequestsId(w http.ResponseWriter, r *
addBadRequestError(ctx, w, err)
return
}
pr, err := a.getPatronRequestById(ctx, id, params.Side, symbol)
if err != nil {
addInternalError(ctx, w, err)
return
}
pr := a.getPatronRequestById(w, ctx, id, params.Side, symbol)
if pr == nil {
addNotFoundError(w)
return
}
var illRequest iso18626.Request
Expand All @@ -331,13 +324,8 @@ func (a *PatronRequestApiHandler) GetPatronRequestsIdActions(w http.ResponseWrit
addBadRequestError(ctx, w, err)
return
}
pr, err := a.getPatronRequestById(ctx, id, params.Side, symbol)
if err != nil {
addInternalError(ctx, w, err)
return
}
pr := a.getPatronRequestById(w, ctx, id, params.Side, symbol)
if pr == nil {
addNotFoundError(w)
return
}
actionMapping, err := a.actionMappingService.GetActionMapping(*pr)
Expand All @@ -361,13 +349,8 @@ func (a *PatronRequestApiHandler) PostPatronRequestsIdAction(w http.ResponseWrit
addBadRequestError(ctx, w, err)
return
}
pr, err := a.getPatronRequestById(ctx, id, params.Side, symbol)
if err != nil {
addInternalError(ctx, w, err)
return
}
pr := a.getPatronRequestById(w, ctx, id, params.Side, symbol)
if pr == nil {
addNotFoundError(w)
return
}
var action proapi.ExecuteAction
Expand Down Expand Up @@ -422,6 +405,7 @@ func (a *PatronRequestApiHandler) PostPatronRequestsIdAction(w http.ResponseWrit
func (a *PatronRequestApiHandler) GetPatronRequestsIdEvents(w http.ResponseWriter, r *http.Request, id string, params proapi.GetPatronRequestsIdEventsParams) {
symbol, err := api.GetSymbolForRequest(r, a.tenant, params.XOkapiTenant, params.Symbol)
logParams := map[string]string{"method": "GetPatronRequestsIdEvents", "id": id, "symbol": symbol}

if params.Side != nil {
logParams["side"] = *params.Side
}
Expand All @@ -431,24 +415,49 @@ func (a *PatronRequestApiHandler) GetPatronRequestsIdEvents(w http.ResponseWrite
addBadRequestError(ctx, w, err)
return
}
pr, err := a.getPatronRequestById(ctx, id, params.Side, symbol)
pr := a.getPatronRequestById(w, ctx, id, params.Side, symbol)
if pr == nil {
return
}
eventsList, err := a.eventRepo.GetPatronRequestEvents(ctx, pr.ID)
if err != nil {
addInternalError(ctx, w, err)
return
}

var responseItems []oapi.Event
for _, event := range eventsList {
responseItems = append(responseItems, api.ToApiEvent(event, "", &event.PatronRequestID))
}
writeJsonResponse(w, responseItems)
}

func (a *PatronRequestApiHandler) GetPatronRequestsIdItems(w http.ResponseWriter, r *http.Request, id string, params proapi.GetPatronRequestsIdItemsParams) {
symbol, err := api.GetSymbolForRequest(r, a.tenant, params.XOkapiTenant, params.Symbol)
logParams := map[string]string{"method": "GetPatronRequestsIdItems", "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 {
addNotFoundError(w)
return
}
events, err := a.eventRepo.GetPatronRequestEvents(ctx, pr.ID)
itemsList, err := a.prRepo.GetItemsByPrId(ctx, pr.ID)
if err != nil {
addInternalError(ctx, w, err)
return
}

var responseItems []oapi.Event
for _, event := range events {
responseItems = append(responseItems, api.ToApiEvent(event, "", &event.PatronRequestID))
var responseItems []proapi.PrItem
for _, item := range itemsList {
responseItems = append(responseItems, toApiItem(item))
}
writeJsonResponse(w, responseItems)
}
Expand Down Expand Up @@ -568,3 +577,14 @@ func getDbText(value *string) pgtype.Text {
String: *value,
}
}

func toApiItem(item pr_db.Item) proapi.PrItem {
return proapi.PrItem{
Id: item.ID,
Barcode: item.Barcode,
CallNumber: toString(item.CallNumber),
ItemId: toString(item.ItemID),
Title: toString(item.Title),
CreatedAt: item.CreatedAt.Time,
}
}
4 changes: 2 additions & 2 deletions broker/patron_request/db/prrepo.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ type PrRepo interface {
GetNextHrid(ctx common.ExtendedContext, prefix string) (string, error)
SaveItem(ctx common.ExtendedContext, params SaveItemParams) (Item, error)
GetItemById(ctx common.ExtendedContext, id string) (Item, error)
GetItemByPrId(ctx common.ExtendedContext, prId string) ([]Item, error)
GetItemsByPrId(ctx common.ExtendedContext, prId string) ([]Item, error)
SaveNotification(ctx common.ExtendedContext, params SaveNotificationParams) (Notification, error)
GetNotificationById(ctx common.ExtendedContext, id string) (Notification, error)
GetNotificationsByPrId(ctx common.ExtendedContext, prId string) ([]Notification, error)
Expand Down Expand Up @@ -111,7 +111,7 @@ func (r *PgPrRepo) GetItemById(ctx common.ExtendedContext, id string) (Item, err
return row.Item, err
}

func (r *PgPrRepo) GetItemByPrId(ctx common.ExtendedContext, prId string) ([]Item, error) {
func (r *PgPrRepo) GetItemsByPrId(ctx common.ExtendedContext, prId string) ([]Item, error) {
rows, err := r.queries.GetItemsByPrId(ctx, r.GetConnOrTx(), prId)
var list []Item
for _, row := range rows {
Expand Down
Loading