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
9 changes: 9 additions & 0 deletions obp-api/src/main/scala/bootstrap/liftweb/Boot.scala
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ import code.featuredapicollection.FeaturedApiCollection
import code.fx.{MappedCurrency, MappedFXRate}
import code.group.Group
import code.organisation.Organisation
import code.routingscheme.{RoutingScheme, BankSupportedRoutingScheme}
import code.payeelookup.PayeeLookup
import code.kycchecks.MappedKycCheck
import code.kycdocuments.MappedKycDocument
import code.kycmedias.MappedKycMedia
Expand Down Expand Up @@ -282,6 +284,10 @@ class Boot extends MdcLoggable {
// Please note that migration scripts are executed after Lift Mapper Schemifier
Migration.database.executeScripts(startedBeforeSchemifier = false)

// Idempotent seed of country-qualified routing schemes (TZ.MSISDN, GePG, Luku, etc.).
// Toggle off via routing_schemes.seed_defaults_at_boot=false in environments that don't want defaults.
code.routingscheme.RoutingSchemeSeed.runIfEnabled()

if (APIUtil.getPropsAsBoolValue("create_system_views_at_boot", true)) {
// Create system views
val owner = Views.views.vend.getOrCreateSystemView(SYSTEM_OWNER_VIEW_ID).isDefined
Expand Down Expand Up @@ -1212,6 +1218,9 @@ object ToSchemify {
BankAccountBalance,
Group,
Organisation,
RoutingScheme,
BankSupportedRoutingScheme,
PayeeLookup,
AccountAccessRequest,
code.chat.ChatRoom,
code.chat.Participant,
Expand Down
15 changes: 13 additions & 2 deletions obp-api/src/main/scala/code/api/util/ApiRole.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1354,6 +1354,17 @@ object ApiRole extends MdcLoggable{
case class CanDeleteOrganisation(requiresBankId: Boolean = false) extends ApiRole
lazy val canDeleteOrganisation = CanDeleteOrganisation()

// Routing Scheme registry roles (system-scoped: schemes are global infrastructure)
case class CanCreateRoutingScheme(requiresBankId: Boolean = false) extends ApiRole
lazy val canCreateRoutingScheme = CanCreateRoutingScheme()
case class CanUpdateRoutingScheme(requiresBankId: Boolean = false) extends ApiRole
lazy val canUpdateRoutingScheme = CanUpdateRoutingScheme()
case class CanDeleteRoutingScheme(requiresBankId: Boolean = false) extends ApiRole
lazy val canDeleteRoutingScheme = CanDeleteRoutingScheme()
// Per-bank opt-in / opt-out for routing schemes the bank's adapter supports
case class CanUpdateBankSupportedRoutingScheme(requiresBankId: Boolean = true) extends ApiRole
lazy val canUpdateBankSupportedRoutingScheme = CanUpdateBankSupportedRoutingScheme()

// Group membership management roles
case class CanAddUserToGroupAtAllBanks(requiresBankId: Boolean = false) extends ApiRole
lazy val canAddUserToGroupAtAllBanks = CanAddUserToGroupAtAllBanks()
Expand Down Expand Up @@ -1459,7 +1470,6 @@ object Util {
"CanGet",
"CanUpdate",
"CanDelete",
"CanMaintain",
"CanSearch",
"CanEnable",
"CanDisable"
Expand All @@ -1478,7 +1488,8 @@ object Util {
"CanRefreshUser",
"CanReadFx",
"CanSetCallLimits",
"CanDeleteRateLimits"
"CanDeleteRateLimits",
"CanMaintainProductCollection"
)

val allowed = allowedPrefixes ::: allowedExistingNames
Expand Down
2 changes: 2 additions & 0 deletions obp-api/src/main/scala/code/api/util/ApiTag.scala
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ object ApiTag {
val apiTagChat = ResourceDocTag("Chat")
val apiTagGroup = ResourceDocTag("Group")
val apiTagOrganisation = ResourceDocTag("Organisation")
val apiTagRoutingScheme = ResourceDocTag("Routing-Scheme")
val apiTagPayee = ResourceDocTag("Payee")
val apiTagWebhook = ResourceDocTag("Webhook")
val apiTagMockedData = ResourceDocTag("Mocked-Data")
val apiTagConsent = ResourceDocTag("Consent")
Expand Down
28 changes: 28 additions & 0 deletions obp-api/src/main/scala/code/api/util/ErrorMessages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,34 @@ object ErrorMessages {
val UpdateOrganisationError = "OBP-30512: Could not update Organisation."
val DeleteOrganisationError = "OBP-30513: Could not delete Organisation."

// Routing Scheme registry (OBP-30514 .. OBP-30525)
val RoutingSchemeNotFound = "OBP-30514: Routing Scheme not found. Please specify a valid value for SCHEME."
val RoutingSchemeAlreadyExists = "OBP-30515: Routing Scheme already exists. Please specify a different value for scheme."
val InvalidRoutingSchemeName = "OBP-30516: Invalid Routing Scheme name. Must match ^(?:IBAN|BIC|OBP|[A-Z]{2}(?:\\.[A-Z][A-Z0-9_]*)+)$ — either an allow-listed global scheme (IBAN, BIC, OBP) or a country-qualified name like TZ.MSISDN."
val RoutingSchemeCountryMismatch = "OBP-30517: Routing Scheme country prefix does not match the country field."
val InvalidRoutingSchemeCategory = "OBP-30518: Invalid Routing Scheme category. Allowed values are: ACCOUNT, BANK, BRANCH, IDENTITY, BILL, UTILITY."
val InvalidRoutingSchemeStatus = "OBP-30519: Invalid Routing Scheme status. Allowed values are: ACTIVE, RESERVED, DEPRECATED, RETIRED."
val InvalidRoutingSchemeAddressPattern = "OBP-30520: Invalid Routing Scheme address_pattern. The value must be a valid regular expression."
val RoutingSchemeExampleAddressMismatch = "OBP-30521: Routing Scheme example_address does not match address_pattern."
val CreateRoutingSchemeError = "OBP-30522: Could not create Routing Scheme."
val UpdateRoutingSchemeError = "OBP-30523: Could not update Routing Scheme."
val DeleteRoutingSchemeError = "OBP-30524: Could not delete Routing Scheme."
val RoutingSchemeNotSupportedByBank = "OBP-30525: This bank does not support the requested Routing Scheme."

// Payee Lookup (OBP-30526 .. OBP-30530)
val PayeeLookupIdentifierTypeNotRegistered = "OBP-30526: identifier_type is not a registered Routing Scheme. Register it via POST /routing-schemes first."
val PayeeLookupIdentifierTypeWrongCategory = "OBP-30527: identifier_type category is not valid for payee lookup. Allowed categories are: ACCOUNT, BILL, UTILITY."
val PayeeLookupAddressMismatch = "OBP-30528: identifier does not match the address_pattern of the identifier_type."
val PayeeNotFound = "OBP-30529: No payee was found for the given identifier."
val PayeeLookupCreateError = "OBP-30530: Could not create payee lookup."

// Mobile-Wallet transaction-request (OBP-30531 .. OBP-30535)
val PayeeLookupExpiredOrNotFound = "OBP-30531: verified_payee_lookup_id is unknown or has expired. Lookups are valid for 10 minutes."
val PayeeLookupMismatch = "OBP-30532: verified_payee_lookup_id does not match the supplied identifier."
val MobileWalletDestinationNotFound = "OBP-30533: No mobile-wallet account is registered for the supplied msisdn. In mapped mode the destination must have an account routing for the country-qualified MSISDN scheme (e.g. TZ.MSISDN)."
val MobileWalletInvalidMsisdn = "OBP-30534: Invalid msisdn — does not match the address_pattern of the country-qualified MSISDN routing scheme."
val MobileWalletPaymentError = "OBP-30535: Could not create MOBILE_WALLET transaction request."

val FeaturedApiCollectionNotFound = "OBP-30400: FeaturedApiCollection not found. Please specify a valid value for API_COLLECTION_ID."
val CreateFeaturedApiCollectionError = "OBP-30401: Could not create FeaturedApiCollection."
val UpdateFeaturedApiCollectionError = "OBP-30402: Could not update FeaturedApiCollection."
Expand Down
24 changes: 24 additions & 0 deletions obp-api/src/main/scala/code/api/util/http4s/Http4sSupport.scala
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,30 @@ object Http4sRequestAttributes {
}
}

/**
* Execute POST business logic with JSON body parsing, requiring validated User, BankAccount, and View.
* Returns 201 Created on success, 400 on body parse failure, converts errors via ErrorResponseConverter.
*/
def withViewAndBodyCreated[B, A](req: Request[IO])(f: (User, BankAccount, View, B, CallContext) => Future[A])(implicit formats: Formats, mf: Manifest[B]): IO[Response[IO]] = {
implicit val cc: CallContext = req.callContext
parseBody[B](cc) match {
case Left(msg) => ErrorResponseConverter.createErrorResponse(400, msg, cc).flatTap(recordMetric(msg, _))
case Right(body) =>
val io = for {
user <- IO.fromOption(cc.user.toOption)(new RuntimeException("User not found in CallContext"))
bankAccount <- IO.fromOption(cc.bankAccount)(new RuntimeException("BankAccount not found in CallContext"))
view <- IO.fromOption(cc.view)(new RuntimeException("View not found in CallContext"))
result <- RequestScopeConnection.fromFuture(f(user, bankAccount, view, body, cc))
} yield result
io.attempt.flatMap {
case Right(result) =>
val jsonString = prettyRender(Extraction.decompose(result))
Created(jsonString).flatTap(recordMetric(result, _))
case Left(err) => ErrorResponseConverter.toHttp4sResponse(err, cc).flatTap(recordMetric(err.getMessage, _))
}
}
}

/**
* Execute business logic requiring validated User, BankAccount, and View (URL must contain VIEW_ID).
* Returns 200 OK on success, converts errors via ErrorResponseConverter.
Expand Down
Loading
Loading