mirror of
https://github.com/OpenBankProject/OBP-API.git
synced 2026-02-06 17:56:46 +00:00
Merge branch 'OpenBankProject:develop' into develop
This commit is contained in:
commit
8d36d130a5
4
.gitignore
vendored
4
.gitignore
vendored
@ -8,6 +8,7 @@
|
||||
.settings
|
||||
.metals
|
||||
.vscode
|
||||
.zed
|
||||
.classpath
|
||||
.project
|
||||
.cache
|
||||
@ -30,8 +31,9 @@ obp-api/src/main/scala/code/api/v3_0_0/custom/
|
||||
marketing_diagram_generation/outputs/*
|
||||
|
||||
.bloop
|
||||
!.bloop/*.json
|
||||
.bsp
|
||||
.specstory
|
||||
project/project
|
||||
coursier
|
||||
*.code-workspace
|
||||
*.code-workspace
|
||||
|
||||
76
.metals-config.json
Normal file
76
.metals-config.json
Normal file
@ -0,0 +1,76 @@
|
||||
{
|
||||
"maven": {
|
||||
"enabled": true
|
||||
},
|
||||
"metals": {
|
||||
"serverVersion": "1.0.0",
|
||||
"javaHome": "/usr/lib/jvm/java-17-openjdk-amd64",
|
||||
"bloopVersion": "2.0.0",
|
||||
"superMethodLensesEnabled": true,
|
||||
"enableSemanticHighlighting": true,
|
||||
"compileOnSave": true,
|
||||
"testUserInterface": "Code Lenses",
|
||||
"inlayHints": {
|
||||
"enabled": true,
|
||||
"hintsInPatternMatch": {
|
||||
"enabled": true
|
||||
},
|
||||
"implicitArguments": {
|
||||
"enabled": true
|
||||
},
|
||||
"implicitConversions": {
|
||||
"enabled": true
|
||||
},
|
||||
"inferredTypes": {
|
||||
"enabled": true
|
||||
},
|
||||
"typeParameters": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"buildTargets": [
|
||||
{
|
||||
"id": "obp-commons",
|
||||
"displayName": "obp-commons",
|
||||
"baseDirectory": "file:///home/marko/Tesobe/GitHub/constantine2nd/OBP-API/obp-commons/",
|
||||
"tags": ["library"],
|
||||
"languageIds": ["scala", "java"],
|
||||
"dependencies": [],
|
||||
"capabilities": {
|
||||
"canCompile": true,
|
||||
"canTest": true,
|
||||
"canRun": false,
|
||||
"canDebug": true
|
||||
},
|
||||
"dataKind": "scala",
|
||||
"data": {
|
||||
"scalaOrganization": "org.scala-lang",
|
||||
"scalaVersion": "2.12.20",
|
||||
"scalaBinaryVersion": "2.12",
|
||||
"platform": "jvm"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "obp-api",
|
||||
"displayName": "obp-api",
|
||||
"baseDirectory": "file:///home/marko/Tesobe/GitHub/constantine2nd/OBP-API/obp-api/",
|
||||
"tags": ["application"],
|
||||
"languageIds": ["scala", "java"],
|
||||
"dependencies": ["obp-commons"],
|
||||
"capabilities": {
|
||||
"canCompile": true,
|
||||
"canTest": true,
|
||||
"canRun": true,
|
||||
"canDebug": true
|
||||
},
|
||||
"dataKind": "scala",
|
||||
"data": {
|
||||
"scalaOrganization": "org.scala-lang",
|
||||
"scalaVersion": "2.12.20",
|
||||
"scalaBinaryVersion": "2.12",
|
||||
"platform": "jvm"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
12
README.md
12
README.md
@ -61,11 +61,22 @@ OpenJDK 11 is available for download here: [https://jdk.java.net/archive/](https
|
||||
The project uses Maven 3 as its build tool.
|
||||
|
||||
To compile and run Jetty, install Maven 3, create your configuration in `obp-api/src/main/resources/props/default.props` and execute:
|
||||
To compile and run Jetty, install Maven 3, create your configuration in `obp-api/src/main/resources/props/`, copy `sample.props.template` to `default.props` and edit the latter. Then:
|
||||
|
||||
```sh
|
||||
mvn install -pl .,obp-commons && mvn jetty:run -pl obp-api
|
||||
```
|
||||
|
||||
### ZED IDE Setup
|
||||
|
||||
For ZED IDE users, we provide a complete development environment with Scala language server support:
|
||||
|
||||
```bash
|
||||
./zed/setup-zed-ide.sh
|
||||
```
|
||||
|
||||
This sets up automated build tasks, code navigation, and real-time error checking. See [`zed/README.md`](zed/README.md) for complete documentation.
|
||||
|
||||
In case the above command fails try the next one:
|
||||
|
||||
```sh
|
||||
@ -222,6 +233,7 @@ Once Postgres is installed (On macOS, use `brew`):
|
||||
1. Grant all on database `obpdb` to `obp`; (So OBP-API can create tables etc.)
|
||||
|
||||
#### For newer versions of postgres 16 and above, you need to follow the following instructions
|
||||
|
||||
-- Connect to the sandbox database
|
||||
\c sandbox;
|
||||
|
||||
|
||||
@ -1518,3 +1518,48 @@ regulated_entities = []
|
||||
|
||||
|
||||
# Note: For secure and http only settings for cookies see resources/web.xml which is mentioned in the README.md
|
||||
|
||||
|
||||
|
||||
##########################################################
|
||||
# Redis Logging #
|
||||
##########################################################
|
||||
## Enable Redis logging (true/false)
|
||||
redis_logging_enabled = false
|
||||
|
||||
## Batch size for sending logs to Redis
|
||||
## Smaller batch size reduces latency for logging critical messages.
|
||||
redis_logging_batch_size = 50
|
||||
|
||||
## Flush interval for batch logs in milliseconds
|
||||
## Flush every 500ms to keep Redis queues up-to-date without too much delay.
|
||||
redis_logging_flush_interval_ms = 500
|
||||
|
||||
## Maximum number of retries for failed log writes
|
||||
## Ensures transient Redis errors are retried before failing.
|
||||
redis_logging_max_retries = 3
|
||||
|
||||
## Number of consecutive failures before opening circuit breaker
|
||||
## Prevents hammering Redis when it is down.
|
||||
redis_logging_circuit_breaker_threshold = 10
|
||||
|
||||
## Number of threads for asynchronous Redis operations
|
||||
## Keep small for lightweight logging; adjust if heavy logging is expected.
|
||||
redis_logging_thread_pool_size = 2
|
||||
|
||||
## SIX different FIFO Redis queues. Each queue has a maximum number of entries.
|
||||
## These control how many messages are kept per log level.
|
||||
## 1000 is a reasonable default; adjust if you expect higher traffic.
|
||||
redis_logging_trace_queue_max_entries = 1000 # Max TRACE messages
|
||||
redis_logging_debug_queue_max_entries = 1000 # Max DEBUG messages
|
||||
redis_logging_info_queue_max_entries = 1000 # Max INFO messages
|
||||
redis_logging_warning_queue_max_entries = 1000 # Max WARNING messages
|
||||
redis_logging_error_queue_max_entries = 1000 # Max ERROR messages
|
||||
redis_logging_all_queue_max_entries = 1000 # Max ALL messages
|
||||
|
||||
## Optional: Circuit breaker reset interval (ms)
|
||||
## How long before retrying after circuit breaker opens. Default 60s.
|
||||
redis_logging_circuit_breaker_reset_ms = 60000
|
||||
##########################################################
|
||||
# Redis Logging #
|
||||
##########################################################
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package bootstrap.liftweb
|
||||
|
||||
import code.api.util.APIUtil
|
||||
import code.util.Helper.MdcLoggable
|
||||
import com.zaxxer.hikari.pool.ProxyConnection
|
||||
import com.zaxxer.hikari.{HikariConfig, HikariDataSource}
|
||||
|
||||
@ -21,19 +22,17 @@ import net.liftweb.util.Helpers.tryo
|
||||
class CustomDBVendor(driverName: String,
|
||||
dbUrl: String,
|
||||
dbUser: Box[String],
|
||||
dbPassword: Box[String]) extends CustomProtoDBVendor {
|
||||
|
||||
private val logger = Logger(classOf[CustomDBVendor])
|
||||
dbPassword: Box[String]) extends CustomProtoDBVendor with MdcLoggable {
|
||||
|
||||
object HikariDatasource {
|
||||
val config = new HikariConfig()
|
||||
|
||||
|
||||
val connectionTimeout = APIUtil.getPropsAsLongValue("hikari.connectionTimeout")
|
||||
val maximumPoolSize = APIUtil.getPropsAsIntValue("hikari.maximumPoolSize")
|
||||
val idleTimeout = APIUtil.getPropsAsLongValue("hikari.idleTimeout")
|
||||
val keepaliveTime = APIUtil.getPropsAsLongValue("hikari.keepaliveTime")
|
||||
val maxLifetime = APIUtil.getPropsAsLongValue("hikari.maxLifetime")
|
||||
|
||||
|
||||
if(connectionTimeout.isDefined){
|
||||
config.setConnectionTimeout(connectionTimeout.head)
|
||||
}
|
||||
@ -63,7 +62,7 @@ class CustomDBVendor(driverName: String,
|
||||
case _ =>
|
||||
config.setJdbcUrl(dbUrl)
|
||||
}
|
||||
|
||||
|
||||
config.addDataSourceProperty("cachePrepStmts", "true")
|
||||
config.addDataSourceProperty("prepStmtCacheSize", "250")
|
||||
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048")
|
||||
@ -79,8 +78,7 @@ class CustomDBVendor(driverName: String,
|
||||
def closeAllConnections_!(): Unit = HikariDatasource.ds.close()
|
||||
}
|
||||
|
||||
trait CustomProtoDBVendor extends ConnectionManager {
|
||||
private val logger = Logger(classOf[CustomProtoDBVendor])
|
||||
trait CustomProtoDBVendor extends ConnectionManager with MdcLoggable {
|
||||
|
||||
def createOne: Box[Connection]
|
||||
|
||||
@ -90,4 +88,4 @@ trait CustomProtoDBVendor extends ConnectionManager {
|
||||
|
||||
def releaseConnection(conn: Connection): Unit = {conn.asInstanceOf[ProxyConnection].close()}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ import com.openbankproject.commons.model.enums.AccountAttributeType
|
||||
import com.openbankproject.commons.model.{AccountAttribute, AccountId, BankId, BankIdAccountId, ProductAttribute, ProductCode, ViewId}
|
||||
import net.liftweb.common.{Box, Logger}
|
||||
import net.liftweb.util.SimpleInjector
|
||||
import code.util.Helper.MdcLoggable
|
||||
|
||||
import scala.collection.immutable.List
|
||||
import scala.concurrent.Future
|
||||
@ -16,7 +17,7 @@ object AccountAttributeX extends SimpleInjector {
|
||||
val accountAttributeProvider = new Inject(buildOne _) {}
|
||||
|
||||
def buildOne: AccountAttributeProvider = MappedAccountAttributeProvider
|
||||
|
||||
|
||||
// Helper to get the count out of an option
|
||||
def countOfAccountAttribute(listOpt: Option[List[AccountAttribute]]): Int = {
|
||||
val count = listOpt match {
|
||||
@ -29,17 +30,15 @@ object AccountAttributeX extends SimpleInjector {
|
||||
|
||||
}
|
||||
|
||||
trait AccountAttributeProvider {
|
||||
|
||||
private val logger = Logger(classOf[AccountAttributeProvider])
|
||||
trait AccountAttributeProvider extends MdcLoggable {
|
||||
|
||||
def getAccountAttributesFromProvider(accountId: AccountId, productCode: ProductCode): Future[Box[List[AccountAttribute]]]
|
||||
def getAccountAttributesByAccount(bankId: BankId,
|
||||
accountId: AccountId): Future[Box[List[AccountAttribute]]]
|
||||
def getAccountAttributesByAccountCanBeSeenOnView(bankId: BankId,
|
||||
accountId: AccountId,
|
||||
def getAccountAttributesByAccountCanBeSeenOnView(bankId: BankId,
|
||||
accountId: AccountId,
|
||||
viewId: ViewId): Future[Box[List[AccountAttribute]]]
|
||||
def getAccountAttributesByAccountsCanBeSeenOnView(accounts: List[BankIdAccountId],
|
||||
def getAccountAttributesByAccountsCanBeSeenOnView(accounts: List[BankIdAccountId],
|
||||
viewId: ViewId): Future[Box[List[AccountAttribute]]]
|
||||
|
||||
def getAccountAttributeById(productAttributeId: String): Future[Box[AccountAttribute]]
|
||||
@ -58,10 +57,10 @@ trait AccountAttributeProvider {
|
||||
productCode: ProductCode,
|
||||
accountAttributes: List[ProductAttribute],
|
||||
productInstanceCode: Option[String]): Future[Box[List[AccountAttribute]]]
|
||||
|
||||
|
||||
def deleteAccountAttribute(accountAttributeId: String): Future[Box[Boolean]]
|
||||
|
||||
def getAccountIdsByParams(bankId: BankId, params: Map[String, List[String]]): Future[Box[List[String]]]
|
||||
|
||||
// End of Trait
|
||||
}
|
||||
}
|
||||
|
||||
@ -328,18 +328,26 @@ object OAuth2Login extends RestHelper with MdcLoggable {
|
||||
def getOrCreateResourceUser(idToken: String): Box[User] = {
|
||||
val uniqueIdGivenByProvider = JwtUtil.getSubject(idToken).getOrElse("")
|
||||
val provider = resolveProvider(idToken)
|
||||
Users.users.vend.getUserByProviderId(provider = provider, idGivenByProvider = uniqueIdGivenByProvider).or { // Find a user
|
||||
Users.users.vend.createResourceUser( // Otherwise create a new one
|
||||
provider = provider,
|
||||
providerId = Some(uniqueIdGivenByProvider),
|
||||
None,
|
||||
name = getClaim(name = "given_name", idToken = idToken).orElse(Some(uniqueIdGivenByProvider)),
|
||||
email = getClaim(name = "email", idToken = idToken),
|
||||
userId = None,
|
||||
createdByUserInvitationId = None,
|
||||
company = None,
|
||||
lastMarketingAgreementSignedDate = None
|
||||
)
|
||||
KeycloakFederatedUserReference.parse(uniqueIdGivenByProvider) match {
|
||||
case Right(fedRef) => // Users log on via Keycloak, which uses User Federation to access the external OBP database.
|
||||
logger.debug(s"External ID = ${fedRef.externalId}")
|
||||
logger.debug(s"Storage Provider ID = ${fedRef.storageProviderId}")
|
||||
Users.users.vend.getUserByResourceUserId(fedRef.externalId)
|
||||
case Left(error) =>
|
||||
logger.debug(s"Parse error: $error")
|
||||
Users.users.vend.getUserByProviderId(provider = provider, idGivenByProvider = uniqueIdGivenByProvider).or { // Find a user
|
||||
Users.users.vend.createResourceUser( // Otherwise create a new one
|
||||
provider = provider,
|
||||
providerId = Some(uniqueIdGivenByProvider),
|
||||
None,
|
||||
name = getClaim(name = "given_name", idToken = idToken).orElse(Some(uniqueIdGivenByProvider)),
|
||||
email = getClaim(name = "email", idToken = idToken),
|
||||
userId = None,
|
||||
createdByUserInvitationId = None,
|
||||
company = None,
|
||||
lastMarketingAgreementSignedDate = None
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -526,7 +534,7 @@ object OAuth2Login extends RestHelper with MdcLoggable {
|
||||
*/
|
||||
override def wellKnownOpenidConfiguration: URI =
|
||||
new URI(
|
||||
APIUtil.getPropsValue(nameOfProperty = "oauth2.keycloak.well_known", "http://localhost:7070/realms/master/.well-known/openid-configuration")
|
||||
APIUtil.getPropsValue(nameOfProperty = "oauth2.keycloak.well_known", "http://localhost:8000/realms/master/.well-known/openid-configuration")
|
||||
)
|
||||
override def urlOfJwkSets: Box[String] = checkUrlOfJwkSets(identityProvider = keycloakHost)
|
||||
def isIssuer(jwt: String): Boolean = isIssuer(jwtToken=jwt, identityProvider = keycloakHost)
|
||||
|
||||
@ -29,6 +29,7 @@ import com.openbankproject.commons.model._
|
||||
import com.openbankproject.commons.model.enums.TransactionRequestTypes._
|
||||
import com.openbankproject.commons.model.enums.{AttributeCategory, CardAttributeType, ChallengeType, TransactionRequestStatus}
|
||||
import com.openbankproject.commons.util.{ApiVersion, FieldNameApiVersions, ReflectUtils}
|
||||
import net.liftweb.common.Full
|
||||
import net.liftweb.json
|
||||
|
||||
import java.net.URLEncoder
|
||||
@ -4255,6 +4256,8 @@ object SwaggerDefinitionsJSON {
|
||||
consent_reference_id = consentReferenceIdExample.value,
|
||||
consumer_id = consumerIdExample.value,
|
||||
created_by_user_id = userIdExample.value,
|
||||
provider = Some(providerValueExample.value),
|
||||
provider_id = Some(providerIdExample.value),
|
||||
last_action_date = dateExample.value,
|
||||
last_usage_date = dateTimeExample.value,
|
||||
status = ConsentStatus.INITIATED.toString,
|
||||
@ -4282,7 +4285,7 @@ object SwaggerDefinitionsJSON {
|
||||
consents = List(consentInfoJsonV510)
|
||||
)
|
||||
|
||||
lazy val consentsJsonV510 = ConsentsJsonV510(List(allConsentJsonV510))
|
||||
lazy val consentsJsonV510 = ConsentsJsonV510(number_of_rows = 1, List(allConsentJsonV510))
|
||||
|
||||
lazy val revokedConsentJsonV310 = ConsentJsonV310(
|
||||
consent_id = "9d429899-24f5-42c8-8565-943ffa6a7945",
|
||||
|
||||
@ -57,6 +57,22 @@ object Redis extends MdcLoggable {
|
||||
|
||||
def jedisPoolDestroy: Unit = jedisPool.destroy()
|
||||
|
||||
def isRedisReady: Boolean = {
|
||||
var jedisConnection: Option[Jedis] = None
|
||||
try {
|
||||
jedisConnection = Some(jedisPool.getResource)
|
||||
val pong = jedisConnection.get.ping() // sends PING command
|
||||
pong == "PONG"
|
||||
} catch {
|
||||
case e: Throwable =>
|
||||
logger.error(s"Redis is not ready: ${e.getMessage}")
|
||||
false
|
||||
} finally {
|
||||
jedisConnection.foreach(_.close())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private def configureSslContext(): SSLContext = {
|
||||
|
||||
// Load the CA certificate
|
||||
|
||||
339
obp-api/src/main/scala/code/api/cache/RedisLogger.scala
vendored
Normal file
339
obp-api/src/main/scala/code/api/cache/RedisLogger.scala
vendored
Normal file
@ -0,0 +1,339 @@
|
||||
package code.api.cache
|
||||
|
||||
import code.api.util.ApiRole._
|
||||
import code.api.util.{APIUtil, ApiRole}
|
||||
|
||||
import net.liftweb.common.{Box, Empty, Failure => LiftFailure, Full, Logger}
|
||||
import redis.clients.jedis.{Jedis, Pipeline}
|
||||
|
||||
import java.util.concurrent.{Executors, ScheduledThreadPoolExecutor, TimeUnit}
|
||||
import java.util.concurrent.atomic.{AtomicBoolean, AtomicLong}
|
||||
import scala.collection.JavaConverters._
|
||||
import scala.concurrent.{ExecutionContext, Future}
|
||||
import scala.util.{Failure, Success, Try}
|
||||
|
||||
/**
|
||||
* Redis queue configuration per log level.
|
||||
*/
|
||||
case class RedisLogConfig(
|
||||
queueName: String,
|
||||
maxEntries: Int
|
||||
)
|
||||
|
||||
/**
|
||||
* Simple Redis FIFO log writer.
|
||||
*/
|
||||
object RedisLogger {
|
||||
|
||||
private val logger = Logger(RedisLogger.getClass)
|
||||
|
||||
// Performance and reliability improvements
|
||||
private val redisLoggingEnabled = APIUtil.getPropsAsBoolValue("redis_logging_enabled", false)
|
||||
private val batchSize = APIUtil.getPropsAsIntValue("redis_logging_batch_size", 100)
|
||||
private val flushIntervalMs = APIUtil.getPropsAsIntValue("redis_logging_flush_interval_ms", 1000)
|
||||
private val maxRetries = APIUtil.getPropsAsIntValue("redis_logging_max_retries", 3)
|
||||
private val circuitBreakerThreshold = APIUtil.getPropsAsIntValue("redis_logging_circuit_breaker_threshold", 10)
|
||||
|
||||
// Circuit breaker state
|
||||
private val consecutiveFailures = new AtomicLong(0)
|
||||
private val circuitBreakerOpen = new AtomicBoolean(false)
|
||||
private var lastFailureTime = 0L
|
||||
|
||||
// Async executor for Redis operations
|
||||
private val redisExecutor: ScheduledThreadPoolExecutor = Executors.newScheduledThreadPool(
|
||||
APIUtil.getPropsAsIntValue("redis_logging_thread_pool_size", 2)
|
||||
).asInstanceOf[ScheduledThreadPoolExecutor]
|
||||
private implicit val redisExecutionContext: ExecutionContext = ExecutionContext.fromExecutor(redisExecutor)
|
||||
|
||||
// Batch logging support
|
||||
private val logBuffer = new java.util.concurrent.ConcurrentLinkedQueue[LogEntry]()
|
||||
|
||||
case class LogEntry(level: LogLevel.LogLevel, message: String, timestamp: Long = System.currentTimeMillis())
|
||||
|
||||
// Start background flusher
|
||||
startBackgroundFlusher()
|
||||
|
||||
/**
|
||||
* Redis-backed logging utilities for OBP.
|
||||
*/
|
||||
object LogLevel extends Enumeration {
|
||||
type LogLevel = Value
|
||||
val TRACE, DEBUG, INFO, WARNING, ERROR, ALL = Value
|
||||
|
||||
/** Parse a string into LogLevel, throw if unknown */
|
||||
def valueOf(str: String): LogLevel = str.toUpperCase match {
|
||||
case "TRACE" => TRACE
|
||||
case "DEBUG" => DEBUG
|
||||
case "INFO" => INFO
|
||||
case "WARN" | "WARNING" => WARNING
|
||||
case "ERROR" => ERROR
|
||||
case "ALL" => ALL
|
||||
case other => throw new IllegalArgumentException(s"Invalid log level: $other")
|
||||
}
|
||||
|
||||
/** Map a LogLevel to its required entitlements */
|
||||
def requiredRoles(level: LogLevel): List[ApiRole] = level match {
|
||||
case TRACE => List(canGetTraceLevelLogsAtAllBanks, canGetAllLevelLogsAtAllBanks)
|
||||
case DEBUG => List(canGetDebugLevelLogsAtAllBanks, canGetAllLevelLogsAtAllBanks)
|
||||
case INFO => List(canGetInfoLevelLogsAtAllBanks, canGetAllLevelLogsAtAllBanks)
|
||||
case WARNING => List(canGetWarningLevelLogsAtAllBanks, canGetAllLevelLogsAtAllBanks)
|
||||
case ERROR => List(canGetErrorLevelLogsAtAllBanks, canGetAllLevelLogsAtAllBanks)
|
||||
case ALL => List(canGetAllLevelLogsAtAllBanks)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Define FIFO queues, sizes configurable via props
|
||||
val configs: Map[LogLevel.Value, RedisLogConfig] = Map(
|
||||
LogLevel.TRACE -> RedisLogConfig("obp_trace_logs", APIUtil.getPropsAsIntValue("redis_logging_trace_queue_max_entries", 1000)),
|
||||
LogLevel.DEBUG -> RedisLogConfig("obp_debug_logs", APIUtil.getPropsAsIntValue("redis_logging_debug_queue_max_entries", 1000)),
|
||||
LogLevel.INFO -> RedisLogConfig("obp_info_logs", APIUtil.getPropsAsIntValue("redis_logging_info_queue_max_entries", 1000)),
|
||||
LogLevel.WARNING -> RedisLogConfig("obp_warning_logs", APIUtil.getPropsAsIntValue("redis_logging_warning_queue_max_entries", 1000)),
|
||||
LogLevel.ERROR -> RedisLogConfig("obp_error_logs", APIUtil.getPropsAsIntValue("redis_logging_error_queue_max_entries", 1000)),
|
||||
LogLevel.ALL -> RedisLogConfig("obp_all_logs", APIUtil.getPropsAsIntValue("redis_logging_all_queue_max_entries", 1000))
|
||||
)
|
||||
|
||||
/**
|
||||
* Synchronous log (blocking until Redis writes are done).
|
||||
*/
|
||||
def logSync(level: LogLevel.LogLevel, message: String): Try[Unit] = {
|
||||
if (!redisLoggingEnabled || circuitBreakerOpen.get()) {
|
||||
return Success(()) // Skip if disabled or circuit breaker is open
|
||||
}
|
||||
|
||||
var attempt = 0
|
||||
var lastException: Throwable = null
|
||||
|
||||
while (attempt < maxRetries) {
|
||||
try {
|
||||
withPipeline { pipeline =>
|
||||
// log to requested level
|
||||
configs.get(level).foreach(cfg => pushLog(pipeline, cfg, message))
|
||||
// also log to ALL
|
||||
configs.get(LogLevel.ALL).foreach(cfg => pushLog(pipeline, cfg, s"[$level] $message"))
|
||||
pipeline.sync()
|
||||
}
|
||||
|
||||
// Reset circuit breaker on success
|
||||
consecutiveFailures.set(0)
|
||||
circuitBreakerOpen.set(false)
|
||||
return Success(())
|
||||
|
||||
} catch {
|
||||
case e: Exception =>
|
||||
lastException = e
|
||||
attempt += 1
|
||||
|
||||
if (attempt < maxRetries) {
|
||||
Thread.sleep(100 * attempt) // Exponential backoff
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle circuit breaker
|
||||
val failures = consecutiveFailures.incrementAndGet()
|
||||
if (failures >= circuitBreakerThreshold) {
|
||||
circuitBreakerOpen.set(true)
|
||||
lastFailureTime = System.currentTimeMillis()
|
||||
logger.warn(s"Redis logging circuit breaker opened after $failures consecutive failures")
|
||||
}
|
||||
|
||||
Failure(lastException)
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronous log with batching support (fire-and-forget).
|
||||
* Returns a Future[Unit], failures are handled gracefully.
|
||||
*/
|
||||
def logAsync(level: LogLevel.LogLevel, message: String): Future[Unit] = {
|
||||
if (!redisLoggingEnabled) {
|
||||
return Future.successful(())
|
||||
}
|
||||
|
||||
// Add to batch buffer for better performance
|
||||
logBuffer.offer(LogEntry(level, message))
|
||||
|
||||
// If buffer is full, flush immediately
|
||||
if (logBuffer.size() >= batchSize) {
|
||||
Future {
|
||||
flushLogBuffer()
|
||||
}(redisExecutionContext).recover {
|
||||
case e => logger.debug(s"RedisLogger batch flush failed: ${e.getMessage}")
|
||||
}
|
||||
} else {
|
||||
Future.successful(())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Immediate async log without batching for critical messages.
|
||||
*/
|
||||
def logAsyncImmediate(level: LogLevel.LogLevel, message: String): Future[Unit] = {
|
||||
Future {
|
||||
logSync(level, message) match {
|
||||
case Success(_) => // ok
|
||||
case Failure(e) => logger.debug(s"RedisLogger immediate async failed: ${e.getMessage}")
|
||||
}
|
||||
}(redisExecutionContext)
|
||||
}
|
||||
|
||||
private def withPipeline(block: Pipeline => Unit): Unit = {
|
||||
Option(Redis.jedisPool).foreach { pool =>
|
||||
val jedis = pool.getResource()
|
||||
try {
|
||||
val pipeline: Pipeline = jedis.pipelined()
|
||||
block(pipeline)
|
||||
} catch {
|
||||
case e: Exception =>
|
||||
logger.debug(s"Redis pipeline operation failed: ${e.getMessage}")
|
||||
throw e
|
||||
} finally {
|
||||
if (jedis != null) {
|
||||
jedis.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def flushLogBuffer(): Unit = {
|
||||
if (logBuffer.isEmpty || circuitBreakerOpen.get()) {
|
||||
return
|
||||
}
|
||||
|
||||
val entriesToFlush = new java.util.ArrayList[LogEntry]()
|
||||
var entry = logBuffer.poll()
|
||||
while (entry != null && entriesToFlush.size() < batchSize) {
|
||||
entriesToFlush.add(entry)
|
||||
entry = logBuffer.poll()
|
||||
}
|
||||
|
||||
if (!entriesToFlush.isEmpty) {
|
||||
try {
|
||||
withPipeline { pipeline =>
|
||||
entriesToFlush.asScala.foreach { logEntry =>
|
||||
configs.get(logEntry.level).foreach(cfg => pushLog(pipeline, cfg, logEntry.message))
|
||||
configs.get(LogLevel.ALL).foreach(cfg => pushLog(pipeline, cfg, s"[${logEntry.level}] ${logEntry.message}"))
|
||||
}
|
||||
pipeline.sync()
|
||||
}
|
||||
|
||||
// Reset circuit breaker on success
|
||||
consecutiveFailures.set(0)
|
||||
circuitBreakerOpen.set(false)
|
||||
|
||||
} catch {
|
||||
case e: Exception =>
|
||||
val failures = consecutiveFailures.incrementAndGet()
|
||||
if (failures >= circuitBreakerThreshold) {
|
||||
circuitBreakerOpen.set(true)
|
||||
lastFailureTime = System.currentTimeMillis()
|
||||
logger.warn(s"Redis logging circuit breaker opened after batch flush failure")
|
||||
}
|
||||
logger.debug(s"Redis batch flush failed: ${e.getMessage}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def startBackgroundFlusher(): Unit = {
|
||||
val flusher = new Runnable {
|
||||
override def run(): Unit = {
|
||||
try {
|
||||
// Check if circuit breaker should be reset (after 60 seconds)
|
||||
if (circuitBreakerOpen.get() && System.currentTimeMillis() - lastFailureTime > 60000) {
|
||||
circuitBreakerOpen.set(false)
|
||||
consecutiveFailures.set(0)
|
||||
logger.info("Redis logging circuit breaker reset")
|
||||
}
|
||||
|
||||
flushLogBuffer()
|
||||
} catch {
|
||||
case e: Exception =>
|
||||
logger.debug(s"Background log flusher failed: ${e.getMessage}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
redisExecutor.scheduleAtFixedRate(
|
||||
flusher,
|
||||
flushIntervalMs,
|
||||
flushIntervalMs,
|
||||
TimeUnit.MILLISECONDS
|
||||
)
|
||||
}
|
||||
|
||||
private def pushLog(pipeline: Pipeline, cfg: RedisLogConfig, msg: String): Unit = {
|
||||
if (cfg.maxEntries > 0) {
|
||||
pipeline.lpush(cfg.queueName, msg)
|
||||
pipeline.ltrim(cfg.queueName, 0, cfg.maxEntries - 1)
|
||||
}
|
||||
}
|
||||
|
||||
case class LogTailEntry(level: String, message: String)
|
||||
case class LogTail(entries: List[LogTailEntry])
|
||||
|
||||
private val LogPattern = """\[(\w+)\]\s+(.*)""".r
|
||||
|
||||
/**
|
||||
* Read latest messages from Redis FIFO queue.
|
||||
*/
|
||||
def getLogTail(level: LogLevel.LogLevel): LogTail = {
|
||||
Option(Redis.jedisPool).map { pool =>
|
||||
val jedis = pool.getResource()
|
||||
try {
|
||||
val cfg = configs(level)
|
||||
val rawLogs = jedis.lrange(cfg.queueName, 0, -1).asScala.toList
|
||||
|
||||
val entries: List[LogTailEntry] = level match {
|
||||
case LogLevel.ALL =>
|
||||
rawLogs.collect {
|
||||
case LogPattern(lvl, msg) => LogTailEntry(lvl, msg)
|
||||
}
|
||||
case other =>
|
||||
rawLogs.map(msg => LogTailEntry(other.toString, msg))
|
||||
}
|
||||
|
||||
LogTail(entries)
|
||||
} finally {
|
||||
jedis.close()
|
||||
}
|
||||
}.getOrElse(LogTail(Nil))
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Redis logging statistics
|
||||
*/
|
||||
def getStats: Map[String, Any] = Map(
|
||||
"redisLoggingEnabled" -> redisLoggingEnabled,
|
||||
"circuitBreakerOpen" -> circuitBreakerOpen.get(),
|
||||
"consecutiveFailures" -> consecutiveFailures.get(),
|
||||
"bufferSize" -> logBuffer.size(),
|
||||
"batchSize" -> batchSize,
|
||||
"flushIntervalMs" -> flushIntervalMs,
|
||||
"threadPoolActiveCount" -> redisExecutor.getActiveCount,
|
||||
"threadPoolQueueSize" -> redisExecutor.getQueue.size()
|
||||
)
|
||||
|
||||
/**
|
||||
* Shutdown the Redis logger gracefully
|
||||
*/
|
||||
def shutdown(): Unit = {
|
||||
logger.info("Shutting down Redis logger...")
|
||||
|
||||
// Flush remaining logs
|
||||
flushLogBuffer()
|
||||
|
||||
// Shutdown executor
|
||||
redisExecutor.shutdown()
|
||||
try {
|
||||
if (!redisExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||
redisExecutor.shutdownNow()
|
||||
}
|
||||
} catch {
|
||||
case _: InterruptedException =>
|
||||
redisExecutor.shutdownNow()
|
||||
}
|
||||
|
||||
logger.info("Redis logger shutdown complete")
|
||||
}
|
||||
}
|
||||
@ -2937,8 +2937,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
|
||||
val errorResponse = getFilteredOrFullErrorMessage(e)
|
||||
Full(reply.apply(errorResponse))
|
||||
case Failure(msg, e, _) =>
|
||||
surroundErrorMessage(msg)
|
||||
e.foreach(logger.debug("", _))
|
||||
e.foreach(logger.error(msg, _))
|
||||
extractAPIFailureNewStyle(msg) match {
|
||||
case Some(af) =>
|
||||
val callContextLight = af.ccl.map(_.copy(httpCode = Some(af.failCode)))
|
||||
@ -3013,8 +3012,8 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
|
||||
val xRequestId: Option[String] =
|
||||
reqHeaders.find(_.name.toLowerCase() == RequestHeader.`X-Request-ID`.toLowerCase())
|
||||
.map(_.values.mkString(","))
|
||||
val title = s"Request Headers for verb: $verb, URL: $url"
|
||||
surroundDebugMessage(reqHeaders.map(h => h.name + ": " + h.values.mkString(",")).mkString, title)
|
||||
logger.debug(s"Request Headers for verb: $verb, URL: $url")
|
||||
logger.debug(reqHeaders.map(h => h.name + ": " + h.values.mkString(",")).mkString)
|
||||
val remoteIpAddress = getRemoteIpAddress()
|
||||
|
||||
val authHeaders = AuthorisationUtil.getAuthorisationHeaders(reqHeaders)
|
||||
@ -4003,8 +4002,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
|
||||
tpp <- BerlinGroupSigning.getTppByCertificate(certificate, cc)
|
||||
} yield {
|
||||
if (tpp.nonEmpty) {
|
||||
val berlinGroupRole = PemCertificateRole.toBerlinGroup(serviceProvider)
|
||||
val hasRole = tpp.exists(_.services.contains(berlinGroupRole))
|
||||
val hasRole = tpp.exists(_.services.contains(serviceProvider))
|
||||
if (hasRole) {
|
||||
Full(true)
|
||||
} else {
|
||||
|
||||
@ -107,6 +107,38 @@ object ApiRole extends MdcLoggable{
|
||||
|
||||
case class CanCreateCustomer(requiresBankId: Boolean = true) extends ApiRole
|
||||
lazy val canCreateCustomer = CanCreateCustomer()
|
||||
|
||||
|
||||
// TRACE
|
||||
case class CanGetTraceLevelLogsAtOneBank(requiresBankId: Boolean = true) extends ApiRole
|
||||
lazy val canGetTraceLevelLogsAtOneBank = CanGetTraceLevelLogsAtOneBank()
|
||||
case class CanGetTraceLevelLogsAtAllBanks(requiresBankId: Boolean = false) extends ApiRole
|
||||
lazy val canGetTraceLevelLogsAtAllBanks = CanGetTraceLevelLogsAtAllBanks()
|
||||
// DEBUG
|
||||
case class CanGetDebugLevelLogsAtOneBank(requiresBankId: Boolean = true) extends ApiRole
|
||||
lazy val canGetDebugLevelLogsAtOneBank = CanGetDebugLevelLogsAtOneBank()
|
||||
case class CanGetDebugLevelLogsAtAllBanks(requiresBankId: Boolean = false) extends ApiRole
|
||||
lazy val canGetDebugLevelLogsAtAllBanks = CanGetDebugLevelLogsAtAllBanks()
|
||||
// INFO
|
||||
case class CanGetInfoLevelLogsAtOneBank(requiresBankId: Boolean = true) extends ApiRole
|
||||
lazy val canGetInfoLevelLogsAtOneBank = CanGetInfoLevelLogsAtOneBank()
|
||||
case class CanGetInfoLevelLogsAtAllBanks(requiresBankId: Boolean = false) extends ApiRole
|
||||
lazy val canGetInfoLevelLogsAtAllBanks = CanGetInfoLevelLogsAtAllBanks()
|
||||
// WARNING
|
||||
case class CanGetWarningLevelLogsAtOneBank(requiresBankId: Boolean = true) extends ApiRole
|
||||
lazy val canGetWarningLevelLogsAtOneBank = CanGetWarningLevelLogsAtOneBank()
|
||||
case class CanGetWarningLevelLogsAtAllBanks(requiresBankId: Boolean = false) extends ApiRole
|
||||
lazy val canGetWarningLevelLogsAtAllBanks = CanGetWarningLevelLogsAtAllBanks()
|
||||
// ERROR
|
||||
case class CanGetErrorLevelLogsAtOneBank(requiresBankId: Boolean = true) extends ApiRole
|
||||
lazy val canGetErrorLevelLogsAtOneBank = CanGetErrorLevelLogsAtOneBank()
|
||||
case class CanGetErrorLevelLogsAtAllBanks(requiresBankId: Boolean = false) extends ApiRole
|
||||
lazy val canGetErrorLevelLogsAtAllBanks = CanGetErrorLevelLogsAtAllBanks()
|
||||
// ALL
|
||||
case class CanGetAllLevelLogsAtOneBank(requiresBankId: Boolean = true) extends ApiRole
|
||||
lazy val canGetAllLevelLogsAtOneBank = CanGetAllLevelLogsAtOneBank()
|
||||
case class CanGetAllLevelLogsAtAllBanks(requiresBankId: Boolean = false) extends ApiRole
|
||||
lazy val canGetAllLevelLogsAtAllBanks = CanGetAllLevelLogsAtAllBanks()
|
||||
|
||||
case class CanUpdateAgentStatusAtAnyBank(requiresBankId: Boolean = false) extends ApiRole
|
||||
lazy val canUpdateAgentStatusAtAnyBank = CanUpdateAgentStatusAtAnyBank()
|
||||
|
||||
@ -46,6 +46,9 @@ object BerlinGroupError {
|
||||
*/
|
||||
def translateToBerlinGroupError(code: String, message: String): String = {
|
||||
code match {
|
||||
// If this error occurs it implies that its error handling MUST be refined in OBP code
|
||||
case "400" if message.contains("OBP-50005") => "INTERNAL_ERROR"
|
||||
|
||||
case "401" if message.contains("OBP-20001") => "PSU_CREDENTIALS_INVALID"
|
||||
case "401" if message.contains("OBP-20201") => "PSU_CREDENTIALS_INVALID"
|
||||
case "401" if message.contains("OBP-20214") => "PSU_CREDENTIALS_INVALID"
|
||||
|
||||
@ -127,6 +127,7 @@ object ErrorMessages {
|
||||
val ScaMethodNotDefined = "OBP-10030: Strong customer authentication method is not defined at this instance."
|
||||
|
||||
val createFxCurrencyIssue = "OBP-10050: Cannot create FX currency. "
|
||||
val invalidLogLevel = "OBP-10051: Invalid log level. "
|
||||
|
||||
|
||||
|
||||
|
||||
@ -3,16 +3,13 @@ package code.api.util
|
||||
|
||||
import code.api.OAuth2Login.Keycloak
|
||||
import code.model.{AppType, Consumer}
|
||||
import code.util.Helper.MdcLoggable
|
||||
import net.liftweb.common.{Box, Failure, Full}
|
||||
import okhttp3._
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
|
||||
object KeycloakAdmin {
|
||||
|
||||
// Initialize Logback logger
|
||||
private val logger = LoggerFactory.getLogger("okhttp3")
|
||||
object KeycloakAdmin extends MdcLoggable {
|
||||
|
||||
val integrateWithKeycloak: Boolean = APIUtil.getPropsAsBoolValue("integrate_with_keycloak", defaultValue = false)
|
||||
// Define variables (replace with actual values)
|
||||
@ -104,4 +101,4 @@ object KeycloakAdmin {
|
||||
Failure(e.getMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,37 @@
|
||||
package code.api.util
|
||||
|
||||
import java.util.UUID
|
||||
import scala.util.Try
|
||||
|
||||
final case class KeycloakFederatedUserReference(
|
||||
prefix: Char,
|
||||
storageProviderId: UUID, // Keycloak component UUID
|
||||
externalId: Long // autoincrement PK in external DB
|
||||
)
|
||||
|
||||
object KeycloakFederatedUserReference {
|
||||
// Pattern: f:<storageProviderId>:<externalId>
|
||||
private val Pattern =
|
||||
"^([A-Za-z]):([0-9a-fA-F-]{8}-[0-9a-fA-F-]{4}-[0-9a-fA-F-]{4}-[0-9a-fA-F-]{4}-[0-9a-fA-F-]{12}):(\\d+)$".r
|
||||
|
||||
/** Safe parser */
|
||||
def parse(s: String): Either[String, KeycloakFederatedUserReference] =
|
||||
s match {
|
||||
case Pattern(p, providerIdStr, externalIdStr) if p == "f" =>
|
||||
for {
|
||||
providerId <- Try(UUID.fromString(providerIdStr))
|
||||
.toEither.left.map(_ => s"Invalid storageProviderId: $providerIdStr")
|
||||
externalId <- Try(externalIdStr.toLong)
|
||||
.toEither.left.map(_ => s"Invalid externalId: $externalIdStr")
|
||||
} yield KeycloakFederatedUserReference('f', providerId, externalId)
|
||||
|
||||
case Pattern(p, _, _) =>
|
||||
Left(s"Invalid prefix: '$p'. Expected 'f'.")
|
||||
|
||||
case _ =>
|
||||
Left("Invalid format. Expected: f:<storageProviderId>:<externalId>")
|
||||
}
|
||||
|
||||
def unsafe(s: String): KeycloakFederatedUserReference =
|
||||
parse(s).fold(err => throw new IllegalArgumentException(err), identity)
|
||||
}
|
||||
@ -6,6 +6,7 @@ import code.api.Constant._
|
||||
import code.api.OAuth2Login.{Keycloak, OBPOIDC}
|
||||
import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON._
|
||||
import code.api.berlin.group.v1_3.JSONFactory_BERLIN_GROUP_1_3.{ConsentAccessAccountsJson, ConsentAccessJson}
|
||||
import code.api.cache.RedisLogger
|
||||
import code.api.util.APIUtil._
|
||||
import code.api.util.ApiRole._
|
||||
import code.api.util.ApiTag._
|
||||
@ -203,6 +204,47 @@ trait APIMethods510 {
|
||||
}
|
||||
}
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
logCacheEndpoint,
|
||||
implementedInApiVersion,
|
||||
nameOf(logCacheEndpoint),
|
||||
"GET",
|
||||
"/dev-ops/log-cache/LOG_LEVEL",
|
||||
"Get Log Cache",
|
||||
"""Returns information about:
|
||||
|
|
||||
|* Log Cache
|
||||
""",
|
||||
EmptyBody,
|
||||
EmptyBody,
|
||||
List($UserNotLoggedIn, UnknownError),
|
||||
apiTagApi :: Nil,
|
||||
Some(List(canGetAllLevelLogsAtAllBanks)))
|
||||
|
||||
lazy val logCacheEndpoint: OBPEndpoint = {
|
||||
case "dev-ops" :: "log-cache" :: logLevel :: Nil JsonGet _ =>
|
||||
cc =>
|
||||
implicit val ec = EndpointContext(Some(cc))
|
||||
for {
|
||||
// Parse and validate log level
|
||||
level <- NewStyle.function.tryons(ErrorMessages.invalidLogLevel, 400, cc.callContext) {
|
||||
RedisLogger.LogLevel.valueOf(logLevel)
|
||||
}
|
||||
// Check entitlements using helper
|
||||
_ <- NewStyle.function.handleEntitlementsAndScopes(
|
||||
bankId = "",
|
||||
userId = cc.userId,
|
||||
roles = RedisLogger.LogLevel.requiredRoles(level),
|
||||
callContext = cc.callContext
|
||||
)
|
||||
// Fetch logs
|
||||
logs <- Future(RedisLogger.getLogTail(level))
|
||||
} yield {
|
||||
(logs, HttpCode.`200`(cc.callContext))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
getRegulatedEntityById,
|
||||
implementedInApiVersion,
|
||||
@ -1677,12 +1719,12 @@ trait APIMethods510 {
|
||||
for {
|
||||
httpParams <- NewStyle.function.extractHttpParamsFromUrl(cc.url)
|
||||
(obpQueryParams, callContext) <- createQueriesByHttpParamsFuture(httpParams, cc.callContext)
|
||||
consents <- Future {
|
||||
(consents, totalPages) <- Future {
|
||||
Consents.consentProvider.vend.getConsents(obpQueryParams)
|
||||
}
|
||||
} yield {
|
||||
val consentsOfBank = Consent.filterByBankId(consents, bankId)
|
||||
(createConsentsJsonV510(consentsOfBank), HttpCode.`200`(callContext))
|
||||
(createConsentsJsonV510(consentsOfBank, totalPages), HttpCode.`200`(callContext))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1739,11 +1781,11 @@ trait APIMethods510 {
|
||||
for {
|
||||
httpParams <- NewStyle.function.extractHttpParamsFromUrl(cc.url)
|
||||
(obpQueryParams, callContext) <- createQueriesByHttpParamsFuture(httpParams, cc.callContext)
|
||||
consents <- Future {
|
||||
(consents, totalPages) <- Future {
|
||||
Consents.consentProvider.vend.getConsents(obpQueryParams)
|
||||
}
|
||||
} yield {
|
||||
(createConsentsJsonV510(consents), HttpCode.`200`(callContext))
|
||||
(createConsentsJsonV510(consents, totalPages), HttpCode.`200`(callContext))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -128,6 +128,9 @@ case class RegulatedEntityPostJsonV510(
|
||||
)
|
||||
case class RegulatedEntitiesJsonV510(entities: List[RegulatedEntityJsonV510])
|
||||
|
||||
case class LogCacheJsonV510(level: String, message: String)
|
||||
case class LogsCacheJsonV510(logs: List[String])
|
||||
|
||||
case class WaitingForGodotJsonV510(sleep_in_milliseconds: Long)
|
||||
|
||||
case class CertificateInfoJsonV510(
|
||||
@ -171,6 +174,8 @@ case class PutConsentPayloadJsonV510(access: ConsentAccessJson)
|
||||
case class AllConsentJsonV510(consent_reference_id: String,
|
||||
consumer_id: String,
|
||||
created_by_user_id: String,
|
||||
provider: Option[String],
|
||||
provider_id: Option[String],
|
||||
status: String,
|
||||
last_action_date: String,
|
||||
last_usage_date: String,
|
||||
@ -181,7 +186,7 @@ case class AllConsentJsonV510(consent_reference_id: String,
|
||||
api_version: String,
|
||||
note: String,
|
||||
)
|
||||
case class ConsentsJsonV510(consents: List[AllConsentJsonV510])
|
||||
case class ConsentsJsonV510(number_of_rows: Long, consents: List[AllConsentJsonV510])
|
||||
|
||||
|
||||
case class CurrencyJsonV510(alphanumeric_code: String)
|
||||
@ -959,17 +964,22 @@ object JSONFactory510 extends CustomJsonFormats with MdcLoggable {
|
||||
}
|
||||
|
||||
def createConsentsInfoJsonV510(consents: List[MappedConsent]): ConsentsInfoJsonV510 = {
|
||||
|
||||
ConsentsInfoJsonV510(
|
||||
consents.map { c =>
|
||||
val jwtPayload: Box[ConsentJWT] = JwtUtil.getSignedPayloadAsJson(c.jsonWebToken).map(parse(_).extract[ConsentJWT])
|
||||
val jwtPayload: Box[ConsentJWT] =
|
||||
JwtUtil.getSignedPayloadAsJson(c.jsonWebToken).map(parse(_).extract[ConsentJWT])
|
||||
|
||||
ConsentInfoJsonV510(
|
||||
consent_reference_id = c.consentReferenceId,
|
||||
consent_id = c.consentId,
|
||||
consumer_id = c.consumerId,
|
||||
created_by_user_id = c.userId,
|
||||
status = c.status,
|
||||
last_action_date = if (c.lastActionDate != null) new SimpleDateFormat(DateWithDay).format(c.lastActionDate) else null,
|
||||
last_usage_date = if (c.usesSoFarTodayCounterUpdatedAt != null) new SimpleDateFormat(DateWithSeconds).format(c.usesSoFarTodayCounterUpdatedAt) else null,
|
||||
last_action_date =
|
||||
if (c.lastActionDate != null) new SimpleDateFormat(DateWithDay).format(c.lastActionDate) else null,
|
||||
last_usage_date =
|
||||
if (c.usesSoFarTodayCounterUpdatedAt != null) new SimpleDateFormat(DateWithSeconds).format(c.usesSoFarTodayCounterUpdatedAt) else null,
|
||||
jwt = c.jsonWebToken,
|
||||
jwt_payload = jwtPayload,
|
||||
api_standard = c.apiStandard,
|
||||
@ -978,9 +988,18 @@ object JSONFactory510 extends CustomJsonFormats with MdcLoggable {
|
||||
}
|
||||
)
|
||||
}
|
||||
def createConsentsJsonV510(consents: List[MappedConsent]): ConsentsJsonV510 = {
|
||||
|
||||
def createConsentsJsonV510(consents: List[MappedConsent], totalPages: Long): ConsentsJsonV510 = {
|
||||
// Temporary cache (cleared after function ends)
|
||||
val cache = scala.collection.mutable.HashMap.empty[String, Box[User]]
|
||||
|
||||
// Cached lookup
|
||||
def getUserCached(userId: String): Box[User] = {
|
||||
cache.getOrElseUpdate(userId, Users.users.vend.getUserByUserId(userId))
|
||||
}
|
||||
ConsentsJsonV510(
|
||||
consents.map { c =>
|
||||
number_of_rows = totalPages,
|
||||
consents = consents.map { c =>
|
||||
val jwtPayload = JwtUtil
|
||||
.getSignedPayloadAsJson(c.jsonWebToken)
|
||||
.flatMap { payload =>
|
||||
@ -995,6 +1014,8 @@ object JSONFactory510 extends CustomJsonFormats with MdcLoggable {
|
||||
consent_reference_id = c.consentReferenceId,
|
||||
consumer_id = c.consumerId,
|
||||
created_by_user_id = c.userId,
|
||||
provider = getUserCached(c.userId).map(_.provider).orElse(Some(null)), // cached version
|
||||
provider_id = getUserCached(c.userId).map(_.idGivenByProvider).orElse(Some(null)), // cached version
|
||||
status = c.status,
|
||||
last_action_date = if (c.lastActionDate != null) new SimpleDateFormat(DateWithDay).format(c.lastActionDate) else null,
|
||||
last_usage_date = if (c.usesSoFarTodayCounterUpdatedAt != null) new SimpleDateFormat(DateWithSeconds).format(c.usesSoFarTodayCounterUpdatedAt) else null,
|
||||
|
||||
@ -6,6 +6,7 @@ import com.openbankproject.commons.model.{AtmId, BankId}
|
||||
import com.openbankproject.commons.model.enums.AtmAttributeType
|
||||
import net.liftweb.common.{Box, Logger}
|
||||
import net.liftweb.util.SimpleInjector
|
||||
import code.util.Helper.MdcLoggable
|
||||
|
||||
import scala.concurrent.Future
|
||||
|
||||
@ -27,9 +28,7 @@ object AtmAttributeX extends SimpleInjector {
|
||||
|
||||
}
|
||||
|
||||
trait AtmAttributeProviderTrait {
|
||||
|
||||
private val logger = Logger(classOf[AtmAttributeProviderTrait])
|
||||
trait AtmAttributeProviderTrait extends MdcLoggable {
|
||||
|
||||
def getAtmAttributesFromProvider(bankId: BankId, atmId: AtmId): Future[Box[List[AtmAttribute]]]
|
||||
|
||||
@ -40,10 +39,10 @@ trait AtmAttributeProviderTrait {
|
||||
AtmAttributeId: Option[String],
|
||||
name: String,
|
||||
attributeType: AtmAttributeType.Value,
|
||||
value: String,
|
||||
value: String,
|
||||
isActive: Option[Boolean]): Future[Box[AtmAttribute]]
|
||||
def deleteAtmAttribute(AtmAttributeId: String): Future[Box[Boolean]]
|
||||
|
||||
|
||||
def deleteAtmAttributesByAtmId(atmId: AtmId): Future[Box[Boolean]]
|
||||
// End of Trait
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ import code.api.util.OBPQueryParam
|
||||
import com.openbankproject.commons.model._
|
||||
import net.liftweb.common.{Box, Logger}
|
||||
import net.liftweb.util.SimpleInjector
|
||||
import code.util.Helper.MdcLoggable
|
||||
|
||||
import scala.collection.immutable.List
|
||||
|
||||
@ -62,7 +63,7 @@ object Atms extends SimpleInjector {
|
||||
balanceInquiryFee: Option[String] = None,
|
||||
atmType: Option[String] = None,
|
||||
phone: Option[String] = None,
|
||||
|
||||
|
||||
) extends AtmT
|
||||
|
||||
val atmsProvider = new Inject(buildOne _) {}
|
||||
@ -81,9 +82,7 @@ object Atms extends SimpleInjector {
|
||||
|
||||
}
|
||||
|
||||
trait AtmsProvider {
|
||||
|
||||
private val logger = Logger(classOf[AtmsProvider])
|
||||
trait AtmsProvider extends MdcLoggable {
|
||||
|
||||
|
||||
/*
|
||||
@ -107,9 +106,3 @@ trait AtmsProvider {
|
||||
def deleteAtm(atm: AtmT): Box[Boolean]
|
||||
// End of Trait
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@ import com.openbankproject.commons.model.BankId
|
||||
import com.openbankproject.commons.model.enums.BankAttributeType
|
||||
import net.liftweb.common.{Box, Logger}
|
||||
import net.liftweb.util.SimpleInjector
|
||||
import code.util.Helper.MdcLoggable
|
||||
|
||||
import scala.concurrent.Future
|
||||
|
||||
@ -14,7 +15,7 @@ object BankAttributeX extends SimpleInjector {
|
||||
|
||||
val bankAttributeProvider = new Inject(buildOne _) {}
|
||||
|
||||
def buildOne: BankAttributeProviderTrait = BankAttributeProvider
|
||||
def buildOne: BankAttributeProviderTrait = BankAttributeProvider
|
||||
|
||||
// Helper to get the count out of an option
|
||||
def countOfBankAttribute(listOpt: Option[List[BankAttribute]]): Int = {
|
||||
@ -28,9 +29,7 @@ object BankAttributeX extends SimpleInjector {
|
||||
|
||||
}
|
||||
|
||||
trait BankAttributeProviderTrait {
|
||||
|
||||
private val logger = Logger(classOf[BankAttributeProviderTrait])
|
||||
trait BankAttributeProviderTrait extends MdcLoggable {
|
||||
|
||||
def getBankAttributesFromProvider(bankId: BankId): Future[Box[List[BankAttribute]]]
|
||||
|
||||
@ -40,8 +39,8 @@ trait BankAttributeProviderTrait {
|
||||
bankAttributeId: Option[String],
|
||||
name: String,
|
||||
attributType: BankAttributeType.Value,
|
||||
value: String,
|
||||
value: String,
|
||||
isActive: Option[Boolean]): Future[Box[BankAttribute]]
|
||||
def deleteBankAttribute(bankAttributeId: String): Future[Box[Boolean]]
|
||||
// End of Trait
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,8 +7,9 @@ package code.branches
|
||||
import code.api.util.OBPQueryParam
|
||||
|
||||
import com.openbankproject.commons.model._
|
||||
import net.liftweb.common.Logger
|
||||
import net.liftweb.common.{Box, Logger}
|
||||
import net.liftweb.util.SimpleInjector
|
||||
import code.util.Helper.MdcLoggable
|
||||
|
||||
object Branches extends SimpleInjector {
|
||||
|
||||
@ -207,9 +208,7 @@ object Branches extends SimpleInjector {
|
||||
|
||||
}
|
||||
|
||||
trait BranchesProvider {
|
||||
|
||||
private val logger = Logger(classOf[BranchesProvider])
|
||||
trait BranchesProvider extends MdcLoggable {
|
||||
|
||||
|
||||
/*
|
||||
@ -235,4 +234,3 @@ trait BranchesProvider {
|
||||
|
||||
// End of Trait
|
||||
}
|
||||
|
||||
|
||||
@ -5,10 +5,9 @@ import code.util.{TwentyFourHourClockString, UUIDString}
|
||||
import com.openbankproject.commons.model._
|
||||
import net.liftweb.common.Logger
|
||||
import net.liftweb.mapper.{By, _}
|
||||
import code.util.Helper.MdcLoggable
|
||||
|
||||
object MappedBranchesProvider extends BranchesProvider {
|
||||
|
||||
private val logger = Logger(classOf[BranchesProvider])
|
||||
object MappedBranchesProvider extends BranchesProvider with MdcLoggable {
|
||||
|
||||
override protected def getBranchFromProvider(bankId: BankId, branchId: BranchId): Option[BranchT] =
|
||||
MappedBranch.find(
|
||||
@ -18,15 +17,15 @@ object MappedBranchesProvider extends BranchesProvider {
|
||||
|
||||
override protected def getBranchesFromProvider(bankId: BankId, queryParams: List[OBPQueryParam]): Option[List[BranchT]] = {
|
||||
logger.debug(s"getBranchesFromProvider says bankId is $bankId")
|
||||
|
||||
|
||||
val limit = queryParams.collect { case OBPLimit(value) => MaxRows[MappedBranch](value) }.headOption
|
||||
val offset = queryParams.collect { case OBPOffset(value) => StartAt[MappedBranch](value) }.headOption
|
||||
|
||||
|
||||
val optionalParams : Seq[QueryParam[MappedBranch]] = Seq(limit.toSeq, offset.toSeq).flatten
|
||||
val mapperParams = Seq(By(MappedBranch.mBankId, bankId.value), By(MappedBranch.mIsDeleted, false)) ++ optionalParams
|
||||
|
||||
|
||||
val branches: Option[List[BranchT]] = Some(MappedBranch.findAll(mapperParams:_*))
|
||||
|
||||
|
||||
branches
|
||||
}
|
||||
}
|
||||
@ -285,4 +284,4 @@ Else could store a link to this with each open data record - or via config for e
|
||||
//
|
||||
//object MappedLicense extends MappedLicense with LongKeyedMetaMapper[MappedLicense] {
|
||||
// override def dbIndexes = Index(mBankId) :: super.dbIndexes
|
||||
//}
|
||||
//}
|
||||
|
||||
@ -7,6 +7,7 @@ import com.openbankproject.commons.model.enums.CardAttributeType
|
||||
import com.openbankproject.commons.model.{AccountId, BankId, CardAttribute, ProductCode}
|
||||
import net.liftweb.common.{Box, Logger}
|
||||
import net.liftweb.util.SimpleInjector
|
||||
import code.util.Helper.MdcLoggable
|
||||
|
||||
import scala.concurrent.Future
|
||||
|
||||
@ -27,9 +28,7 @@ object CardAttributeX extends SimpleInjector {
|
||||
|
||||
}
|
||||
|
||||
trait CardAttributeProvider {
|
||||
|
||||
private val logger = Logger(classOf[CardAttributeProvider])
|
||||
trait CardAttributeProvider extends MdcLoggable {
|
||||
|
||||
def getCardAttributesFromProvider(cardId: String): Future[Box[List[CardAttribute]]]
|
||||
|
||||
@ -43,7 +42,7 @@ trait CardAttributeProvider {
|
||||
attributeType: CardAttributeType.Value,
|
||||
value: String
|
||||
): Future[Box[CardAttribute]]
|
||||
|
||||
|
||||
def deleteCardAttribute(cardAttributeId: String): Future[Box[Boolean]]
|
||||
// End of Trait
|
||||
}
|
||||
|
||||
@ -17,7 +17,7 @@ object Consents extends SimpleInjector {
|
||||
}
|
||||
|
||||
trait ConsentProvider {
|
||||
def getConsents(queryParams: List[OBPQueryParam]): List[MappedConsent]
|
||||
def getConsents(queryParams: List[OBPQueryParam]): (List[MappedConsent], Long)
|
||||
def getConsentByConsentId(consentId: String): Box[MappedConsent]
|
||||
def getConsentByConsentRequestId(consentRequestId: String): Box[MappedConsent]
|
||||
def updateConsentStatus(consentId: String, status: ConsentStatus): Box[MappedConsent]
|
||||
|
||||
@ -68,57 +68,64 @@ object MappedConsentProvider extends ConsentProvider {
|
||||
}
|
||||
|
||||
|
||||
private def getQueryParams(queryParams: List[OBPQueryParam]) = {
|
||||
val limit = queryParams.collectFirst { case OBPLimit(value) => MaxRows[MappedConsent](value) }
|
||||
val offset = queryParams.collectFirst { case OBPOffset(value) => StartAt[MappedConsent](value) }
|
||||
// The optional variables:
|
||||
|
||||
private def getPagedConsents(queryParams: List[OBPQueryParam]): (List[MappedConsent], Long) = {
|
||||
// Extract pagination params
|
||||
val limitOpt = queryParams.collectFirst { case OBPLimit(value) => value }
|
||||
val offsetOpt = queryParams.collectFirst { case OBPOffset(value) => value }
|
||||
|
||||
// Extract filters (exclude limit/offset)
|
||||
val consumerId = queryParams.collectFirst { case OBPConsumerId(value) => By(MappedConsent.mConsumerId, value) }
|
||||
val consentId = queryParams.collectFirst { case OBPConsentId(value) => By(MappedConsent.mConsentId, value) }
|
||||
val providerProviderId: Option[Cmp[MappedConsent, String]] = queryParams.collectFirst {
|
||||
case ProviderProviderId(value) =>
|
||||
val (provider, providerId) = value.split("\\|") match { // split by literal '|'
|
||||
val (provider, providerId) = value.split("\\|") match {
|
||||
case Array(a, b) => (a, b)
|
||||
case _ => ("", "") // fallback if format is unexpected
|
||||
case _ => ("", "")
|
||||
}
|
||||
ResourceUser.findAll(By(ResourceUser.provider_, provider), By(ResourceUser.providerId, providerId)) match {
|
||||
case x :: Nil => // exactly one
|
||||
Some(By(MappedConsent.mUserId, x.userId))
|
||||
case _ =>
|
||||
None
|
||||
case x :: Nil => Some(By(MappedConsent.mUserId, x.userId))
|
||||
case _ => None
|
||||
}
|
||||
}.flatten
|
||||
|
||||
val userId = queryParams.collectFirst { case OBPUserId(value) => By(MappedConsent.mUserId, value) }
|
||||
|
||||
val status = queryParams.collectFirst {
|
||||
case OBPStatus(value) =>
|
||||
// Split the comma-separated string into a List, and trim whitespace from each element
|
||||
val statuses: List[String] = value.split(",").toList.map(_.trim)
|
||||
|
||||
// For each distinct status:
|
||||
// - create both lowercase ancheckIsLockedd uppercase versions
|
||||
// - flatten the resulting list of lists into a single list
|
||||
// - remove duplicates from the final list
|
||||
val distinctLowerAndUpperCaseStatuses: List[String] =
|
||||
statuses.distinct // Remove duplicates (case-sensitive)
|
||||
.flatMap(s => List( // For each element, generate:
|
||||
s.toLowerCase, // - lowercase version
|
||||
s.toUpperCase // - uppercase version
|
||||
))
|
||||
.distinct // Remove any duplicates caused by lowercase/uppercase repetition
|
||||
|
||||
val statuses = value.split(",").toList.map(_.trim)
|
||||
val distinctLowerAndUpperCaseStatuses =
|
||||
statuses.distinct.flatMap(s => List(s.toLowerCase, s.toUpperCase)).distinct
|
||||
ByList(MappedConsent.mStatus, distinctLowerAndUpperCaseStatuses)
|
||||
}
|
||||
|
||||
Seq(
|
||||
offset.toSeq,
|
||||
limit.toSeq,
|
||||
// Build filters (without limit/offset)
|
||||
val filters = Seq(
|
||||
status.toSeq,
|
||||
userId.orElse(providerProviderId).toSeq,
|
||||
consentId.toSeq,
|
||||
consumerId.toSeq
|
||||
).flatten
|
||||
|
||||
// Total count for pagination
|
||||
val totalCount = MappedConsent.count(filters: _*)
|
||||
|
||||
// Apply limit/offset if provided
|
||||
val pageData = (limitOpt, offsetOpt) match {
|
||||
case (Some(limit), Some(offset)) => MappedConsent.findAll(filters: _*).drop(offset).take(limit)
|
||||
case (Some(limit), None) => MappedConsent.findAll(filters: _*).take(limit)
|
||||
case _ => MappedConsent.findAll(filters: _*)
|
||||
}
|
||||
|
||||
// Compute number of pages
|
||||
val totalPages = limitOpt match {
|
||||
case Some(limit) if limit > 0 => Math.ceil(totalCount.toDouble / limit).toInt
|
||||
case _ => 1
|
||||
}
|
||||
|
||||
(pageData, totalCount)
|
||||
}
|
||||
|
||||
|
||||
private def sortConsents(consents: List[MappedConsent], sortByParam: String): List[MappedConsent] = {
|
||||
// Parse sort_by param like "created_date:desc,status:asc,consumer_id:asc"
|
||||
val sortFields: List[(String, String)] = sortByParam
|
||||
@ -164,17 +171,20 @@ object MappedConsentProvider extends ConsentProvider {
|
||||
}
|
||||
|
||||
|
||||
override def getConsents(queryParams: List[OBPQueryParam]): List[MappedConsent] = {
|
||||
val optionalParams = getQueryParams(queryParams)
|
||||
override def getConsents(queryParams: List[OBPQueryParam]): (List[MappedConsent], Long) = {
|
||||
val sortBy: Option[String] = queryParams.collectFirst { case OBPSortBy(value) => value }
|
||||
val consents = MappedConsent.findAll(optionalParams: _*)
|
||||
val (consents, totalCount) = getPagedConsents(queryParams)
|
||||
val bankId: Option[String] = queryParams.collectFirst { case OBPBankId(value) => value }
|
||||
if(bankId.isDefined) {
|
||||
Consent.filterStrictlyByBank(consents, bankId.get)
|
||||
(Consent.filterStrictlyByBank(consents, bankId.get), totalCount)
|
||||
} else {
|
||||
sortConsents(consents, sortBy.getOrElse(""))
|
||||
(sortConsents(consents, sortBy.getOrElse("")), totalCount)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
override def createObpConsent(user: User, challengeAnswer: String, consentRequestId:Option[String], consumer: Option[Consumer]): Box[MappedConsent] = {
|
||||
tryo {
|
||||
val salt = BCrypt.gensalt()
|
||||
|
||||
@ -10,6 +10,7 @@ import code.model.dataAccess.ResourceUser
|
||||
import net.liftweb.common.Logger
|
||||
import net.liftweb.util
|
||||
import net.liftweb.util.SimpleInjector
|
||||
import code.util.Helper.MdcLoggable
|
||||
import java.util.Date
|
||||
|
||||
import com.openbankproject.commons.model.{BankId, MetaT}
|
||||
@ -48,9 +49,7 @@ object CrmEvent extends util.SimpleInjector {
|
||||
|
||||
}
|
||||
|
||||
trait CrmEventProvider {
|
||||
|
||||
private val logger = Logger(classOf[CrmEventProvider])
|
||||
trait CrmEventProvider extends MdcLoggable {
|
||||
|
||||
|
||||
/*
|
||||
|
||||
@ -8,6 +8,7 @@ import com.openbankproject.commons.model.enums.CustomerAttributeType
|
||||
import com.openbankproject.commons.model.{BankId, Customer, CustomerAttribute, CustomerId}
|
||||
import net.liftweb.common.{Box, Logger}
|
||||
import net.liftweb.util.SimpleInjector
|
||||
import code.util.Helper.MdcLoggable
|
||||
|
||||
import scala.collection.immutable.List
|
||||
import scala.concurrent.Future
|
||||
@ -16,7 +17,7 @@ object CustomerAttributeX extends SimpleInjector {
|
||||
|
||||
val customerAttributeProvider = new Inject(buildOne _) {}
|
||||
|
||||
def buildOne: CustomerAttributeProvider = MappedCustomerAttributeProvider
|
||||
def buildOne: CustomerAttributeProvider = MappedCustomerAttributeProvider
|
||||
|
||||
// Helper to get the count out of an option
|
||||
def countOfCustomerAttribute(listOpt: Option[List[CustomerAttribute]]): Int = {
|
||||
@ -30,9 +31,7 @@ object CustomerAttributeX extends SimpleInjector {
|
||||
|
||||
}
|
||||
|
||||
trait CustomerAttributeProvider {
|
||||
|
||||
private val logger = Logger(classOf[CustomerAttributeProvider])
|
||||
trait CustomerAttributeProvider extends MdcLoggable {
|
||||
|
||||
def getCustomerAttributesFromProvider(customerId: CustomerId): Future[Box[List[CustomerAttribute]]]
|
||||
def getCustomerAttributes(bankId: BankId,
|
||||
@ -41,7 +40,7 @@ trait CustomerAttributeProvider {
|
||||
def getCustomerIdsByAttributeNameValues(bankId: BankId, params: Map[String, List[String]]): Future[Box[List[String]]]
|
||||
|
||||
def getCustomerAttributesForCustomers(customers: List[Customer]): Future[Box[List[CustomerAndAttribute]]]
|
||||
|
||||
|
||||
def getCustomerAttributeById(customerAttributeId: String): Future[Box[CustomerAttribute]]
|
||||
|
||||
def createOrUpdateCustomerAttribute(bankId: BankId,
|
||||
@ -54,7 +53,7 @@ trait CustomerAttributeProvider {
|
||||
def createCustomerAttributes(bankId: BankId,
|
||||
customerId: CustomerId,
|
||||
customerAttributes: List[CustomerAttribute]): Future[Box[List[CustomerAttribute]]]
|
||||
|
||||
|
||||
def deleteCustomerAttribute(customerAttributeId: String): Future[Box[Boolean]]
|
||||
// End of Trait
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import code.api.util.APIUtil
|
||||
import com.openbankproject.commons.model.BankId
|
||||
import net.liftweb.common.Logger
|
||||
import net.liftweb.util.SimpleInjector
|
||||
import code.util.Helper.MdcLoggable
|
||||
|
||||
object Thing extends SimpleInjector {
|
||||
|
||||
@ -45,9 +46,7 @@ A trait that defines interfaces to Thing
|
||||
i.e. a ThingProvider should provide these:
|
||||
*/
|
||||
|
||||
trait ThingProvider {
|
||||
|
||||
private val logger = Logger(classOf[ThingProvider])
|
||||
trait ThingProvider extends MdcLoggable {
|
||||
|
||||
|
||||
/*
|
||||
@ -79,4 +78,3 @@ trait ThingProvider {
|
||||
protected def getThingsFromProvider(bank : BankId) : Option[List[Thing]]
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -36,12 +36,11 @@ import com.openbankproject.commons.model._
|
||||
import com.openbankproject.commons.model.enums.AccountRoutingScheme
|
||||
import net.liftweb.common._
|
||||
import net.liftweb.util.StringHelpers
|
||||
import code.util.Helper.MdcLoggable
|
||||
|
||||
import java.util.Date
|
||||
|
||||
case class ViewExtended(val view: View) {
|
||||
|
||||
val viewLogger = Logger(classOf[ViewExtended])
|
||||
case class ViewExtended(val view: View) extends MdcLoggable {
|
||||
|
||||
def getViewPermissions: List[String] =
|
||||
if (view.isSystem) {
|
||||
@ -205,7 +204,7 @@ case class ViewExtended(val view: View) {
|
||||
|
||||
if(!belongsToModeratedAccount) {
|
||||
val failMsg = "Attempted to moderate a transaction using the incorrect moderated account"
|
||||
view.viewLogger.warn(failMsg)
|
||||
logger.warn(failMsg)
|
||||
Failure(failMsg)
|
||||
} else {
|
||||
Full(moderatedTransaction)
|
||||
@ -272,7 +271,7 @@ case class ViewExtended(val view: View) {
|
||||
|
||||
if(!belongsToModeratedAccount) {
|
||||
val failMsg = "Attempted to moderate a transaction using the incorrect moderated account"
|
||||
view.viewLogger.warn(failMsg)
|
||||
logger.warn(failMsg)
|
||||
Failure(failMsg)
|
||||
} else {
|
||||
Full(moderatedTransaction)
|
||||
@ -287,7 +286,7 @@ case class ViewExtended(val view: View) {
|
||||
|
||||
// This function will only accept transactions which have the same This Account.
|
||||
if(accountUids.toSet.size > 1) {
|
||||
view.viewLogger.warn("Attempted to moderate transactions not belonging to the same account in a call where they should")
|
||||
logger.warn("Attempted to moderate transactions not belonging to the same account in a call where they should")
|
||||
Failure("Could not moderate transactions as they do not all belong to the same account")
|
||||
} else {
|
||||
Full(transactions.flatMap(
|
||||
@ -306,7 +305,7 @@ case class ViewExtended(val view: View) {
|
||||
|
||||
// This function will only accept transactions which have the same This Account.
|
||||
if(accountUids.toSet.size > 1) {
|
||||
view.viewLogger.warn("Attempted to moderate transactions not belonging to the same account in a call where they should")
|
||||
logger.warn("Attempted to moderate transactions not belonging to the same account in a call where they should")
|
||||
Failure("Could not moderate transactions as they do not all belong to the same account")
|
||||
} else {
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@ import code.api.v4_0_0.{BankJson400, BanksJson400, JSONFactory400, OBPAPI4_0_0}
|
||||
import code.obp.grpc.api.BanksJson400Grpc.{BankJson400Grpc, BankRoutingJsonV121Grpc}
|
||||
import code.obp.grpc.api._
|
||||
import code.util.Helper
|
||||
import code.util.Helper.MdcLoggable
|
||||
import code.views.Views
|
||||
import com.google.protobuf.empty.Empty
|
||||
import com.openbankproject.commons.ExecutionContext.Implicits.global
|
||||
@ -17,14 +18,12 @@ import net.liftweb.json.JsonAST.{JField, JObject}
|
||||
import net.liftweb.json.JsonDSL._
|
||||
import net.liftweb.json.{Extraction, JArray}
|
||||
|
||||
import java.util.logging.Logger
|
||||
import scala.concurrent.{ExecutionContext, Future}
|
||||
|
||||
/**
|
||||
* [[https://github.com/grpc/grpc-java/blob/v0.15.0/examples/src/main/java/io/grpc/examples/helloworld/HelloWorldServer.java]]
|
||||
*/
|
||||
object HelloWorldServer {
|
||||
private val logger = Logger.getLogger(classOf[HelloWorldServer].getName)
|
||||
|
||||
def main(args: Array[String] = Array.empty): Unit = {
|
||||
val server = new HelloWorldServer(ExecutionContext.global)
|
||||
@ -32,10 +31,10 @@ object HelloWorldServer {
|
||||
server.blockUntilShutdown()
|
||||
}
|
||||
|
||||
val port = APIUtil.getPropsAsIntValue("grpc.server.port", Helper.findAvailablePort())
|
||||
val port = APIUtil.getPropsAsIntValue("grpc.server.port", Helper.findAvailablePort())
|
||||
}
|
||||
|
||||
class HelloWorldServer(executionContext: ExecutionContext) { self =>
|
||||
class HelloWorldServer(executionContext: ExecutionContext) extends MdcLoggable { self =>
|
||||
private[this] var server: Server = null
|
||||
def start(): Unit = {
|
||||
|
||||
@ -43,7 +42,7 @@ class HelloWorldServer(executionContext: ExecutionContext) { self =>
|
||||
.addService(ObpServiceGrpc.bindService(ObpServiceImpl, executionContext))
|
||||
.asInstanceOf[ServerBuilder[_]]
|
||||
server = serverBuilder.build.start;
|
||||
HelloWorldServer.logger.info("Server started, listening on " + HelloWorldServer.port)
|
||||
logger.info("Server started, listening on " + HelloWorldServer.port)
|
||||
sys.addShutdownHook {
|
||||
System.err.println("*** shutting down gRPC server since JVM is shutting down")
|
||||
self.stop()
|
||||
@ -139,4 +138,3 @@ class HelloWorldServer(executionContext: ExecutionContext) { self =>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@ import com.openbankproject.commons.model.enums.ProductAttributeType
|
||||
import com.openbankproject.commons.model.{BankId, ProductAttribute, ProductCode}
|
||||
import net.liftweb.common.{Box, Logger}
|
||||
import net.liftweb.util.SimpleInjector
|
||||
import code.util.Helper.MdcLoggable
|
||||
|
||||
import scala.concurrent.Future
|
||||
|
||||
@ -29,9 +30,7 @@ object ProductAttributeX extends SimpleInjector {
|
||||
|
||||
}
|
||||
|
||||
trait ProductAttributeProvider {
|
||||
|
||||
private val logger = Logger(classOf[ProductAttributeProvider])
|
||||
trait ProductAttributeProvider extends MdcLoggable {
|
||||
|
||||
def getProductAttributesFromProvider(bank: BankId, productCode: ProductCode): Future[Box[List[ProductAttribute]]]
|
||||
|
||||
@ -42,7 +41,7 @@ trait ProductAttributeProvider {
|
||||
productAttributeId: Option[String],
|
||||
name: String,
|
||||
attributeType: ProductAttributeType.Value,
|
||||
value: String,
|
||||
value: String,
|
||||
isActive: Option[Boolean]): Future[Box[ProductAttribute]]
|
||||
def deleteProductAttribute(productAttributeId: String): Future[Box[Boolean]]
|
||||
// End of Trait
|
||||
|
||||
@ -6,6 +6,7 @@ import code.api.util.APIUtil
|
||||
import com.openbankproject.commons.model.{BankId, ProductCode, ProductFeeTrait}
|
||||
import net.liftweb.common.{Box, Logger}
|
||||
import net.liftweb.util.SimpleInjector
|
||||
import code.util.Helper.MdcLoggable
|
||||
|
||||
import scala.concurrent.Future
|
||||
import scala.math.BigDecimal
|
||||
@ -28,9 +29,7 @@ object ProductFeeX extends SimpleInjector {
|
||||
|
||||
}
|
||||
|
||||
trait ProductFeeProvider {
|
||||
|
||||
private val logger = Logger(classOf[ProductFeeProvider])
|
||||
trait ProductFeeProvider extends MdcLoggable {
|
||||
|
||||
def getProductFeesFromProvider(bankId: BankId, productCode: ProductCode): Future[Box[List[ProductFeeTrait]]]
|
||||
|
||||
@ -48,6 +47,6 @@ trait ProductFeeProvider {
|
||||
frequency: String,
|
||||
`type`: String
|
||||
): Future[Box[ProductFeeTrait]]
|
||||
|
||||
|
||||
def deleteProductFee(productFeeId: String): Future[Box[Boolean]]
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ package code.products
|
||||
import com.openbankproject.commons.model.{BankId, ProductCode}
|
||||
import net.liftweb.common.Logger
|
||||
import net.liftweb.util.SimpleInjector
|
||||
import code.util.Helper.MdcLoggable
|
||||
import com.openbankproject.commons.model.Product
|
||||
|
||||
object Products extends SimpleInjector {
|
||||
@ -27,9 +28,7 @@ object Products extends SimpleInjector {
|
||||
|
||||
}
|
||||
|
||||
trait ProductsProvider {
|
||||
|
||||
private val logger = Logger(classOf[ProductsProvider])
|
||||
trait ProductsProvider extends MdcLoggable {
|
||||
|
||||
|
||||
/*
|
||||
@ -38,7 +37,7 @@ trait ProductsProvider {
|
||||
*/
|
||||
final def getProducts(bankId : BankId, adminView: Boolean = false) : Option[List[Product]] = {
|
||||
logger.info(s"Hello from getProducts bankId is: $bankId")
|
||||
getProductsFromProvider(bankId)
|
||||
getProductsFromProvider(bankId)
|
||||
}
|
||||
|
||||
/*
|
||||
@ -53,9 +52,3 @@ trait ProductsProvider {
|
||||
|
||||
// End of Trait
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -1,18 +1,17 @@
|
||||
package code.regulatedentities
|
||||
|
||||
|
||||
import com.openbankproject.commons.model.RegulatedEntityTrait
|
||||
import com.openbankproject.commons.model.{RegulatedEntityTrait}
|
||||
import net.liftweb.common.{Box, Logger}
|
||||
import net.liftweb.util.SimpleInjector
|
||||
import code.util.Helper.MdcLoggable
|
||||
|
||||
object RegulatedEntityX extends SimpleInjector {
|
||||
val regulatedEntityProvider = new Inject(buildOne _) {}
|
||||
def buildOne: RegulatedEntityProvider = MappedRegulatedEntityProvider
|
||||
}
|
||||
/* For ProductFee */
|
||||
trait RegulatedEntityProvider {
|
||||
|
||||
private val logger = Logger(classOf[RegulatedEntityProvider])
|
||||
trait RegulatedEntityProvider extends MdcLoggable {
|
||||
|
||||
def getRegulatedEntities(): List[RegulatedEntityTrait]
|
||||
|
||||
@ -33,4 +32,4 @@ trait RegulatedEntityProvider {
|
||||
|
||||
def deleteRegulatedEntity(id: String): Box[Boolean]
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,17 +1,17 @@
|
||||
package code.signingbaskets
|
||||
|
||||
|
||||
import com.openbankproject.commons.model.{SigningBasketContent, SigningBasketTrait}
|
||||
import net.liftweb.common.{Box, Logger}
|
||||
import net.liftweb.util.SimpleInjector
|
||||
import code.util.Helper.MdcLoggable
|
||||
|
||||
object SigningBasketX extends SimpleInjector {
|
||||
val signingBasketProvider: SigningBasketX.Inject[SigningBasketProvider] = new Inject(buildOne _) {}
|
||||
private def buildOne: SigningBasketProvider = MappedSigningBasketProvider
|
||||
}
|
||||
|
||||
trait SigningBasketProvider {
|
||||
|
||||
private val logger = Logger(classOf[SigningBasketProvider])
|
||||
trait SigningBasketProvider extends MdcLoggable {
|
||||
|
||||
def getSigningBaskets(): List[SigningBasketTrait]
|
||||
|
||||
@ -24,4 +24,4 @@ trait SigningBasketProvider {
|
||||
|
||||
def deleteSigningBasket(id: String): Box[Boolean]
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,13 +3,12 @@ package code.transactionRequestAttribute
|
||||
import com.openbankproject.commons.model.enums.TransactionRequestAttributeType
|
||||
import com.openbankproject.commons.model.{BankId, TransactionRequestAttributeJsonV400, TransactionRequestAttributeTrait, TransactionRequestId, ViewId}
|
||||
import net.liftweb.common.{Box, Logger}
|
||||
import code.util.Helper.MdcLoggable
|
||||
|
||||
import scala.collection.immutable.List
|
||||
import scala.concurrent.Future
|
||||
|
||||
trait TransactionRequestAttributeProvider {
|
||||
|
||||
private val logger = Logger(classOf[TransactionRequestAttributeProvider])
|
||||
trait TransactionRequestAttributeProvider extends MdcLoggable {
|
||||
|
||||
def getTransactionRequestAttributesFromProvider(transactionRequestId: TransactionRequestId): Future[Box[List[TransactionRequestAttributeTrait]]]
|
||||
|
||||
@ -23,9 +22,9 @@ trait TransactionRequestAttributeProvider {
|
||||
def getTransactionRequestAttributeById(transactionRequestAttributeId: String): Future[Box[TransactionRequestAttributeTrait]]
|
||||
|
||||
def getTransactionRequestIdsByAttributeNameValues(bankId: BankId, params: Map[String, List[String]], isPersonal: Boolean): Future[Box[List[String]]]
|
||||
|
||||
|
||||
def getByAttributeNameValues(bankId: BankId, params: Map[String, List[String]], isPersonal: Boolean): Future[Box[List[TransactionRequestAttributeTrait]]]
|
||||
|
||||
|
||||
def createOrUpdateTransactionRequestAttribute(bankId: BankId,
|
||||
transactionRequestId: TransactionRequestId,
|
||||
transactionRequestAttributeId: Option[String],
|
||||
@ -40,4 +39,4 @@ trait TransactionRequestAttributeProvider {
|
||||
|
||||
def deleteTransactionRequestAttribute(transactionRequestAttributeId: String): Future[Box[Boolean]]
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ import com.openbankproject.commons.model.enums.TransactionAttributeType
|
||||
import com.openbankproject.commons.model.{BankId, TransactionAttribute, TransactionId, ViewId}
|
||||
import net.liftweb.common.{Box, Logger}
|
||||
import net.liftweb.util.SimpleInjector
|
||||
import code.util.Helper.MdcLoggable
|
||||
|
||||
import scala.collection.immutable.List
|
||||
import scala.concurrent.Future
|
||||
@ -29,9 +30,7 @@ object TransactionAttributeX extends SimpleInjector {
|
||||
|
||||
}
|
||||
|
||||
trait TransactionAttributeProvider {
|
||||
|
||||
private val logger = Logger(classOf[TransactionAttributeProvider])
|
||||
trait TransactionAttributeProvider extends MdcLoggable {
|
||||
|
||||
def getTransactionAttributesFromProvider(transactionId: TransactionId): Future[Box[List[TransactionAttribute]]]
|
||||
def getTransactionAttributes(bankId: BankId,
|
||||
@ -45,7 +44,7 @@ trait TransactionAttributeProvider {
|
||||
def getTransactionAttributeById(transactionAttributeId: String): Future[Box[TransactionAttribute]]
|
||||
|
||||
def getTransactionIdsByAttributeNameValues(bankId: BankId, params: Map[String, List[String]]): Future[Box[List[String]]]
|
||||
|
||||
|
||||
def createOrUpdateTransactionAttribute(bankId: BankId,
|
||||
transactionId: TransactionId,
|
||||
transactionAttributeId: Option[String],
|
||||
@ -56,7 +55,7 @@ trait TransactionAttributeProvider {
|
||||
def createTransactionAttributes(bankId: BankId,
|
||||
transactionId: TransactionId,
|
||||
transactionAttributes: List[TransactionAttribute]): Future[Box[List[TransactionAttribute]]]
|
||||
|
||||
|
||||
def deleteTransactionAttribute(transactionAttributeId: String): Future[Box[Boolean]]
|
||||
// End of Trait
|
||||
}
|
||||
|
||||
@ -12,14 +12,13 @@ import com.openbankproject.commons.model._
|
||||
import com.openbankproject.commons.model.enums.TransactionRequestTypes.{COUNTERPARTY, SEPA}
|
||||
import com.openbankproject.commons.model.enums.{AccountRoutingScheme, TransactionRequestStatus, TransactionRequestTypes}
|
||||
import net.liftweb.common.{Box, Failure, Full, Logger}
|
||||
import code.util.Helper.MdcLoggable
|
||||
import net.liftweb.json
|
||||
import net.liftweb.json.JsonAST.{JField, JObject, JString}
|
||||
import net.liftweb.mapper._
|
||||
import net.liftweb.util.Helpers._
|
||||
|
||||
object MappedTransactionRequestProvider extends TransactionRequestProvider {
|
||||
|
||||
private val logger = Logger(classOf[TransactionRequestProvider])
|
||||
object MappedTransactionRequestProvider extends TransactionRequestProvider with MdcLoggable {
|
||||
|
||||
override def getMappedTransactionRequest(transactionRequestId: TransactionRequestId): Box[MappedTransactionRequest] =
|
||||
MappedTransactionRequest.find(By(MappedTransactionRequest.mTransactionRequestId, transactionRequestId.value))
|
||||
@ -51,7 +50,7 @@ object MappedTransactionRequestProvider extends TransactionRequestProvider {
|
||||
By(MappedTransactionRequest.mTransactionIDs, transactionId.value)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
override def bulkDeleteTransactionRequests(): Boolean = {
|
||||
MappedTransactionRequest.bulkDelete_!!()
|
||||
}
|
||||
@ -101,7 +100,7 @@ object MappedTransactionRequestProvider extends TransactionRequestProvider {
|
||||
.orElse(toAccount.accountRoutings.headOption)
|
||||
case _ => toAccount.accountRoutings.headOption
|
||||
}
|
||||
|
||||
|
||||
val counterpartyIdOption = TransactionRequestTypes.withName(transactionRequestType.value) match {
|
||||
case COUNTERPARTY => Some(transactionRequestCommonBody.asInstanceOf[TransactionRequestBodyCounterpartyJSON].to.counterparty_id)
|
||||
case _ => None
|
||||
@ -109,10 +108,10 @@ object MappedTransactionRequestProvider extends TransactionRequestProvider {
|
||||
|
||||
val (paymentStartDate, paymentEndDate, executionRule, frequency, dayOfExecution) = if(paymentService == Some("periodic-payments")){
|
||||
val paymentFields = berlinGroupPayments.asInstanceOf[Option[PeriodicSepaCreditTransfersBerlinGroupV13]]
|
||||
|
||||
|
||||
val paymentStartDate = paymentFields.map(_.startDate).map(DateWithMsFormat.parse).orNull
|
||||
val paymentEndDate = paymentFields.flatMap(_.endDate).map(DateWithMsFormat.parse).orNull
|
||||
|
||||
|
||||
val executionRule = paymentFields.flatMap(_.executionRule).orNull
|
||||
val frequency = paymentFields.map(_.frequency).orNull
|
||||
val dayOfExecution = paymentFields.flatMap(_.dayOfExecution).orNull
|
||||
@ -125,7 +124,7 @@ object MappedTransactionRequestProvider extends TransactionRequestProvider {
|
||||
val consentIdOption = callContext.map(_.requestHeaders).map(APIUtil.getConsentIdRequestHeaderValue).flatten
|
||||
val consentOption = consentIdOption.map(consentId =>Consents.consentProvider.vend.getConsentByConsentId(consentId).toOption).flatten
|
||||
val consentReferenceIdOption = consentOption.map(_.consentReferenceId)
|
||||
|
||||
|
||||
// Note: We don't save transaction_ids, status and challenge here.
|
||||
val mappedTransactionRequest = MappedTransactionRequest.create
|
||||
|
||||
@ -158,9 +157,9 @@ object MappedTransactionRequestProvider extends TransactionRequestProvider {
|
||||
.mOtherBankRoutingAddress(toAccount.attributes.flatMap(_.find(_.name == "BANK_ROUTING_ADDRESS")
|
||||
.map(_.value)).getOrElse(toAccount.bankRoutingScheme))
|
||||
// We need transfer CounterpartyTrait to BankAccount, so We lost some data. can not fill the following fields .
|
||||
//.mThisBankId(toAccount.bankId.value)
|
||||
//.mThisBankId(toAccount.bankId.value)
|
||||
//.mThisAccountId(toAccount.accountId.value)
|
||||
//.mThisViewId(toAccount.v)
|
||||
//.mThisViewId(toAccount.v)
|
||||
.mCounterpartyId(counterpartyIdOption.getOrElse(null))
|
||||
//.mIsBeneficiary(toAccount.isBeneficiary)
|
||||
|
||||
@ -169,7 +168,7 @@ object MappedTransactionRequestProvider extends TransactionRequestProvider {
|
||||
.mBody_Value_Amount(transactionRequestCommonBody.value.amount)
|
||||
.mBody_Description(transactionRequestCommonBody.description)
|
||||
.mDetails(details) // This is the details / body of the request (contains all fields in the body)
|
||||
|
||||
|
||||
.mDetails(details) // This is the details / body of the request (contains all fields in the body)
|
||||
|
||||
.mPaymentStartDate(paymentStartDate)
|
||||
@ -226,9 +225,7 @@ object MappedTransactionRequestProvider extends TransactionRequestProvider {
|
||||
|
||||
}
|
||||
|
||||
class MappedTransactionRequest extends LongKeyedMapper[MappedTransactionRequest] with IdPK with CreatedUpdated with CustomJsonFormats {
|
||||
|
||||
private val logger = Logger(classOf[MappedTransactionRequest])
|
||||
class MappedTransactionRequest extends LongKeyedMapper[MappedTransactionRequest] with IdPK with CreatedUpdated with CustomJsonFormats with MdcLoggable {
|
||||
|
||||
override def getSingleton = MappedTransactionRequest
|
||||
|
||||
@ -278,56 +275,56 @@ class MappedTransactionRequest extends LongKeyedMapper[MappedTransactionRequest]
|
||||
object mOtherBankRoutingScheme extends MappedString(this, 32)
|
||||
object mOtherBankRoutingAddress extends MappedString(this, 64)
|
||||
object mIsBeneficiary extends MappedBoolean(this)
|
||||
|
||||
//Here are for Berlin Group V1.3
|
||||
|
||||
//Here are for Berlin Group V1.3
|
||||
object mPaymentStartDate extends MappedDate(this) //BGv1.3 Open API Document example value: "startDate":"2024-08-12"
|
||||
object mPaymentEndDate extends MappedDate(this) //BGv1.3 Open API Document example value: "startDate":"2025-08-01"
|
||||
object mPaymentExecutionRule extends MappedString(this, 64) //BGv1.3 Open API Document example value: "executionRule":"preceding"
|
||||
object mPaymentFrequency extends MappedString(this, 64) //BGv1.3 Open API Document example value: "frequency":"Monthly",
|
||||
object mPaymentDayOfExecution extends MappedString(this, 64)//BGv1.3 Open API Document example value: "dayOfExecution":"01"
|
||||
|
||||
object mPaymentExecutionRule extends MappedString(this, 64) //BGv1.3 Open API Document example value: "executionRule":"preceding"
|
||||
object mPaymentFrequency extends MappedString(this, 64) //BGv1.3 Open API Document example value: "frequency":"Monthly",
|
||||
object mPaymentDayOfExecution extends MappedString(this, 64)//BGv1.3 Open API Document example value: "dayOfExecution":"01"
|
||||
|
||||
object mConsentReferenceId extends MappedString(this, 64)
|
||||
|
||||
object mApiStandard extends MappedString(this, 50)
|
||||
object mApiVersion extends MappedString(this, 50)
|
||||
|
||||
|
||||
def updateStatus(newStatus: String) = {
|
||||
mStatus.set(newStatus)
|
||||
}
|
||||
|
||||
def toTransactionRequest : Option[TransactionRequest] = {
|
||||
|
||||
|
||||
val details = mDetails.toString
|
||||
|
||||
|
||||
val parsedDetails = json.parse(details)
|
||||
|
||||
|
||||
val transactionType = mType.get
|
||||
|
||||
|
||||
val t_amount = AmountOfMoney (
|
||||
currency = mBody_Value_Currency.get,
|
||||
amount = mBody_Value_Amount.get
|
||||
)
|
||||
|
||||
|
||||
val t_to_sandbox_tan = if (
|
||||
TransactionRequestTypes.withName(transactionType) == TransactionRequestTypes.SANDBOX_TAN ||
|
||||
TransactionRequestTypes.withName(transactionType) == TransactionRequestTypes.ACCOUNT_OTP ||
|
||||
TransactionRequestTypes.withName(transactionType) == TransactionRequestTypes.SANDBOX_TAN ||
|
||||
TransactionRequestTypes.withName(transactionType) == TransactionRequestTypes.ACCOUNT_OTP ||
|
||||
TransactionRequestTypes.withName(transactionType) == TransactionRequestTypes.ACCOUNT)
|
||||
Some(TransactionRequestAccount (bank_id = mTo_BankId.get, account_id = mTo_AccountId.get))
|
||||
else
|
||||
None
|
||||
|
||||
|
||||
val t_to_sepa = if (TransactionRequestTypes.withName(transactionType) == TransactionRequestTypes.SEPA){
|
||||
val ibanList: List[String] = for {
|
||||
JObject(child) <- parsedDetails
|
||||
JField("iban", JString(iban)) <- child
|
||||
} yield
|
||||
iban
|
||||
val ibanValue = if (ibanList.isEmpty) "" else ibanList.head
|
||||
val ibanValue = if (ibanList.isEmpty) "" else ibanList.head
|
||||
Some(TransactionRequestIban(iban = ibanValue))
|
||||
}
|
||||
else
|
||||
None
|
||||
|
||||
|
||||
val t_to_counterparty = if (TransactionRequestTypes.withName(transactionType) == TransactionRequestTypes.COUNTERPARTY ||
|
||||
TransactionRequestTypes.withName(transactionType) == TransactionRequestTypes.CARD){
|
||||
val counterpartyIdList: List[String] = for {
|
||||
@ -363,29 +360,29 @@ class MappedTransactionRequest extends LongKeyedMapper[MappedTransactionRequest]
|
||||
otherAccountSecondaryRoutingScheme,
|
||||
otherAccountSecondaryRoutingAddress
|
||||
)
|
||||
if(transactionRequestSimples.isEmpty)
|
||||
Some(TransactionRequestSimple("","","","","","","",""))
|
||||
else
|
||||
if(transactionRequestSimples.isEmpty)
|
||||
Some(TransactionRequestSimple("","","","","","","",""))
|
||||
else
|
||||
Some(transactionRequestSimples.head)
|
||||
}
|
||||
else
|
||||
None
|
||||
|
||||
|
||||
val t_to_transfer_to_phone = if (TransactionRequestTypes.withName(transactionType) == TransactionRequestTypes.TRANSFER_TO_PHONE && details.nonEmpty)
|
||||
Some(parsedDetails.extract[TransactionRequestTransferToPhone])
|
||||
else
|
||||
None
|
||||
|
||||
val t_to_transfer_to_atm = if (TransactionRequestTypes.withName(transactionType) == TransactionRequestTypes.TRANSFER_TO_ATM && details.nonEmpty)
|
||||
val t_to_transfer_to_atm = if (TransactionRequestTypes.withName(transactionType) == TransactionRequestTypes.TRANSFER_TO_ATM && details.nonEmpty)
|
||||
Some(parsedDetails.extract[TransactionRequestTransferToAtm])
|
||||
else
|
||||
None
|
||||
|
||||
|
||||
val t_to_transfer_to_account = if (TransactionRequestTypes.withName(transactionType) == TransactionRequestTypes.TRANSFER_TO_ACCOUNT && details.nonEmpty)
|
||||
Some(parsedDetails.extract[TransactionRequestTransferToAccount])
|
||||
else
|
||||
None
|
||||
|
||||
|
||||
val t_to_agent = if (TransactionRequestTypes.withName(transactionType) == TransactionRequestTypes.AGENT_CASH_WITHDRAWAL && details.nonEmpty) {
|
||||
val agentNumberList: List[String] = for {
|
||||
JObject(child) <- parsedDetails
|
||||
@ -406,20 +403,20 @@ class MappedTransactionRequest extends LongKeyedMapper[MappedTransactionRequest]
|
||||
}
|
||||
else
|
||||
None
|
||||
|
||||
|
||||
|
||||
|
||||
//This is Berlin Group Types:
|
||||
val t_to_sepa_credit_transfers = if (TransactionRequestTypes.withName(transactionType) == TransactionRequestTypes.SEPA_CREDIT_TRANSFERS && details.nonEmpty)
|
||||
Some(parsedDetails.extract[SepaCreditTransfers]) //TODO, here may need a internal case class, but for now, we used it from request json body.
|
||||
else
|
||||
None
|
||||
|
||||
|
||||
val t_body = TransactionRequestBodyAllTypes(
|
||||
to_sandbox_tan = t_to_sandbox_tan,
|
||||
to_sepa = t_to_sepa,
|
||||
to_counterparty = t_to_counterparty,
|
||||
to_simple = t_to_simple,
|
||||
to_transfer_to_phone = t_to_transfer_to_phone,
|
||||
to_transfer_to_phone = t_to_transfer_to_phone,
|
||||
to_transfer_to_atm = t_to_transfer_to_atm,
|
||||
to_transfer_to_account = t_to_transfer_to_account,
|
||||
to_sepa_credit_transfers = t_to_sepa_credit_transfers,
|
||||
|
||||
@ -5,6 +5,7 @@ import code.api.util.{APIUtil, CallContext}
|
||||
import com.openbankproject.commons.model.{TransactionRequest, TransactionRequestChallenge, TransactionRequestCharge, _}
|
||||
import net.liftweb.common.{Box, Logger}
|
||||
import net.liftweb.util.SimpleInjector
|
||||
import code.util.Helper.MdcLoggable
|
||||
|
||||
object TransactionRequests extends SimpleInjector {
|
||||
|
||||
@ -29,9 +30,7 @@ object TransactionRequests extends SimpleInjector {
|
||||
|
||||
}
|
||||
|
||||
trait TransactionRequestProvider {
|
||||
|
||||
private val logger = Logger(classOf[TransactionRequestProvider])
|
||||
trait TransactionRequestProvider extends MdcLoggable {
|
||||
|
||||
final def getTransactionRequest(transactionRequestId : TransactionRequestId) : Box[TransactionRequest] = {
|
||||
getTransactionRequestFromProvider(transactionRequestId)
|
||||
@ -80,7 +79,7 @@ trait TransactionRequestProvider {
|
||||
apiStandard: Option[String],
|
||||
apiVersion: Option[String],
|
||||
callContext: Option[CallContext]): Box[TransactionRequest]
|
||||
|
||||
|
||||
def saveTransactionRequestTransactionImpl(transactionRequestId: TransactionRequestId, transactionId: TransactionId): Box[Boolean]
|
||||
def saveTransactionRequestChallengeImpl(transactionRequestId: TransactionRequestId, challenge: TransactionRequestChallenge): Box[Boolean]
|
||||
def saveTransactionRequestStatusImpl(transactionRequestId: TransactionRequestId, status: String): Box[Boolean]
|
||||
@ -88,5 +87,3 @@ trait TransactionRequestProvider {
|
||||
def bulkDeleteTransactionRequestsByTransactionId(transactionId: TransactionId): Boolean
|
||||
def bulkDeleteTransactionRequests(): Boolean
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ import code.TransactionTypes.TransactionTypeProvider
|
||||
import code.model._
|
||||
import code.TransactionTypes.TransactionType._
|
||||
import code.util.{MediumString, UUIDString}
|
||||
import code.util.Helper.MdcLoggable
|
||||
import net.liftweb.common._
|
||||
import net.liftweb.mapper._
|
||||
import code.api.util.ErrorMessages
|
||||
@ -61,9 +62,7 @@ object MappedTransactionTypeProvider extends TransactionTypeProvider {
|
||||
}
|
||||
|
||||
}
|
||||
class MappedTransactionType extends LongKeyedMapper[MappedTransactionType] with IdPK with CreatedUpdated {
|
||||
|
||||
private val logger = Logger(classOf[MappedTransactionType])
|
||||
class MappedTransactionType extends LongKeyedMapper[MappedTransactionType] with IdPK with CreatedUpdated with MdcLoggable {
|
||||
|
||||
override def getSingleton = MappedTransactionType
|
||||
|
||||
@ -109,4 +108,4 @@ class MappedTransactionType extends LongKeyedMapper[MappedTransactionType] with
|
||||
|
||||
object MappedTransactionType extends MappedTransactionType with LongKeyedMetaMapper[MappedTransactionType] {
|
||||
override def dbIndexes = UniqueIndex(mTransactionTypeId) :: UniqueIndex(mBankId, mShortCode) :: super.dbIndexes
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ import code.transaction_types.MappedTransactionTypeProvider
|
||||
import com.openbankproject.commons.model.{AmountOfMoney, BankId, TransactionTypeId}
|
||||
import net.liftweb.common.{Box, Logger}
|
||||
import net.liftweb.util.SimpleInjector
|
||||
import code.util.Helper.MdcLoggable
|
||||
|
||||
|
||||
// See http://simply.liftweb.net/index-8.2.html for info about "vend" and SimpleInjector
|
||||
@ -48,15 +49,13 @@ object TransactionType extends SimpleInjector {
|
||||
case "mapped" => MappedTransactionTypeProvider
|
||||
case ttc: String => throw new IllegalArgumentException("No such connector for Transaction Types: " + ttc)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
trait TransactionTypeProvider {
|
||||
trait TransactionTypeProvider extends MdcLoggable {
|
||||
|
||||
import code.TransactionTypes.TransactionType.TransactionType
|
||||
|
||||
private val logger = Logger(classOf[TransactionTypeProvider])
|
||||
|
||||
|
||||
// Transaction types for bank (we may add getTransactionTypesForBankAccount and getTransactionTypesForBankAccountView)
|
||||
final def getTransactionTypesForBank(bankId : BankId) : Option[List[TransactionType]] = {
|
||||
@ -77,4 +76,3 @@ trait TransactionTypeProvider {
|
||||
|
||||
protected def createOrUpdateTransactionTypeAtProvider(postedData: TransactionTypeJsonV200): Box[TransactionType]
|
||||
}
|
||||
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
package code.users
|
||||
|
||||
/* For UserAttribute */
|
||||
|
||||
import code.api.util.APIUtil
|
||||
import com.openbankproject.commons.model.AccountAttribute
|
||||
import com.openbankproject.commons.model.enums.{AccountAttributeType, UserAttributeType}
|
||||
import code.util.Helper.MdcLoggable
|
||||
import com.openbankproject.commons.model.enums.UserAttributeType
|
||||
import net.liftweb.common.{Box, Logger}
|
||||
import net.liftweb.util.SimpleInjector
|
||||
|
||||
@ -14,7 +15,7 @@ object UserAttributeProvider extends SimpleInjector {
|
||||
|
||||
val userAttributeProvider = new Inject(buildOne _) {}
|
||||
|
||||
def buildOne: UserAttributeProvider = MappedUserAttributeProvider
|
||||
def buildOne: UserAttributeProvider = MappedUserAttributeProvider
|
||||
|
||||
// Helper to get the count out of an option
|
||||
def countOfUserAttribute(listOpt: Option[List[UserAttribute]]): Int = {
|
||||
@ -25,12 +26,9 @@ object UserAttributeProvider extends SimpleInjector {
|
||||
count
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
trait UserAttributeProvider {
|
||||
|
||||
private val logger = Logger(classOf[UserAttributeProvider])
|
||||
trait UserAttributeProvider extends MdcLoggable {
|
||||
|
||||
def getUserAttributesByUser(userId: String): Future[Box[List[UserAttribute]]]
|
||||
def getPersonalUserAttributes(userId: String): Future[Box[List[UserAttribute]]]
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
package code.util
|
||||
|
||||
import code.api.cache.{Redis, RedisLogger}
|
||||
|
||||
import java.net.{Socket, SocketException, URL}
|
||||
import java.util.UUID.randomUUID
|
||||
import java.util.{Date, GregorianCalendar}
|
||||
@ -24,6 +26,7 @@ import net.liftweb.util.Helpers.tryo
|
||||
import net.sf.cglib.proxy.{Enhancer, MethodInterceptor, MethodProxy}
|
||||
|
||||
import java.lang.reflect.Method
|
||||
import java.text.SimpleDateFormat
|
||||
import scala.concurrent.Future
|
||||
import scala.util.Random
|
||||
import scala.reflect.runtime.universe.Type
|
||||
@ -171,36 +174,36 @@ object Helper extends Loggable {
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param redirectUrl eg: http://localhost:8082/oauthcallback?oauth_token=G5AEA2U1WG404EGHTIGBHKRR4YJZAPPHWKOMNEEV&oauth_verifier=53018
|
||||
* @return http://localhost:8082/oauthcallback
|
||||
*/
|
||||
def getStaticPortionOfRedirectURL(redirectUrl: String): Box[String] = {
|
||||
tryo(redirectUrl.split("\\?")(0)) //return everything before the "?"
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* extract clean redirect url from input value, because input may have some parameters, such as the following examples <br/>
|
||||
* eg1: http://localhost:8082/oauthcallback?....--> http://localhost:8082 <br/>
|
||||
* extract clean redirect url from input value, because input may have some parameters, such as the following examples <br/>
|
||||
* eg1: http://localhost:8082/oauthcallback?....--> http://localhost:8082 <br/>
|
||||
* eg2: http://localhost:8016?oautallback?=3NLMGV ...--> http://localhost:8016
|
||||
*
|
||||
* @param redirectUrl -> http://localhost:8082/oauthcallback?oauth_token=G5AEA2U1WG404EGHTIGBHKRR4YJZAPPHWKOMNEEV&oauth_verifier=53018
|
||||
* @return hostOnlyOfRedirectURL-> http://localhost:8082
|
||||
*/
|
||||
@deprecated("We can not only use hostname as the redirectUrl, now add new method `getStaticPortionOfRedirectURL` ","05.12.2023")
|
||||
@deprecated("We can not only use hostname as the redirectUrl, now add new method `getStaticPortionOfRedirectURL` ","05.12.2023")
|
||||
def getHostOnlyOfRedirectURL(redirectUrl: String): Box[String] = {
|
||||
val url = new URL(redirectUrl) //eg: http://localhost:8082/oauthcallback?oauth_token=G5AEA2U1WG404EGHTIGBHKRR4YJZAPPHWKOMNEEV&oauth_verifier=53018
|
||||
val protocol = url.getProtocol() // http
|
||||
val authority = url.getAuthority()// localhost:8082, this will contain the port.
|
||||
tryo(s"$protocol://$authority") // http://localhost:8082
|
||||
tryo(s"$protocol://$authority") // http://localhost:8082
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* extract Oauth Token String from input value, because input may have some parameters, such as the following examples <br/>
|
||||
* http://localhost:8082/oauthcallback?oauth_token=DKR242MB3IRCUVG35UZ0QQOK3MBS1G2HL2ZIKK2O&oauth_verifier=64465
|
||||
* extract Oauth Token String from input value, because input may have some parameters, such as the following examples <br/>
|
||||
* http://localhost:8082/oauthcallback?oauth_token=DKR242MB3IRCUVG35UZ0QQOK3MBS1G2HL2ZIKK2O&oauth_verifier=64465
|
||||
* --> DKR242MB3IRCUVG35UZ0QQOK3MBS1G2HL2ZIKK2O
|
||||
*
|
||||
* @param input a long url with parameters
|
||||
*
|
||||
* @param input a long url with parameters
|
||||
* @return Oauth Token String
|
||||
*/
|
||||
def extractOauthToken(input: String): Box[String] = {
|
||||
@ -236,7 +239,7 @@ object Helper extends Loggable {
|
||||
* Used for version extraction from props string
|
||||
*/
|
||||
val matchAnyStoredProcedure = "stored_procedure.*|star".r
|
||||
|
||||
|
||||
/**
|
||||
* change the TimeZone to the current TimeZOne
|
||||
* reference the following trait
|
||||
@ -246,25 +249,25 @@ object Helper extends Loggable {
|
||||
*/
|
||||
//TODO need clean this format, we have set the TimeZone in boot.scala
|
||||
val DateFormatWithCurrentTimeZone = new Formats {
|
||||
|
||||
|
||||
import java.text.{ParseException, SimpleDateFormat}
|
||||
|
||||
|
||||
val dateFormat = new DateFormat {
|
||||
def parse(s: String) = try {
|
||||
Some(formatter.parse(s))
|
||||
} catch {
|
||||
case e: ParseException => None
|
||||
}
|
||||
|
||||
|
||||
def format(d: Date) = formatter.format(d)
|
||||
|
||||
|
||||
private def formatter = {
|
||||
val f = dateFormatter
|
||||
f.setTimeZone(new GregorianCalendar().getTimeZone)
|
||||
f
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected def dateFormatter = APIUtil.DateWithMsFormat
|
||||
}
|
||||
|
||||
@ -315,33 +318,95 @@ object Helper extends Loggable {
|
||||
candidatePort
|
||||
}
|
||||
|
||||
|
||||
|
||||
trait MdcLoggable extends Loggable {
|
||||
protected def initiate(): Unit = () // The type is Unit and the only value this type can take is the literal ()
|
||||
protected def surroundWarnMessage(msg: String, title: String = ""): Unit = {
|
||||
|
||||
logger.warn(s"+-${title}${StringUtils.repeat("-", msg.length - title.length)}-+")
|
||||
logger.warn(s"| $msg |")
|
||||
logger.warn(s"+-${StringUtils.repeat("-", msg.length)}-+")
|
||||
}
|
||||
protected def surroundInfoMessage(msg: String, title: String = ""): Unit = {
|
||||
logger.info(s"+-${title}${StringUtils.repeat("-", msg.length - title.length)}-+")
|
||||
logger.info(s"| $msg |")
|
||||
logger.info(s"+-${StringUtils.repeat("-", msg.length)}-+")
|
||||
}
|
||||
protected def surroundErrorMessage(msg: String, title: String = ""): Unit = {
|
||||
logger.error(s"+-${title}${StringUtils.repeat("-", msg.length - title.length)}-+")
|
||||
logger.error(s"| $msg |")
|
||||
logger.error(s"+-${StringUtils.repeat("-", msg.length)}-+")
|
||||
}
|
||||
protected def surroundDebugMessage(msg: String, title: String = ""): Unit = {
|
||||
logger.debug(s"+-${title}${StringUtils.repeat("-", msg.length - title.length)}-+")
|
||||
logger.debug(s"| $msg |")
|
||||
logger.debug(s"+-${StringUtils.repeat("-", msg.length)}-+")
|
||||
|
||||
// Capture the class name of the component mixing in this trait
|
||||
private val clazzName: String = this.getClass.getSimpleName.replaceAll("\\$", "")
|
||||
|
||||
override protected val logger: net.liftweb.common.Logger = {
|
||||
val loggerName = this.getClass.getName
|
||||
|
||||
new net.liftweb.common.Logger {
|
||||
|
||||
private val underlyingLogger = net.liftweb.common.Logger(loggerName)
|
||||
|
||||
private val dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ssX")
|
||||
dateFormat.setTimeZone(java.util.TimeZone.getDefault) // force local TZ
|
||||
|
||||
private def toRedisFormat(msg: AnyRef): String = {
|
||||
val ts = dateFormat.format(new Date())
|
||||
val thread = Thread.currentThread().getName
|
||||
s"[$ts] [$thread] [$clazzName] ${msg.toString}"
|
||||
}
|
||||
|
||||
// INFO
|
||||
override def info(msg: => AnyRef): Unit = {
|
||||
underlyingLogger.info(msg)
|
||||
RedisLogger.logAsync(RedisLogger.LogLevel.INFO, toRedisFormat(msg))
|
||||
}
|
||||
|
||||
override def info(msg: => AnyRef, t: => Throwable): Unit = {
|
||||
underlyingLogger.info(msg, t)
|
||||
RedisLogger.logAsync(RedisLogger.LogLevel.INFO, toRedisFormat(msg) + "\n" + t.toString)
|
||||
}
|
||||
|
||||
// WARN
|
||||
override def warn(msg: => AnyRef): Unit = {
|
||||
underlyingLogger.warn(msg)
|
||||
RedisLogger.logAsync(RedisLogger.LogLevel.WARNING, toRedisFormat(msg))
|
||||
}
|
||||
|
||||
override def warn(msg: => AnyRef, t: Throwable): Unit = {
|
||||
underlyingLogger.warn(msg, t)
|
||||
RedisLogger.logAsync(RedisLogger.LogLevel.WARNING, toRedisFormat(msg) + "\n" + t.toString)
|
||||
}
|
||||
|
||||
// ERROR
|
||||
override def error(msg: => AnyRef): Unit = {
|
||||
underlyingLogger.error(msg)
|
||||
RedisLogger.logAsync(RedisLogger.LogLevel.ERROR, toRedisFormat(msg))
|
||||
}
|
||||
|
||||
override def error(msg: => AnyRef, t: Throwable): Unit = {
|
||||
underlyingLogger.error(msg, t)
|
||||
RedisLogger.logAsync(RedisLogger.LogLevel.ERROR, toRedisFormat(msg) + "\n" + t.toString)
|
||||
}
|
||||
|
||||
// DEBUG
|
||||
override def debug(msg: => AnyRef): Unit = {
|
||||
underlyingLogger.debug(msg)
|
||||
RedisLogger.logAsync(RedisLogger.LogLevel.DEBUG, toRedisFormat(msg))
|
||||
}
|
||||
|
||||
override def debug(msg: => AnyRef, t: Throwable): Unit = {
|
||||
underlyingLogger.debug(msg, t)
|
||||
RedisLogger.logAsync(RedisLogger.LogLevel.DEBUG, toRedisFormat(msg) + "\n" + t.toString)
|
||||
}
|
||||
|
||||
// TRACE
|
||||
override def trace(msg: => AnyRef): Unit = {
|
||||
underlyingLogger.trace(msg)
|
||||
RedisLogger.logAsync(RedisLogger.LogLevel.TRACE, toRedisFormat(msg))
|
||||
}
|
||||
|
||||
// Delegate enabled checks
|
||||
override def isDebugEnabled: Boolean = underlyingLogger.isDebugEnabled
|
||||
override def isErrorEnabled: Boolean = underlyingLogger.isErrorEnabled
|
||||
override def isInfoEnabled: Boolean = underlyingLogger.isInfoEnabled
|
||||
override def isTraceEnabled: Boolean = underlyingLogger.isTraceEnabled
|
||||
override def isWarnEnabled: Boolean = underlyingLogger.isWarnEnabled
|
||||
}
|
||||
}
|
||||
|
||||
protected def initiate(): Unit = ()
|
||||
|
||||
initiate()
|
||||
MDC.put("host" -> getHostname)
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Return true for Y, YES and true etc.
|
||||
*/
|
||||
@ -393,7 +458,7 @@ object Helper extends Loggable {
|
||||
case _ => Nil
|
||||
}
|
||||
default.getOrElse(words.mkString(" ") + ".")
|
||||
} else
|
||||
} else
|
||||
S.?(message)
|
||||
} else {
|
||||
logger.error(s"i18n(message($message), default${default}: Attempted to use resource bundles outside of an initialized S scope. " +
|
||||
@ -411,8 +476,8 @@ object Helper extends Loggable {
|
||||
* @return modified instance
|
||||
*/
|
||||
private def convertId[T](
|
||||
obj: T,
|
||||
customerIdConverter: String=> String,
|
||||
obj: T,
|
||||
customerIdConverter: String=> String,
|
||||
accountIdConverter: String=> String,
|
||||
transactionIdConverter: String=> String
|
||||
): T = {
|
||||
@ -433,7 +498,7 @@ object Helper extends Loggable {
|
||||
(ownerType <:< typeOf[AccountBalances] && fieldName.equalsIgnoreCase("id") && fieldType =:= typeOf[String])||
|
||||
(ownerType <:< typeOf[AccountHeld] && fieldName.equalsIgnoreCase("id") && fieldType =:= typeOf[String])
|
||||
}
|
||||
|
||||
|
||||
def isTransactionId(fieldName: String, fieldType: Type, fieldValue: Any, ownerType: Type) = {
|
||||
ownerType <:< typeOf[TransactionId] ||
|
||||
(fieldName.equalsIgnoreCase("transactionId") && fieldType =:= typeOf[String])||
|
||||
@ -502,10 +567,10 @@ object Helper extends Loggable {
|
||||
|
||||
lazy val result = method.invoke(net.liftweb.http.S, args: _*)
|
||||
val methodName = method.getName
|
||||
|
||||
|
||||
if (methodName.equals("param")&&result.isInstanceOf[Box[String]]&&result.asInstanceOf[Box[String]].isDefined) {
|
||||
//we provide the basic check for all the parameters
|
||||
val resultAfterChecked =
|
||||
val resultAfterChecked =
|
||||
if((args.length>0) && args.apply(0).toString.equalsIgnoreCase("username")) {
|
||||
result.asInstanceOf[Box[String]].filter(APIUtil.checkUsernameString(_)==SILENCE_IS_GOLDEN)
|
||||
}else if((args.length>0) && args.apply(0).toString.equalsIgnoreCase("password")){
|
||||
@ -517,7 +582,7 @@ object Helper extends Loggable {
|
||||
} else{
|
||||
result.asInstanceOf[Box[String]].filter(APIUtil.checkMediumString(_)==SILENCE_IS_GOLDEN)
|
||||
}
|
||||
if(resultAfterChecked.isEmpty) {
|
||||
if(resultAfterChecked.isEmpty) {
|
||||
logger.debug(s"ObpS.${methodName} validation failed. (resultAfterChecked.isEmpty A) The input key is: ${if (args.length>0)args.apply(0) else ""}, value is:$result")
|
||||
}
|
||||
resultAfterChecked
|
||||
@ -532,7 +597,7 @@ object Helper extends Loggable {
|
||||
} else if (methodName.equals("uriAndQueryString") && result.isInstanceOf[Box[String]] && result.asInstanceOf[Box[String]].isDefined ||
|
||||
methodName.equals("queryString") && result.isInstanceOf[Box[String]]&&result.asInstanceOf[Box[String]].isDefined){
|
||||
val resultAfterChecked = result.asInstanceOf[Box[String]].filter(APIUtil.basicUriAndQueryStringValidation(_))
|
||||
if(resultAfterChecked.isEmpty) {
|
||||
if(resultAfterChecked.isEmpty) {
|
||||
logger.debug(s"ObpS.${methodName} validation failed. (resultAfterChecked.isEmpty B) The value is:$result")
|
||||
}
|
||||
resultAfterChecked
|
||||
@ -540,7 +605,7 @@ object Helper extends Loggable {
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val enhancer: Enhancer = new Enhancer()
|
||||
enhancer.setSuperclass(classOf[S])
|
||||
enhancer.setCallback(intercept)
|
||||
@ -602,4 +667,4 @@ object Helper extends Loggable {
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -174,14 +174,6 @@ object PemCertificateRole extends OBPEnumeration[PemCertificateRole] {
|
||||
object PSP_IC extends Value
|
||||
object PSP_AI extends Value
|
||||
object PSP_PI extends Value
|
||||
|
||||
def toBerlinGroup(role: String): String = {
|
||||
role match {
|
||||
case item if PSP_AI.toString == item => "AISP"
|
||||
case item if PSP_PI.toString == item => "PISP"
|
||||
case _ => ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed trait UserInvitationPurpose extends EnumValue
|
||||
|
||||
76
zed/.metals-config.json
Normal file
76
zed/.metals-config.json
Normal file
@ -0,0 +1,76 @@
|
||||
{
|
||||
"maven": {
|
||||
"enabled": true
|
||||
},
|
||||
"metals": {
|
||||
"serverVersion": "1.0.0",
|
||||
"javaHome": "/usr/lib/jvm/java-17-openjdk-amd64",
|
||||
"bloopVersion": "2.0.0",
|
||||
"superMethodLensesEnabled": true,
|
||||
"enableSemanticHighlighting": true,
|
||||
"compileOnSave": true,
|
||||
"testUserInterface": "Code Lenses",
|
||||
"inlayHints": {
|
||||
"enabled": true,
|
||||
"hintsInPatternMatch": {
|
||||
"enabled": true
|
||||
},
|
||||
"implicitArguments": {
|
||||
"enabled": true
|
||||
},
|
||||
"implicitConversions": {
|
||||
"enabled": true
|
||||
},
|
||||
"inferredTypes": {
|
||||
"enabled": true
|
||||
},
|
||||
"typeParameters": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"buildTargets": [
|
||||
{
|
||||
"id": "obp-commons",
|
||||
"displayName": "obp-commons",
|
||||
"baseDirectory": "file:///home/marko/Tesobe/GitHub/constantine2nd/OBP-API/obp-commons/",
|
||||
"tags": ["library"],
|
||||
"languageIds": ["scala", "java"],
|
||||
"dependencies": [],
|
||||
"capabilities": {
|
||||
"canCompile": true,
|
||||
"canTest": true,
|
||||
"canRun": false,
|
||||
"canDebug": true
|
||||
},
|
||||
"dataKind": "scala",
|
||||
"data": {
|
||||
"scalaOrganization": "org.scala-lang",
|
||||
"scalaVersion": "2.12.20",
|
||||
"scalaBinaryVersion": "2.12",
|
||||
"platform": "jvm"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "obp-api",
|
||||
"displayName": "obp-api",
|
||||
"baseDirectory": "file:///home/marko/Tesobe/GitHub/constantine2nd/OBP-API/obp-api/",
|
||||
"tags": ["application"],
|
||||
"languageIds": ["scala", "java"],
|
||||
"dependencies": ["obp-commons"],
|
||||
"capabilities": {
|
||||
"canCompile": true,
|
||||
"canTest": true,
|
||||
"canRun": true,
|
||||
"canDebug": true
|
||||
},
|
||||
"dataKind": "scala",
|
||||
"data": {
|
||||
"scalaOrganization": "org.scala-lang",
|
||||
"scalaVersion": "2.12.20",
|
||||
"scalaBinaryVersion": "2.12",
|
||||
"platform": "jvm"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
298
zed/README.md
Normal file
298
zed/README.md
Normal file
@ -0,0 +1,298 @@
|
||||
# ZED IDE Setup for OBP-API Development
|
||||
|
||||
> **Complete ZED IDE integration for the Open Bank Project API**
|
||||
|
||||
This folder contains everything needed to set up ZED IDE with full Scala language server support, automated build tasks, and streamlined development workflows for OBP-API.
|
||||
|
||||
## 🚀 Quick Setup (5 minutes)
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- **Java 17+** (OpenJDK recommended)
|
||||
- **Maven 3.6+**
|
||||
- **ZED IDE** (latest version)
|
||||
|
||||
### Single Setup Script
|
||||
|
||||
```bash
|
||||
cd OBP-API
|
||||
./zed/setup-zed-ide.sh
|
||||
```
|
||||
|
||||
This unified script automatically:
|
||||
|
||||
- ✅ Installs missing dependencies (Coursier, Bloop)
|
||||
- ✅ Compiles the project and resolves dependencies
|
||||
- ✅ Generates dynamic Bloop configurations
|
||||
- ✅ Sets up Metals language server
|
||||
- ✅ Copies ZED configuration files to `.zed/` folder
|
||||
- ✅ Configures build and run tasks
|
||||
- ✅ Sets up manual-only code formatting
|
||||
|
||||
## 📁 What's Included
|
||||
|
||||
```
|
||||
zed/
|
||||
├── README.md # This comprehensive guide
|
||||
├── setup-zed-ide.sh # Single unified setup script
|
||||
├── generate-bloop-config.sh # Dynamic Bloop config generator
|
||||
├── settings.json # ZED IDE settings template
|
||||
├── tasks.json # Pre-configured build/run tasks
|
||||
├── .metals-config.json # Metals language server config
|
||||
└── setup-zed.bat # Windows setup script
|
||||
```
|
||||
|
||||
## ⌨️ Essential Keyboard Shortcuts
|
||||
|
||||
| Action | Linux | macOS/Windows | Purpose |
|
||||
| -------------------- | -------------- | ------------- | ----------------------------- |
|
||||
| **Command Palette** | `Ctrl+Shift+P` | `Cmd+Shift+P` | Access all tasks |
|
||||
| **Go to Definition** | `F12` | `F12` | Navigate to symbol definition |
|
||||
| **Find References** | `Shift+F12` | `Shift+F12` | Find all symbol usages |
|
||||
| **Quick Open File** | `Ctrl+P` | `Cmd+P` | Fast file navigation |
|
||||
| **Format Code** | `Ctrl+Shift+I` | `Cmd+Shift+I` | Auto-format Scala code |
|
||||
| **Symbol Search** | `Ctrl+T` | `Cmd+T` | Search symbols project-wide |
|
||||
|
||||
## 🛠️ Available Development Tasks
|
||||
|
||||
Access via Command Palette (`Ctrl+Shift+P` on Linux, `Cmd+Shift+P` on macOS/Windows) → `"task: spawn"` (Linux) or `"Tasks: Spawn"` (macOS/Windows):
|
||||
|
||||
### Core Development Tasks
|
||||
|
||||
| Task | Purpose | Duration | When to Use |
|
||||
| ---------------------------- | ------------------------ | --------- | ------------------------------------ |
|
||||
| **Quick Build Dependencies** | Build only dependencies | 1-3 min | First step, after dependency changes |
|
||||
| **[1] Run OBP-API Server** | Start development server | 3-5 min | Daily development |
|
||||
| **🔨 Build OBP-API** | Full project build | 2-5 min | After code changes |
|
||||
| **Run Tests** | Execute test suite | 5-15 min | Before commits |
|
||||
| **[3] Compile Only** | Quick syntax check | 30s-1 min | During development |
|
||||
|
||||
### Utility Tasks
|
||||
|
||||
| Task | Purpose |
|
||||
| --------------------------------- | ------------------------- |
|
||||
| **[4] Clean Target Folders** | Remove build artifacts |
|
||||
| **🔄 Continuous Compile (Scala)** | Auto-recompile on changes |
|
||||
| **[2] Test API Root Endpoint** | Verify server status |
|
||||
| **🔧 Kill Server on Port 8080** | Stop stuck processes |
|
||||
| **🔍 Check Dependencies** | Verify Maven dependencies |
|
||||
|
||||
## 🏗️ Development Workflow
|
||||
|
||||
### Daily Development
|
||||
|
||||
1. **Start Development Session**
|
||||
- Linux: `Ctrl+Shift+P` → `"task: spawn"` → `"Quick Build Dependencies"`
|
||||
- macOS: `Cmd+Shift+P` → `"Tasks: Spawn"` → `"Quick Build Dependencies"`
|
||||
|
||||
2. **Start API Server**
|
||||
- Use task `"[1] Run OBP-API Server"`
|
||||
- Server runs on: `http://localhost:8080`
|
||||
- Test endpoint: `http://localhost:8080/obp/v5.1.0/root`
|
||||
|
||||
3. **Code Development**
|
||||
- Edit Scala files in `obp-api/src/main/scala/`
|
||||
- Use `F12` for Go to Definition
|
||||
- Auto-completion with `Ctrl+Space`
|
||||
- Real-time error highlighting
|
||||
- Format code with `Ctrl+Shift+I`
|
||||
|
||||
4. **Testing & Validation**
|
||||
- Quick compile: `"[3] Compile Only"` task
|
||||
- Run tests: `"Run Tests"` task
|
||||
- API testing: `"[2] Test API Root Endpoint"` task
|
||||
|
||||
## 🔧 Configuration Details
|
||||
|
||||
### ZED IDE Settings (`settings.json`)
|
||||
|
||||
- **Format on Save**: DISABLED (manual formatting only - use `Ctrl+Shift+I`)
|
||||
- **Scala LSP**: Optimized Metals configuration
|
||||
- **Maven Integration**: Proper MAVEN_OPTS for Java 17+
|
||||
- **UI Preferences**: One Dark theme, consistent layout
|
||||
- **Inlay Hints**: Enabled for better code understanding
|
||||
|
||||
### Build Tasks (`tasks.json`)
|
||||
|
||||
All tasks include proper environment variables:
|
||||
|
||||
```bash
|
||||
MAVEN_OPTS="-Xss128m --add-opens=java.base/java.util.jar=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED"
|
||||
```
|
||||
|
||||
### Metals LSP (`.metals-config.json`)
|
||||
|
||||
- **Build Tool**: Maven
|
||||
- **Bloop Integration**: Dynamic configuration generation
|
||||
- **Scala Version**: 2.12.20
|
||||
- **Java Target**: Java 11 (compatible with Java 17)
|
||||
|
||||
## 🚨 Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
| Problem | Symptom | Solution |
|
||||
| ------------------------------- | ------------------------------------ | ------------------------------------------------ |
|
||||
| **Language Server Not Working** | No go-to-definition, no autocomplete | Restart ZED, wait for Metals initialization |
|
||||
| **Compilation Errors** | Red squiggly lines, build failures | Check Problems panel, run "Clean Target Folders" |
|
||||
| **Server Won't Start** | Port 8080 busy | Run "Kill Server on Port 8080" task |
|
||||
| **Out of Memory** | Build fails with heap space error | Already configured in tasks |
|
||||
| **Missing Dependencies** | Import errors | Run "Check Dependencies" task |
|
||||
|
||||
### Recovery Procedures
|
||||
|
||||
1. **Full Reset**:
|
||||
|
||||
```bash
|
||||
./zed/setup-zed-ide.sh # Re-run complete setup
|
||||
```
|
||||
|
||||
2. **Regenerate Bloop Configurations**:
|
||||
|
||||
```bash
|
||||
./zed/generate-bloop-config.sh # Regenerate configs
|
||||
```
|
||||
|
||||
3. **Clean Restart**:
|
||||
- Clean build with "Clean Target Folders" task
|
||||
- Restart ZED IDE
|
||||
- Wait for Metals to reinitialize (2-3 minutes)
|
||||
|
||||
### Platform-Specific Notes
|
||||
|
||||
#### Linux Users
|
||||
|
||||
- Use `"task: spawn"` in command palette (not `"Tasks: Spawn"`)
|
||||
- Ensure proper Java permissions for Maven
|
||||
|
||||
#### macOS/Windows Users
|
||||
|
||||
- Use `"Tasks: Spawn"` in command palette
|
||||
- Windows users can also use `setup-zed.bat`
|
||||
|
||||
## 🌐 API Development
|
||||
|
||||
### Project Structure
|
||||
|
||||
```
|
||||
OBP-API/
|
||||
├── obp-api/ # Main API application
|
||||
│ └── src/main/scala/ # Scala source code
|
||||
│ └── code/api/ # API endpoint definitions
|
||||
│ ├── v5_1_0/ # Latest API version
|
||||
│ ├── v4_0_0/ # Previous versions
|
||||
│ └── util/ # Utility functions
|
||||
├── obp-commons/ # Shared utilities and models
|
||||
│ └── src/main/scala/ # Common Scala code
|
||||
└── .zed/ # ZED IDE configuration (generated)
|
||||
```
|
||||
|
||||
### Adding New API Endpoints
|
||||
|
||||
1. Navigate to `obp-api/src/main/scala/code/api/v5_1_0/`
|
||||
2. Find appropriate API trait (e.g., `OBPAPI5_1_0.scala`)
|
||||
3. Follow existing endpoint patterns
|
||||
4. Use `F12` to navigate to helper functions
|
||||
5. Test with API test task
|
||||
|
||||
### Testing Endpoints
|
||||
|
||||
```bash
|
||||
# Root API information
|
||||
curl http://localhost:8080/obp/v5.1.0/root
|
||||
|
||||
# Health check
|
||||
curl http://localhost:8080/obp/v5.1.0/config
|
||||
|
||||
# Banks list (requires proper setup)
|
||||
curl http://localhost:8080/obp/v5.1.0/banks
|
||||
```
|
||||
|
||||
## 🎯 Pro Tips
|
||||
|
||||
### Code Navigation
|
||||
|
||||
- **Quick file access**: `Ctrl+P` then type filename
|
||||
- **Symbol search**: `Ctrl+T` then type function/class name
|
||||
- **Project-wide text search**: `Ctrl+Shift+F`
|
||||
|
||||
### Efficiency Shortcuts
|
||||
|
||||
- `Ctrl+/` - Toggle line comment
|
||||
- `Ctrl+D` - Select next occurrence
|
||||
- `Ctrl+Shift+L` - Select all occurrences
|
||||
- `F2` - Rename symbol
|
||||
- `Alt+←/→` - Navigate back/forward
|
||||
|
||||
### Performance Optimization
|
||||
|
||||
- Close unused files to reduce memory usage
|
||||
- Use "Continuous Compile" for faster feedback
|
||||
- Limit test runs to specific modules during development
|
||||
|
||||
## 📚 Additional Resources
|
||||
|
||||
### Documentation
|
||||
|
||||
- **OBP-API Project**: https://github.com/OpenBankProject/OBP-API
|
||||
- **API Documentation**: https://apiexplorer.openbankproject.com
|
||||
- **Community Forums**: https://openbankproject.com
|
||||
|
||||
### Learning Resources
|
||||
|
||||
- **Scala**: https://docs.scala-lang.org/
|
||||
- **Lift Framework**: https://liftweb.net/
|
||||
- **Maven**: https://maven.apache.org/guides/
|
||||
- **ZED IDE**: https://zed.dev/docs
|
||||
|
||||
## 🆘 Getting Help
|
||||
|
||||
### Diagnostic Commands
|
||||
|
||||
```bash
|
||||
# Check Java version
|
||||
java -version
|
||||
|
||||
# Check Maven
|
||||
mvn -version
|
||||
|
||||
# Check Bloop status
|
||||
bloop projects
|
||||
|
||||
# Test compilation
|
||||
bloop compile obp-commons obp-api
|
||||
|
||||
# Check ZED configuration
|
||||
ls -la .zed/
|
||||
```
|
||||
|
||||
### Common Error Messages
|
||||
|
||||
| Error | Cause | Solution |
|
||||
| --------------------------- | ----------------------------- | ----------------------------- |
|
||||
| "Java module system" errors | Java 17+ module restrictions | Already handled in MAVEN_OPTS |
|
||||
| "Port 8080 already in use" | Previous server still running | Use "Kill Server" task |
|
||||
| "Metals not responding" | Language server crashed | Restart ZED IDE |
|
||||
| "Compilation failed" | Dependency issues | Run "Check Dependencies" |
|
||||
|
||||
---
|
||||
|
||||
## 🎉 Getting Started Checklist
|
||||
|
||||
- [ ] Install Java 17+, Maven 3.6+, ZED IDE
|
||||
- [ ] Clone OBP-API repository
|
||||
- [ ] Run `./zed/setup-zed-ide.sh` (single setup script)
|
||||
- [ ] Open project in ZED IDE
|
||||
- [ ] Wait for Metals initialization (2-3 minutes)
|
||||
- [ ] Run "Quick Build Dependencies" task
|
||||
- [ ] Start server with "[1] Run OBP-API Server" task
|
||||
- [ ] Test API at http://localhost:8080/obp/v5.1.0/root
|
||||
- [ ] Try "Go to Definition" (F12) on Scala symbol
|
||||
- [ ] Format code manually with `Ctrl+Shift+I` (auto-format disabled)
|
||||
- [ ] Make a small code change and test compilation
|
||||
|
||||
**Welcome to productive OBP-API development with ZED IDE! 🚀**
|
||||
|
||||
---
|
||||
|
||||
_This setup provides a complete, optimized development environment for the Open Bank Project API using ZED IDE with full Scala language server support._
|
||||
263
zed/generate-bloop-config.sh
Executable file
263
zed/generate-bloop-config.sh
Executable file
@ -0,0 +1,263 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Generate portable Bloop configuration files for OBP-API
|
||||
# This script creates Bloop JSON configurations with proper paths for any system
|
||||
|
||||
set -e
|
||||
|
||||
echo "🔧 Generating Bloop configuration files..."
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Get the project root directory (parent of zed folder)
|
||||
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
echo "📁 Project root: $PROJECT_ROOT"
|
||||
|
||||
# Check if we're in the zed directory and project structure exists
|
||||
if [[ ! -f "$PROJECT_ROOT/pom.xml" ]] || [[ ! -d "$PROJECT_ROOT/obp-api" ]] || [[ ! -d "$PROJECT_ROOT/obp-commons" ]]; then
|
||||
echo -e "${RED}❌ Error: Could not find OBP-API project structure${NC}"
|
||||
echo "Make sure you're running this from the zed/ folder of the OBP-API project"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Change to project root for Maven operations
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
# Detect Java home
|
||||
if [[ -z "$JAVA_HOME" ]]; then
|
||||
JAVA_HOME=$(dirname $(dirname $(readlink -f $(which java))))
|
||||
echo -e "${YELLOW}⚠️ JAVA_HOME not set, detected: $JAVA_HOME${NC}"
|
||||
else
|
||||
echo -e "${GREEN}✅ JAVA_HOME: $JAVA_HOME${NC}"
|
||||
fi
|
||||
|
||||
# Get Maven local repository
|
||||
M2_REPO=$(mvn help:evaluate -Dexpression=settings.localRepository -q -DforceStdout 2>/dev/null || echo "$HOME/.m2/repository")
|
||||
echo "📦 Maven repository: $M2_REPO"
|
||||
|
||||
# Ensure .bloop directory exists in project root
|
||||
mkdir -p "$PROJECT_ROOT/.bloop"
|
||||
|
||||
# Generate obp-commons.json
|
||||
echo "🔨 Generating obp-commons configuration..."
|
||||
cat > "$PROJECT_ROOT/.bloop/obp-commons.json" << EOF
|
||||
{
|
||||
"version": "1.5.5",
|
||||
"project": {
|
||||
"name": "obp-commons",
|
||||
"directory": "${PROJECT_ROOT}/obp-commons",
|
||||
"workspaceDir": "${PROJECT_ROOT}",
|
||||
"sources": [
|
||||
"${PROJECT_ROOT}/obp-commons/src/main/scala",
|
||||
"${PROJECT_ROOT}/obp-commons/src/main/java"
|
||||
],
|
||||
"dependencies": [],
|
||||
"classpath": [
|
||||
"${PROJECT_ROOT}/obp-commons/target/classes",
|
||||
"${M2_REPO}/net/liftweb/lift-common_2.12/3.5.0/lift-common_2.12-3.5.0.jar",
|
||||
"${M2_REPO}/org/scala-lang/scala-library/2.12.12/scala-library-2.12.12.jar",
|
||||
"${M2_REPO}/org/slf4j/slf4j-api/1.7.25/slf4j-api-1.7.25.jar",
|
||||
"${M2_REPO}/org/scala-lang/modules/scala-xml_2.12/1.3.0/scala-xml_2.12-1.3.0.jar",
|
||||
"${M2_REPO}/org/scala-lang/modules/scala-parser-combinators_2.12/1.1.2/scala-parser-combinators_2.12-1.1.2.jar",
|
||||
"${M2_REPO}/net/liftweb/lift-util_2.12/3.5.0/lift-util_2.12-3.5.0.jar",
|
||||
"${M2_REPO}/org/scala-lang/scala-compiler/2.12.12/scala-compiler-2.12.12.jar",
|
||||
"${M2_REPO}/net/liftweb/lift-actor_2.12/3.5.0/lift-actor_2.12-3.5.0.jar",
|
||||
"${M2_REPO}/net/liftweb/lift-markdown_2.12/3.5.0/lift-markdown_2.12-3.5.0.jar",
|
||||
"${M2_REPO}/joda-time/joda-time/2.10/joda-time-2.10.jar",
|
||||
"${M2_REPO}/org/joda/joda-convert/2.1/joda-convert-2.1.jar",
|
||||
"${M2_REPO}/commons-codec/commons-codec/1.11/commons-codec-1.11.jar",
|
||||
"${M2_REPO}/nu/validator/htmlparser/1.4.12/htmlparser-1.4.12.jar",
|
||||
"${M2_REPO}/xerces/xercesImpl/2.11.0/xercesImpl-2.11.0.jar",
|
||||
"${M2_REPO}/xml-apis/xml-apis/1.4.01/xml-apis-1.4.01.jar",
|
||||
"${M2_REPO}/org/mindrot/jbcrypt/0.4/jbcrypt-0.4.jar",
|
||||
"${M2_REPO}/net/liftweb/lift-mapper_2.12/3.5.0/lift-mapper_2.12-3.5.0.jar",
|
||||
"${M2_REPO}/net/liftweb/lift-db_2.12/3.5.0/lift-db_2.12-3.5.0.jar",
|
||||
"${M2_REPO}/net/liftweb/lift-webkit_2.12/3.5.0/lift-webkit_2.12-3.5.0.jar",
|
||||
"${M2_REPO}/commons-fileupload/commons-fileupload/1.3.3/commons-fileupload-1.3.3.jar",
|
||||
"${M2_REPO}/commons-io/commons-io/2.2/commons-io-2.2.jar",
|
||||
"${M2_REPO}/org/mozilla/rhino/1.7.10/rhino-1.7.10.jar",
|
||||
"${M2_REPO}/net/liftweb/lift-proto_2.12/3.5.0/lift-proto_2.12-3.5.0.jar",
|
||||
"${M2_REPO}/org/scala-lang/scala-reflect/2.12.20/scala-reflect-2.12.20.jar",
|
||||
"${M2_REPO}/org/scalatest/scalatest_2.12/3.0.8/scalatest_2.12-3.0.8.jar",
|
||||
"${M2_REPO}/org/scalactic/scalactic_2.12/3.0.8/scalactic_2.12-3.0.8.jar",
|
||||
"${M2_REPO}/net/liftweb/lift-json_2.12/3.5.0/lift-json_2.12-3.5.0.jar",
|
||||
"${M2_REPO}/org/scala-lang/scalap/2.12.12/scalap-2.12.12.jar",
|
||||
"${M2_REPO}/com/thoughtworks/paranamer/paranamer/2.8/paranamer-2.8.jar",
|
||||
"${M2_REPO}/com/alibaba/transmittable-thread-local/2.11.5/transmittable-thread-local-2.11.5.jar",
|
||||
"${M2_REPO}/org/apache/commons/commons-lang3/3.12.0/commons-lang3-3.12.0.jar",
|
||||
"${M2_REPO}/org/apache/commons/commons-text/1.10.0/commons-text-1.10.0.jar",
|
||||
"${M2_REPO}/com/google/guava/guava/32.0.0-jre/guava-32.0.0-jre.jar",
|
||||
"${M2_REPO}/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar",
|
||||
"${M2_REPO}/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar",
|
||||
"${M2_REPO}/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar",
|
||||
"${M2_REPO}/org/checkerframework/checker-qual/3.33.0/checker-qual-3.33.0.jar",
|
||||
"${M2_REPO}/com/google/errorprone/error_prone_annotations/2.18.0/error_prone_annotations-2.18.0.jar",
|
||||
"${M2_REPO}/com/google/j2objc/j2objc-annotations/2.8/j2objc-annotations-2.8.jar"
|
||||
],
|
||||
"out": "${PROJECT_ROOT}/obp-commons/target/classes",
|
||||
"classesDir": "${PROJECT_ROOT}/obp-commons/target/classes",
|
||||
"resources": [
|
||||
"${PROJECT_ROOT}/obp-commons/src/main/resources"
|
||||
],
|
||||
"scala": {
|
||||
"organization": "org.scala-lang",
|
||||
"name": "scala-compiler",
|
||||
"version": "2.12.20",
|
||||
"options": [
|
||||
"-unchecked",
|
||||
"-explaintypes",
|
||||
"-encoding",
|
||||
"UTF-8",
|
||||
"-feature"
|
||||
],
|
||||
"jars": [
|
||||
"${M2_REPO}/org/scala-lang/scala-library/2.12.20/scala-library-2.12.20.jar",
|
||||
"${M2_REPO}/org/scala-lang/scala-compiler/2.12.20/scala-compiler-2.12.20.jar",
|
||||
"${M2_REPO}/org/scala-lang/scala-reflect/2.12.20/scala-reflect-2.12.20.jar"
|
||||
],
|
||||
"analysis": "${PROJECT_ROOT}/obp-commons/target/bloop-bsp-clients-classes/classes-Metals-",
|
||||
"setup": {
|
||||
"order": "mixed",
|
||||
"addLibraryToBootClasspath": true,
|
||||
"addCompilerToClasspath": false,
|
||||
"addExtraJarsToClasspath": false,
|
||||
"manageBootClasspath": true,
|
||||
"filterLibraryFromClasspath": true
|
||||
}
|
||||
},
|
||||
"java": {
|
||||
"options": ["-source", "11", "-target", "11"]
|
||||
},
|
||||
"platform": {
|
||||
"name": "jvm",
|
||||
"config": {
|
||||
"home": "${JAVA_HOME}",
|
||||
"options": []
|
||||
},
|
||||
"mainClass": []
|
||||
},
|
||||
"resolution": {
|
||||
"modules": []
|
||||
},
|
||||
"tags": ["library"]
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# Generate obp-api.json
|
||||
echo "🔨 Generating obp-api configuration..."
|
||||
cat > "$PROJECT_ROOT/.bloop/obp-api.json" << EOF
|
||||
{
|
||||
"version": "1.5.5",
|
||||
"project": {
|
||||
"name": "obp-api",
|
||||
"directory": "${PROJECT_ROOT}/obp-api",
|
||||
"workspaceDir": "${PROJECT_ROOT}",
|
||||
"sources": [
|
||||
"${PROJECT_ROOT}/obp-api/src/main/scala",
|
||||
"${PROJECT_ROOT}/obp-api/src/main/java"
|
||||
],
|
||||
"dependencies": ["obp-commons"],
|
||||
"classpath": [
|
||||
"${PROJECT_ROOT}/obp-api/target/classes",
|
||||
"${PROJECT_ROOT}/obp-commons/target/classes",
|
||||
"${M2_REPO}/com/tesobe/obp-commons/1.10.1/obp-commons-1.10.1.jar",
|
||||
"${M2_REPO}/net/liftweb/lift-common_2.12/3.5.0/lift-common_2.12-3.5.0.jar",
|
||||
"${M2_REPO}/org/scala-lang/scala-library/2.12.12/scala-library-2.12.12.jar",
|
||||
"${M2_REPO}/org/slf4j/slf4j-api/1.7.32/slf4j-api-1.7.32.jar",
|
||||
"${M2_REPO}/org/scala-lang/modules/scala-xml_2.12/1.3.0/scala-xml_2.12-1.3.0.jar",
|
||||
"${M2_REPO}/net/liftweb/lift-util_2.12/3.5.0/lift-util_2.12-3.5.0.jar",
|
||||
"${M2_REPO}/org/scala-lang/scala-compiler/2.12.12/scala-compiler-2.12.12.jar",
|
||||
"${M2_REPO}/net/liftweb/lift-mapper_2.12/3.5.0/lift-mapper_2.12-3.5.0.jar",
|
||||
"${M2_REPO}/net/liftweb/lift-json_2.12/3.5.0/lift-json_2.12-3.5.0.jar",
|
||||
"${M2_REPO}/org/scala-lang/scala-reflect/2.12.20/scala-reflect-2.12.20.jar",
|
||||
"${M2_REPO}/net/databinder/dispatch/dispatch-lift-json_2.12/0.13.1/dispatch-lift-json_2.12-0.13.1.jar",
|
||||
"${M2_REPO}/ch/qos/logback/logback-classic/1.2.13/logback-classic-1.2.13.jar",
|
||||
"${M2_REPO}/org/slf4j/log4j-over-slf4j/1.7.26/log4j-over-slf4j-1.7.26.jar",
|
||||
"${M2_REPO}/org/slf4j/slf4j-ext/1.7.26/slf4j-ext-1.7.26.jar",
|
||||
"${M2_REPO}/org/bouncycastle/bcpg-jdk15on/1.70/bcpg-jdk15on-1.70.jar",
|
||||
"${M2_REPO}/org/bouncycastle/bcpkix-jdk15on/1.70/bcpkix-jdk15on-1.70.jar",
|
||||
"${M2_REPO}/org/apache/commons/commons-lang3/3.12.0/commons-lang3-3.12.0.jar",
|
||||
"${M2_REPO}/org/apache/commons/commons-text/1.10.0/commons-text-1.10.0.jar",
|
||||
"${M2_REPO}/com/github/everit-org/json-schema/org.everit.json.schema/1.6.1/org.everit.json.schema-1.6.1.jar"
|
||||
],
|
||||
"out": "${PROJECT_ROOT}/obp-api/target/classes",
|
||||
"classesDir": "${PROJECT_ROOT}/obp-api/target/classes",
|
||||
"resources": [
|
||||
"${PROJECT_ROOT}/obp-api/src/main/resources"
|
||||
],
|
||||
"scala": {
|
||||
"organization": "org.scala-lang",
|
||||
"name": "scala-compiler",
|
||||
"version": "2.12.20",
|
||||
"options": [
|
||||
"-unchecked",
|
||||
"-explaintypes",
|
||||
"-encoding",
|
||||
"UTF-8",
|
||||
"-feature"
|
||||
],
|
||||
"jars": [
|
||||
"${M2_REPO}/org/scala-lang/scala-library/2.12.20/scala-library-2.12.20.jar",
|
||||
"${M2_REPO}/org/scala-lang/scala-compiler/2.12.20/scala-compiler-2.12.20.jar",
|
||||
"${M2_REPO}/org/scala-lang/scala-reflect/2.12.20/scala-reflect-2.12.20.jar"
|
||||
],
|
||||
"analysis": "${PROJECT_ROOT}/obp-api/target/bloop-bsp-clients-classes/classes-Metals-",
|
||||
"setup": {
|
||||
"order": "mixed",
|
||||
"addLibraryToBootClasspath": true,
|
||||
"addCompilerToClasspath": false,
|
||||
"addExtraJarsToClasspath": false,
|
||||
"manageBootClasspath": true,
|
||||
"filterLibraryFromClasspath": true
|
||||
}
|
||||
},
|
||||
"java": {
|
||||
"options": ["-source", "11", "-target", "11"]
|
||||
},
|
||||
"platform": {
|
||||
"name": "jvm",
|
||||
"config": {
|
||||
"home": "${JAVA_HOME}",
|
||||
"options": []
|
||||
},
|
||||
"mainClass": []
|
||||
},
|
||||
"resolution": {
|
||||
"modules": []
|
||||
},
|
||||
"tags": ["application"]
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
echo -e "${GREEN}✅ Generated $PROJECT_ROOT/.bloop/obp-commons.json${NC}"
|
||||
echo -e "${GREEN}✅ Generated $PROJECT_ROOT/.bloop/obp-api.json${NC}"
|
||||
|
||||
# Verify the configurations
|
||||
echo "🔍 Verifying generated configurations..."
|
||||
if command -v bloop &> /dev/null; then
|
||||
if bloop projects | grep -q "obp-api\|obp-commons"; then
|
||||
echo -e "${GREEN}✅ Bloop can detect the projects${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ Bloop server may need to be restarted to detect new configurations${NC}"
|
||||
echo "Run: pkill -f bloop && bloop server &"
|
||||
fi
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ Bloop not found, skipping verification${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}🎉 Bloop configuration generation complete!${NC}"
|
||||
echo ""
|
||||
echo "📋 Next steps:"
|
||||
echo "1. Restart Bloop server if needed: pkill -f bloop && bloop server &"
|
||||
echo "2. Verify projects are detected: bloop projects"
|
||||
echo "3. Test compilation: bloop compile obp-commons obp-api"
|
||||
echo "4. Open project in Zed IDE for full language server support"
|
||||
echo ""
|
||||
echo -e "${GREEN}Happy coding! 🚀${NC}"
|
||||
80
zed/settings.json
Normal file
80
zed/settings.json
Normal file
@ -0,0 +1,80 @@
|
||||
{
|
||||
"format_on_save": "off",
|
||||
"tab_size": 2,
|
||||
"terminal": {
|
||||
"env": {
|
||||
"MAVEN_OPTS": "-Xss128m --add-opens=java.base/java.util.jar=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED"
|
||||
}
|
||||
},
|
||||
"project_panel": {
|
||||
"dock": "left",
|
||||
"default_width": 300
|
||||
},
|
||||
"outline_panel": {
|
||||
"dock": "right"
|
||||
},
|
||||
"theme": "One Dark",
|
||||
"ui_font_size": 14,
|
||||
"buffer_font_size": 14,
|
||||
"soft_wrap": "editor_width",
|
||||
"show_whitespaces": "selection",
|
||||
"tabs": {
|
||||
"git_status": true,
|
||||
"file_icons": true
|
||||
},
|
||||
"gutter": {
|
||||
"line_numbers": true
|
||||
},
|
||||
"scrollbar": {
|
||||
"show": "auto"
|
||||
},
|
||||
"indent_guides": {
|
||||
"enabled": true
|
||||
},
|
||||
"lsp": {
|
||||
"metals": {
|
||||
"initialization_options": {
|
||||
"compileOnSave": true,
|
||||
"debuggingProvider": true,
|
||||
"decorationProvider": true,
|
||||
"didFocusProvider": true,
|
||||
"doctorProvider": "html",
|
||||
"executeClientCommandProvider": true,
|
||||
"inputBoxProvider": true,
|
||||
"quickPickProvider": true,
|
||||
"renameProvider": true,
|
||||
"statusBarProvider": "on",
|
||||
"treeViewProvider": true,
|
||||
"buildTool": "maven"
|
||||
},
|
||||
"settings": {
|
||||
"metals.ammoniteJvmProperties": ["-Xmx1G"],
|
||||
"metals.buildServer.version": "2.0.0",
|
||||
"metals.javaFormat.eclipseConfigPath": "",
|
||||
"metals.javaFormat.eclipseProfile": "",
|
||||
"metals.superMethodLensesEnabled": true,
|
||||
"metals.testUserInterface": "Code Lenses",
|
||||
"metals.bloopSbtAlreadyInstalled": true,
|
||||
"metals.gradleScript": "",
|
||||
"metals.mavenScript": "",
|
||||
"metals.millScript": "",
|
||||
"metals.sbtScript": "",
|
||||
"metals.scalafmtConfigPath": ".scalafmt.conf",
|
||||
"metals.enableSemanticHighlighting": true,
|
||||
"metals.allowMultilineStringFormatting": true,
|
||||
"metals.inlayHints.enabled": true,
|
||||
"metals.inlayHints.hintsInPatternMatch.enabled": true,
|
||||
"metals.inlayHints.implicitArguments.enabled": true,
|
||||
"metals.inlayHints.implicitConversions.enabled": true,
|
||||
"metals.inlayHints.inferredTypes.enabled": true,
|
||||
"metals.inlayHints.typeParameters.enabled": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"languages": {
|
||||
"Scala": {
|
||||
"language_servers": ["metals"],
|
||||
"format_on_save": "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
221
zed/setup-zed-ide.sh
Executable file
221
zed/setup-zed-ide.sh
Executable file
@ -0,0 +1,221 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ZED IDE Complete Setup Script for OBP-API
|
||||
# This script provides a unified setup for ZED IDE with full Scala language server support
|
||||
|
||||
set -e
|
||||
|
||||
echo "🚀 Setting up ZED IDE for OBP-API Scala development..."
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Get the project root directory (parent of zed folder)
|
||||
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
echo "📁 Project root: $PROJECT_ROOT"
|
||||
|
||||
# Check if we're in the zed directory and project structure exists
|
||||
if [[ ! -f "$PROJECT_ROOT/pom.xml" ]] || [[ ! -d "$PROJECT_ROOT/obp-api" ]] || [[ ! -d "$PROJECT_ROOT/obp-commons" ]]; then
|
||||
echo -e "${RED}❌ Error: Could not find OBP-API project structure${NC}"
|
||||
echo "Make sure you're running this from the zed/ folder of the OBP-API project"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Change to project root for Maven operations
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
echo "📁 Working directory: $(pwd)"
|
||||
|
||||
# Check prerequisites
|
||||
echo "🔍 Checking prerequisites..."
|
||||
|
||||
# Check Java
|
||||
if ! command -v java &> /dev/null; then
|
||||
echo -e "${RED}❌ Java not found. Please install Java 11 or 17${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
JAVA_VERSION=$(java -version 2>&1 | head -1 | cut -d'"' -f2 | cut -d'.' -f1-2)
|
||||
echo -e "${GREEN}✅ Java found: ${JAVA_VERSION}${NC}"
|
||||
|
||||
# Check Maven
|
||||
if ! command -v mvn &> /dev/null; then
|
||||
echo -e "${RED}❌ Maven not found. Please install Maven${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
MVN_VERSION=$(mvn -version 2>&1 | head -1 | cut -d' ' -f3)
|
||||
echo -e "${GREEN}✅ Maven found: ${MVN_VERSION}${NC}"
|
||||
|
||||
# Check Coursier
|
||||
if ! command -v cs &> /dev/null; then
|
||||
echo -e "${YELLOW}⚠️ Coursier not found. Installing...${NC}"
|
||||
curl -fL https://github.com/coursier/coursier/releases/latest/download/cs-x86_64-pc-linux.gz | gzip -d > cs
|
||||
chmod +x cs
|
||||
sudo mv cs /usr/local/bin/
|
||||
echo -e "${GREEN}✅ Coursier installed${NC}"
|
||||
else
|
||||
echo -e "${GREEN}✅ Coursier found${NC}"
|
||||
fi
|
||||
|
||||
# Check/Install Bloop
|
||||
if ! command -v bloop &> /dev/null; then
|
||||
echo -e "${YELLOW}⚠️ Bloop not found. Installing...${NC}"
|
||||
cs install bloop
|
||||
echo -e "${GREEN}✅ Bloop installed${NC}"
|
||||
else
|
||||
echo -e "${GREEN}✅ Bloop found: $(bloop about | head -1)${NC}"
|
||||
fi
|
||||
|
||||
# Start Bloop server if not running
|
||||
if ! pgrep -f "bloop.*server" > /dev/null; then
|
||||
echo "🔧 Starting Bloop server..."
|
||||
bloop server &
|
||||
sleep 3
|
||||
echo -e "${GREEN}✅ Bloop server started${NC}"
|
||||
else
|
||||
echo -e "${GREEN}✅ Bloop server already running${NC}"
|
||||
fi
|
||||
|
||||
# Compile the project to ensure dependencies are resolved
|
||||
echo "🔨 Compiling Maven project (this may take a few minutes)..."
|
||||
if mvn compile -q; then
|
||||
echo -e "${GREEN}✅ Maven compilation successful${NC}"
|
||||
else
|
||||
echo -e "${RED}❌ Maven compilation failed. Please fix compilation errors first.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Copy ZED configuration files to project root
|
||||
echo "📋 Setting up ZED IDE configuration..."
|
||||
ZED_DIR="$PROJECT_ROOT/.zed"
|
||||
ZED_SRC_DIR="$PROJECT_ROOT/zed"
|
||||
|
||||
# Create .zed directory if it doesn't exist
|
||||
if [ ! -d "$ZED_DIR" ]; then
|
||||
echo "📁 Creating .zed directory..."
|
||||
mkdir -p "$ZED_DIR"
|
||||
else
|
||||
echo "📁 .zed directory already exists"
|
||||
fi
|
||||
|
||||
# Copy settings.json
|
||||
if [ -f "$ZED_SRC_DIR/settings.json" ]; then
|
||||
echo "⚙️ Copying settings.json..."
|
||||
cp "$ZED_SRC_DIR/settings.json" "$ZED_DIR/settings.json"
|
||||
echo -e "${GREEN}✅ settings.json copied successfully${NC}"
|
||||
else
|
||||
echo -e "${RED}❌ Error: settings.json not found in zed folder${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Copy tasks.json
|
||||
if [ -f "$ZED_SRC_DIR/tasks.json" ]; then
|
||||
echo "📋 Copying tasks.json..."
|
||||
cp "$ZED_SRC_DIR/tasks.json" "$ZED_DIR/tasks.json"
|
||||
echo -e "${GREEN}✅ tasks.json copied successfully${NC}"
|
||||
else
|
||||
echo -e "${RED}❌ Error: tasks.json not found in zed folder${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Copy .metals-config.json if it exists
|
||||
if [[ -f "$ZED_SRC_DIR/.metals-config.json" ]]; then
|
||||
echo "🔧 Copying Metals configuration..."
|
||||
cp "$ZED_SRC_DIR/.metals-config.json" "$PROJECT_ROOT/.metals-config.json"
|
||||
echo -e "${GREEN}✅ Metals configuration copied${NC}"
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✅ ZED configuration files copied to .zed/ folder${NC}"
|
||||
|
||||
# Generate Bloop configuration files dynamically
|
||||
echo "🔧 Generating Bloop configuration files..."
|
||||
if [[ -f "$ZED_SRC_DIR/generate-bloop-config.sh" ]]; then
|
||||
chmod +x "$ZED_SRC_DIR/generate-bloop-config.sh"
|
||||
"$ZED_SRC_DIR/generate-bloop-config.sh"
|
||||
echo -e "${GREEN}✅ Bloop configuration files generated${NC}"
|
||||
else
|
||||
# Fallback: Check if existing configurations are present
|
||||
if [[ -f "$PROJECT_ROOT/.bloop/obp-commons.json" && -f "$PROJECT_ROOT/.bloop/obp-api.json" ]]; then
|
||||
echo -e "${GREEN}✅ Bloop configuration files already exist${NC}"
|
||||
else
|
||||
echo -e "${RED}❌ Bloop configuration files missing and generator not found.${NC}"
|
||||
echo "Please ensure .bloop/*.json files exist or run zed/generate-bloop-config.sh manually"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Restart Bloop server to pick up new configurations
|
||||
echo "🔄 Restarting Bloop server to detect new configurations..."
|
||||
pkill -f bloop 2>/dev/null || true
|
||||
sleep 1
|
||||
bloop server &
|
||||
sleep 2
|
||||
|
||||
# Verify Bloop can see projects
|
||||
echo "🔍 Verifying Bloop projects..."
|
||||
BLOOP_PROJECTS=$(bloop projects 2>/dev/null || echo "")
|
||||
if [[ "$BLOOP_PROJECTS" == *"obp-api"* && "$BLOOP_PROJECTS" == *"obp-commons"* ]]; then
|
||||
echo -e "${GREEN}✅ Bloop projects detected:${NC}"
|
||||
echo "$BLOOP_PROJECTS" | sed 's/^/ /'
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ Bloop projects not immediately detected. This is normal for fresh setups.${NC}"
|
||||
echo "The configuration should work when you open ZED IDE."
|
||||
fi
|
||||
|
||||
# Test Bloop compilation
|
||||
echo "🧪 Testing Bloop compilation..."
|
||||
if bloop compile obp-commons > /dev/null 2>&1; then
|
||||
echo -e "${GREEN}✅ Bloop compilation test successful${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ Bloop compilation test failed, but setup is complete. Try restarting ZED IDE.${NC}"
|
||||
fi
|
||||
|
||||
# Check ZED configuration
|
||||
if [[ -f "$PROJECT_ROOT/.zed/settings.json" ]]; then
|
||||
echo -e "${GREEN}✅ ZED configuration found${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ ZED configuration not found in .zed/settings.json${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}🎉 ZED IDE setup completed successfully!${NC}"
|
||||
echo ""
|
||||
echo "Your ZED configuration includes:"
|
||||
echo " • Format on save: DISABLED (manual formatting only - use Ctrl+Shift+I)"
|
||||
echo " • Scala/Metals LSP configuration optimized for OBP-API"
|
||||
echo " • Pre-configured build and run tasks"
|
||||
echo " • Dynamic Bloop configuration for language server support"
|
||||
echo ""
|
||||
echo "📋 Next steps:"
|
||||
echo "1. Open ZED IDE"
|
||||
echo "2. Open the OBP-API project directory in ZED"
|
||||
echo "3. Wait for Metals to initialize (may take a few minutes)"
|
||||
echo "4. Try 'Go to Definition' on a Scala symbol (F12 or Cmd+Click)"
|
||||
echo ""
|
||||
echo "🛠️ Available tasks (access with Cmd/Ctrl + Shift + P → 'task: spawn'):"
|
||||
echo " • [1] Run OBP-API Server - Start development server"
|
||||
echo " • [2] Test API Root Endpoint - Quick health check"
|
||||
echo " • [3] Compile Only - Fast syntax check"
|
||||
echo " • [4] Clean Target Folders - Remove build artifacts"
|
||||
echo " • Quick Build Dependencies - Build deps only (for onboarding)"
|
||||
echo " • Run Tests - Execute full test suite"
|
||||
echo ""
|
||||
echo "💡 Troubleshooting:"
|
||||
echo "• If 'Go to Definition' doesn't work immediately, restart ZED IDE"
|
||||
echo "• Use 'ZED: Reload Window' from the command palette if needed"
|
||||
echo "• Check zed/README.md for comprehensive documentation"
|
||||
echo "• Run './zed/generate-bloop-config.sh' to regenerate configurations if needed"
|
||||
echo ""
|
||||
echo "🔗 Resources:"
|
||||
echo "• Complete ZED setup guide: zed/README.md"
|
||||
echo "• Bloop projects: bloop projects"
|
||||
echo "• Bloop compilation: bloop compile obp-commons obp-api"
|
||||
echo ""
|
||||
echo "Note: The .zed folder is in .gitignore, so you can customize settings"
|
||||
echo " without affecting other developers."
|
||||
echo ""
|
||||
echo -e "${GREEN}Happy coding! 🚀${NC}"
|
||||
64
zed/setup-zed.bat
Normal file
64
zed/setup-zed.bat
Normal file
@ -0,0 +1,64 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
REM Zed IDE Setup Script for OBP-API (Windows)
|
||||
REM This script copies the recommended Zed configuration to your local .zed folder
|
||||
|
||||
echo 🔧 Setting up Zed IDE configuration for OBP-API...
|
||||
|
||||
set "SCRIPT_DIR=%~dp0"
|
||||
set "PROJECT_ROOT=%SCRIPT_DIR%.."
|
||||
set "ZED_DIR=%PROJECT_ROOT%\.zed"
|
||||
|
||||
REM Create .zed directory if it doesn't exist
|
||||
if not exist "%ZED_DIR%" (
|
||||
echo 📁 Creating .zed directory...
|
||||
mkdir "%ZED_DIR%"
|
||||
) else (
|
||||
echo 📁 .zed directory already exists
|
||||
)
|
||||
|
||||
REM Copy settings.json
|
||||
if exist "%SCRIPT_DIR%settings.json" (
|
||||
echo ⚙️ Copying settings.json...
|
||||
copy "%SCRIPT_DIR%settings.json" "%ZED_DIR%\settings.json" >nul
|
||||
if !errorlevel! equ 0 (
|
||||
echo ✅ settings.json copied successfully
|
||||
) else (
|
||||
echo ❌ Error copying settings.json
|
||||
exit /b 1
|
||||
)
|
||||
) else (
|
||||
echo ❌ Error: settings.json not found in zed folder
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM Copy tasks.json
|
||||
if exist "%SCRIPT_DIR%tasks.json" (
|
||||
echo 📋 Copying tasks.json...
|
||||
copy "%SCRIPT_DIR%tasks.json" "%ZED_DIR%\tasks.json" >nul
|
||||
if !errorlevel! equ 0 (
|
||||
echo ✅ tasks.json copied successfully
|
||||
) else (
|
||||
echo ❌ Error copying tasks.json
|
||||
exit /b 1
|
||||
)
|
||||
) else (
|
||||
echo ❌ Error: tasks.json not found in zed folder
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo.
|
||||
echo 🎉 Zed IDE setup completed successfully!
|
||||
echo.
|
||||
echo Your Zed configuration includes:
|
||||
echo • Format on save: DISABLED (preserves your code formatting)
|
||||
echo • Scala/Metals LSP configuration optimized for OBP-API
|
||||
echo • 9 predefined tasks for building, running, and testing
|
||||
echo.
|
||||
echo To see available tasks in Zed, use: Ctrl + Shift + P → 'task: spawn'
|
||||
echo.
|
||||
echo Note: The .zed folder is in .gitignore, so you can customize settings
|
||||
echo without affecting other developers.
|
||||
|
||||
pause
|
||||
111
zed/tasks.json
Normal file
111
zed/tasks.json
Normal file
@ -0,0 +1,111 @@
|
||||
[
|
||||
{
|
||||
"label": "[1] Run OBP-API Server",
|
||||
"command": "mvn",
|
||||
"args": ["jetty:run", "-pl", "obp-api"],
|
||||
"env": {
|
||||
"MAVEN_OPTS": "-Xss128m --add-opens=java.base/java.util.jar=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED"
|
||||
},
|
||||
"use_new_terminal": true,
|
||||
"allow_concurrent_runs": false,
|
||||
"reveal": "always",
|
||||
"tags": ["run", "server"]
|
||||
},
|
||||
{
|
||||
"label": "[2] Test API Root Endpoint",
|
||||
"command": "curl",
|
||||
"args": [
|
||||
"-X",
|
||||
"GET",
|
||||
"http://localhost:8080/obp/v5.1.0/root",
|
||||
"-H",
|
||||
"accept: application/json"
|
||||
],
|
||||
"use_new_terminal": false,
|
||||
"allow_concurrent_runs": true,
|
||||
"reveal": "always",
|
||||
"tags": ["test", "api"]
|
||||
},
|
||||
{
|
||||
"label": "[3] Compile Only",
|
||||
"command": "mvn",
|
||||
"args": ["compile", "-pl", "obp-api"],
|
||||
"env": {
|
||||
"MAVEN_OPTS": "-Xss128m --add-opens=java.base/java.util.jar=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED"
|
||||
},
|
||||
"use_new_terminal": false,
|
||||
"allow_concurrent_runs": false,
|
||||
"reveal": "always",
|
||||
"tags": ["compile", "build"]
|
||||
},
|
||||
{
|
||||
"label": "[4] Build OBP-API",
|
||||
"command": "mvn",
|
||||
"args": [
|
||||
"install",
|
||||
"-pl",
|
||||
".,obp-commons",
|
||||
"-am",
|
||||
"-DskipTests",
|
||||
"-Ddependency-check.skip=true"
|
||||
],
|
||||
"env": {
|
||||
"MAVEN_OPTS": "-Xss128m --add-opens=java.base/java.util.jar=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED"
|
||||
},
|
||||
"use_new_terminal": false,
|
||||
"allow_concurrent_runs": false,
|
||||
"reveal": "always",
|
||||
"tags": ["build"]
|
||||
},
|
||||
{
|
||||
"label": "[5] Clean Target Folders",
|
||||
"command": "mvn",
|
||||
"args": ["clean"],
|
||||
"use_new_terminal": false,
|
||||
"allow_concurrent_runs": false,
|
||||
"reveal": "always",
|
||||
"tags": ["clean", "build"]
|
||||
},
|
||||
{
|
||||
"label": "[6] Kill OBP-APIServer on Port 8080",
|
||||
"command": "bash",
|
||||
"args": [
|
||||
"-c",
|
||||
"lsof -ti:8080 | xargs kill -9 || echo 'No process found on port 8080'"
|
||||
],
|
||||
"use_new_terminal": false,
|
||||
"allow_concurrent_runs": true,
|
||||
"reveal": "always",
|
||||
"tags": ["utility"]
|
||||
},
|
||||
{
|
||||
"label": "[7] Run Tests",
|
||||
"command": "mvn",
|
||||
"args": ["test", "-pl", "obp-api"],
|
||||
"env": {
|
||||
"MAVEN_OPTS": "-Xss128m --add-opens=java.base/java.util.jar=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED"
|
||||
},
|
||||
"use_new_terminal": false,
|
||||
"allow_concurrent_runs": false,
|
||||
"reveal": "always",
|
||||
"tags": ["test"]
|
||||
},
|
||||
{
|
||||
"label": "[8] Maven Validate",
|
||||
"command": "mvn",
|
||||
"args": ["validate"],
|
||||
"use_new_terminal": false,
|
||||
"allow_concurrent_runs": false,
|
||||
"reveal": "always",
|
||||
"tags": ["validate"]
|
||||
},
|
||||
{
|
||||
"label": "[9] Check Dependencies",
|
||||
"command": "mvn",
|
||||
"args": ["dependency:resolve"],
|
||||
"use_new_terminal": false,
|
||||
"allow_concurrent_runs": false,
|
||||
"reveal": "always",
|
||||
"tags": ["dependencies"]
|
||||
}
|
||||
]
|
||||
Loading…
Reference in New Issue
Block a user