From f3042f02341dbaddd991e84f537cb011b80c191e Mon Sep 17 00:00:00 2001 From: simonredfern Date: Tue, 10 Feb 2026 14:18:49 +0100 Subject: [PATCH 1/2] Changing path of users/password-reset in v6.0.0 --- obp-api/src/main/scala/code/api/v6_0_0/APIMethods600.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/obp-api/src/main/scala/code/api/v6_0_0/APIMethods600.scala b/obp-api/src/main/scala/code/api/v6_0_0/APIMethods600.scala index f14f0f926d..b2dc44e099 100644 --- a/obp-api/src/main/scala/code/api/v6_0_0/APIMethods600.scala +++ b/obp-api/src/main/scala/code/api/v6_0_0/APIMethods600.scala @@ -4755,7 +4755,7 @@ trait APIMethods600 { implementedInApiVersion, nameOf(resetPasswordUrl), "POST", - "/management/user/reset-password-url", + "/users/password-reset", "Create Password Reset URL and Send Email", s"""Create a password reset URL for a user and automatically send it via email. | @@ -4798,7 +4798,7 @@ trait APIMethods600 { ) lazy val resetPasswordUrl: OBPEndpoint = { - case "management" :: "user" :: "reset-password-url" :: Nil JsonPost json -> _ => { + case "users" :: "password-reset" :: Nil JsonPost json -> _ => { cc => implicit val ec = EndpointContext(Some(cc)) for { (Full(u), callContext) <- authenticatedAccess(cc) From 1c257fe9c4afd092c780408e5a364119b410f1bb Mon Sep 17 00:00:00 2001 From: simonredfern Date: Wed, 11 Feb 2026 13:10:17 +0100 Subject: [PATCH 2/2] Adding Connector Trace and System Config --- .../main/scala/bootstrap/liftweb/Boot.scala | 3 +- .../SwaggerDefinitionsJSON.scala | 25 ++++ .../main/scala/code/api/util/APIUtil.scala | 30 +++++ .../main/scala/code/api/util/ApiRole.scala | 6 + .../scala/code/api/v6_0_0/APIMethods600.scala | 107 ++++++++++++++++- .../code/api/v6_0_0/JSONFactory6.0.0.scala | 45 +++++++ .../scala/code/bankconnectors/package.scala | 113 +++++++++++++++++- .../scala/code/metrics/ConnectorTrace.scala | 88 ++++++++++++++ 8 files changed, 414 insertions(+), 3 deletions(-) create mode 100644 obp-api/src/main/scala/code/metrics/ConnectorTrace.scala diff --git a/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala b/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala index a6f2d3fe51..54cc20f1e4 100644 --- a/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala +++ b/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala @@ -98,7 +98,7 @@ import code.metadata.tags.MappedTag import code.metadata.transactionimages.MappedTransactionImage import code.metadata.wheretags.MappedWhereTag import code.methodrouting.MethodRouting -import code.metrics.{MappedConnectorMetric, MappedMetric, MetricArchive} +import code.metrics.{MappedConnectorTrace, MappedConnectorMetric, MappedMetric, MetricArchive} import code.migration.MigrationScriptLog import code.model._ import code.model.dataAccess._ @@ -1162,6 +1162,7 @@ object ToSchemify { MapperAccountHolders, MappedEntitlement, MappedConnectorMetric, + MappedConnectorTrace, MappedExpectedChallengeAnswer, MappedEntitlementRequest, MappedScope, diff --git a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala index d60ce5844a..5d56313bed 100644 --- a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala +++ b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala @@ -6077,6 +6077,31 @@ object SwaggerDefinitionsJSON { description = descriptionExample.value ) + lazy val connectorTraceJsonV600 = ConnectorTraceJsonV600( + connector_trace_id = 1, + correlation_id = "12345-abcde", + connector_name = "mapped", + function_name = "getBanks", + bank_id = "gh.29.uk", + outbound_message = """{"bankId":"gh.29.uk"}""", + inbound_message = """{"status":"Success","data":[{"bankId":"gh.29.uk","shortName":"Test Bank"}]}""", + date = DateWithDayExampleObject, + duration = 150, + is_successful = true, + user_id = "9ca9a7e4-6d02-40e3-a129-0b2bf89de9f0", + http_verb = "GET", + url = "/obp/v6.0.0/banks" + ) + + lazy val connectorTracesJsonV600 = ConnectorTracesJsonV600( + connector_traces = List(connectorTraceJsonV600) + ) + + lazy val configPropsJsonV600 = ListResult( + "config_props", + List(ConfigPropJsonV600("connector", "star"), ConfigPropJsonV600("write_metrics", "true")) + ) + // HOLD sample (V600) lazy val transactionRequestBodyHoldJsonV600 = TransactionRequestBodyHoldJsonV600( value = amountOfMoneyJsonV121, diff --git a/obp-api/src/main/scala/code/api/util/APIUtil.scala b/obp-api/src/main/scala/code/api/util/APIUtil.scala index 379fcfa7fb..925fcfdc14 100644 --- a/obp-api/src/main/scala/code/api/util/APIUtil.scala +++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala @@ -3932,6 +3932,36 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ proPairs } + def getConfigPropsPairs: List[(String, String)] = { + val filepath = this.getClass.getResource("/props/sample.props.template").getPath + val bufferedSource: BufferedSource = scala.io.Source.fromFile(filepath) + try { + val keys: List[String] = (for { + line <- bufferedSource.getLines.toList + trimmed = line.trim + if trimmed.nonEmpty + if !trimmed.startsWith("webui_") && !trimmed.startsWith("#webui_") + cleaned = if (trimmed.startsWith("#")) trimmed.substring(1).trim else trimmed + if cleaned.contains("=") && !cleaned.startsWith("#") + parts = cleaned.split("=", 2) + key = parts(0).trim + if key.nonEmpty + } yield key).distinct + keys.map { key => + (key, getPropsValue(key).openOr("")) + } + } finally { + bufferedSource.close() + } + } + + private val sensitivePropsPatterns = List("password", "secret", "passphrase", "credential", "token_secret") + + def maskSensitivePropValue(key: String, value: String): String = { + if (sensitivePropsPatterns.exists(p => key.toLowerCase.contains(p)) && value.nonEmpty) "****" + else value + } + /** * This function is used to centralize generation of UUID values * @return UUID as a String value diff --git a/obp-api/src/main/scala/code/api/util/ApiRole.scala b/obp-api/src/main/scala/code/api/util/ApiRole.scala index f6c4af9cbc..43fbb5894a 100644 --- a/obp-api/src/main/scala/code/api/util/ApiRole.scala +++ b/obp-api/src/main/scala/code/api/util/ApiRole.scala @@ -456,6 +456,12 @@ object ApiRole extends MdcLoggable{ case class CanGetConnectorMetrics(requiresBankId: Boolean = false) extends ApiRole lazy val canGetConnectorMetrics = CanGetConnectorMetrics() + case class CanGetConnectorTrace(requiresBankId: Boolean = false) extends ApiRole + lazy val canGetConnectorTrace = CanGetConnectorTrace() + + case class CanGetConfigProps(requiresBankId: Boolean = false) extends ApiRole + lazy val canGetConfigProps = CanGetConfigProps() + case class CanDeleteEntitlementRequestsAtAnyBank(requiresBankId: Boolean = false) extends ApiRole lazy val canDeleteEntitlementRequestsAtAnyBank = CanDeleteEntitlementRequestsAtAnyBank() diff --git a/obp-api/src/main/scala/code/api/v6_0_0/APIMethods600.scala b/obp-api/src/main/scala/code/api/v6_0_0/APIMethods600.scala index 6735b92f42..da7e7d0395 100644 --- a/obp-api/src/main/scala/code/api/v6_0_0/APIMethods600.scala +++ b/obp-api/src/main/scala/code/api/v6_0_0/APIMethods600.scala @@ -33,7 +33,7 @@ import code.api.dynamic.entity.helper.{DynamicEntityHelper, DynamicEntityInfo} import code.api.v6_0_0.JSONFactory600.{AddUserToGroupResponseJsonV600, DynamicEntityDiagnosticsJsonV600, DynamicEntityIssueJsonV600, GroupEntitlementJsonV600, GroupEntitlementsJsonV600, GroupJsonV600, GroupsJsonV600, PostGroupJsonV600, PostGroupMembershipJsonV600, PostResetPasswordUrlJsonV600, PutGroupJsonV600, ReferenceTypeJsonV600, ReferenceTypesJsonV600, ResetPasswordUrlJsonV600, RoleWithEntitlementCountJsonV600, RolesWithEntitlementCountsJsonV600, ScannedApiVersionJsonV600, UpdateViewJsonV600, UserGroupMembershipJsonV600, UserGroupMembershipsJsonV600, ValidateUserEmailJsonV600, ValidateUserEmailResponseJsonV600, ViewJsonV600, ViewPermissionJsonV600, ViewPermissionsJsonV600, ViewsJsonV600, createAbacRuleJsonV600, createAbacRulesJsonV600, createActiveRateLimitsJsonV600, createActiveRateLimitsJsonV600FromCallLimit, createCallLimitJsonV600, createConsumerJsonV600, createRedisCallCountersJson, createFeaturedApiCollectionJsonV600, createFeaturedApiCollectionsJsonV600} import code.api.v6_0_0.OBPAPI6_0_0 import code.abacrule.{AbacRuleEngine, MappedAbacRuleProvider} -import code.metrics.{APIMetrics, ConnectorCountsRedis} +import code.metrics.{APIMetrics, ConnectorCountsRedis, ConnectorTraceProvider} import code.bankconnectors.{Connector, LocalMappedConnectorInternal} import code.bankconnectors.storedprocedure.StoredProcedureUtils import code.bankconnectors.LocalMappedConnectorInternal._ @@ -8687,6 +8687,111 @@ trait APIMethods600 { } } + staticResourceDocs += ResourceDoc( + getConnectorTraces, + implementedInApiVersion, + nameOf(getConnectorTraces), + "GET", + "/management/connector/traces", + "Get Connector Traces", + s"""Get connector traces which capture the full outbound/inbound messages for each connector call. + | + |This endpoint requires the CanGetConnectorTrace role. + | + |Connector tracing must be enabled via the write_connector_trace=true property. + | + |Filters Part 1.*filtering* parameters to GET /management/connector/traces + | + |Should be able to filter on the following fields: + | + |eg: /management/connector/traces?from_date=$DateWithMsExampleString&to_date=$DateWithMsExampleString&limit=50&offset=2 + | + |1 from_date (defaults to one week before current date): eg:from_date=$DateWithMsExampleString + | + |2 to_date (defaults to current date) eg:to_date=$DateWithMsExampleString + | + |3 limit (for pagination: defaults to 1000) eg:limit=2000 + | + |4 offset (for pagination: zero index, defaults to 0) eg: offset=10 + | + |5 connector_name (if null ignore) + | + |6 function_name (if null ignore) + | + |7 correlation_id (if null ignore) + | + |8 bank_id (if null ignore) + | + |9 user_id (if null ignore) + | + |Authentication is Required. + | + |""".stripMargin, + EmptyBody, + connectorTracesJsonV600, + List( + InvalidDateFormat, + UnknownError + ), + List(apiTagMetric, apiTagApi), + Some(List(canGetConnectorTrace))) + + lazy val getConnectorTraces: OBPEndpoint = { + case "management" :: "connector" :: "traces" :: Nil JsonGet _ => { + cc => implicit val ec = EndpointContext(Some(cc)) + for { + (Full(u), callContext) <- authenticatedAccess(cc) + _ <- NewStyle.function.hasEntitlement("", u.userId, ApiRole.canGetConnectorTrace, callContext) + httpParams <- NewStyle.function.extractHttpParamsFromUrl(cc.url) + (obpQueryParams, callContext) <- createQueriesByHttpParamsFuture(httpParams, callContext) + traces <- Future(ConnectorTraceProvider.getAllConnectorTraces(obpQueryParams)) + } yield { + (JSONFactory600.createConnectorTracesJsonV600(traces), HttpCode.`200`(callContext)) + } + } + } + + staticResourceDocs += ResourceDoc( + getConfigProps, + implementedInApiVersion, + nameOf(getConfigProps), + "GET", + "/management/config-props", + "Get Config Props", + s"""Get the configuration properties (non-WebUI) and their runtime values. + | + |This endpoint reads all property keys from the sample.props.template file + |(excluding webui_ properties) and returns their current runtime values. + | + |Sensitive properties (containing password, secret, passphrase, credential, token_secret) + |will have their values masked as ****. + | + |Authentication is Required. + | + |""".stripMargin, + EmptyBody, + configPropsJsonV600, + List( + UnknownError + ), + List(apiTagApi), + Some(List(canGetConfigProps))) + + lazy val getConfigProps: OBPEndpoint = { + case "management" :: "config-props" :: Nil JsonGet _ => { + cc => implicit val ec = EndpointContext(Some(cc)) + for { + (Full(u), callContext) <- authenticatedAccess(cc) + _ <- NewStyle.function.hasEntitlement("", u.userId, ApiRole.canGetConfigProps, callContext) + configProps = getConfigPropsPairs.map { case (key, value) => + ConfigPropJsonV600(key, maskSensitivePropValue(key, value)) + } + } yield { + (ListResult("config_props", configProps), HttpCode.`200`(callContext)) + } + } + } + } } diff --git a/obp-api/src/main/scala/code/api/v6_0_0/JSONFactory6.0.0.scala b/obp-api/src/main/scala/code/api/v6_0_0/JSONFactory6.0.0.scala index ef0c020fe9..e281c77169 100644 --- a/obp-api/src/main/scala/code/api/v6_0_0/JSONFactory6.0.0.scala +++ b/obp-api/src/main/scala/code/api/v6_0_0/JSONFactory6.0.0.scala @@ -14,6 +14,7 @@ package code.api.v6_0_0 import code.api.util.APIUtil.stringOrNull +import code.metrics.MappedConnectorTrace import code.api.util.RateLimitingPeriod.LimitCallPeriod import code.api.util._ import code.api.v1_2_1.{AccountHolderJSON, BankRoutingJsonV121, OtherAccountMetadataJSON, TransactionDetailsJSON, TransactionMetadataJSON} @@ -817,6 +818,28 @@ case class ApiProductAttributeResponseJsonV600( is_active: Option[Boolean] ) +case class ConnectorTraceJsonV600( + connector_trace_id: Long, + correlation_id: String, + connector_name: String, + function_name: String, + bank_id: String, + outbound_message: String, + inbound_message: String, + date: Date, + duration: Long, + is_successful: Boolean, + user_id: String, + http_verb: String, + url: String +) + +case class ConnectorTracesJsonV600( + connector_traces: List[ConnectorTraceJsonV600] +) + +case class ConfigPropJsonV600(name: String, value: String) + object JSONFactory600 extends CustomJsonFormats with MdcLoggable { def createRedisCallCountersJson( @@ -2085,4 +2108,26 @@ object JSONFactory600 extends CustomJsonFormats with MdcLoggable { ApiProductsJsonV600(products.map(p => createApiProductJsonV600(p, None))) } + def createConnectorTraceJsonV600(trace: MappedConnectorTrace): ConnectorTraceJsonV600 = { + ConnectorTraceJsonV600( + connector_trace_id = trace.id.get, + correlation_id = trace.correlationId.get, + connector_name = trace.connectorName.get, + function_name = trace.functionName.get, + bank_id = trace.bankId.get, + outbound_message = trace.outboundMessage.get, + inbound_message = trace.inboundMessage.get, + date = trace.date.get, + duration = trace.duration.get, + is_successful = trace.isSuccessful.get, + user_id = trace.userId.get, + http_verb = trace.httpVerb.get, + url = trace.url.get + ) + } + + def createConnectorTracesJsonV600(traces: List[MappedConnectorTrace]): ConnectorTracesJsonV600 = { + ConnectorTracesJsonV600(traces.map(createConnectorTraceJsonV600)) + } + } diff --git a/obp-api/src/main/scala/code/bankconnectors/package.scala b/obp-api/src/main/scala/code/bankconnectors/package.scala index c2d761dea3..521c452969 100644 --- a/obp-api/src/main/scala/code/bankconnectors/package.scala +++ b/obp-api/src/main/scala/code/bankconnectors/package.scala @@ -9,7 +9,7 @@ import code.api.util.{CallContext, FutureUtil, NewStyle} import code.api.util.APIUtil.{canOpenFuture, fullBoxOrException, getCorrelationId, getPropsAsBoolValue} import code.api.util.ErrorMessages.{InvalidConnectorResponseForMissingRequiredValues, ServiceIsTooBusy} import code.methodrouting.{MethodRouting, MethodRoutingT} -import code.metrics.{ConnectorMetricsProvider, ConnectorCountsRedis} +import code.metrics.{ConnectorTraceProvider, ConnectorMetricsProvider, ConnectorCountsRedis} import code.util.Helper import code.util.Helper.MdcLoggable import com.openbankproject.commons.model.{AccountId, BankId} @@ -98,6 +98,21 @@ package object bankconnectors extends MdcLoggable { connectorName, methodName, correlationId, now, duration, params, isSuccess) } } + + // Record connector trace (outbound/inbound messages) + if (getPropsAsBoolValue("write_connector_trace", false)) { + val outbound = serializeOutboundArgs(method, args) + val inbound = serializeInboundResult(result) + val correlationId = getCorrelationId() + val (detailUserId, detailHttpVerb, detailApiUrl) = extractCallContextInfo(args) + val bankIdValue = extractBankIdFromArgs(args) + Future { + ConnectorTraceProvider.saveConnectorTrace( + correlationId, connectorName, methodName, bankIdValue, + outbound, inbound, now, duration, isSuccess, + detailUserId, detailHttpVerb, detailApiUrl) + } + } } } else { // Non-future (legacy Box) result - track synchronously @@ -115,6 +130,21 @@ package object bankconnectors extends MdcLoggable { connectorName, methodName, correlationId, now, duration, params, isSuccess) } } + + // Record connector trace (outbound/inbound messages) + if (getPropsAsBoolValue("write_connector_trace", false)) { + val outbound = serializeOutboundArgs(method, args) + val inbound = serializeInboundResult(TrySuccess(connectorMethodResult)) + val correlationId = getCorrelationId() + val (detailUserId, detailHttpVerb, detailApiUrl) = extractCallContextInfo(args) + val bankIdValue = extractBankIdFromArgs(args) + Future { + ConnectorTraceProvider.saveConnectorTrace( + correlationId, connectorName, methodName, bankIdValue, + outbound, inbound, now, duration, isSuccess, + detailUserId, detailHttpVerb, detailApiUrl) + } + } } if (connectorMethodResult.isInstanceOf[Future[_]] && canOpenFuture(method.getName)) { @@ -444,6 +474,87 @@ package object bankconnectors extends MdcLoggable { } } + /** + * Serialize method arguments to a JSON string for connector trace. + * Filters out CallContext to avoid capturing session data. + */ + private def serializeOutboundArgs(method: Method, args: Array[AnyRef]): String = { + try { + val paramNames = method.getParameters.map(_.getName) + val filtered = paramNames.zip(args).filterNot { + case (_, v) => v.isInstanceOf[Option[_]] && v.asInstanceOf[Option[_]].exists(_.isInstanceOf[CallContext]) + case _ => false + }.filterNot { + case (_, v) => v.isInstanceOf[CallContext] + case _ => false + } + filtered.map { case (name, value) => + s"$name=${Option(value).map(_.toString).getOrElse("null")}" + }.mkString(", ") + } catch { + case e: Throwable => s"Failed to serialize args: ${e.getMessage}" + } + } + + /** + * Serialize the result of a connector call for connector trace. + * Extracts data from Box[(T, Option[CallContext])] tuples, filtering out CallContext. + */ + private def serializeInboundResult(result: scala.util.Try[Any]): String = { + try { + val value = result match { + case TrySuccess(Full((data, Some(_: CallContext)))) => data + case TrySuccess(Full((data, None))) => data + case TrySuccess(Full(data)) => data + case TrySuccess((data, Some(_: CallContext))) => data + case TrySuccess((data, None)) => data + case TrySuccess(data) => data + case TryFailure(e) => s"Exception: ${e.getMessage}" + } + Option(value).map(_.toString).getOrElse("null") + } catch { + case e: Throwable => s"Failed to serialize result: ${e.getMessage}" + } + } + + /** + * Extract userId, httpVerb, and url from the CallContext found in method args. + */ + private def extractCallContextInfo(args: Array[AnyRef]): (String, String, String) = { + try { + val cc: Option[CallContext] = args.collectFirst { + case Some(cc: CallContext) => cc + case Full(cc: CallContext) => cc + } + cc match { + case Some(callContext) => + val userId = callContext.user.map(_.userId).getOrElse("") + val httpVerb = callContext.verb + val apiUrl = callContext.url + (userId, httpVerb, apiUrl) + case None => ("", "", "") + } + } catch { + case _: Throwable => ("", "", "") + } + } + + /** + * Extract bankId from method args for connector trace. + */ + private def extractBankIdFromArgs(args: Array[AnyRef]): String = { + try { + args.collectFirst { + case bankId: BankId => bankId.value + }.getOrElse { + // Try nested search + args.toStream.map(findBankIdIn(_)).find(_.isDefined).flatten.getOrElse("") + } + } catch { + case _: Throwable => "" + } + } + /** * Check if a connector result value represents a failure (Failure or Empty Box). */ diff --git a/obp-api/src/main/scala/code/metrics/ConnectorTrace.scala b/obp-api/src/main/scala/code/metrics/ConnectorTrace.scala new file mode 100644 index 0000000000..acd4f0bc4f --- /dev/null +++ b/obp-api/src/main/scala/code/metrics/ConnectorTrace.scala @@ -0,0 +1,88 @@ +package code.metrics + +import java.util.Date + +import code.api.util._ +import net.liftweb.mapper._ + +class MappedConnectorTrace extends LongKeyedMapper[MappedConnectorTrace] with IdPK { + override def getSingleton = MappedConnectorTrace + + object correlationId extends MappedString(this, 256) + object connectorName extends MappedString(this, 64) + object functionName extends MappedString(this, 128) + object bankId extends MappedString(this, 256) + object outboundMessage extends MappedText(this) + object inboundMessage extends MappedText(this) + object date extends MappedDateTime(this) + object duration extends MappedLong(this) + object isSuccessful extends MappedBoolean(this) + object userId extends MappedString(this, 256) + object httpVerb extends MappedString(this, 16) + object url extends MappedString(this, 2000) +} + +object MappedConnectorTrace extends MappedConnectorTrace with LongKeyedMetaMapper[MappedConnectorTrace] { + override def dbTableName = "connector_trace" + override def dbIndexes = Index(correlationId) :: Index(connectorName) :: Index(functionName) :: + Index(date) :: Index(userId) :: Index(bankId) :: super.dbIndexes +} + +object ConnectorTraceProvider { + + def saveConnectorTrace( + correlationId: String, + connectorName: String, + functionName: String, + bankId: String, + outboundMessage: String, + inboundMessage: String, + date: Date, + duration: Long, + isSuccessful: Boolean, + userId: String, + httpVerb: String, + url: String + ): Unit = { + MappedConnectorTrace.create + .correlationId(correlationId) + .connectorName(connectorName) + .functionName(functionName) + .bankId(bankId) + .outboundMessage(outboundMessage) + .inboundMessage(inboundMessage) + .date(date) + .duration(duration) + .isSuccessful(isSuccessful) + .userId(userId) + .httpVerb(httpVerb) + .url(url) + .save + } + + def getAllConnectorTraces(queryParams: List[OBPQueryParam]): List[MappedConnectorTrace] = { + val limit = queryParams.collect { case OBPLimit(value) => MaxRows[MappedConnectorTrace](value) }.headOption + val offset = queryParams.collect { case OBPOffset(value) => StartAt[MappedConnectorTrace](value) }.headOption + val fromDate = queryParams.collect { case OBPFromDate(date) => By_>=(MappedConnectorTrace.date, date) }.headOption + val toDate = queryParams.collect { case OBPToDate(date) => By_<=(MappedConnectorTrace.date, date) }.headOption + val correlationId = queryParams.collect { case OBPCorrelationId(value) => By(MappedConnectorTrace.correlationId, value) }.headOption + val functionName = queryParams.collect { case OBPFunctionName(value) => By(MappedConnectorTrace.functionName, value) }.headOption + val connectorName = queryParams.collect { case OBPConnectorName(value) => By(MappedConnectorTrace.connectorName, value) }.headOption + val userId = queryParams.collect { case OBPUserId(value) => By(MappedConnectorTrace.userId, value) }.headOption + val bankId = queryParams.collect { case OBPBankId(value) => By(MappedConnectorTrace.bankId, value) }.headOption + val ordering = queryParams.collect { + case OBPOrdering(_, direction) => + direction match { + case OBPAscending => OrderBy(MappedConnectorTrace.date, Ascending) + case OBPDescending => OrderBy(MappedConnectorTrace.date, Descending) + } + } + val optionalParams: Seq[QueryParam[MappedConnectorTrace]] = Seq( + limit.toSeq, offset.toSeq, fromDate.toSeq, toDate.toSeq, ordering, + correlationId.toSeq, functionName.toSeq, connectorName.toSeq, + userId.toSeq, bankId.toSeq + ).flatten + + MappedConnectorTrace.findAll(optionalParams: _*) + } +}