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
2 changes: 2 additions & 0 deletions api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1435,6 +1435,7 @@ components:
enum:
- Institution
- Consortium
- Branch
parent:
type: string
format: uuid
Expand Down Expand Up @@ -1496,6 +1497,7 @@ components:
enum:
- Institution
- Consortium
- Branch
nullable: true
parent:
type: string
Expand Down
88 changes: 45 additions & 43 deletions api/directory.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

105 changes: 102 additions & 3 deletions api/entries.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,23 @@ func getSymbolAuthority() string {
return symbolAuthority
}

func isValidParentForType(entryType EntryType, parentEntry *db.Entry) (bool, string) {

if entryType == "Institution" {
if parentEntry.Type == "Consortium" {
return true, ""
}
return false, "Institution parent must be of type Consortium"
} else if entryType == "Branch" {
if parentEntry.Type == "Institution" {
return true, ""
}
return false, "Branch parent must be of type Institution"
} else {
return false, "Invalid type to have parent"
}
}

func scanEntryRow(rows pgx.Rows) (Entry, int, error) {
var (
id uuid.UUID
Expand Down Expand Up @@ -482,6 +499,35 @@ func (a ApiImpl) AddEntry(ctx context.Context, request AddEntryRequestObject) (A
}
defer func() { _ = tx.Rollback(ctx) }()
qtx := a.queries.WithTx(tx)
entryType := derefOrDefault(request.Body.Type, EntryType("Institution"))

if entryType == "Consortium" {
if err := qtx.LockConsortiumEntryChanges(ctx); err != nil {
slog.ErrorContext(ctx, "failed to lock consortium entry changes", "error", err)
return AddEntry500TextResponse("Internal server error"), nil
}
_, err := qtx.GetConsortialEntry(ctx)
if err == nil {
return AddEntry400TextResponse("An entry of type Consortium already exists"), nil
} else if !errors.Is(err, pgx.ErrNoRows) {
slog.ErrorContext(ctx, "failed to check for consortium entry", "error", err)
return AddEntry500TextResponse("Internal server error"), nil
}
}

if request.Body.Parent != nil {
parentEntry, err := qtx.EntryByIdForUpdate(ctx, *request.Body.Parent)
if errors.Is(err, pgx.ErrNoRows) {
return AddEntry400TextResponse("Value for Parent is not a valid Entry"), nil
} else if err != nil {
slog.ErrorContext(ctx, "failed to fetch parent entry", "error", err)
return AddEntry500TextResponse("Internal server error"), nil
}
validParent, reason := isValidParentForType(entryType, &parentEntry)
if !validParent {
return AddEntry400TextResponse("Invalid entry for parent: " + reason), nil
}
}

toInsert := db.CreateEntryParams{
Name: request.Body.Name,
Expand All @@ -490,7 +536,7 @@ func (a ApiImpl) AddEntry(ctx context.Context, request AddEntryRequestObject) (A
PhoneNumber: request.Body.PhoneNumber,
TimeZone: request.Body.TimeZone,
OrganizationID: request.Body.OrganizationId,
Type: string(derefOrDefault(request.Body.Type, "Institution")),
Type: string(entryType),
Parent: request.Body.Parent,
LmsLocationCode: request.Body.LmsLocationCode,
LenderOfLastResort: request.Body.LenderOfLastResort,
Expand Down Expand Up @@ -656,8 +702,61 @@ func (a ApiImpl) UpdateEntry(ctx context.Context, request UpdateEntryRequestObje
slog.ErrorContext(ctx, "type cannot be null")
return UpdateEntry400TextResponse("'type' cannot be set to null"), nil
}

origTypeEntryPatch := EntryPatchType(orig.Type)
resultingType := string(*maybeUpdateCol(&origTypeEntryPatch, request.Body.Type))
var parentEntry db.Entry
parent := maybeUpdateCol(orig.Parent, request.Body.Parent)
if parent != nil {
if *parent == orig.ID {
return UpdateEntry400TextResponse("An entry cannot be its own parent"), nil
}
parentEntry, err = qtx.EntryByIdForUpdate(ctx, *parent)
if errors.Is(err, pgx.ErrNoRows) {
return UpdateEntry400TextResponse("Value for Parent is not a valid Entry"), nil
} else if err != nil {
slog.ErrorContext(ctx, "failed to fetch parent entry", "error", err)
return UpdateEntry500TextResponse("Internal server error"), nil
}
}

if parent != nil {
validParent, reason := isValidParentForType(EntryType(resultingType), &parentEntry)
if !validParent {
return UpdateEntry400TextResponse("Invalid entry for parent: " + reason), nil
}
}

if resultingType != orig.Type {
children, err := qtx.EntriesByParent(ctx, &orig.ID)
if err != nil {
slog.ErrorContext(ctx, "failed to fetch child entries", "error", err)
return UpdateEntry500TextResponse("Internal server error"), nil
}
resultingParent := orig
resultingParent.Type = resultingType
for _, child := range children {
valid, reason := isValidParentForType(EntryType(child.Type), &resultingParent)
if !valid {
return UpdateEntry400TextResponse("Entry type is invalid for existing child: " + reason), nil
}
}
}

if resultingType == "Consortium" || orig.Type == "Consortium" {
if err := qtx.LockConsortiumEntryChanges(ctx); err != nil {
slog.ErrorContext(ctx, "failed to lock consortium entry changes", "error", err)
return UpdateEntry500TextResponse("Internal server error"), nil
}
}
if resultingType == "Consortium" && resultingType != orig.Type {
consortialEntry, err := qtx.GetConsortialEntry(ctx)
if err == nil && consortialEntry.ID != orig.ID {
return UpdateEntry400TextResponse("An entry of type Consortium already exists"), nil
} else if err != nil && !errors.Is(err, pgx.ErrNoRows) {
Comment on lines +751 to +755
slog.ErrorContext(ctx, "failed to check for consortium entry", "error", err)
return UpdateEntry500TextResponse("Internal server error"), nil
}
}

err = qtx.UpdateEntry(ctx, db.UpdateEntryParams{
Name: derefOrDefault(request.Body.Name, orig.Name),
Expand All @@ -669,7 +768,7 @@ func (a ApiImpl) UpdateEntry(ctx context.Context, request UpdateEntryRequestObje
LmsLocationCode: maybeUpdateCol(orig.LmsLocationCode, request.Body.LmsLocationCode),
LenderOfLastResort: maybeUpdateCol(orig.LenderOfLastResort, request.Body.LenderOfLastResort),
Hrid: maybeUpdateCol(orig.Hrid, request.Body.Hrid),
Type: string(*maybeUpdateCol(&origTypeEntryPatch, request.Body.Type)),
Type: resultingType,
TimeZone: maybeUpdateCol(orig.TimeZone, request.Body.TimeZone),
OrganizationID: maybeUpdateCol(orig.OrganizationID, request.Body.OrganizationId),
ID: orig.ID,
Expand Down
72 changes: 72 additions & 0 deletions db/query.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions query.sql
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@ SELECT e.* FROM entries e, symbols s WHERE e.id = s.owner AND s.authority = @aut
-- name: EntryBySymbol :one
SELECT e.* FROM entries e, symbols s WHERE e.id = s.owner AND s.authority = @authority AND s.symbol = @symbol LIMIT 1;

-- name: GetConsortialEntry :one
SELECT * FROM entries WHERE type = 'Consortium' LIMIT 1;

-- name: EntriesByParent :many
SELECT * FROM entries WHERE parent = @parent;

-- name: LockConsortiumEntryChanges :exec
SELECT pg_advisory_xact_lock(hashtextextended('directoryish:consortium-entry', 0));

-- name: CreateEntry :one
INSERT INTO entries (
name, description, contact_name, email, phone_number, time_zone, organization_id, type, parent, lms_location_code, lender_of_last_resort
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name":"New Branch",
"type":"Branch",
"symbols": [
{"authority":"TEST", "symbol":"NWBRCH"}
],
"parent" : "00000000-0000-0000-0000-000000000004",
"endpoints": [
{
"name": "Primary",
"type": "ISO18626",
"address": "https://some.host.tld/path"
}
]
}
Loading
Loading