mirror of
https://github.com/OpenBankProject/OBP-API.git
synced 2026-02-06 15:27:01 +00:00
Complete update to develop branch, commit:b4df0cefc2e8122e8bb55e72a2016cfb7a9ea12f
This commit is contained in:
parent
c3936b3151
commit
40df926919
@ -91,9 +91,9 @@ Address: ________________________
|
||||
Us
|
||||
________________________
|
||||
Name: Simon Redfern
|
||||
Title: CEO TESOBE / Music Pictures Ltd
|
||||
Title: CEO TESOBE Ltd
|
||||
Address: Osloerstrasse 16/17
|
||||
Berlin 13359, German
|
||||
Berlin 13359, Germany
|
||||
|
||||
|
||||
|
||||
@ -125,4 +125,4 @@ Address: Osloerstrasse 16/17
|
||||
|
||||
|
||||
|
||||
This work is licensed under a Creative Commons Attribution 3.0 Unported License.
|
||||
This work is licensed under a Creative Commons Attribution 3.0 Unported License.
|
||||
|
||||
29
README.md
29
README.md
@ -26,8 +26,7 @@ Please refer to the [wiki](https://github.com/OpenBankProject/OBP-API/wiki) to s
|
||||
|
||||
## LICENSE
|
||||
|
||||
This project is dual licensed under the AGPL V3 (see NOTICE) and a commercial license from TESOBE
|
||||
Some files (OAuth related) are licensed under the Apache 2 license.
|
||||
This project is dual licensed under the AGPL V3 (see NOTICE) and commercial licenses from TESOBE Ltd.
|
||||
|
||||
## SETUP
|
||||
|
||||
@ -85,7 +84,33 @@ To compile and run jetty, install Maven 3 and execute:
|
||||
|
||||
----
|
||||
|
||||
## Ubuntu
|
||||
|
||||
If you use Ubuntu (or a derivate) and encrypted home directories (e.g. you have ~/.Private), you might run into the following error when the project is built:
|
||||
|
||||
uncaught exception during compilation: java.io.IOException
|
||||
[ERROR] File name too long
|
||||
[ERROR] two errors found
|
||||
[DEBUG] Compilation failed (CompilerInterface)
|
||||
|
||||
The current workaround is to move the project directory onto a different partition, e.g. under /opt/ .
|
||||
|
||||
|
||||
# Databases:
|
||||
|
||||
The default database for testing etc is H2. PostgreSQL is used for the sandboxes (user accounts, metadata, transaction cache).
|
||||
|
||||
|
||||
# Kafka (optional):
|
||||
|
||||
If Kafka connector is selected in props (connector=kafka), Kafka and Zookeeper have to be installed, as well as OBP-Kafka-Python (which can be either running from command-propmpt or from inside Docker container):
|
||||
|
||||
* Kafka and Zookeeper can be installed using system's default installer or by unpacking the archives (http://apache.mirrors.spacedump.net/kafka/ and http://apache.mirrors.spacedump.net/zookeeper/)
|
||||
|
||||
* OBP-Kafka-Python can be downloaded from https://github.com/OpenBankProject/OBP-Kafka-Python
|
||||
|
||||
|
||||
# Lift
|
||||
# We use liftweb http://www.liftweb.net/
|
||||
# Advanced architecture: http://exploring.liftweb.net/master/index-9.html
|
||||
|
||||
|
||||
7
pom.xml
7
pom.xml
@ -161,6 +161,11 @@
|
||||
<artifactId>pegdown</artifactId>
|
||||
<version>1.6.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.jason-goodwin</groupId>
|
||||
<artifactId>authentikat-jwt_2.10</artifactId>
|
||||
<version>0.4.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
@ -224,7 +229,7 @@
|
||||
<jvmArgs>
|
||||
<jvmArg>-DpackageLinkDefs=file://${project.build.directory}/packageLinkDefs.properties</jvmArg>
|
||||
</jvmArgs>
|
||||
<scalaVersion>${scala.version}</scalaVersion>
|
||||
<scalaVersion>${scala.compiler}</scalaVersion>
|
||||
<scalaCompatVersion>${scala.version}</scalaCompatVersion>
|
||||
<recompileMode>incremental</recompileMode>
|
||||
<useZincServer>true</useZincServer>
|
||||
|
||||
@ -38,14 +38,8 @@ object LiftProjectBuild extends Build {
|
||||
}).toMap
|
||||
|
||||
private lazy val PropertiesExpr = """.*\$\{(.*?)\}.*""".r
|
||||
private lazy val TwoPropertiesExpr = """.*\$\{(.*?)\}.*\$\{(.*?)\}.*""".r
|
||||
|
||||
def populateProps(t: String) = t match {
|
||||
case TwoPropertiesExpr(p, q) => {
|
||||
t
|
||||
.replace("${"+p+"}", pomProperties.get(p).getOrElse(throw new Exception("Cannot find property: '" + p + "' required by: '" + t + "' in: " + pomFile)))
|
||||
.replace("${"+q+"}", pomProperties.get(q).getOrElse(throw new Exception("Cannot find property: '" + q + "' required by: '" + t + "' in: " + pomFile)))
|
||||
}
|
||||
case PropertiesExpr(p) => {
|
||||
val replaceWith = pomProperties.get(p)
|
||||
t.replace("${"+p+"}", replaceWith.getOrElse(throw new Exception("Cannot find property: '" + p + "' required by: '" + t + "' in: " + pomFile)))
|
||||
|
||||
@ -30,7 +30,7 @@ db.url=jdbc:postgresql://localhost:5432/dbname?user=dbusername&password=thepassw
|
||||
#our own remotely accessible URL
|
||||
#this is needed for oauth to work. it's important to access the api over this url, e.g.
|
||||
# if this is 127.0.0.1 don't use localhost to access it.
|
||||
# (this needs to be an URL)
|
||||
# (this needs to be a URL)
|
||||
hostname=http://127.0.0.1:8080
|
||||
|
||||
#this is only useful for running the api locally via RunWebApp
|
||||
@ -38,6 +38,14 @@ hostname=http://127.0.0.1:8080
|
||||
#if you want to change the port when running via the command line, use "mvn -Djetty.port=8089 jetty:run" instead
|
||||
dev.port=8080
|
||||
|
||||
|
||||
#The start of the api path (before the version)
|
||||
#It is *strongly* recommended not to change this - since Apps will be expecting the api at /obp/+version
|
||||
#Including it here so we have a canonical source of the value
|
||||
#This was introduced March 2016, some code might use hardcoded value instead.
|
||||
#Default value is obp (highly recomended)
|
||||
apiPathZero=obp
|
||||
|
||||
#sending mail out
|
||||
#not need in dev mode, but important for production
|
||||
mail.api.consumer.registered.sender.address=no-reply@example.com
|
||||
@ -88,7 +96,7 @@ connection.password=thepassword
|
||||
importer_secret=change_me
|
||||
|
||||
#set this to true if you want to have the api send a message to the hbci project to refresh transactions for an account
|
||||
messageQueue.updateBankAccountsTransaction=true
|
||||
messageQueue.updateBankAccountsTransaction=false
|
||||
|
||||
#the minimum time between updates in hours
|
||||
messageQueue.updateTransactionsInterval=1
|
||||
@ -114,13 +122,25 @@ webui_index_page_about_section_text = <p class="about-text"> \
|
||||
</p>
|
||||
|
||||
|
||||
# API Explorer url. Change this to the instance of Sofi linked to this API. e.g. sofi.openbankproject.com/api-explorer
|
||||
webui_api_explorer_url = http://sofi.openbankproject.com/api-explorer
|
||||
# API Explorer url. Change to your instance
|
||||
webui_api_explorer_url = http://apiexplorer.openbankproject.com
|
||||
|
||||
# Sofi url. (AKA Social Finance) Change to your instance
|
||||
webui_sofi_url = http://sofi.openbankproject.com
|
||||
|
||||
# Starting page of documentation. Change this if you have a specific landing page.
|
||||
webui_api_documentation_url = https://github.com/OpenBankProject/OBP-API/wiki
|
||||
|
||||
|
||||
# To display a custom message above the username / password box
|
||||
# We currently use this to display example customer login in sandbox etc.
|
||||
webui_login_page_special_instructions=
|
||||
|
||||
# Link for SDKs
|
||||
webui_sdks_url = https://github.com/OpenBankProject/OBP-API/wiki/OAuth-Client-SDKS
|
||||
|
||||
|
||||
|
||||
## For partner logos and links
|
||||
webui_main_partners=[\
|
||||
{"logoUrl":"http://www.example.com/images/logo.png", "homePageUrl":"http://www.example.com", "altText":"Example 1"},\
|
||||
@ -131,3 +151,13 @@ webui_main_style_sheet = /media/css/website.css
|
||||
|
||||
# Override certain elements (with important styles)
|
||||
webui_override_style_sheet =
|
||||
|
||||
|
||||
## API Options
|
||||
apiOptions.getBranchesIsPublic = true
|
||||
apiOptions.getAtmsIsPublic = true
|
||||
apiOptions.getProductsIsPublic = true
|
||||
|
||||
# Default Bank. Incase the server wants to support a default bank so developers don't have to specify BANK_ID
|
||||
# e.g. developers could use /my/accounts as well as /my/banks/BANK_ID/accounts
|
||||
defaultBank.bank_id=THE_DEFAULT_BANK_ID
|
||||
|
||||
@ -31,41 +31,45 @@ Berlin 13359, Germany
|
||||
*/
|
||||
package bootstrap.liftweb
|
||||
|
||||
import java.io.{File, FileInputStream}
|
||||
import java.util.Locale
|
||||
import javax.mail.internet.MimeMessage
|
||||
|
||||
import code.api.ResourceDocs1_4_0.ResourceDocs
|
||||
import code.api._
|
||||
import code.api.sandbox.SandboxApiCalls
|
||||
import code.atms.MappedAtm
|
||||
import code.branches.MappedBranch
|
||||
import code.crm.MappedCrmEvent
|
||||
import code.management.ImporterAPI
|
||||
import code.management.AccountsAPI
|
||||
import code.customer.{MappedCustomer, MappedCustomerMessage}
|
||||
import code.kycdocuments.MappedKycDocument
|
||||
import code.kycmedias.MappedKycMedia
|
||||
import code.kycchecks.MappedKycCheck
|
||||
import code.kycstatuses.MappedKycStatus
|
||||
import code.socialmedia.MappedSocialMedia
|
||||
import code.management.{AccountsAPI, ImporterAPI}
|
||||
import code.metadata.comments.MappedComment
|
||||
import code.metadata.counterparties.{MappedCounterpartyWhereTag, MappedCounterpartyMetadata}
|
||||
import code.metadata.counterparties.{MappedCounterpartyMetadata, MappedCounterpartyWhereTag}
|
||||
import code.metadata.narrative.MappedNarrative
|
||||
import code.metadata.tags.MappedTag
|
||||
import code.metadata.transactionimages.MappedTransactionImage
|
||||
import code.metadata.wheretags.MappedWhereTag
|
||||
import code.metrics.MappedMetric
|
||||
import code.branches.{MappedBranch}
|
||||
import code.atms.{MappedAtm}
|
||||
import code.customer.{MappedCustomerMessage, MappedCustomer}
|
||||
import code.products.MappedProduct
|
||||
import code.tesobe.CashAccountAPI
|
||||
import code.transactionrequests.MappedTransactionRequest
|
||||
import net.liftweb._
|
||||
import util._
|
||||
import common._
|
||||
import http._
|
||||
import sitemap._
|
||||
import Loc._
|
||||
import mapper._
|
||||
import net.liftweb.util.Helpers._
|
||||
import net.liftweb.util.Schedule
|
||||
import net.liftweb.util.Helpers
|
||||
import java.util.Locale
|
||||
import java.io.FileInputStream
|
||||
import java.io.File
|
||||
import javax.mail.internet.MimeMessage
|
||||
import code.model._
|
||||
import code.model.dataAccess._
|
||||
import code.api._
|
||||
import code.products.MappedProduct
|
||||
import code.snippet.{OAuthAuthorisation, OAuthWorkedThanks}
|
||||
import code.tesobe.CashAccountAPI
|
||||
import code.transactionrequests.MappedTransactionRequest
|
||||
import net.liftweb.common._
|
||||
import net.liftweb.http._
|
||||
import net.liftweb.mapper._
|
||||
import net.liftweb.sitemap.Loc._
|
||||
import net.liftweb.sitemap._
|
||||
import net.liftweb.util.Helpers._
|
||||
import net.liftweb.util.{Helpers, Schedule, _}
|
||||
import code.api.Constant._
|
||||
|
||||
|
||||
/**
|
||||
* A class that's instantiated early and run. It allows the application
|
||||
@ -175,6 +179,7 @@ class Boot extends Loggable{
|
||||
}
|
||||
|
||||
logger.info("running mode: " + runningMode)
|
||||
logger.info(s"ApiPathZero (the bit before version) is $ApiPathZero")
|
||||
|
||||
// where to search snippets
|
||||
LiftRules.addToPackages("code")
|
||||
@ -182,13 +187,20 @@ class Boot extends Loggable{
|
||||
//OAuth API call
|
||||
LiftRules.statelessDispatch.append(OAuthHandshake)
|
||||
|
||||
// JWT auth endpoints
|
||||
if(Props.getBool("allow_direct_login", true)) {
|
||||
LiftRules.statelessDispatch.append(DirectLogin)
|
||||
}
|
||||
|
||||
// Add the various API versions
|
||||
LiftRules.statelessDispatch.append(v1_0.OBPAPI1_0)
|
||||
LiftRules.statelessDispatch.append(v1_1.OBPAPI1_1)
|
||||
LiftRules.statelessDispatch.append(v1_2.OBPAPI1_2)
|
||||
// Can we depreciate the above?
|
||||
LiftRules.statelessDispatch.append(v1_2_1.OBPAPI1_2_1)
|
||||
LiftRules.statelessDispatch.append(v1_3_0.OBPAPI1_3_0)
|
||||
LiftRules.statelessDispatch.append(v1_4_0.OBPAPI1_4_0)
|
||||
LiftRules.statelessDispatch.append(v2_0_0.OBPAPI2_0_0)
|
||||
|
||||
//add management apis
|
||||
LiftRules.statelessDispatch.append(ImporterAPI)
|
||||
@ -198,6 +210,11 @@ class Boot extends Loggable{
|
||||
LiftRules.statelessDispatch.append(CashAccountAPI)
|
||||
LiftRules.statelessDispatch.append(BankMockAPI)
|
||||
|
||||
|
||||
|
||||
// Add Resource Docs
|
||||
LiftRules.statelessDispatch.append(ResourceDocs)
|
||||
|
||||
// LiftRules.statelessDispatch.append(Metrics) TODO: see metric menu entry below
|
||||
|
||||
//add sandbox api calls only if we're running in sandbox mode
|
||||
@ -282,13 +299,13 @@ class Boot extends Loggable{
|
||||
// Make a transaction span the whole HTTP request
|
||||
S.addAround(DB.buildLoanWrapper)
|
||||
|
||||
val useMessageQueue = Props.getBool("messageQueue.createBankAccounts", false)
|
||||
if(useMessageQueue)
|
||||
try {
|
||||
try {
|
||||
val useMessageQueue = Props.getBool("messageQueue.createBankAccounts", false)
|
||||
if(useMessageQueue)
|
||||
BankAccountCreationListener.startListen
|
||||
} catch {
|
||||
case e: java.lang.ExceptionInInitializerError => logger.warn(s"BankAccountCreationListener Exception: $e")
|
||||
}
|
||||
} catch {
|
||||
case e: java.lang.ExceptionInInitializerError => logger.warn(s"BankAccountCreationListener Exception: $e")
|
||||
}
|
||||
|
||||
Mailer.devModeSend.default.set( (m : MimeMessage) => {
|
||||
logger.info("Would have sent email if not in dev mode: " + m.getContent)
|
||||
@ -327,8 +344,8 @@ class Boot extends Loggable{
|
||||
}
|
||||
|
||||
private def sendExceptionEmail(exception: Throwable): Unit = {
|
||||
import Mailer.{From, PlainMailBodyType, Subject, To}
|
||||
import net.liftweb.util.Helpers.now
|
||||
import Mailer.{From, To, Subject, PlainMailBodyType}
|
||||
|
||||
val outputStream = new java.io.ByteArrayOutputStream
|
||||
val printStream = new java.io.PrintStream(outputStream)
|
||||
@ -390,5 +407,10 @@ object ToSchemify {
|
||||
MappedBranch,
|
||||
MappedAtm,
|
||||
MappedProduct,
|
||||
MappedCrmEvent)
|
||||
MappedCrmEvent,
|
||||
MappedKycDocument,
|
||||
MappedKycMedia,
|
||||
MappedKycCheck,
|
||||
MappedKycStatus,
|
||||
MappedSocialMedia)
|
||||
}
|
||||
|
||||
@ -28,7 +28,7 @@ Berlin 13359, Germany
|
||||
Everett Sochowski : everett AT tesobe DOT com
|
||||
Ayoub Benali: ayoub AT tesobe DOT com
|
||||
|
||||
*/
|
||||
*/
|
||||
|
||||
package code.api
|
||||
|
||||
@ -43,6 +43,8 @@ import code.model.User
|
||||
import code.api.OAuthHandshake._
|
||||
import net.liftweb.json.JsonAST.JValue
|
||||
import net.liftweb.json.Extraction
|
||||
import net.liftweb.util.Props
|
||||
import code.api.Constant._
|
||||
|
||||
trait APIFailure{
|
||||
val msg : String
|
||||
@ -61,7 +63,7 @@ object APIFailure {
|
||||
//that all stable versions retain the same behavior
|
||||
case class UserNotFound(providerId : String, userId: String) extends APIFailure {
|
||||
val responseCode = 400 //TODO: better as 404? -> would break some backwards compatibility (or at least the tests!)
|
||||
|
||||
|
||||
//to reiterate the comment about preserving backwards compatibility:
|
||||
//consider the case that an app may be parsing this string to decide what message to show their users
|
||||
//e.g. when granting view permissions, an app may not give their users a choice of provider and only
|
||||
@ -79,7 +81,8 @@ trait OBPRestHelper extends RestHelper with Loggable {
|
||||
|
||||
val VERSION : String
|
||||
def vPlusVersion = "v" + VERSION
|
||||
def apiPrefix = ("obp" / vPlusVersion).oPrefix(_)
|
||||
|
||||
def apiPrefix = (ApiPathZero / vPlusVersion).oPrefix(_)
|
||||
|
||||
/*
|
||||
An implicit function to convert magically between a Boxed JsonResponse and a JsonResponse
|
||||
@ -135,16 +138,25 @@ trait OBPRestHelper extends RestHelper with Loggable {
|
||||
case Failure(msg, _, _) => errorJsonResponse(msg)
|
||||
case _ => errorJsonResponse("oauth error")
|
||||
}
|
||||
} else fn(Empty)
|
||||
} else if (Props.getBool("allow_direct_login", true) && isThereDirectLoginHeader) {
|
||||
val user = DirectLogin.getUser
|
||||
if (user.isDefined) {
|
||||
fn(user)
|
||||
} else {
|
||||
fn(Empty)
|
||||
}
|
||||
} else {
|
||||
fn(Empty)
|
||||
}
|
||||
}
|
||||
|
||||
class RichStringList(list: List[String]) {
|
||||
val listLen = list.length
|
||||
|
||||
/**
|
||||
* Normally we would use ListServeMagic's prefix function, but it works with PartialFunction[Req, () => Box[LiftResponse]]
|
||||
* instead of the PartialFunction[Req, Box[User] => Box[JsonResponse]] that we need. This function does the same thing, really.
|
||||
*/
|
||||
* Normally we would use ListServeMagic's prefix function, but it works with PartialFunction[Req, () => Box[LiftResponse]]
|
||||
* instead of the PartialFunction[Req, Box[User] => Box[JsonResponse]] that we need. This function does the same thing, really.
|
||||
*/
|
||||
def oPrefix(pf: PartialFunction[Req, Box[User] => Box[JsonResponse]]): PartialFunction[Req, Box[User] => Box[JsonResponse]] =
|
||||
new PartialFunction[Req, Box[User] => Box[JsonResponse]] {
|
||||
def isDefinedAt(req: Req): Boolean =
|
||||
|
||||
23
src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocs.scala
Normal file
23
src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocs.scala
Normal file
@ -0,0 +1,23 @@
|
||||
package code.api.ResourceDocs1_4_0
|
||||
|
||||
import code.api.OBPRestHelper
|
||||
|
||||
|
||||
import net.liftweb.common.Loggable
|
||||
|
||||
|
||||
object ResourceDocs extends OBPRestHelper with ResourceDocsAPIMethods with Loggable {
|
||||
|
||||
val VERSION = "1.4.0"
|
||||
|
||||
|
||||
val routes = List(
|
||||
ImplementationsResourceDocs.getResourceDocsObp,
|
||||
ImplementationsResourceDocs.getResourceDocsSwagger
|
||||
)
|
||||
|
||||
routes.foreach(route => {
|
||||
oauthServe(apiPrefix{route})
|
||||
})
|
||||
|
||||
}
|
||||
@ -0,0 +1,236 @@
|
||||
package code.api.ResourceDocs1_4_0
|
||||
|
||||
import code.api.v1_4_0.{APIMethods140, JSONFactory1_4_0, OBPAPI1_4_0}
|
||||
import net.liftweb.common.{Box, Full, Loggable}
|
||||
import net.liftweb.http.rest.RestHelper
|
||||
import net.liftweb.http.{JsonResponse, Req}
|
||||
import net.liftweb.json.Extraction
|
||||
import net.liftweb.json.JsonAST.JValue
|
||||
import net.liftweb.json.JsonDSL._
|
||||
import net.liftweb.util.Props
|
||||
|
||||
import scala.collection.immutable.Nil
|
||||
|
||||
// JObject creation
|
||||
import code.api.v1_2_1.{APIInfoJSON, APIMethods121, HostedBy, OBPAPI1_2_1}
|
||||
import code.api.v1_3_0.{APIMethods130, OBPAPI1_3_0}
|
||||
import code.api.v2_0_0.{APIMethods200, OBPAPI2_0_0}
|
||||
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
|
||||
// So we can include resource docs from future versions
|
||||
import java.text.SimpleDateFormat
|
||||
|
||||
import code.api.util.APIUtil.{ResourceDoc, _}
|
||||
import code.model._
|
||||
|
||||
trait ResourceDocsAPIMethods extends Loggable with APIMethods200 with APIMethods140 with APIMethods130 with APIMethods121{
|
||||
//needs to be a RestHelper to get access to JsonGet, JsonPost, etc.
|
||||
// We add previous APIMethods so we have access to the Resource Docs
|
||||
self: RestHelper =>
|
||||
|
||||
val ImplementationsResourceDocs = new Object() {
|
||||
|
||||
val resourceDocs = ArrayBuffer[ResourceDoc]()
|
||||
val emptyObjectJson : JValue = Nil
|
||||
val apiVersion : String = "1_4_0"
|
||||
|
||||
val exampleDateString : String ="22/08/2013"
|
||||
val simpleDateFormat : SimpleDateFormat = new SimpleDateFormat("dd/mm/yyyy")
|
||||
val exampleDate = simpleDateFormat.parse(exampleDateString)
|
||||
|
||||
|
||||
def getResourceDocsList(requestedApiVersion : String) : Option[List[ResourceDoc]] =
|
||||
{
|
||||
// Return a different list of resource docs depending on the version being called.
|
||||
// For instance 1_3_0 will have the docs for 1_3_0 and 1_2_1 (when we started adding resource docs) etc.
|
||||
|
||||
logger.info(s"getResourceDocsList says requestedApiVersion is $requestedApiVersion")
|
||||
|
||||
val resourceDocs = requestedApiVersion match {
|
||||
case "2.0.0" => Implementations2_0_0.resourceDocs ++ Implementations1_4_0.resourceDocs ++ Implementations1_3_0.resourceDocs ++ Implementations1_2_1.resourceDocs
|
||||
case "1.4.0" => Implementations1_4_0.resourceDocs ++ Implementations1_3_0.resourceDocs ++ Implementations1_2_1.resourceDocs
|
||||
case "1.3.0" => Implementations1_3_0.resourceDocs ++ Implementations1_2_1.resourceDocs
|
||||
case "1.2.1" => Implementations1_2_1.resourceDocs
|
||||
}
|
||||
|
||||
logger.info(s"There are ${resourceDocs.length} resource docs available to $requestedApiVersion")
|
||||
|
||||
val versionRoutes = requestedApiVersion match {
|
||||
case "2.0.0" => OBPAPI2_0_0.routes
|
||||
case "1.4.0" => OBPAPI1_4_0.routes
|
||||
case "1.3.0" => OBPAPI1_3_0.routes
|
||||
case "1.2.1" => OBPAPI1_2_1.routes
|
||||
}
|
||||
|
||||
logger.info(s"There are ${versionRoutes.length} routes available to $requestedApiVersion")
|
||||
|
||||
|
||||
// We only want the resource docs for which a API route exists else users will see 404s
|
||||
// Get a list of the partial function classes represented in the routes available to this version.
|
||||
val versionRoutesClasses = versionRoutes.map { vr => vr.getClass }
|
||||
|
||||
// Only return the resource docs that have available routes
|
||||
val activeResourceDocs = resourceDocs.filter(rd => versionRoutesClasses.contains(rd.partialFunction.getClass))
|
||||
|
||||
logger.info(s"There are ${activeResourceDocs.length} resource docs available to $requestedApiVersion")
|
||||
|
||||
// Sort by endpoint, verb. Thus / is shown first then /accounts and /banks etc. Seems to read quite well like that.
|
||||
Some(activeResourceDocs.toList.sortBy(rd => (rd.requestUrl, rd.requestVerb)))
|
||||
}
|
||||
|
||||
|
||||
|
||||
resourceDocs += ResourceDoc(
|
||||
getResourceDocsObp,
|
||||
apiVersion,
|
||||
"getResourceDocsObp",
|
||||
"GET",
|
||||
"/resource-docs/obp",
|
||||
"Get Resource Documentation in OBP format.",
|
||||
"""Returns documentation about the RESTful resources on this server including example body for POST or PUT requests.
|
||||
| Thus the OBP API Explorer (and other apps) can display and work with the API documentation.
|
||||
| In the future this information will be used to create Swagger (WIP) and RAML files.
|
||||
|<ul>
|
||||
|<li> operation_id is concatenation of version and function and should be unque (the aim of this is to allow links to code) </li>
|
||||
|<li> version references the version that the API call is defined in.</li>
|
||||
|<li> function is the (scala) function.</li>
|
||||
|<li> request_url is empty for the root call, else the path.</li>
|
||||
|<li> summary is a short description inline with the swagger terminology. </li>
|
||||
|<li> description may contain html markup (generated from markdown on the server).</li>
|
||||
|</ul>
|
||||
""",
|
||||
emptyObjectJson,
|
||||
emptyObjectJson,
|
||||
emptyObjectJson :: Nil,
|
||||
false,
|
||||
false,
|
||||
List(apiTagApiInfo)
|
||||
)
|
||||
|
||||
// Provides resource documents so that API Explorer (or other apps) can display API documentation
|
||||
// Note: description uses html markup because original markdown doesn't easily support "_" and there are multiple versions of markdown.
|
||||
|
||||
|
||||
// TODO constrain version?
|
||||
// strip the leading v
|
||||
def cleanApiVersionString (version: String) : String = {version.stripPrefix("v").stripPrefix("V")}
|
||||
|
||||
def getResourceDocsObp : PartialFunction[Req, Box[User] => Box[JsonResponse]] = {
|
||||
case "resource-docs" :: requestedApiVersion :: "obp" :: Nil JsonGet _ => {
|
||||
user => {
|
||||
for {
|
||||
rd <- getResourceDocsList(cleanApiVersionString(requestedApiVersion))
|
||||
} yield {
|
||||
// Format the data as json
|
||||
val json = JSONFactory1_4_0.createResourceDocsJson(rd)
|
||||
// Return
|
||||
successJsonResponse(Extraction.decompose(json))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
resourceDocs += ResourceDoc(
|
||||
getResourceDocsSwagger,
|
||||
apiVersion,
|
||||
"getResourceDocsSwagger",
|
||||
"GET",
|
||||
"/resource-docs/swagger",
|
||||
"Get Resource Documentation in Swagger format. Work In Progress!",
|
||||
"""Returns documentation about the RESTful resources on this server in Swagger format.
|
||||
| Currently this is incomplete.
|
||||
""",
|
||||
emptyObjectJson,
|
||||
emptyObjectJson,
|
||||
emptyObjectJson :: Nil,
|
||||
false,
|
||||
false,
|
||||
List(apiTagApiInfo)
|
||||
)
|
||||
|
||||
def getResourceDocsSwagger : PartialFunction[Req, Box[User] => Box[JsonResponse]] = {
|
||||
case "resource-docs" :: requestedApiVersion :: "swagger" :: Nil JsonGet _ => {
|
||||
user => {
|
||||
for {
|
||||
rd <- getResourceDocsList(cleanApiVersionString(requestedApiVersion))
|
||||
} yield {
|
||||
// Format the data as json
|
||||
val json = SwaggerJSONFactory.createSwaggerResourceDoc(rd)
|
||||
// Return
|
||||
successJsonResponse(Extraction.decompose(json))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if (Props.devMode) {
|
||||
resourceDocs += ResourceDoc(
|
||||
dummy(apiVersion),
|
||||
apiVersion,
|
||||
"testResourceDoc",
|
||||
"GET",
|
||||
"/dummy",
|
||||
"I am only a test resource Doc",
|
||||
"""
|
||||
|
|
||||
|#This should be H1
|
||||
|
|
||||
|##This should be H2
|
||||
|
|
||||
|###This should be H3
|
||||
|
|
||||
|####This should be H4
|
||||
|
|
||||
|Here is a list with two items:
|
||||
|
|
||||
|* One
|
||||
|* Two
|
||||
|
|
||||
|There are underscores by them selves _
|
||||
|
|
||||
|There are _underscores_ around a word
|
||||
|
|
||||
|There are underscores_in_words
|
||||
|
|
||||
|There are 'underscores_in_words_inside_quotes'
|
||||
|
|
||||
|There are (underscores_in_words_in_brackets)
|
||||
|
|
||||
|_etc_...""",
|
||||
emptyObjectJson,
|
||||
emptyObjectJson,
|
||||
emptyObjectJson :: Nil,
|
||||
false,
|
||||
false,
|
||||
List(apiTagApiInfo))
|
||||
}
|
||||
|
||||
|
||||
|
||||
def dummy(apiVersion : String) : PartialFunction[Req, Box[User] => Box[JsonResponse]] = {
|
||||
case "dummy" :: Nil JsonGet json => {
|
||||
user =>
|
||||
val apiDetails: JValue = {
|
||||
val hostedBy = new HostedBy("TESOBE", "contact@tesobe.com", "+49 (0)30 8145 3994")
|
||||
val apiInfoJSON = new APIInfoJSON(apiVersion, gitCommit, hostedBy)
|
||||
Extraction.decompose(apiInfoJSON)
|
||||
}
|
||||
|
||||
Full(successJsonResponse(apiDetails, 200))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,88 @@
|
||||
package code.api.ResourceDocs1_4_0
|
||||
|
||||
import code.api.util.APIUtil.ResourceDoc
|
||||
import code.api.Constant._
|
||||
import net.liftweb.json._
|
||||
import net.liftweb.util.Props
|
||||
|
||||
import scala.collection.immutable.ListMap
|
||||
|
||||
object SwaggerJSONFactory {
|
||||
|
||||
case class ContactJson(
|
||||
name: String,
|
||||
url: String
|
||||
)
|
||||
|
||||
case class InfoJson(
|
||||
title: String,
|
||||
description: String,
|
||||
contact: ContactJson,
|
||||
version: String
|
||||
)
|
||||
|
||||
case class ResponseObjectSchemaJson(`$ref`: String)
|
||||
case class ResponseObjectJson(description: Option[String], schema: Option[ResponseObjectSchemaJson])
|
||||
|
||||
case class MethodJson(tags: List[String],
|
||||
summary: String,
|
||||
operationId: String,
|
||||
responses: Map[String, ResponseObjectJson])
|
||||
|
||||
case class PathsJson(get: MethodJson)
|
||||
|
||||
case class MessageJson(`type`: String)
|
||||
|
||||
case class CodeJson(`type`: String, format: String)
|
||||
|
||||
case class PropertiesJson(code: CodeJson, message: MessageJson)
|
||||
|
||||
case class ErrorDefinitionJson(`type`: String, required: List[String], properties: PropertiesJson)
|
||||
|
||||
case class DefinitionsJson(Error: ErrorDefinitionJson)
|
||||
|
||||
case class SwaggerResourceDoc(swagger: String,
|
||||
info: InfoJson,
|
||||
host: String,
|
||||
basePath: String,
|
||||
schemes: List[String],
|
||||
paths: Map[String, Map[String, MethodJson]],
|
||||
definitions: DefinitionsJson
|
||||
)
|
||||
|
||||
def createSwaggerResourceDoc(resourceDocList: List[ResourceDoc]): SwaggerResourceDoc = {
|
||||
implicit val formats = DefaultFormats
|
||||
|
||||
val contact = ContactJson("OBP", "https://openbankproject.com/")
|
||||
val apiVersion = "v1.4.0"
|
||||
val title = "Open Bank Project API"
|
||||
val description = "An open source API for banks."
|
||||
val info = InfoJson(title, description, contact, apiVersion)
|
||||
// TODO check / improve host, basePath and version
|
||||
val host = Props.get("hostname", "unknown host").replaceFirst("http://", "")
|
||||
val basePath = s"/$ApiPathZero/" + apiVersion
|
||||
val schemas = List("http")
|
||||
val paths: ListMap[String, Map[String, MethodJson]] = resourceDocList.groupBy(x => x.requestUrl).toSeq.sortBy(x => x._1).map { mrd =>
|
||||
val methods: Map[String, MethodJson] = mrd._2.map(rd =>
|
||||
(rd.requestVerb,
|
||||
MethodJson(
|
||||
List(s"${rd.apiVersion.toString}"),
|
||||
rd.summary,
|
||||
s"${rd.apiVersion.toString}-${rd.apiFunction.toString}",
|
||||
Map("200" -> ResponseObjectJson(Some("Success") , None), "400" -> ResponseObjectJson(Some("Error"), Some(ResponseObjectSchemaJson("#/definitions/Error"))))))
|
||||
).toMap
|
||||
(mrd._1, methods.toSeq.sortBy(m => m._1).toMap)
|
||||
}(collection.breakOut)
|
||||
|
||||
val `type` = "object"
|
||||
val required = List("code", "message")
|
||||
val code = CodeJson("integer", "int32")
|
||||
val message = MessageJson("string")
|
||||
val properties = PropertiesJson(code, message)
|
||||
val errorDef = ErrorDefinitionJson(`type`, required, properties)
|
||||
val defs = DefinitionsJson(errorDef)
|
||||
|
||||
SwaggerResourceDoc("2.0", info, host, basePath, schemas, paths, defs)
|
||||
}
|
||||
|
||||
}
|
||||
17
src/main/scala/code/api/constant/constant.scala
Normal file
17
src/main/scala/code/api/constant/constant.scala
Normal file
@ -0,0 +1,17 @@
|
||||
package code.api
|
||||
|
||||
import code.api.util.ErrorMessages
|
||||
import net.liftweb.common.Loggable
|
||||
import net.liftweb.util.Props
|
||||
|
||||
|
||||
// Note: Import this with: import code.api.Constant._
|
||||
object Constant extends Loggable {
|
||||
logger.info("Instantiating Constants")
|
||||
|
||||
final val HostName = Props.get("hostname").openOrThrowException(ErrorMessages.HostnameNotSpecified)
|
||||
|
||||
// This is the part before the version. Do not change this default!
|
||||
final val ApiPathZero = Props.get("apiPathZero", "obp")
|
||||
|
||||
}
|
||||
313
src/main/scala/code/api/directlogin.scala
Normal file
313
src/main/scala/code/api/directlogin.scala
Normal file
@ -0,0 +1,313 @@
|
||||
/**
|
||||
Open Bank Project
|
||||
|
||||
Copyright 2011,2012 TESOBE / Music Pictures Ltd.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
Open Bank Project (http://www.openbankproject.com)
|
||||
Copyright 2011,2012 TESOBE / Music Pictures Ltd
|
||||
|
||||
This product includes software developed at
|
||||
TESOBE (http://www.tesobe.com/)
|
||||
by
|
||||
Simon Redfern : simon AT tesobe DOT com
|
||||
Everett Sochowski: everett AT tesobe DOT com
|
||||
Ayoub Benali : ayoub AT tesobe Dot com
|
||||
*/
|
||||
package code.api
|
||||
|
||||
import java.util.Date
|
||||
|
||||
import authentikat.jwt.{JsonWebToken, JwtClaimsSet, JwtHeader}
|
||||
import code.api.util.APIUtil._
|
||||
import code.model.dataAccess.OBPUser
|
||||
import code.model.{Consumer, Token, TokenType, User}
|
||||
import net.liftweb.common._
|
||||
import net.liftweb.http._
|
||||
import net.liftweb.http.rest.RestHelper
|
||||
import net.liftweb.json.Extraction
|
||||
import net.liftweb.mapper.By
|
||||
import net.liftweb.util.{Props, Helpers}
|
||||
import net.liftweb.util.Helpers._
|
||||
|
||||
import scala.compat.Platform
|
||||
import code.api.util.ErrorMessages
|
||||
|
||||
/**
|
||||
* This object provides the API calls necessary to
|
||||
* authenticate users using JSON Web Tokens (http://jwt.io).
|
||||
*/
|
||||
|
||||
object JSONFactory {
|
||||
case class TokenJSON( token : String )
|
||||
|
||||
def stringOrNull(text: String) =
|
||||
if (text == null || text.isEmpty)
|
||||
null
|
||||
else
|
||||
text
|
||||
|
||||
def stringOptionOrNull(text: Option[String]) =
|
||||
text match {
|
||||
case Some(t) => stringOrNull(t)
|
||||
case _ => null
|
||||
}
|
||||
|
||||
def createTokenJSON(token: String): TokenJSON = {
|
||||
new TokenJSON(
|
||||
stringOrNull(token)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
object DirectLogin extends RestHelper with Loggable {
|
||||
|
||||
// Our version of serve
|
||||
def dlServe(handler : PartialFunction[Req, JsonResponse]) : Unit = {
|
||||
val obpHandler : PartialFunction[Req, () => Box[LiftResponse]] = {
|
||||
new PartialFunction[Req, () => Box[LiftResponse]] {
|
||||
def apply(r : Req) = {
|
||||
handler(r)
|
||||
}
|
||||
def isDefinedAt(r : Req) = handler.isDefinedAt(r)
|
||||
}
|
||||
}
|
||||
super.serve(obpHandler)
|
||||
}
|
||||
|
||||
dlServe
|
||||
{
|
||||
//Handling get request for a token
|
||||
case Req("my" :: "logins" :: "direct" :: Nil,_ , PostRequest|GetRequest) => {
|
||||
|
||||
//Extract the directLogin parameters from the header and test if the request is valid
|
||||
var (httpCode, message, directLoginParameters) = validator("authorizationToken", getHttpMethod)
|
||||
//Test if the request is valid
|
||||
if(httpCode==200)
|
||||
{
|
||||
val claims = Map("" -> "")
|
||||
val (token,secret) = generateTokenAndSecret(claims)
|
||||
|
||||
//Save the token that we have generated
|
||||
if(saveAuthorizationToken(directLoginParameters,token, secret))
|
||||
message = token
|
||||
else
|
||||
message = "invalid"
|
||||
}
|
||||
val tokenJSON = JSONFactory.createTokenJSON(message)
|
||||
successJsonResponse(Extraction.decompose(tokenJSON))
|
||||
}
|
||||
}
|
||||
|
||||
def getHttpMethod = S.request match {
|
||||
case Full(s) => s.post_? match {
|
||||
case true => "POST"
|
||||
case _ => "GET"
|
||||
}
|
||||
case _ => "ERROR"
|
||||
}
|
||||
|
||||
//Check if the request (access token or request token) is valid and return a tuple
|
||||
def validator(requestType : String, httpMethod : String) : (Int, String, Map[String,String]) = {
|
||||
//return a Map containing the directLogin parameters : prameter -> value
|
||||
def getAllParameters: Map[String, String] = {
|
||||
def toMap(parametersList: String) = {
|
||||
//transform the string "directLogin_prameter="value""
|
||||
//to a tuple (directLogin_parameter,Decoded(value))
|
||||
def dynamicListExtract(input: String) = {
|
||||
val directLoginPossibleParameters =
|
||||
List(
|
||||
"consumer_key",
|
||||
"token",
|
||||
"username",
|
||||
"password"
|
||||
)
|
||||
if (input contains "=") {
|
||||
val split = input.split("=", 2)
|
||||
val parameterValue = split(1).replace("\"", "")
|
||||
//add only OAuth parameters and not empty
|
||||
if (directLoginPossibleParameters.contains(split(0)) && !parameterValue.isEmpty)
|
||||
Some(split(0), parameterValue) // return key , value
|
||||
else
|
||||
None
|
||||
}
|
||||
else
|
||||
None
|
||||
}
|
||||
//we delete the "DirectLogin" prefix and all the white spaces that may exist in the string
|
||||
val cleanedParameterList = parametersList.stripPrefix("DirectLogin").replaceAll("\\s", "")
|
||||
val params = Map(cleanedParameterList.split(",").flatMap(dynamicListExtract _): _*)
|
||||
params
|
||||
}
|
||||
|
||||
S.request match {
|
||||
case Full(a) => a.header("Authorization") match {
|
||||
case Full(header) => {
|
||||
if (header.contains("DirectLogin"))
|
||||
toMap(header)
|
||||
else
|
||||
Map("error" -> "header incorrect")
|
||||
}
|
||||
case _ => Map("error" -> "missing header")
|
||||
}
|
||||
case _ => Map("error" -> "request incorrect")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def registeredApplication(consumerKey: String): Boolean = {
|
||||
Consumer.find(By(Consumer.key, consumerKey)) match {
|
||||
case Full(application) => application.isActive
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
def validAccessToken(tokenKey: String) = {
|
||||
Token.find(By(Token.key, tokenKey), By(Token.tokenType, TokenType.Access)) match {
|
||||
case Full(token) => token.isValid
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
//@return the missing parameters depending of the request type
|
||||
def missingDirectLoginParameters(parameters: Map[String, String], requestType: String): Set[String] = {
|
||||
requestType match {
|
||||
case "authorizationToken" =>
|
||||
("username" :: "password" :: "consumer_key" :: List()).toSet diff parameters.keySet
|
||||
case "protectedResource" =>
|
||||
("token" :: List()).toSet diff parameters.keySet
|
||||
case _ =>
|
||||
parameters.keySet
|
||||
}
|
||||
}
|
||||
|
||||
var message = ""
|
||||
var httpCode: Int = 500
|
||||
|
||||
val parameters = getAllParameters
|
||||
|
||||
//are all the necessary directLogin parameters present?
|
||||
val missingParams = missingDirectLoginParameters(parameters, requestType)
|
||||
if (missingParams.nonEmpty) {
|
||||
message = ErrorMessages.DirectLoginMissingParameters + missingParams.mkString(", ")
|
||||
httpCode = 400
|
||||
}
|
||||
else if (
|
||||
requestType == "protectedResource" &&
|
||||
!validAccessToken(parameters.get("token").getOrElse(""))
|
||||
) {
|
||||
message = ErrorMessages.DirectLoginInvalidToken + parameters.get("token").getOrElse("")
|
||||
httpCode = 401
|
||||
}
|
||||
//check if the application is registered and active
|
||||
else if (
|
||||
requestType == "authorizationToken" &&
|
||||
Props.getBool("direct_login_consumer_key_mandatory", true) &&
|
||||
!registeredApplication(parameters.get("consumer_key").getOrElse(""))) {
|
||||
|
||||
logger.error("application: " + parameters.get("consumer_key").getOrElse("") + " not found")
|
||||
message = ErrorMessages.InvalidLoginCredentials
|
||||
httpCode = 401
|
||||
}
|
||||
//checking if the signature is correct
|
||||
//else if(! correctSignature(parameters, httpMethod))
|
||||
//{
|
||||
// message = "Invalid signature"
|
||||
// httpCode = 401
|
||||
//}
|
||||
else
|
||||
httpCode = 200
|
||||
if(message.nonEmpty)
|
||||
logger.error("error message : " + message)
|
||||
(httpCode, message, parameters)
|
||||
}
|
||||
|
||||
private def generateTokenAndSecret(claims: Map[String,String]) =
|
||||
{
|
||||
// generate random string
|
||||
val secret_message = Helpers.randomString(40)
|
||||
// jwt header
|
||||
val header = JwtHeader("HS256")
|
||||
// generate jwt token
|
||||
val token_message = JsonWebToken(header, JwtClaimsSet(claims), secret_message)
|
||||
(token_message, secret_message)
|
||||
}
|
||||
|
||||
private def saveAuthorizationToken(directLoginParameters : Map[String, String], tokenKey : String, tokenSecret : String) =
|
||||
{
|
||||
import code.model.{Token, TokenType}
|
||||
val token = Token.create
|
||||
token.tokenType(TokenType.Access)
|
||||
Consumer.find(By(Consumer.key, directLoginParameters.get("consumer_key").getOrElse(""))) match {
|
||||
case Full(consumer) => token.consumerId(consumer.id)
|
||||
case _ => None
|
||||
}
|
||||
val username = directLoginParameters.get("username").getOrElse("").toString
|
||||
val password = directLoginParameters.get("password").getOrElse("") match {
|
||||
case p: String => p
|
||||
case _ => "error"
|
||||
}
|
||||
val userId = OBPUser.getUserId(username, password)
|
||||
token.userForeignKey(userId)
|
||||
token.key(tokenKey)
|
||||
token.secret(tokenSecret)
|
||||
val currentTime = Platform.currentTime
|
||||
val tokenDuration : Long = Helpers.weeks(4)
|
||||
token.duration(tokenDuration)
|
||||
token.expirationDate(new Date(currentTime+tokenDuration))
|
||||
token.insertDate(new Date(currentTime))
|
||||
val tokenSaved = token.save()
|
||||
tokenSaved
|
||||
}
|
||||
|
||||
def getUser : Box[User] = {
|
||||
val httpMethod = S.request match {
|
||||
case Full(r) => r.request.method
|
||||
case _ => "GET"
|
||||
}
|
||||
val (httpCode, message, directLoginParameters) = validator("protectedResource", httpMethod)
|
||||
val user = getUser(200, directLoginParameters.get("token"))
|
||||
if (user != Empty ) {
|
||||
val res = Full(user.get)
|
||||
res
|
||||
} else {
|
||||
ParamFailure(message, Empty, Empty, APIFailure(message, httpCode))
|
||||
}
|
||||
}
|
||||
|
||||
def getUser(httpCode : Int, tokenID : Box[String]) : Box[User] =
|
||||
if(httpCode==200)
|
||||
{
|
||||
import code.model.Token
|
||||
logger.info("DirectLogin header correct ")
|
||||
Token.find(By(Token.key, tokenID.getOrElse(""))) match {
|
||||
case Full(token) => {
|
||||
logger.info("access token: "+ token + " found")
|
||||
val user = token.user
|
||||
//just a log
|
||||
user match {
|
||||
case Full(u) => logger.info("user " + u.emailAddress + " was found from the DirectLogin token")
|
||||
case _ => logger.info("no user was found for the DirectLogin token")
|
||||
}
|
||||
user
|
||||
}
|
||||
case _ =>{
|
||||
logger.warn("no token " + tokenID.getOrElse("") + " found")
|
||||
Empty
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
Empty
|
||||
}
|
||||
@ -250,7 +250,7 @@ object OAuthHandshake extends RestHelper with Loggable {
|
||||
parameters
|
||||
}
|
||||
|
||||
//prepare the base string
|
||||
//prepare the base string (should we really have openOr here?)
|
||||
var baseString = httpMethod+"&"+URLEncoder.encode(Props.get("hostname").openOr(S.hostAndPath) + S.uri,"UTF-8")+"&"
|
||||
baseString+= generateOAuthParametersString(OAuthparameters)
|
||||
|
||||
|
||||
12424
src/main/scala/code/api/sandbox/example_data/example_import.json
Normal file
12424
src/main/scala/code/api/sandbox/example_data/example_import.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -32,25 +32,67 @@ Berlin 13359, Germany
|
||||
|
||||
package code.api.util
|
||||
|
||||
import code.api.util.APIUtil.ApiLink
|
||||
import code.api.v1_2.ErrorMessage
|
||||
import code.metrics.APIMetrics
|
||||
import net.liftweb.common.{Full, Loggable}
|
||||
import net.liftweb.http.{JsonResponse, S}
|
||||
import code.model._
|
||||
import net.liftweb.common.{Box, Full, Loggable}
|
||||
import net.liftweb.http.js.JE.JsRaw
|
||||
import net.liftweb.http.js.JsExp
|
||||
import net.liftweb.http.{JsonResponse, Req, S}
|
||||
import net.liftweb.json.Extraction
|
||||
import net.liftweb.json.JsonAST.{JObject, JValue}
|
||||
|
||||
|
||||
|
||||
|
||||
import net.liftweb.json._
|
||||
import net.liftweb.json.JsonAST.JValue
|
||||
import net.liftweb.util.Helpers._
|
||||
|
||||
import bootstrap.liftweb.Boot
|
||||
import net.liftweb.util.Props
|
||||
|
||||
import scala.collection.JavaConversions.asScalaSet
|
||||
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
|
||||
import net.liftweb.http.CurrentReq
|
||||
import code.api.Constant._
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
object ErrorMessages {
|
||||
|
||||
// Infrastructure / config messages
|
||||
val HostnameNotSpecified = "OBP-00001: Hostname not specified. Could not get hostname from Props. Please edit your props file. Here are some example settings: hostname=http://127.0.0.1:8080 or hostname=https://www.example.com"
|
||||
|
||||
// General messages
|
||||
val InvalidJsonFormat = "OBP-10001: Incorrect json format."
|
||||
val InvalidNumber = "OBP-10002: Invalid Number. Could not convert value to a number."
|
||||
val InvalidInitalBalance = "OBP-10003: Invalid Number. Initial balance must be a number, e.g 1000.00"
|
||||
|
||||
|
||||
|
||||
// Authentication / Authorisation messages
|
||||
val UserNotLoggedIn = "OBP-20001: User not logged in. Authentication is required!"
|
||||
|
||||
val DirectLoginMissingParameters = "OBP-20002: These DirectLogin parameters are missing: "
|
||||
val DirectLoginInvalidToken = "OBP-20003: This DirectLogin token is invalid or expired: "
|
||||
|
||||
|
||||
val InvalidLoginCredentials = "OBP-20004: Invalid login credentials. Check username/password."
|
||||
|
||||
|
||||
|
||||
// Resource related messages
|
||||
val BankNotFound = "OBP-30001: Bank not found. Please specify a valid value for BANK_ID."
|
||||
val CustomerNotFound = "OBP-30002: Customer not found. Please specify a valid value for CUSTOMER_NUMBER."
|
||||
|
||||
val AccountNotFound = "OBP-30003: Account not found. Please specify a valid value for ACCOUNT_ID."
|
||||
val CounterpartyNotFound = "OBP-30004: Counterparty not found."
|
||||
|
||||
val ViewNotFound = "OBP-30005: View not found for Account. Please specify a valid value for VIEW_ID"
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
object APIUtil extends Loggable {
|
||||
|
||||
@ -64,6 +106,16 @@ object APIUtil extends Loggable {
|
||||
case _ => "GET"
|
||||
}
|
||||
|
||||
def isThereDirectLoginHeader : Boolean = {
|
||||
S.request match {
|
||||
case Full(a) => a.header("Authorization") match {
|
||||
case Full(parameters) => parameters.contains("DirectLogin")
|
||||
case _ => false
|
||||
}
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
def isThereAnOAuthHeader : Boolean = {
|
||||
S.request match {
|
||||
case Full(a) => a.header("Authorization") match {
|
||||
@ -76,6 +128,7 @@ object APIUtil extends Loggable {
|
||||
|
||||
def logAPICall = {
|
||||
if(Props.getBool("write_metrics", false)) {
|
||||
// TODO This should use Elastic Search or Kafka not an RDBMS
|
||||
APIMetrics.apiMetrics.vend.saveMetric(S.uriAndQueryString.getOrElse(""), (now: TimeSpan))
|
||||
}
|
||||
}
|
||||
@ -132,7 +185,7 @@ object APIUtil extends Loggable {
|
||||
import org.apache.http.protocol.HTTP.UTF_8
|
||||
|
||||
import scala.collection.Map
|
||||
import scala.collection.immutable.{TreeMap, Map => IMap}
|
||||
import scala.collection.immutable.{Map => IMap, TreeMap}
|
||||
import scala.collection.mutable.Set
|
||||
|
||||
case class Consumer(key: String, secret: String)
|
||||
@ -261,16 +314,243 @@ object APIUtil extends Loggable {
|
||||
}
|
||||
|
||||
/*
|
||||
Used to document API calls / resources. correct place for this?
|
||||
Used to document API calls / resources.
|
||||
|
||||
TODO Can we extract apiVersion, apiFunction, requestVerb and requestUrl from partialFunction?
|
||||
|
||||
*/
|
||||
|
||||
// Used to tag Resource Docs
|
||||
case class ResourceDocTag(tag: String)
|
||||
|
||||
val apiTagPayment = ResourceDocTag("Payments")
|
||||
val apiTagApiInfo = ResourceDocTag("APIInfo")
|
||||
val apiTagBanks = ResourceDocTag("Banks")
|
||||
val apiTagAccounts = ResourceDocTag("Accounts")
|
||||
val apiTagPublicData = ResourceDocTag("PublicData")
|
||||
val apiTagPrivateData = ResourceDocTag("PrivateData")
|
||||
val apiTagTransactions = ResourceDocTag("Transactions")
|
||||
val apiTagMetaData = ResourceDocTag("Meta Data")
|
||||
val apiTagViews = ResourceDocTag("Views")
|
||||
val apiTagEntitlements = ResourceDocTag("Entitlements")
|
||||
val apiTagOwnerRequired = ResourceDocTag("OwnerViewRequired")
|
||||
val apiTagCounterparties = ResourceDocTag("Counterparties")
|
||||
val apiTagKyc = ResourceDocTag("KYC")
|
||||
val apiTagCustomer = ResourceDocTag("Customer")
|
||||
|
||||
|
||||
// Used to document the API calls
|
||||
case class ResourceDoc(
|
||||
apiVersion: String, // TODO: Constrain to certain strings?
|
||||
apiFunction: String, // The partial function that implements this resource. Could use it to link to the source code that implements the call
|
||||
requestVerb: String, // GET, POST etc. TODO: Constrain to GET, POST etc.
|
||||
requestUrl: String, // The URL (not including /obp/vX.X). Starts with / No trailing slash. TODO Constrain the string?
|
||||
summary: String, // A summary of the call (originally taken from code comment) SHOULD be under 120 chars to be inline with Swagger
|
||||
description: String, // Longer description (originally taken from github wiki)
|
||||
exampleRequestBody: JValue, // An example of the body required (maybe empty)
|
||||
successResponseBody: JValue, // A successful response body
|
||||
errorResponseBodies: List[JValue]) // Possible error responses
|
||||
partialFunction : PartialFunction[Req, Box[User] => Box[JsonResponse]],
|
||||
apiVersion: String, // TODO: Constrain to certain strings?
|
||||
apiFunction: String, // The partial function that implements this resource. Could use it to link to the source code that implements the call
|
||||
requestVerb: String, // GET, POST etc. TODO: Constrain to GET, POST etc.
|
||||
requestUrl: String, // The URL (not including /obp/vX.X). Starts with / No trailing slash. TODO Constrain the string?
|
||||
summary: String, // A summary of the call (originally taken from code comment) SHOULD be under 120 chars to be inline with Swagger
|
||||
description: String, // Longer description (originally taken from github wiki)
|
||||
exampleRequestBody: JValue, // An example of the body required (maybe empty)
|
||||
successResponseBody: JValue, // A successful response body
|
||||
errorResponseBodies: List[JValue], // Possible error responses
|
||||
isCore: Boolean,
|
||||
isPSD2: Boolean,
|
||||
tags: List[ResourceDocTag]
|
||||
)
|
||||
|
||||
// Define relations between API end points. Used to create _links in the JSON and maybe later for API Explorer browsing
|
||||
case class ApiRelation(
|
||||
fromPF : PartialFunction[Req, Box[User] => Box[JsonResponse]],
|
||||
toPF : PartialFunction[Req, Box[User] => Box[JsonResponse]],
|
||||
rel : String
|
||||
)
|
||||
|
||||
// Populated from Resource Doc and ApiRelation
|
||||
case class InternalApiLink(
|
||||
fromPF : PartialFunction[Req, Box[User] => Box[JsonResponse]],
|
||||
toPF : PartialFunction[Req, Box[User] => Box[JsonResponse]],
|
||||
rel : String,
|
||||
requestUrl: String
|
||||
)
|
||||
|
||||
// Used to pass context of current API call to the function that generates links for related Api calls.
|
||||
case class DataContext(
|
||||
user : Box[User],
|
||||
bankId : Option[BankId],
|
||||
accountId: Option[AccountId],
|
||||
viewId: Option[ViewId],
|
||||
counterpartyId: Option[CounterpartyId],
|
||||
transactionId: Option[TransactionId]
|
||||
)
|
||||
|
||||
case class CallerContext(
|
||||
caller : PartialFunction[Req, Box[User] => Box[JsonResponse]]
|
||||
)
|
||||
|
||||
case class CodeContext(
|
||||
resourceDocsArrayBuffer : ArrayBuffer[ResourceDoc],
|
||||
relationsArrayBuffer : ArrayBuffer[ApiRelation]
|
||||
)
|
||||
|
||||
|
||||
|
||||
case class ApiLink(
|
||||
rel: String,
|
||||
href: String
|
||||
)
|
||||
|
||||
case class LinksJSON(
|
||||
_links: List[ApiLink]
|
||||
)
|
||||
|
||||
case class ResultAndLinksJSON(
|
||||
result : JValue,
|
||||
_links: List[ApiLink]
|
||||
)
|
||||
|
||||
|
||||
def createResultAndLinksJSON(result : JValue, links : List[ApiLink] ) : ResultAndLinksJSON = {
|
||||
new ResultAndLinksJSON(
|
||||
result,
|
||||
links
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
def authenticationRequiredMessage(authRequired: Boolean) : String =
|
||||
authRequired match {
|
||||
case true => "Authentication IS required"
|
||||
case false => "Authentication is NOT required"
|
||||
}
|
||||
|
||||
|
||||
|
||||
def apiVersionWithV(apiVersion : String) : String = {
|
||||
// TODO Define a list of supported versions (put in Constant) and constrain the input
|
||||
// Append v and replace _ with .
|
||||
s"v${apiVersion.replaceAll("_",".")}"
|
||||
}
|
||||
|
||||
def fullBaseUrl : String = {
|
||||
val crv = CurrentReq.value
|
||||
val apiPathZeroFromRequest = crv.path.partPath(0)
|
||||
if (apiPathZeroFromRequest != ApiPathZero) throw new Exception("Configured ApiPathZero is not the same as the actual.")
|
||||
|
||||
val path = s"$HostName/$ApiPathZero"
|
||||
path
|
||||
}
|
||||
|
||||
|
||||
// Modify URL replacing placeholders for Ids
|
||||
def contextModifiedUrl(url: String, context: DataContext) = {
|
||||
|
||||
// Potentially replace BANK_ID
|
||||
val url2: String = context.bankId match {
|
||||
case Some(x) => url.replaceAll("BANK_ID", x.value)
|
||||
case _ => url
|
||||
}
|
||||
|
||||
val url3: String = context.accountId match {
|
||||
// Take care *not* to change OTHER_ACCOUNT_ID HERE
|
||||
case Some(x) => url2.replaceAll("/ACCOUNT_ID", s"/${x.value}").replaceAll("COUNTERPARTY_ID", x.value)
|
||||
case _ => url2
|
||||
}
|
||||
|
||||
val url4: String = context.viewId match {
|
||||
case Some(x) => url3.replaceAll("VIEW_ID", {x.value})
|
||||
case _ => url3
|
||||
}
|
||||
|
||||
val url5: String = context.counterpartyId match {
|
||||
// Change OTHER_ACCOUNT_ID or COUNTERPARTY_ID
|
||||
case Some(x) => url4.replaceAll("OTHER_ACCOUNT_ID", x.value).replaceAll("COUNTERPARTY_ID", x.value)
|
||||
case _ => url4
|
||||
}
|
||||
|
||||
val url6: String = context.transactionId match {
|
||||
case Some(x) => url5.replaceAll("TRANSACTION_ID", x.value)
|
||||
case _ => url5
|
||||
}
|
||||
|
||||
// Add host, port, prefix, version.
|
||||
|
||||
// not correct because call could be in other version
|
||||
val fullUrl = s"$fullBaseUrl$url6"
|
||||
|
||||
fullUrl
|
||||
}
|
||||
|
||||
|
||||
def getApiLinkTemplates(callerContext: CallerContext,
|
||||
codeContext: CodeContext
|
||||
) : List[InternalApiLink] = {
|
||||
|
||||
|
||||
|
||||
// Relations of the API version where the caller is defined.
|
||||
val relations = codeContext.relationsArrayBuffer.toList
|
||||
|
||||
// Resource Docs
|
||||
// Note: This doesn't allow linking to calls in earlier versions of the API
|
||||
// TODO: Fix me
|
||||
val resourceDocs = codeContext.resourceDocsArrayBuffer
|
||||
|
||||
val pf = callerContext.caller
|
||||
|
||||
val internalApiLinks: List[InternalApiLink] = for {
|
||||
relation <- relations.filter(r => r.fromPF == pf)
|
||||
toResourceDoc <- resourceDocs.find(rd => rd.partialFunction == relation.toPF)
|
||||
}
|
||||
yield new InternalApiLink(
|
||||
pf,
|
||||
toResourceDoc.partialFunction,
|
||||
relation.rel,
|
||||
// Add the vVersion to the documented url
|
||||
s"/${apiVersionWithV(toResourceDoc.apiVersion)}${toResourceDoc.requestUrl}"
|
||||
)
|
||||
internalApiLinks
|
||||
}
|
||||
|
||||
|
||||
|
||||
// This is not currently including "templated" attribute
|
||||
def halLinkFragment (link: ApiLink) : String = {
|
||||
"\"" + link.rel +"\": { \"href\": \"" +link.href + "\" }"
|
||||
}
|
||||
|
||||
|
||||
// Since HAL links can't be represented via a case class, (they have dynamic attributes rather than a list) we need to generate them here.
|
||||
def buildHalLinks(links: List[ApiLink]): JValue = {
|
||||
|
||||
val halLinksString = links match {
|
||||
case head :: tail => tail.foldLeft("{"){(r: String, c: ApiLink) => ( r + " " + halLinkFragment(c) + " ," ) } + halLinkFragment(head) + "}"
|
||||
case Nil => "{}"
|
||||
}
|
||||
parse(halLinksString)
|
||||
}
|
||||
|
||||
|
||||
// Returns API links (a list of them) that have placeholders (e.g. BANK_ID) replaced by values (e.g. ulster-bank)
|
||||
def getApiLinks(callerContext: CallerContext, codeContext: CodeContext, dataContext: DataContext) : List[ApiLink] = {
|
||||
val templates = getApiLinkTemplates(callerContext, codeContext)
|
||||
// Replace place holders in the urls like BANK_ID with the current value e.g. 'ulster-bank' and return as ApiLinks for external consumption
|
||||
val links = templates.map(i => ApiLink(i.rel,
|
||||
contextModifiedUrl(i.requestUrl, dataContext) )
|
||||
)
|
||||
links
|
||||
}
|
||||
|
||||
|
||||
// Returns links formatted at objects.
|
||||
def getHalLinks(callerContext: CallerContext, codeContext: CodeContext, dataContext: DataContext) : JValue = {
|
||||
val links = getApiLinks(callerContext, codeContext, dataContext)
|
||||
getHalLinksFromApiLinks(links)
|
||||
}
|
||||
|
||||
|
||||
|
||||
def getHalLinksFromApiLinks(links: List[ApiLink]) : JValue = {
|
||||
val halLinksJson = buildHalLinks(links)
|
||||
halLinksJson
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -36,6 +36,8 @@ import net.liftweb.json.JsonAST._
|
||||
import net.liftweb.common.Loggable
|
||||
import code.api.OBPRestHelper
|
||||
|
||||
// Added so we can add resource docs for this version of the API
|
||||
|
||||
object OBPAPI1_2_1 extends OBPRestHelper with APIMethods121 with Loggable {
|
||||
|
||||
|
||||
|
||||
@ -30,6 +30,7 @@ trait APIMethods130 {
|
||||
|
||||
|
||||
resourceDocs += ResourceDoc(
|
||||
getCards,
|
||||
apiVersion,
|
||||
"getCards",
|
||||
"GET",
|
||||
@ -38,7 +39,10 @@ trait APIMethods130 {
|
||||
"Returns data about all the physical cards a user has been issued. These could be debit cards, credit cards, etc.",
|
||||
emptyObjectJson,
|
||||
emptyObjectJson,
|
||||
emptyObjectJson :: Nil)
|
||||
emptyObjectJson :: Nil,
|
||||
true,
|
||||
true,
|
||||
Nil)
|
||||
|
||||
lazy val getCards : PartialFunction[Req, Box[User] => Box[JsonResponse]] = {
|
||||
case "cards" :: Nil JsonGet _ => {
|
||||
@ -58,6 +62,7 @@ trait APIMethods130 {
|
||||
|
||||
|
||||
resourceDocs += ResourceDoc(
|
||||
getCardsForBank,
|
||||
apiVersion,
|
||||
"getCardsForBank",
|
||||
"GET",
|
||||
@ -66,7 +71,10 @@ trait APIMethods130 {
|
||||
"",
|
||||
emptyObjectJson,
|
||||
emptyObjectJson,
|
||||
emptyObjectJson :: Nil)
|
||||
emptyObjectJson :: Nil,
|
||||
false,
|
||||
false,
|
||||
Nil)
|
||||
|
||||
|
||||
def getCardsForBank : PartialFunction[Req, Box[User] => Box[JsonResponse]] = {
|
||||
|
||||
@ -4,6 +4,9 @@ import code.api.OBPRestHelper
|
||||
import code.api.v1_2_1.APIMethods121
|
||||
import net.liftweb.common.Loggable
|
||||
|
||||
|
||||
// Added so we can add resource docs for this version of the API
|
||||
|
||||
//has APIMethods121 as all api calls that went unchanged from 1.2.1 to 1.3.0 will use the old
|
||||
//implementation
|
||||
object OBPAPI1_3_0 extends OBPRestHelper with APIMethods130 with APIMethods121 with Loggable {
|
||||
|
||||
@ -3,7 +3,9 @@ package code.api.v1_4_0
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
|
||||
import code.api.v1_4_0.JSONFactory1_4_0._
|
||||
import code.bankconnectors.Connector
|
||||
import code.metadata.comments.MappedComment
|
||||
import code.transactionrequests.TransactionRequests.{TransactionRequestBody, TransactionRequestAccount}
|
||||
import net.liftweb.common.{Failure, Loggable, Box, Full}
|
||||
import net.liftweb.http.js.JE.JsRaw
|
||||
@ -16,15 +18,21 @@ import net.liftweb.json.JsonDSL._
|
||||
import net.liftweb.util.Props
|
||||
import net.liftweb.json.JsonAST.JValue
|
||||
|
||||
|
||||
import code.api.v1_2_1.{AmountOfMoneyJSON}
|
||||
|
||||
import scala.collection.immutable.Nil
|
||||
|
||||
// JObject creation
|
||||
import collection.mutable.ArrayBuffer
|
||||
|
||||
import code.api.APIFailure
|
||||
import code.api.v1_2_1.APIMethods121
|
||||
import code.api.v1_3_0.APIMethods130
|
||||
import code.api.v1_4_0.JSONFactory1_4_0._
|
||||
import code.api.v1_2_1.{OBPAPI1_2_1, APIInfoJSON, HostedBy, APIMethods121}
|
||||
import code.api.v1_3_0.{OBPAPI1_3_0, APIMethods130}
|
||||
//import code.api.v2_0_0.{OBPAPI2_0_0, APIMethods200}
|
||||
|
||||
// So we can include resource docs from future versions
|
||||
//import code.api.v1_4_0.JSONFactory1_4_0._
|
||||
import code.atms.Atms
|
||||
import code.branches.Branches
|
||||
import code.crm.CrmEvent
|
||||
@ -32,10 +40,15 @@ import code.customer.{MockCustomerFaceImage, CustomerMessages, Customer}
|
||||
import code.model._
|
||||
import code.products.Products
|
||||
import code.api.util.APIUtil._
|
||||
import code.api.util.ErrorMessages
|
||||
|
||||
import code.util.Helper._
|
||||
import code.api.util.APIUtil.ResourceDoc
|
||||
import java.text.SimpleDateFormat
|
||||
|
||||
import code.api.util.APIUtil.authenticationRequiredMessage
|
||||
|
||||
|
||||
trait APIMethods140 extends Loggable with APIMethods130 with APIMethods121{
|
||||
//needs to be a RestHelper to get access to JsonGet, JsonPost, etc.
|
||||
// We add previous APIMethods so we have access to the Resource Docs
|
||||
@ -52,15 +65,8 @@ trait APIMethods140 extends Loggable with APIMethods130 with APIMethods121{
|
||||
val exampleDate = simpleDateFormat.parse(exampleDateString)
|
||||
|
||||
|
||||
def getResourceDocsList : Option[List[ResourceDoc]] =
|
||||
{
|
||||
// Get the Resource Docs for this and previous versions of the API
|
||||
val cumulativeResourceDocs = resourceDocs ++ Implementations1_3_0.resourceDocs ++ Implementations1_2_1.resourceDocs
|
||||
// Sort by endpoint, verb. Thus / is shown first then /accounts and /banks etc. Seems to read quite well like that.
|
||||
Some(cumulativeResourceDocs.toList.sortBy(rd => (rd.requestUrl, rd.requestVerb)))
|
||||
}
|
||||
|
||||
resourceDocs += ResourceDoc(
|
||||
getCustomer,
|
||||
apiVersion,
|
||||
"getCustomer",
|
||||
"GET",
|
||||
@ -71,14 +77,17 @@ trait APIMethods140 extends Loggable with APIMethods130 with APIMethods121{
|
||||
|Authentication via OAuth is required.""",
|
||||
emptyObjectJson,
|
||||
emptyObjectJson,
|
||||
emptyObjectJson :: Nil)
|
||||
emptyObjectJson :: Nil,
|
||||
false,
|
||||
false,
|
||||
List(apiTagCustomer))
|
||||
|
||||
lazy val getCustomer : PartialFunction[Req, Box[User] => Box[JsonResponse]] = {
|
||||
case "banks" :: BankId(bankId) :: "customer" :: Nil JsonGet _ => {
|
||||
user => {
|
||||
for {
|
||||
u <- user ?~! "User must be logged in to retrieve Customer"
|
||||
bank <- tryo(Bank(bankId).get) ?~! {"Unknown bank id"}
|
||||
u <- user ?~! ErrorMessages.UserNotLoggedIn
|
||||
bank <- tryo(Bank(bankId).get) ?~! {ErrorMessages.BankNotFound}
|
||||
info <- Customer.customerProvider.vend.getCustomer(bankId, u) ?~ "No customer information found for current user"
|
||||
} yield {
|
||||
val json = JSONFactory1_4_0.createCustomerJson(info)
|
||||
@ -89,6 +98,7 @@ trait APIMethods140 extends Loggable with APIMethods130 with APIMethods121{
|
||||
}
|
||||
|
||||
resourceDocs += ResourceDoc(
|
||||
getCustomerMessages,
|
||||
apiVersion,
|
||||
"getCustomerMessages",
|
||||
"GET",
|
||||
@ -99,14 +109,17 @@ trait APIMethods140 extends Loggable with APIMethods130 with APIMethods121{
|
||||
|Authentication via OAuth is required.""",
|
||||
emptyObjectJson,
|
||||
emptyObjectJson,
|
||||
emptyObjectJson :: Nil)
|
||||
emptyObjectJson :: Nil,
|
||||
false,
|
||||
false,
|
||||
List(apiTagCustomer))
|
||||
|
||||
lazy val getCustomerMessages : PartialFunction[Req, Box[User] => Box[JsonResponse]] = {
|
||||
case "banks" :: BankId(bankId) :: "customer" :: "messages" :: Nil JsonGet _ => {
|
||||
user => {
|
||||
for {
|
||||
u <- user ?~! "User must be logged in to retrieve customer messages"
|
||||
bank <- tryo(Bank(bankId).get) ?~! {"Unknown bank id"}
|
||||
u <- user ?~! ErrorMessages.UserNotLoggedIn
|
||||
bank <- tryo(Bank(bankId).get) ?~! {ErrorMessages.BankNotFound}
|
||||
//au <- APIUser.find(By(APIUser.id, u.apiId))
|
||||
//role <- au.isCustomerMessageAdmin ~> APIFailure("User does not have sufficient permissions", 401)
|
||||
} yield {
|
||||
@ -119,6 +132,7 @@ trait APIMethods140 extends Loggable with APIMethods130 with APIMethods121{
|
||||
}
|
||||
|
||||
resourceDocs += ResourceDoc(
|
||||
addCustomerMessage,
|
||||
apiVersion,
|
||||
"addCustomerMessage",
|
||||
"POST",
|
||||
@ -128,16 +142,18 @@ trait APIMethods140 extends Loggable with APIMethods130 with APIMethods121{
|
||||
// We use Extraction.decompose to convert to json
|
||||
Extraction.decompose(AddCustomerMessageJson("message to send", "from department", "from person")),
|
||||
emptyObjectJson,
|
||||
emptyObjectJson :: Nil
|
||||
emptyObjectJson :: Nil,
|
||||
false,
|
||||
false,
|
||||
List(apiTagCustomer)
|
||||
)
|
||||
|
||||
lazy val addCustomerMessage : PartialFunction[Req, Box[User] => Box[JsonResponse]] = {
|
||||
case "banks" :: BankId(bankId) :: "customer" :: customerNumber :: "messages" :: Nil JsonPost json -> _ => {
|
||||
val test = Bank(bankId)
|
||||
user => {
|
||||
for {
|
||||
postedData <- tryo{json.extract[AddCustomerMessageJson]} ?~! "Incorrect json format"
|
||||
bank <- tryo(Bank(bankId).get) ?~! {"Unknown bank id"}
|
||||
bank <- tryo(Bank(bankId).get) ?~! {ErrorMessages.BankNotFound}
|
||||
customer <- Customer.customerProvider.vend.getUser(bankId, customerNumber) ?~! "Customer not found"
|
||||
messageCreated <- booleanToBox(
|
||||
CustomerMessages.customerMessageProvider.vend.addMessage(
|
||||
@ -150,31 +166,41 @@ trait APIMethods140 extends Loggable with APIMethods130 with APIMethods121{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val getBranchesIsPublic = Props.getBool("apiOptions.getBranchesIsPublic", true)
|
||||
|
||||
resourceDocs += ResourceDoc(
|
||||
getBranches,
|
||||
apiVersion,
|
||||
"getBranches",
|
||||
"GET",
|
||||
"/banks/BANK_ID/branches",
|
||||
"Get branches for the bank",
|
||||
"""Returns information about branches for a single bank specified by BANK_ID including:
|
||||
s"""Returns information about branches for a single bank specified by BANK_ID including:
|
||||
|
|
||||
|* Name
|
||||
|* Address
|
||||
|* Geo Location
|
||||
|* License the data under this endpoint is released under
|
||||
|
|
||||
|Authentication via OAuth *may* be required.""",
|
||||
|${authenticationRequiredMessage(!getBranchesIsPublic)}""",
|
||||
emptyObjectJson,
|
||||
emptyObjectJson,
|
||||
emptyObjectJson :: Nil
|
||||
emptyObjectJson :: Nil,
|
||||
true,
|
||||
false,
|
||||
List(apiTagBanks)
|
||||
)
|
||||
|
||||
lazy val getBranches : PartialFunction[Req, Box[User] => Box[JsonResponse]] = {
|
||||
case "banks" :: BankId(bankId) :: "branches" :: Nil JsonGet _ => {
|
||||
user => {
|
||||
for {
|
||||
u <- user ?~! "User must be logged in to retrieve Branches data"
|
||||
bank <- tryo(Bank(bankId).get) ?~! {"Unknown bank id"}
|
||||
u <- if(getBranchesIsPublic)
|
||||
Box(Some(1))
|
||||
else
|
||||
user ?~! "User must be logged in to retrieve Branches data"
|
||||
bank <- tryo(Bank(bankId).get) ?~! {ErrorMessages.BankNotFound}
|
||||
// Get branches from the active provider
|
||||
branches <- Box(Branches.branchesProvider.vend.getBranches(bankId)) ~> APIFailure("No branches available. License may not be set.", 204)
|
||||
} yield {
|
||||
@ -187,23 +213,28 @@ trait APIMethods140 extends Loggable with APIMethods130 with APIMethods121{
|
||||
}
|
||||
|
||||
|
||||
val getAtmsIsPublic = Props.getBool("apiOptions.getAtmsIsPublic", true)
|
||||
|
||||
resourceDocs += ResourceDoc(
|
||||
getAtms,
|
||||
apiVersion,
|
||||
"getAtms",
|
||||
"GET",
|
||||
"/banks/BANK_ID/atms",
|
||||
"Get ATMS for the bank",
|
||||
"""Returns information about ATMs for a single bank specified by BANK_ID including:
|
||||
s"""Returns information about ATMs for a single bank specified by BANK_ID including:
|
||||
|
|
||||
|* Address
|
||||
|* Geo Location
|
||||
|* License the data under this endpoint is released under
|
||||
|
|
||||
|Authentication via OAuth *may* be required.""",
|
||||
|${authenticationRequiredMessage(!getAtmsIsPublic)}""",
|
||||
emptyObjectJson,
|
||||
emptyObjectJson,
|
||||
emptyObjectJson :: Nil
|
||||
emptyObjectJson :: Nil,
|
||||
true,
|
||||
false,
|
||||
List(apiTagBanks)
|
||||
)
|
||||
|
||||
lazy val getAtms : PartialFunction[Req, Box[User] => Box[JsonResponse]] = {
|
||||
@ -211,8 +242,12 @@ trait APIMethods140 extends Loggable with APIMethods130 with APIMethods121{
|
||||
user => {
|
||||
for {
|
||||
// Get atms from the active provider
|
||||
u <- user ?~! "User must be logged in to retrieve ATM data"
|
||||
bank <- tryo(Bank(bankId).get) ?~! {"Unknown bank id"}
|
||||
|
||||
u <- if(getAtmsIsPublic)
|
||||
Box(Some(1))
|
||||
else
|
||||
user ?~! "User must be logged in to retrieve ATM data"
|
||||
bank <- tryo(Bank(bankId).get) ?~! {ErrorMessages.BankNotFound}
|
||||
atms <- Box(Atms.atmsProvider.vend.getAtms(bankId)) ~> APIFailure("No ATMs available. License may not be set.", 204)
|
||||
} yield {
|
||||
// Format the data as json
|
||||
@ -225,13 +260,17 @@ trait APIMethods140 extends Loggable with APIMethods130 with APIMethods121{
|
||||
}
|
||||
|
||||
|
||||
val getProductsIsPublic = Props.getBool("apiOptions.getProductsIsPublic", true)
|
||||
|
||||
|
||||
resourceDocs += ResourceDoc(
|
||||
getProducts,
|
||||
apiVersion,
|
||||
"getProducts",
|
||||
"GET",
|
||||
"/banks/BANK_ID/products",
|
||||
"Get products offered by the bank",
|
||||
"""Returns information about financial products offered by a bank specified by BANK_ID including:
|
||||
s"""Returns information about financial products offered by a bank specified by BANK_ID including:
|
||||
|
|
||||
|* Name
|
||||
|* Code
|
||||
@ -241,10 +280,14 @@ trait APIMethods140 extends Loggable with APIMethods130 with APIMethods121{
|
||||
|* More info URL
|
||||
|* Description
|
||||
|* Terms and Conditions
|
||||
|* License the data under this endpoint is released under""",
|
||||
|* License the data under this endpoint is released under
|
||||
|${authenticationRequiredMessage(!getProductsIsPublic)}""",
|
||||
emptyObjectJson,
|
||||
emptyObjectJson,
|
||||
emptyObjectJson :: Nil
|
||||
emptyObjectJson :: Nil,
|
||||
true,
|
||||
false,
|
||||
List(apiTagBanks)
|
||||
)
|
||||
|
||||
lazy val getProducts : PartialFunction[Req, Box[User] => Box[JsonResponse]] = {
|
||||
@ -252,8 +295,11 @@ trait APIMethods140 extends Loggable with APIMethods130 with APIMethods121{
|
||||
user => {
|
||||
for {
|
||||
// Get products from the active provider
|
||||
u <- user ?~! "User must be logged in to retrieve Products data"
|
||||
bank <- tryo(Bank(bankId).get) ?~! {"Unknown bank id"}
|
||||
u <- if(getProductsIsPublic)
|
||||
Box(Some(1))
|
||||
else
|
||||
user ?~! "User must be logged in to retrieve Products data"
|
||||
bank <- tryo(Bank(bankId).get) ?~! {ErrorMessages.BankNotFound}
|
||||
products <- Box(Products.productsProvider.vend.getProducts(bankId)) ~> APIFailure("No products available. License may not be set.", 204)
|
||||
} yield {
|
||||
// Format the data as json
|
||||
@ -267,6 +313,7 @@ trait APIMethods140 extends Loggable with APIMethods130 with APIMethods121{
|
||||
|
||||
|
||||
resourceDocs += ResourceDoc(
|
||||
getCrmEvents,
|
||||
apiVersion,
|
||||
"getCrmEvents",
|
||||
"GET",
|
||||
@ -275,7 +322,10 @@ trait APIMethods140 extends Loggable with APIMethods130 with APIMethods121{
|
||||
"",
|
||||
emptyObjectJson,
|
||||
emptyObjectJson,
|
||||
emptyObjectJson :: Nil
|
||||
emptyObjectJson :: Nil,
|
||||
false,
|
||||
false,
|
||||
List(apiTagCustomer)
|
||||
)
|
||||
|
||||
lazy val getCrmEvents : PartialFunction[Req, Box[User] => Box[JsonResponse]] = {
|
||||
@ -284,7 +334,7 @@ trait APIMethods140 extends Loggable with APIMethods130 with APIMethods121{
|
||||
for {
|
||||
// Get crm events from the active provider
|
||||
u <- user ?~! "User must be logged in to retrieve CRM Event information"
|
||||
bank <- tryo(Bank(bankId).get) ?~! {"Unknown bank id"}
|
||||
bank <- tryo(Bank(bankId).get) ?~! {ErrorMessages.BankNotFound}
|
||||
crmEvents <- Box(CrmEvent.crmEventProvider.vend.getCrmEvents(bankId)) ~> APIFailure("No CRM Events available.", 204)
|
||||
} yield {
|
||||
// Format the data as json
|
||||
@ -295,40 +345,12 @@ trait APIMethods140 extends Loggable with APIMethods130 with APIMethods121{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resourceDocs += ResourceDoc(
|
||||
apiVersion,
|
||||
"getResourceDocs",
|
||||
"GET",
|
||||
"/resource-docs/obp",
|
||||
"Get Resource Documentation in OBP format.",
|
||||
"Returns documentation about the resources on this server including example body for POST or PUT requests.",
|
||||
emptyObjectJson,
|
||||
emptyObjectJson,
|
||||
emptyObjectJson :: Nil
|
||||
)
|
||||
|
||||
// Provides resource documents so that live docs (currently on Sofi) can display API documentation
|
||||
lazy val getResourceDocs : PartialFunction[Req, Box[User] => Box[JsonResponse]] = {
|
||||
case "resource-docs" :: "obp" :: Nil JsonGet _ => {
|
||||
user => {
|
||||
for {
|
||||
rd <- getResourceDocsList
|
||||
} yield {
|
||||
// Format the data as json
|
||||
val json = JSONFactory1_4_0.createResourceDocsJson(rd)
|
||||
// Return
|
||||
successJsonResponse(Extraction.decompose(json))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
transaction requests (new payments since 1.4.0)
|
||||
*/
|
||||
|
||||
resourceDocs += ResourceDoc(
|
||||
getTransactionRequestTypes,
|
||||
apiVersion,
|
||||
"getTransactionRequestTypes",
|
||||
"GET",
|
||||
@ -337,7 +359,10 @@ trait APIMethods140 extends Loggable with APIMethods130 with APIMethods121{
|
||||
"",
|
||||
emptyObjectJson,
|
||||
emptyObjectJson,
|
||||
emptyObjectJson :: Nil)
|
||||
emptyObjectJson :: Nil,
|
||||
true,
|
||||
true,
|
||||
List(apiTagPayment))
|
||||
|
||||
lazy val getTransactionRequestTypes: PartialFunction[Req, Box[User] => Box[JsonResponse]] = {
|
||||
case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "transaction-request-types" ::
|
||||
@ -345,8 +370,8 @@ trait APIMethods140 extends Loggable with APIMethods130 with APIMethods121{
|
||||
user =>
|
||||
if (Props.getBool("transactionRequests_enabled", false)) {
|
||||
for {
|
||||
u <- user ?~ "User not found"
|
||||
fromBank <- tryo(Bank(bankId).get) ?~! {"Unknown bank id"}
|
||||
u <- user ?~ ErrorMessages.UserNotLoggedIn
|
||||
fromBank <- tryo(Bank(bankId).get) ?~! {ErrorMessages.BankNotFound}
|
||||
fromAccount <- tryo(BankAccount(bankId, accountId).get) ?~ {"Unknown bank account"}
|
||||
view <- tryo(fromAccount.permittedViews(user).find(_ == viewId)) ?~ {"Current user does not have access to the view " + viewId}
|
||||
transactionRequestTypes <- Connector.connector.vend.getTransactionRequestTypes(u, fromAccount)
|
||||
@ -361,6 +386,7 @@ trait APIMethods140 extends Loggable with APIMethods130 with APIMethods121{
|
||||
}
|
||||
|
||||
resourceDocs += ResourceDoc(
|
||||
getTransactionRequests,
|
||||
apiVersion,
|
||||
"getTransactionRequests",
|
||||
"GET",
|
||||
@ -369,16 +395,19 @@ trait APIMethods140 extends Loggable with APIMethods130 with APIMethods121{
|
||||
"",
|
||||
emptyObjectJson,
|
||||
emptyObjectJson,
|
||||
emptyObjectJson :: Nil)
|
||||
emptyObjectJson :: Nil,
|
||||
true,
|
||||
true,
|
||||
List(apiTagPayment))
|
||||
|
||||
lazy val getTransactionRequests: PartialFunction[Req, Box[User] => Box[JsonResponse]] = {
|
||||
case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "transaction-requests" :: Nil JsonGet _ => {
|
||||
user =>
|
||||
if (Props.getBool("transactionRequests_enabled", false)) {
|
||||
for {
|
||||
u <- user ?~ "User not found"
|
||||
fromBank <- tryo(Bank(bankId).get) ?~! {"Unknown bank id"}
|
||||
fromAccount <- tryo(BankAccount(bankId, accountId).get) ?~! {"Unknown bank account"}
|
||||
u <- user ?~ ErrorMessages.UserNotLoggedIn
|
||||
fromBank <- tryo(Bank(bankId).get) ?~! {ErrorMessages.BankNotFound}
|
||||
fromAccount <- tryo(BankAccount(bankId, accountId).get) ?~! {ErrorMessages.AccountNotFound}
|
||||
view <- tryo(fromAccount.permittedViews(user).find(_ == viewId)) ?~ {"Current user does not have access to the view " + viewId}
|
||||
transactionRequests <- Connector.connector.vend.getTransactionRequests(u, fromAccount)
|
||||
}
|
||||
@ -397,12 +426,19 @@ trait APIMethods140 extends Loggable with APIMethods130 with APIMethods121{
|
||||
case class TransactionIdJson(transaction_id : String)
|
||||
|
||||
resourceDocs += ResourceDoc(
|
||||
createTransactionRequest,
|
||||
apiVersion,
|
||||
"createTransactionRequest",
|
||||
"POST",
|
||||
"/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/transaction-request-types/TRANSACTION_REQUEST_TYPE/transaction-requests",
|
||||
"Create Transaction Request.",
|
||||
"",
|
||||
"""Initiate a Payment via a Transaction Request.
|
||||
|
|
||||
|This is the preferred method to create a payment and supersedes makePayment in 1.2.1.
|
||||
|
|
||||
|See [this python code](https://github.com/OpenBankProject/Hello-OBP-DirectLogin-Python/blob/master/hello_payments.py) for a complete example of this flow.
|
||||
|
|
||||
|In sandbox mode, if the amount is < 100 the transaction request will create a transaction without a challenge, else a challenge will need to be answered.""",
|
||||
Extraction.decompose(TransactionRequestBodyJSON (
|
||||
TransactionRequestAccountJSON("BANK_ID", "ACCOUNT_ID"),
|
||||
AmountOfMoneyJSON("EUR", "100.53"),
|
||||
@ -411,7 +447,10 @@ trait APIMethods140 extends Loggable with APIMethods130 with APIMethods121{
|
||||
)
|
||||
),
|
||||
emptyObjectJson,
|
||||
emptyObjectJson :: Nil)
|
||||
emptyObjectJson :: Nil,
|
||||
true,
|
||||
true,
|
||||
List(apiTagPayment))
|
||||
|
||||
lazy val createTransactionRequest: PartialFunction[Req, Box[User] => Box[JsonResponse]] = {
|
||||
case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "transaction-request-types" ::
|
||||
@ -423,14 +462,14 @@ trait APIMethods140 extends Loggable with APIMethods130 with APIMethods121{
|
||||
* check if user has access using the view that is given (now it checks if user has access to owner view), will need some new permissions for transaction requests
|
||||
* test: functionality, error messages if user not given or invalid, if any other value is not existing
|
||||
*/
|
||||
u <- user ?~ "User not found"
|
||||
transBodyJson <- tryo{json.extract[TransactionRequestBodyJSON]} ?~ {"Invalid json format"}
|
||||
u <- user ?~ ErrorMessages.UserNotLoggedIn
|
||||
transBodyJson <- tryo{json.extract[TransactionRequestBodyJSON]} ?~ {ErrorMessages.InvalidJsonFormat}
|
||||
transBody <- tryo{getTransactionRequestBodyFromJson(transBodyJson)}
|
||||
fromBank <- tryo(Bank(bankId).get) ?~! {"Unknown bank id"}
|
||||
fromAccount <- tryo(BankAccount(bankId, accountId).get) ?~! {"Unknown bank account"}
|
||||
fromBank <- tryo(Bank(bankId).get) ?~! {ErrorMessages.BankNotFound}
|
||||
fromAccount <- tryo(BankAccount(bankId, accountId).get) ?~! {ErrorMessages.AccountNotFound}
|
||||
toBankId <- tryo(BankId(transBodyJson.to.bank_id))
|
||||
toAccountId <- tryo(AccountId(transBodyJson.to.account_id))
|
||||
toAccount <- tryo{BankAccount(toBankId, toAccountId).get} ?~! {"Unknown counterparty account"}
|
||||
toAccount <- tryo{BankAccount(toBankId, toAccountId).get} ?~! {ErrorMessages.CounterpartyNotFound}
|
||||
accountsCurrencyEqual <- tryo(assert(fromAccount.currency == toAccount.currency)) ?~! {"Counterparty and holder accounts have differing currencies."}
|
||||
transferCurrencyEqual <- tryo(assert(transBodyJson.value.currency == fromAccount.currency)) ?~! {"Request currency and holder account currency can't be different."}
|
||||
rawAmt <- tryo {BigDecimal(transBodyJson.value.amount)} ?~! s"Amount ${transBodyJson.value.amount} not convertible to number"
|
||||
@ -448,15 +487,19 @@ trait APIMethods140 extends Loggable with APIMethods130 with APIMethods121{
|
||||
|
||||
|
||||
resourceDocs += ResourceDoc(
|
||||
answerTransactionRequestChallenge,
|
||||
apiVersion,
|
||||
"answerTransactionRequestChallenge",
|
||||
"POST",
|
||||
"/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/transaction-request-types/TRANSACTION_REQUEST_TYPE/transaction-requests/TRANSACTION_REQUEST_ID/challenge",
|
||||
"Answer Transaction Request Challenge.",
|
||||
"",
|
||||
"In Sandbox mode, any string that can be converted to a possitive integer will be accepted as an answer.",
|
||||
Extraction.decompose(ChallengeAnswerJSON("89123812", "123345")),
|
||||
emptyObjectJson,
|
||||
emptyObjectJson :: Nil)
|
||||
emptyObjectJson :: Nil,
|
||||
true,
|
||||
true,
|
||||
List(apiTagPayment))
|
||||
|
||||
lazy val answerTransactionRequestChallenge: PartialFunction[Req, Box[User] => Box[JsonResponse]] = {
|
||||
case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "transaction-request-types" ::
|
||||
@ -464,8 +507,8 @@ trait APIMethods140 extends Loggable with APIMethods130 with APIMethods121{
|
||||
user =>
|
||||
if (Props.getBool("transactionRequests_enabled", false)) {
|
||||
for {
|
||||
u <- user ?~ "User not found"
|
||||
fromBank <- tryo(Bank(bankId).get) ?~! {"Unknown bank id"}
|
||||
u <- user ?~ ErrorMessages.UserNotLoggedIn
|
||||
fromBank <- tryo(Bank(bankId).get) ?~! {ErrorMessages.BankNotFound}
|
||||
fromAccount <- tryo(BankAccount(bankId, accountId).get) ?~! {"Unknown bank account"}
|
||||
view <- tryo(fromAccount.permittedViews(user).find(_ == viewId)) ?~ {"Current user does not have access to the view " + viewId}
|
||||
answerJson <- tryo{json.extract[ChallengeAnswerJSON]} ?~ {"Invalid json format"}
|
||||
@ -487,20 +530,26 @@ trait APIMethods140 extends Loggable with APIMethods130 with APIMethods121{
|
||||
|
||||
|
||||
resourceDocs += ResourceDoc(
|
||||
addCustomer,
|
||||
apiVersion,
|
||||
"addCustomer",
|
||||
"POST",
|
||||
"/banks/BANK_ID/customer",
|
||||
"Add a customer.",
|
||||
"""Add a customer linked to the currently authenticated user.
|
||||
|This call is experimental and will require additional permissions/role in the future.
|
||||
|The Customer resource stores the customer number, legal name, email, phone number, their date of birth, relationship status, education attained, a url for a profile image, KYC status etc.
|
||||
|This call may require additional permissions/role in the future.
|
||||
|For now the authenticated user can create at most one linked customer.
|
||||
|OAuth authentication is required.
|
||||
|""",
|
||||
Extraction.decompose(CustomerJson("687687678", "Joe David Bloggs",
|
||||
"+44 07972 444 876", "person@example.com", CustomerFaceImageJson("www.example.com/person/123/image.png", exampleDate))),
|
||||
"+44 07972 444 876", "person@example.com", CustomerFaceImageJson("www.example.com/person/123/image.png", exampleDate),
|
||||
exampleDate, "Single", 1, List(exampleDate), "Bachelor’s Degree", "Employed", true, exampleDate)),
|
||||
emptyObjectJson,
|
||||
emptyObjectJson :: Nil)
|
||||
emptyObjectJson :: Nil,
|
||||
false,
|
||||
false,
|
||||
List(apiTagCustomer))
|
||||
|
||||
lazy val addCustomer : PartialFunction[Req, Box[User] => Box[JsonResponse]] = {
|
||||
//updates a view on a bank account
|
||||
@ -508,7 +557,7 @@ trait APIMethods140 extends Loggable with APIMethods130 with APIMethods121{
|
||||
user =>
|
||||
for {
|
||||
u <- user ?~! "User must be logged in to post Customer"
|
||||
bank <- tryo(Bank(bankId).get) ?~! {"Unknown bank id"}
|
||||
bank <- tryo(Bank(bankId).get) ?~! {ErrorMessages.BankNotFound}
|
||||
customer <- booleanToBox(Customer.customerProvider.vend.getCustomer(bankId, u).isEmpty) ?~ "Customer already exists for this user."
|
||||
postedData <- tryo{json.extract[CustomerJson]} ?~! "Incorrect json format"
|
||||
customer <- Customer.customerProvider.vend.addCustomer(bankId,
|
||||
@ -517,7 +566,15 @@ trait APIMethods140 extends Loggable with APIMethods130 with APIMethods121{
|
||||
postedData.legal_name,
|
||||
postedData.mobile_phone_number,
|
||||
postedData.email,
|
||||
MockCustomerFaceImage(postedData.face_image.date, postedData.face_image.url)) ?~! "Could not create customer"
|
||||
MockCustomerFaceImage(postedData.face_image.date, postedData.face_image.url),
|
||||
postedData.date_of_birth,
|
||||
postedData.relationship_status,
|
||||
postedData.dependants,
|
||||
postedData.dob_of_dependants,
|
||||
postedData.highest_education_attained,
|
||||
postedData.employment_status,
|
||||
postedData.kyc_status,
|
||||
postedData.last_ok_date) ?~! "Could not create customer"
|
||||
} yield {
|
||||
val successJson = Extraction.decompose(customer)
|
||||
successJsonResponse(successJson)
|
||||
@ -529,10 +586,11 @@ trait APIMethods140 extends Loggable with APIMethods130 with APIMethods121{
|
||||
|
||||
if (Props.devMode) {
|
||||
resourceDocs += ResourceDoc(
|
||||
dummy(apiVersion),
|
||||
apiVersion,
|
||||
"testResourceDoc",
|
||||
"GET",
|
||||
"/i-do-not-exist-i-will-404",
|
||||
"/dummy",
|
||||
"I am only a test resource Doc",
|
||||
"""
|
||||
|
|
||||
@ -562,7 +620,26 @@ trait APIMethods140 extends Loggable with APIMethods130 with APIMethods121{
|
||||
|_etc_...""",
|
||||
emptyObjectJson,
|
||||
emptyObjectJson,
|
||||
emptyObjectJson :: Nil)
|
||||
emptyObjectJson :: Nil,
|
||||
false,
|
||||
false,
|
||||
Nil)
|
||||
}
|
||||
|
||||
|
||||
|
||||
def dummy(apiVersion : String) : PartialFunction[Req, Box[User] => Box[JsonResponse]] = {
|
||||
case "dummy" :: Nil JsonGet json => {
|
||||
user =>
|
||||
val apiDetails: JValue = {
|
||||
val hostedBy = new HostedBy("TESOBE", "contact@tesobe.com", "+49 (0)30 8145 3994")
|
||||
val apiInfoJSON = new APIInfoJSON(apiVersion, gitCommit, hostedBy)
|
||||
Extraction.decompose(apiInfoJSON)
|
||||
}
|
||||
|
||||
Full(successJsonResponse(apiDetails, 200))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,13 +17,25 @@ import code.transactionrequests.TransactionRequests._
|
||||
import net.liftweb.json.JsonAST.{JValue, JObject}
|
||||
import org.pegdown.PegDownProcessor
|
||||
|
||||
// Use this so we don't duplicate it
|
||||
import code.api.v1_2_1.{AmountOfMoneyJSON}
|
||||
|
||||
|
||||
object JSONFactory1_4_0 {
|
||||
|
||||
case class CustomerJson(customer_number : String,
|
||||
legal_name : String,
|
||||
mobile_phone_number : String,
|
||||
email : String,
|
||||
face_image : CustomerFaceImageJson)
|
||||
face_image : CustomerFaceImageJson,
|
||||
date_of_birth: Date,
|
||||
relationship_status: String,
|
||||
dependants: Int,
|
||||
dob_of_dependants: List[Date],
|
||||
highest_education_attained: String,
|
||||
employment_status: String,
|
||||
kyc_status: Boolean,
|
||||
last_ok_date: Date)
|
||||
|
||||
case class CustomerFaceImageJson(url : String, date : Date)
|
||||
|
||||
@ -77,7 +89,15 @@ object JSONFactory1_4_0 {
|
||||
mobile_phone_number = cInfo.mobileNumber,
|
||||
email = cInfo.email,
|
||||
face_image = CustomerFaceImageJson(url = cInfo.faceImage.url,
|
||||
date = cInfo.faceImage.date)
|
||||
date = cInfo.faceImage.date),
|
||||
date_of_birth = cInfo.dateOfBirth,
|
||||
relationship_status = cInfo.relationshipStatus,
|
||||
dependants = cInfo.dependents,
|
||||
dob_of_dependants = cInfo.dobOfDependents,
|
||||
highest_education_attained = cInfo.highestEducationAttained,
|
||||
employment_status = cInfo.employmentStatus,
|
||||
kyc_status = cInfo.kycStatus,
|
||||
last_ok_date = cInfo.lastOkDate
|
||||
)
|
||||
|
||||
}
|
||||
@ -231,14 +251,18 @@ object JSONFactory1_4_0 {
|
||||
description: String,
|
||||
example_request_body: JValue,
|
||||
success_response_body: JValue,
|
||||
implemented_by: ImplementedByJson)
|
||||
implemented_by: ImplementedByJson,
|
||||
is_core: Boolean,
|
||||
is_psd2: Boolean,
|
||||
is_obwg: Boolean,
|
||||
tags: List[String])
|
||||
|
||||
|
||||
|
||||
// Creates the json resource_docs
|
||||
case class ResourceDocsJson (resource_docs : List[ResourceDocJson])
|
||||
|
||||
def createResourceDocJson(resourceDoc: ResourceDoc) : ResourceDocJson = {
|
||||
def createResourceDocJson(rd: ResourceDoc) : ResourceDocJson = {
|
||||
|
||||
// There are multiple flavours of markdown. For instance, original markdown emphasises underscores (surrounds _ with (<em>))
|
||||
// But we don't want to have to escape underscores (\_) in our documentation
|
||||
@ -250,15 +274,19 @@ object JSONFactory1_4_0 {
|
||||
val pegDownProcessor : PegDownProcessor = new PegDownProcessor
|
||||
|
||||
ResourceDocJson(
|
||||
operation_id = s"${resourceDoc.apiVersion.toString}-${resourceDoc.apiFunction.toString}",
|
||||
request_verb = resourceDoc.requestVerb,
|
||||
request_url = resourceDoc.requestUrl,
|
||||
summary = resourceDoc.summary,
|
||||
operation_id = s"${rd.apiVersion.toString}-${rd.apiFunction.toString}",
|
||||
request_verb = rd.requestVerb,
|
||||
request_url = rd.requestUrl,
|
||||
summary = rd.summary,
|
||||
// Strip the margin character (|) and line breaks and convert from markdown to html
|
||||
description = pegDownProcessor.markdownToHtml(resourceDoc.description.stripMargin).replaceAll("\n", ""),
|
||||
example_request_body = resourceDoc.exampleRequestBody,
|
||||
success_response_body = resourceDoc.successResponseBody,
|
||||
implemented_by = ImplementedByJson(resourceDoc.apiVersion, resourceDoc.apiFunction)
|
||||
description = pegDownProcessor.markdownToHtml(rd.description.stripMargin).replaceAll("\n", ""),
|
||||
example_request_body = rd.exampleRequestBody,
|
||||
success_response_body = rd.successResponseBody,
|
||||
implemented_by = ImplementedByJson(rd.apiVersion, rd.apiFunction),
|
||||
is_core = rd.isCore,
|
||||
is_psd2 = rd.isPSD2,
|
||||
is_obwg = rd.isCore, // For now, track isCore
|
||||
tags = rd.tags.map(i => i.tag)
|
||||
)
|
||||
}
|
||||
|
||||
@ -309,10 +337,6 @@ object JSONFactory1_4_0 {
|
||||
)
|
||||
}
|
||||
|
||||
case class AmountOfMoneyJSON (
|
||||
currency : String,
|
||||
amount : String
|
||||
)
|
||||
case class TransactionRequestAccountJSON (
|
||||
bank_id: String,
|
||||
account_id : String
|
||||
|
||||
@ -18,6 +18,7 @@ object OBPAPI1_4_0 extends OBPRestHelper with APIMethods140 with Loggable {
|
||||
Implementations1_2_1.privateAccountsAtOneBank,
|
||||
Implementations1_2_1.publicAccountsAtOneBank,
|
||||
Implementations1_2_1.accountById,
|
||||
Implementations1_2_1.updateAccountLabel,
|
||||
Implementations1_2_1.getViewsForBankAccount,
|
||||
Implementations1_2_1.createViewForBankAccount,
|
||||
Implementations1_2_1.updateViewForBankAccount,
|
||||
@ -77,7 +78,7 @@ object OBPAPI1_4_0 extends OBPRestHelper with APIMethods140 with Loggable {
|
||||
Implementations1_2_1.updateWhereTagForViewOnTransaction,
|
||||
Implementations1_2_1.deleteWhereTagForViewOnTransaction,
|
||||
Implementations1_2_1.getCounterpartyForTransaction,
|
||||
//note: removed 1.2.1 makePayment
|
||||
Implementations1_2_1.makePayment, // Back for a while
|
||||
|
||||
// New in 1.3.0
|
||||
Implementations1_3_0.getCards,
|
||||
@ -92,7 +93,6 @@ object OBPAPI1_4_0 extends OBPRestHelper with APIMethods140 with Loggable {
|
||||
Implementations1_4_0.getAtms,
|
||||
Implementations1_4_0.getProducts,
|
||||
Implementations1_4_0.getCrmEvents,
|
||||
Implementations1_4_0.getResourceDocs,
|
||||
Implementations1_4_0.createTransactionRequest,
|
||||
Implementations1_4_0.getTransactionRequests,
|
||||
Implementations1_4_0.getTransactionRequestTypes,
|
||||
|
||||
1014
src/main/scala/code/api/v2_0_0/APIMethods200.scala
Normal file
1014
src/main/scala/code/api/v2_0_0/APIMethods200.scala
Normal file
File diff suppressed because it is too large
Load Diff
438
src/main/scala/code/api/v2_0_0/JSONFactory2.0.0.scala
Normal file
438
src/main/scala/code/api/v2_0_0/JSONFactory2.0.0.scala
Normal file
@ -0,0 +1,438 @@
|
||||
/**
|
||||
Open Bank Project - API
|
||||
Copyright (C) 2011-2015, TESOBE / Music Pictures Ltd
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Email: contact@tesobe.com
|
||||
TESOBE / Music Pictures Ltd
|
||||
Osloerstrasse 16/17
|
||||
Berlin 13359, Germany
|
||||
|
||||
This product includes software developed at
|
||||
TESOBE (http://www.tesobe.com/)
|
||||
by
|
||||
Simon Redfern : simon AT tesobe DOT com
|
||||
Stefan Bethge : stefan AT tesobe DOT com
|
||||
Everett Sochowski : everett AT tesobe DOT com
|
||||
Ayoub Benali: ayoub AT tesobe DOT com
|
||||
|
||||
*/
|
||||
package code.api.v2_0_0
|
||||
|
||||
import java.net.URL
|
||||
import java.util.Date
|
||||
import code.api.util.APIUtil.ApiLink
|
||||
import code.kycdocuments.KycDocument
|
||||
import code.kycmedias.KycMedia
|
||||
import code.kycstatuses.KycStatus
|
||||
import code.kycchecks.KycCheck
|
||||
import code.socialmedia.SocialMedia
|
||||
import net.liftweb.common.{Box, Full}
|
||||
import code.model._
|
||||
import net.liftweb.json.JsonAST.JValue
|
||||
|
||||
// Import explicitly and rename so its clear.
|
||||
import code.api.v1_2_1.{AccountHolderJSON => AccountHolderJSON121, AmountOfMoneyJSON => AmountOfMoneyJSON121, UserJSON => UserJSON121, ViewJSON => ViewJSON121, ThisAccountJSON => ThisAccountJSON121, OtherAccountJSON => OtherAccountJSON121, TransactionDetailsJSON => TransactionDetailsJSON121, JSONFactory => JSONFactory121, MinimalBankJSON => MinimalBankJSON121}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// New in 2.0.0
|
||||
|
||||
class LinkJSON(
|
||||
val href: URL,
|
||||
val rel: String,
|
||||
val method: String
|
||||
)
|
||||
|
||||
class LinksJSON(
|
||||
val _links: List[LinkJSON]
|
||||
)
|
||||
|
||||
class ResultAndLinksJSON(
|
||||
val result : JValue,
|
||||
val links: LinksJSON
|
||||
)
|
||||
|
||||
|
||||
|
||||
class BasicViewJSON(
|
||||
val id: String,
|
||||
val short_name: String,
|
||||
val is_public: Boolean
|
||||
)
|
||||
|
||||
case class BasicAccountsJSON(
|
||||
accounts : List[BasicAccountJSON]
|
||||
)
|
||||
|
||||
// Basic Account has basic View
|
||||
case class BasicAccountJSON(
|
||||
id : String,
|
||||
label : String,
|
||||
views_available : List[BasicViewJSON],
|
||||
bank_id : String
|
||||
)
|
||||
|
||||
|
||||
// Json used in account creation
|
||||
case class CreateAccountJSON(
|
||||
`type` : String,
|
||||
balance : AmountOfMoneyJSON121
|
||||
)
|
||||
|
||||
// No view in core
|
||||
case class CoreAccountJSON(
|
||||
id : String,
|
||||
label : String,
|
||||
bank_id : String,
|
||||
_links: JValue
|
||||
)
|
||||
|
||||
case class KycDocumentJSON(
|
||||
id: String,
|
||||
customer_number: String,
|
||||
`type`: String,
|
||||
number: String,
|
||||
issue_date: Date,
|
||||
issue_place: String,
|
||||
expiry_date: Date
|
||||
)
|
||||
case class KycDocumentsJSON(documents: List[KycDocumentJSON])
|
||||
|
||||
case class KycMediaJSON(
|
||||
id: String,
|
||||
customer_number: String,
|
||||
`type`: String,
|
||||
url: String,
|
||||
date: Date,
|
||||
relates_to_kyc_document_id: String,
|
||||
relates_to_kyc_check_id: String
|
||||
)
|
||||
case class KycMediasJSON(medias: List[KycMediaJSON])
|
||||
|
||||
case class KycCheckJSON(
|
||||
id: String,
|
||||
customer_number: String,
|
||||
date: Date,
|
||||
how: String,
|
||||
staff_user_id: String,
|
||||
staff_name: String,
|
||||
satisfied: Boolean,
|
||||
comments: String
|
||||
)
|
||||
case class KycChecksJSON(checks: List[KycCheckJSON])
|
||||
|
||||
case class KycStatusJSON(
|
||||
customer_number: String,
|
||||
ok: Boolean,
|
||||
date: Date
|
||||
)
|
||||
case class KycStatusesJSON(statuses: List[KycStatusJSON])
|
||||
|
||||
case class SocialMediaJSON(
|
||||
customer_number: String,
|
||||
`type`: String,
|
||||
handle: String,
|
||||
date_added: Date,
|
||||
date_activated: Date
|
||||
)
|
||||
case class SocialMediasJSON(checks: List[SocialMediaJSON])
|
||||
|
||||
object JSONFactory200{
|
||||
|
||||
|
||||
// New in 2.0.0
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def createViewBasicJSON(view : View) : BasicViewJSON = {
|
||||
val alias =
|
||||
if(view.usePublicAliasIfOneExists)
|
||||
"public"
|
||||
else if(view.usePrivateAliasIfOneExists)
|
||||
"private"
|
||||
else
|
||||
""
|
||||
|
||||
new BasicViewJSON(
|
||||
id = view.viewId.value,
|
||||
short_name = stringOrNull(view.name),
|
||||
is_public = view.isPublic
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
def createBasicAccountJSON(account : BankAccount, basicViewsAvailable : List[BasicViewJSON] ) : BasicAccountJSON = {
|
||||
new BasicAccountJSON(
|
||||
account.accountId.value,
|
||||
stringOrNull(account.label),
|
||||
basicViewsAvailable,
|
||||
account.bankId.value
|
||||
)
|
||||
}
|
||||
|
||||
// Contains only minimal info (could have more if owner) plus links
|
||||
def createCoreAccountJSON(account : BankAccount, links: JValue ) : CoreAccountJSON = {
|
||||
val coreAccountJson = new CoreAccountJSON(
|
||||
account.accountId.value,
|
||||
stringOrNull(account.label),
|
||||
account.bankId.value,
|
||||
links
|
||||
)
|
||||
coreAccountJson
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
case class ModeratedCoreAccountJSON(
|
||||
id : String,
|
||||
label : String,
|
||||
number : String,
|
||||
owners : List[UserJSON121],
|
||||
`type` : String,
|
||||
balance : AmountOfMoneyJSON121,
|
||||
IBAN : String,
|
||||
swift_bic: String,
|
||||
bank_id : String
|
||||
)
|
||||
|
||||
|
||||
////
|
||||
|
||||
|
||||
case class CoreTransactionsJSON(
|
||||
transactions: List[CoreTransactionJSON]
|
||||
)
|
||||
|
||||
case class CoreTransactionJSON(
|
||||
id : String,
|
||||
account : ThisAccountJSON121,
|
||||
counterparty : CoreCounterpartyJSON,
|
||||
details : CoreTransactionDetailsJSON
|
||||
)
|
||||
|
||||
|
||||
|
||||
case class CoreAccountHolderJSON(
|
||||
name : String
|
||||
)
|
||||
|
||||
|
||||
case class CoreCounterpartyJSON(
|
||||
id : String,
|
||||
holder : CoreAccountHolderJSON,
|
||||
number : String,
|
||||
kind : String,
|
||||
IBAN : String,
|
||||
swift_bic: String,
|
||||
bank : MinimalBankJSON121
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
def createCoreTransactionsJSON(transactions: List[ModeratedTransaction]) : CoreTransactionsJSON = {
|
||||
new CoreTransactionsJSON(transactions.map(createCoreTransactionJSON))
|
||||
}
|
||||
|
||||
case class CoreTransactionDetailsJSON(
|
||||
`type` : String,
|
||||
description : String,
|
||||
posted : Date,
|
||||
completed : Date,
|
||||
new_balance : AmountOfMoneyJSON121,
|
||||
value : AmountOfMoneyJSON121
|
||||
)
|
||||
|
||||
|
||||
|
||||
def createCoreTransactionDetailsJSON(transaction : ModeratedTransaction) : CoreTransactionDetailsJSON = {
|
||||
new CoreTransactionDetailsJSON(
|
||||
`type` = stringOptionOrNull(transaction.transactionType),
|
||||
description = stringOptionOrNull(transaction.description),
|
||||
posted = transaction.startDate.getOrElse(null),
|
||||
completed = transaction.finishDate.getOrElse(null),
|
||||
new_balance = JSONFactory121.createAmountOfMoneyJSON(transaction.currency, transaction.balance),
|
||||
value= JSONFactory121.createAmountOfMoneyJSON(transaction.currency, transaction.amount.map(_.toString))
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
def createCoreTransactionJSON(transaction : ModeratedTransaction) : CoreTransactionJSON = {
|
||||
new CoreTransactionJSON(
|
||||
id = transaction.id.value,
|
||||
account = transaction.bankAccount.map(JSONFactory121.createThisAccountJSON).getOrElse(null),
|
||||
counterparty = transaction.otherBankAccount.map(createCoreCounterparty).getOrElse(null),
|
||||
details = createCoreTransactionDetailsJSON(transaction)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
case class CounterpartiesJSON(
|
||||
counterparties : List[CoreCounterpartyJSON]
|
||||
)
|
||||
|
||||
|
||||
def createCoreCounterparty(bankAccount : ModeratedOtherBankAccount) : CoreCounterpartyJSON = {
|
||||
new CoreCounterpartyJSON(
|
||||
id = bankAccount.id,
|
||||
number = stringOptionOrNull(bankAccount.number),
|
||||
kind = stringOptionOrNull(bankAccount.kind),
|
||||
IBAN = stringOptionOrNull(bankAccount.iban),
|
||||
swift_bic = stringOptionOrNull(bankAccount.swift_bic),
|
||||
bank = JSONFactory121.createMinimalBankJSON(bankAccount),
|
||||
holder = createAccountHolderJSON(bankAccount.label.display, bankAccount.isAlias)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
def createAccountHolderJSON(owner : User, isAlias : Boolean) : CoreAccountHolderJSON = {
|
||||
// Note we are not using isAlias
|
||||
new CoreAccountHolderJSON(
|
||||
name = owner.name
|
||||
)
|
||||
}
|
||||
|
||||
def createAccountHolderJSON(name : String, isAlias : Boolean) : CoreAccountHolderJSON = {
|
||||
// Note we are not using isAlias
|
||||
new CoreAccountHolderJSON(
|
||||
name = name
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
def createCoreBankAccountJSON(account : ModeratedBankAccount, viewsAvailable : List[ViewJSON121]) : ModeratedCoreAccountJSON = {
|
||||
val bankName = account.bankName.getOrElse("")
|
||||
new ModeratedCoreAccountJSON (
|
||||
account.accountId.value,
|
||||
JSONFactory121.stringOptionOrNull(account.label),
|
||||
JSONFactory121.stringOptionOrNull(account.number),
|
||||
JSONFactory121.createOwnersJSON(account.owners.getOrElse(Set()), bankName),
|
||||
JSONFactory121.stringOptionOrNull(account.accountType),
|
||||
JSONFactory121.createAmountOfMoneyJSON(account.currency.getOrElse(""), account.balance),
|
||||
JSONFactory121.stringOptionOrNull(account.iban),
|
||||
JSONFactory121.stringOptionOrNull(account.swift_bic),
|
||||
stringOrNull(account.bankId.value)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def createKycDocumentJSON(kycDocument : KycDocument) : KycDocumentJSON = {
|
||||
new KycDocumentJSON(
|
||||
id = kycDocument.idKycDocument,
|
||||
customer_number = kycDocument.customerNumber,
|
||||
`type` = kycDocument.`type`,
|
||||
number = kycDocument.number,
|
||||
issue_date = kycDocument.issueDate,
|
||||
issue_place = kycDocument.issuePlace,
|
||||
expiry_date = kycDocument.expiryDate
|
||||
)
|
||||
}
|
||||
|
||||
def createKycDocumentsJSON(messages : List[KycDocument]) : KycDocumentsJSON = {
|
||||
KycDocumentsJSON(messages.map(createKycDocumentJSON))
|
||||
}
|
||||
|
||||
def createKycMediaJSON(kycMedia : KycMedia) : KycMediaJSON = {
|
||||
new KycMediaJSON(
|
||||
id = kycMedia.idKycMedia,
|
||||
customer_number = kycMedia.customerNumber,
|
||||
`type` = kycMedia.`type`,
|
||||
url = kycMedia.url,
|
||||
date = kycMedia.date,
|
||||
relates_to_kyc_document_id = kycMedia.relatesToKycDocumentId,
|
||||
relates_to_kyc_check_id = kycMedia.relatesToKycCheckId
|
||||
)
|
||||
}
|
||||
def createKycMediasJSON(messages : List[KycMedia]) : KycMediasJSON = {
|
||||
KycMediasJSON(messages.map(createKycMediaJSON))
|
||||
}
|
||||
|
||||
def createKycCheckJSON(kycCheck : KycCheck) : KycCheckJSON = {
|
||||
new KycCheckJSON(
|
||||
id = kycCheck.idKycCheck,
|
||||
customer_number = kycCheck.customerNumber,
|
||||
date = kycCheck.date,
|
||||
how = kycCheck.how,
|
||||
staff_user_id = kycCheck.staffUserId,
|
||||
staff_name = kycCheck.staffName,
|
||||
satisfied = kycCheck.satisfied,
|
||||
comments = kycCheck.comments
|
||||
)
|
||||
}
|
||||
def createKycChecksJSON(messages : List[KycCheck]) : KycChecksJSON = {
|
||||
KycChecksJSON(messages.map(createKycCheckJSON))
|
||||
}
|
||||
|
||||
def createKycStatusJSON(kycStatus : KycStatus) : KycStatusJSON = {
|
||||
new KycStatusJSON(
|
||||
customer_number = kycStatus.customerNumber,
|
||||
ok = kycStatus.ok,
|
||||
date = kycStatus.date
|
||||
)
|
||||
}
|
||||
def createKycStatusesJSON(messages : List[KycStatus]) : KycStatusesJSON = {
|
||||
KycStatusesJSON(messages.map(createKycStatusJSON))
|
||||
}
|
||||
|
||||
def createSocialMediaJSON(socialMedia : SocialMedia) : SocialMediaJSON = {
|
||||
new SocialMediaJSON(
|
||||
customer_number = socialMedia.customerNumber,
|
||||
`type` = socialMedia.`type`,
|
||||
handle = socialMedia.handle,
|
||||
date_added = socialMedia.dateAdded,
|
||||
date_activated = socialMedia.dateActivated
|
||||
)
|
||||
}
|
||||
def createSocialMediasJSON(messages : List[SocialMedia]) : SocialMediasJSON = {
|
||||
SocialMediasJSON(messages.map(createSocialMediaJSON))
|
||||
}
|
||||
|
||||
// Copied from 1.2.1 (import just this def instead?)
|
||||
def stringOrNull(text : String) =
|
||||
if(text == null || text.isEmpty)
|
||||
null
|
||||
else
|
||||
text
|
||||
|
||||
// Copied from 1.2.1 (import just this def instead?)
|
||||
def stringOptionOrNull(text : Option[String]) =
|
||||
text match {
|
||||
case Some(t) => stringOrNull(t)
|
||||
case _ => null
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
175
src/main/scala/code/api/v2_0_0/OBPAPI2_0_0.scala
Normal file
175
src/main/scala/code/api/v2_0_0/OBPAPI2_0_0.scala
Normal file
@ -0,0 +1,175 @@
|
||||
/**
|
||||
Open Bank Project - API
|
||||
Copyright (C) 2011-2015, TESOBE / Music Pictures Ltd
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Email: contact@tesobe.com
|
||||
TESOBE / Music Pictures Ltd
|
||||
Osloerstrasse 16/17
|
||||
Berlin 13359, Germany
|
||||
|
||||
This product includes software developed at
|
||||
TESOBE (http://www.tesobe.com/)
|
||||
by
|
||||
Simon Redfern : simon AT tesobe DOT com
|
||||
Stefan Bethge : stefan AT tesobe DOT com
|
||||
Everett Sochowski : everett AT tesobe DOT com
|
||||
Ayoub Benali: ayoub AT tesobe DOT com
|
||||
|
||||
*/
|
||||
package code.api.v2_0_0
|
||||
|
||||
import code.api.v1_3_0.APIMethods130
|
||||
import code.api.v1_4_0.{APIMethods140}
|
||||
import net.liftweb.json.Extraction
|
||||
import net.liftweb.json.JsonAST._
|
||||
import net.liftweb.common.Loggable
|
||||
import code.api.OBPRestHelper
|
||||
|
||||
object OBPAPI2_0_0 extends OBPRestHelper with APIMethods130 with APIMethods140 with APIMethods200 with Loggable {
|
||||
|
||||
|
||||
val VERSION = "2.0.0"
|
||||
|
||||
|
||||
// Note: Since we pattern match on these routes, if two implementations match a given url the first will match
|
||||
|
||||
val routes = List(
|
||||
Implementations1_2_1.root(VERSION),
|
||||
Implementations1_2_1.allBanks,
|
||||
Implementations1_2_1.bankById,
|
||||
// Now in 2_0_0
|
||||
// Implementations1_2_1.allAccountsAllBanks,
|
||||
// Implementations1_2_1.privateAccountsAllBanks,
|
||||
// Implementations1_2_1.publicAccountsAllBanks,
|
||||
// Implementations1_2_1.allAccountsAtOneBank,
|
||||
// Implementations1_2_1.privateAccountsAtOneBank,
|
||||
// Implementations1_2_1.publicAccountsAtOneBank,
|
||||
// Implementations1_2_1.accountById,
|
||||
Implementations1_2_1.updateAccountLabel,
|
||||
Implementations1_2_1.getViewsForBankAccount,
|
||||
Implementations1_2_1.createViewForBankAccount,
|
||||
Implementations1_2_1.updateViewForBankAccount,
|
||||
Implementations1_2_1.deleteViewForBankAccount,
|
||||
// Implementations1_2_1.getPermissionsForBankAccount,
|
||||
// Implementations1_2_1.getPermissionForUserForBankAccount,
|
||||
Implementations1_2_1.addPermissionForUserForBankAccountForMultipleViews,
|
||||
Implementations1_2_1.addPermissionForUserForBankAccountForOneView,
|
||||
Implementations1_2_1.removePermissionForUserForBankAccountForOneView,
|
||||
Implementations1_2_1.removePermissionForUserForBankAccountForAllViews,
|
||||
Implementations1_2_1.getCounterpartiesForBankAccount,
|
||||
Implementations1_2_1.getCounterpartyByIdForBankAccount,
|
||||
Implementations1_2_1.getCounterpartyMetadata,
|
||||
Implementations1_2_1.getCounterpartyPublicAlias,
|
||||
Implementations1_2_1.addCounterpartyPublicAlias,
|
||||
Implementations1_2_1.updateCounterpartyPublicAlias,
|
||||
Implementations1_2_1.deleteCounterpartyPublicAlias,
|
||||
Implementations1_2_1.getCounterpartyPrivateAlias,
|
||||
Implementations1_2_1.addCounterpartyPrivateAlias,
|
||||
Implementations1_2_1.updateCounterpartyPrivateAlias,
|
||||
Implementations1_2_1.deleteCounterpartyPrivateAlias,
|
||||
Implementations1_2_1.addCounterpartyMoreInfo,
|
||||
Implementations1_2_1.updateCounterpartyMoreInfo,
|
||||
Implementations1_2_1.deleteCounterpartyMoreInfo,
|
||||
Implementations1_2_1.addCounterpartyUrl,
|
||||
Implementations1_2_1.updateCounterpartyUrl,
|
||||
Implementations1_2_1.deleteCounterpartyUrl,
|
||||
Implementations1_2_1.addCounterpartyImageUrl,
|
||||
Implementations1_2_1.updateCounterpartyImageUrl,
|
||||
Implementations1_2_1.deleteCounterpartyImageUrl,
|
||||
Implementations1_2_1.addCounterpartyOpenCorporatesUrl,
|
||||
Implementations1_2_1.updateCounterpartyOpenCorporatesUrl,
|
||||
Implementations1_2_1.deleteCounterpartyOpenCorporatesUrl,
|
||||
Implementations1_2_1.addCounterpartyCorporateLocation,
|
||||
Implementations1_2_1.updateCounterpartyCorporateLocation,
|
||||
Implementations1_2_1.deleteCounterpartyCorporateLocation,
|
||||
Implementations1_2_1.addCounterpartyPhysicalLocation,
|
||||
Implementations1_2_1.updateCounterpartyPhysicalLocation,
|
||||
Implementations1_2_1.deleteCounterpartyPhysicalLocation,
|
||||
Implementations1_2_1.getTransactionsForBankAccount,
|
||||
Implementations1_2_1.getTransactionByIdForBankAccount,
|
||||
Implementations1_2_1.getTransactionNarrative,
|
||||
Implementations1_2_1.addTransactionNarrative,
|
||||
Implementations1_2_1.updateTransactionNarrative,
|
||||
Implementations1_2_1.deleteTransactionNarrative,
|
||||
Implementations1_2_1.getCommentsForViewOnTransaction,
|
||||
Implementations1_2_1.addCommentForViewOnTransaction,
|
||||
Implementations1_2_1.deleteCommentForViewOnTransaction,
|
||||
Implementations1_2_1.getTagsForViewOnTransaction,
|
||||
Implementations1_2_1.addTagForViewOnTransaction,
|
||||
Implementations1_2_1.deleteTagForViewOnTransaction,
|
||||
Implementations1_2_1.getImagesForViewOnTransaction,
|
||||
Implementations1_2_1.addImageForViewOnTransaction,
|
||||
Implementations1_2_1.deleteImageForViewOnTransaction,
|
||||
Implementations1_2_1.getWhereTagForViewOnTransaction,
|
||||
Implementations1_2_1.addWhereTagForViewOnTransaction,
|
||||
Implementations1_2_1.updateWhereTagForViewOnTransaction,
|
||||
Implementations1_2_1.deleteWhereTagForViewOnTransaction,
|
||||
Implementations1_2_1.getCounterpartyForTransaction,
|
||||
Implementations1_2_1.makePayment,
|
||||
|
||||
// New in 1.3.0
|
||||
Implementations1_3_0.getCards,
|
||||
Implementations1_3_0.getCardsForBank,
|
||||
|
||||
// New in 1.4.0
|
||||
Implementations1_4_0.getCustomer,
|
||||
Implementations1_4_0.addCustomer,
|
||||
Implementations1_4_0.getCustomerMessages,
|
||||
Implementations1_4_0.addCustomerMessage,
|
||||
Implementations1_4_0.getBranches,
|
||||
Implementations1_4_0.getAtms,
|
||||
Implementations1_4_0.getProducts,
|
||||
Implementations1_4_0.getCrmEvents,
|
||||
Implementations1_4_0.createTransactionRequest,
|
||||
Implementations1_4_0.getTransactionRequests,
|
||||
Implementations1_4_0.getTransactionRequestTypes,
|
||||
Implementations1_4_0.answerTransactionRequestChallenge,
|
||||
|
||||
// Modified in 2.0.0 (less info about the views)
|
||||
Implementations2_0_0.allAccountsAllBanks,
|
||||
Implementations2_0_0.privateAccountsAllBanks,
|
||||
Implementations2_0_0.publicAccountsAllBanks,
|
||||
Implementations2_0_0.allAccountsAtOneBank,
|
||||
Implementations2_0_0.privateAccountsAtOneBank,
|
||||
Implementations2_0_0.publicAccountsAtOneBank,
|
||||
// Modified in 2.0.0 (added sorting and better guards / error messages)
|
||||
Implementations2_0_0.accountById,
|
||||
Implementations2_0_0.getPermissionsForBankAccount,
|
||||
Implementations2_0_0.getPermissionForUserForBankAccount,
|
||||
// New in 2.0.0
|
||||
Implementations2_0_0.getKycDocuments,
|
||||
Implementations2_0_0.getKycMedia,
|
||||
Implementations2_0_0.getKycStatuses,
|
||||
Implementations2_0_0.getKycChecks,
|
||||
Implementations2_0_0.getSocialMediaHandles,
|
||||
Implementations2_0_0.addKycDocument,
|
||||
Implementations2_0_0.addKycMedia,
|
||||
Implementations2_0_0.addKycStatus,
|
||||
Implementations2_0_0.addKycCheck,
|
||||
Implementations2_0_0.addSocialMediaHandle,
|
||||
Implementations2_0_0.getCoreAccountById,
|
||||
Implementations2_0_0.getCoreTransactionsForBankAccount,
|
||||
Implementations2_0_0.createAccount
|
||||
//Implementations2_0_0.testLinks
|
||||
)
|
||||
|
||||
routes.foreach(route => {
|
||||
oauthServe(apiPrefix{route})
|
||||
})
|
||||
|
||||
|
||||
|
||||
}
|
||||
@ -134,7 +134,7 @@ trait Connector {
|
||||
//set initial status
|
||||
//for sandbox / testing: depending on amount, we ask for challenge or not
|
||||
val status =
|
||||
if (transactionRequestType.value == "SANDBOX" && body.value.currency == "EUR" && BigDecimal(body.value.amount) < 100) {
|
||||
if (transactionRequestType.value == TransactionRequests.CHALLENGE_SANDBOX_TAN && BigDecimal(body.value.amount) < 100) {
|
||||
TransactionRequests.STATUS_COMPLETED
|
||||
} else {
|
||||
TransactionRequests.STATUS_INITIATED
|
||||
@ -345,4 +345,4 @@ trait Connector {
|
||||
|
||||
def updateAccountLabel(bankId: BankId, accountId: AccountId, label: String): Boolean
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,305 +0,0 @@
|
||||
package code.bankconnectors
|
||||
|
||||
/*
|
||||
Open Bank Project - API
|
||||
Copyright (C) 2011-2015, TESOBE / Music Pictures Ltd
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
|
||||
Email: contact@tesobe.com
|
||||
TESOBE / Music Pictures Ltd
|
||||
Osloerstrasse 16/17
|
||||
Berlin 13359, Germany
|
||||
*/
|
||||
|
||||
|
||||
import java.text.{SimpleDateFormat, DateFormat}
|
||||
import java.util.{Date, UUID, TimeZone, Locale, Properties}
|
||||
|
||||
import code.model._
|
||||
import code.util.Helper
|
||||
import net.liftweb.common.{Loggable, Empty, Full, Box, Failure}
|
||||
import net.liftweb.mapper._
|
||||
import net.liftweb.util.Helpers._
|
||||
import net.liftweb.util.{False, Props}
|
||||
|
||||
import scala.concurrent.ops._
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import kafka.utils.{ZkUtils, ZKStringSerializer}
|
||||
import org.I0Itec.zkclient.ZkClient
|
||||
import kafka.consumer.Consumer
|
||||
import kafka.consumer._
|
||||
import kafka.consumer.KafkaStream
|
||||
import kafka.message._
|
||||
import kafka.producer.{KeyedMessage, Producer, ProducerConfig}
|
||||
|
||||
import code.model.dataAccess.{Account, MappedBank}
|
||||
import code.model.Transaction
|
||||
|
||||
object KafkaConnector extends Connector with Loggable {
|
||||
|
||||
|
||||
|
||||
//gets banks handled by this connector
|
||||
override def getBanks: List[Bank] = {
|
||||
// Generate random uuid to be used as request-respose match id
|
||||
val reqId: String = UUID.randomUUID().toString
|
||||
|
||||
// Create Kafka producer, using list of brokers from Zookeeper
|
||||
val producer: KafkaProducer = new KafkaProducer()
|
||||
// Send request to Kafka, marked with reqId
|
||||
// so we can fetch the corresponding response
|
||||
val argList: Map[String, String] = Map()
|
||||
|
||||
producer.send(reqId, "getBanks", argList, "1")
|
||||
|
||||
// Request sent, now we wait for response with the same reqId
|
||||
val consumer = new KafkaConsumer()
|
||||
val rList = consumer.getResponse(reqId)
|
||||
// Loop through list of responses and create entry for each
|
||||
val res = { for ( r <- rList ) yield {
|
||||
MappedBank.create
|
||||
.permalink(r.getOrElse("permalink", ""))
|
||||
.fullBankName(r.getOrElse("fullBankName", ""))
|
||||
.shortBankName(r.getOrElse("shortBankName", ""))
|
||||
.logoURL(r.getOrElse("logoURL", ""))
|
||||
.websiteURL(r.getOrElse("websiteURL", ""))
|
||||
}
|
||||
}
|
||||
// Return list of results
|
||||
res
|
||||
}
|
||||
|
||||
|
||||
// Gets bank identified by id
|
||||
override def getBank(bankId: code.model.BankId): Box[Bank] = {
|
||||
// Generate random uuid to be used as request-respose match id
|
||||
val reqId: String = UUID.randomUUID().toString
|
||||
|
||||
// Create Kafka producer, using list of brokers from Zookeeper
|
||||
val producer: KafkaProducer = new KafkaProducer()
|
||||
// Send request to Kafka, marked with reqId
|
||||
// so we can fetch the corresponding response
|
||||
val argList = Map( "bankId" -> bankId.toString )
|
||||
|
||||
producer.send(reqId, "getBank", argList, "1")
|
||||
|
||||
// Request sent, now we wait for response with the same reqId
|
||||
val consumer = new KafkaConsumer()
|
||||
// Create entry only for the first item on returned list
|
||||
val r = consumer.getResponse(reqId).head
|
||||
val res = MappedBank.create
|
||||
.permalink(r.getOrElse("permalink", ""))
|
||||
.fullBankName(r.getOrElse("fullBankName", ""))
|
||||
.shortBankName(r.getOrElse("shortBankName", ""))
|
||||
.logoURL(r.getOrElse("logoURL", ""))
|
||||
.websiteURL(r.getOrElse("websiteURL", ""))
|
||||
// Return result
|
||||
Full(res)
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Gets bank account identified by bankid and accountid
|
||||
override def getBankAccount(bankId : BankId, accountId : AccountId) : Box[BankAccount] = {
|
||||
// Generate random uuid to be used as request-respose match id
|
||||
val reqId: String = UUID.randomUUID().toString
|
||||
|
||||
// Create Kafka producer, using list of brokers from Zookeeper
|
||||
val producer: KafkaProducer = new KafkaProducer()
|
||||
// Send request to Kafka, marked with reqId
|
||||
// so we can fetch the corresponding response
|
||||
val argList = Map( "bankId" -> bankId.toString,
|
||||
"accountId" -> accountId.toString )
|
||||
|
||||
producer.send(reqId, "getBankAccount", argList, "1")
|
||||
|
||||
// Request sent, now we wait for response with the same reqId
|
||||
val consumer = new KafkaConsumer()
|
||||
// Create entry only for the first item on returned list
|
||||
val r = consumer.getResponse(reqId).head
|
||||
val res = new BankAccount {
|
||||
val accountId = AccountId(r.getOrElse("accountId", ""));
|
||||
val bankId = BankId(r.getOrElse("bankId", ""));
|
||||
val uuid = r.getOrElse("uuid", "");
|
||||
val accountHolder = r.getOrElse("accountHolder", "");
|
||||
val accountType = r.getOrElse("accountType", "");
|
||||
val currency = r.getOrElse("currency", "");
|
||||
val label = r.getOrElse("label", "");
|
||||
val name = r.getOrElse("name", "");
|
||||
val number = r.getOrElse("number", "");
|
||||
val balance = BigDecimal(r.getOrElse("balance", "0.0"));
|
||||
val swift_bic = Some(r.getOrElse("swift_bic", ""));
|
||||
val iban = Some(r.getOrElse("iban", ""));
|
||||
val lastUpdate = new SimpleDateFormat("EEE MMMM d HH:mm:ss z yyyy", Locale.ENGLISH).parse(r.getOrElse("lastUpdate", "Thu Jan 1 00:00:00 UTC 1970"))
|
||||
}
|
||||
|
||||
// Return list of results
|
||||
Full(res)
|
||||
}
|
||||
|
||||
// Gets transaction identified by bankid, accountid and transactionId
|
||||
def getTransaction(bankId: BankId, accountId: AccountId, transactionId: TransactionId): Box[Transaction] = {
|
||||
// Generate random uuid to be used as request-respose match id
|
||||
val reqId: String = UUID.randomUUID().toString
|
||||
|
||||
// Create Kafka producer, using list of brokers from Zookeeper
|
||||
val producer: KafkaProducer = new KafkaProducer()
|
||||
// Send request to Kafka, marked with reqId
|
||||
// so we can fetch the corresponding response
|
||||
val argList = Map( "bankId" -> bankId.toString,
|
||||
"accountId" -> accountId.toString,
|
||||
"transactionId" -> transactionId.toString )
|
||||
|
||||
producer.send(reqId, "getTransaction", argList, "1")
|
||||
|
||||
// Request sent, now we wait for response with the same reqId
|
||||
val consumer = new KafkaConsumer()
|
||||
// Create entry only for the first item on returned list
|
||||
val r = consumer.getResponse(reqId).head
|
||||
|
||||
// helper for creating otherbankaccount
|
||||
def createOtherBankAccount(alreadyFoundMetadata : Option[OtherBankAccountMetadata]) = {
|
||||
new OtherBankAccount(
|
||||
id = alreadyFoundMetadata.map(_.metadataId).getOrElse(""),
|
||||
label = r.getOrElse("label", ""),
|
||||
nationalIdentifier = r.getOrElse("nationalIdentifier ", ""),
|
||||
swift_bic = Some(r.getOrElse("swift_bic", "")), //TODO: need to add this to the json/model
|
||||
iban = Some(r.getOrElse("iban", "")),
|
||||
number = r.getOrElse("number", ""),
|
||||
bankName = r.getOrElse("bankName", ""),
|
||||
kind = r.getOrElse("accountType", ""),
|
||||
originalPartyBankId = new BankId(r.getOrElse("bankId", "")),
|
||||
originalPartyAccountId = new AccountId(r.getOrElse("accountId", "")),
|
||||
alreadyFoundMetadata = alreadyFoundMetadata
|
||||
)
|
||||
}
|
||||
|
||||
//creates a dummy OtherBankAccount without an OtherBankAccountMetadata, which results in one being generated (in OtherBankAccount init)
|
||||
val dummyOtherBankAccount = createOtherBankAccount(None)
|
||||
//and create the proper OtherBankAccount with the correct "id" attribute set to the metadataId of the OtherBankAccountMetadata object
|
||||
//note: as we are passing in the OtherBankAccountMetadata we don't incur another db call to get it in OtherBankAccount init
|
||||
val otherAccount = createOtherBankAccount(Some(dummyOtherBankAccount.metadata))
|
||||
|
||||
Full(
|
||||
new Transaction(
|
||||
TransactionId(r.getOrElse("accountId", "")).value, // uuid:String
|
||||
TransactionId(r.getOrElse("accountId", "")), // id:TransactionId
|
||||
getBankAccount(BankId(r.getOrElse("bankId", "")), AccountId(r.getOrElse("accountId", ""))).openOr(null), // thisAccount:BankAccount
|
||||
otherAccount, // otherAccount:OtherBankAccount
|
||||
r.getOrElse("transactionType", ""), // transactionType:String
|
||||
BigDecimal(r.getOrElse("amount", "0.0")), // val amount:BigDecimal
|
||||
r.getOrElse("currency", ""), // currency:String
|
||||
Some(r.getOrElse("description", "")), // description:Option[String]
|
||||
new SimpleDateFormat("EEE MMMM d HH:mm:ss z yyyy", Locale.ENGLISH).parse(r.getOrElse("startDate", "Thu Jan 1 00:00:00 UTC 1970")), // startDate:Date
|
||||
new SimpleDateFormat("EEE MMMM d HH:mm:ss z yyyy", Locale.ENGLISH).parse(r.getOrElse("finishDate", "Thu Jan 1 00:00:00 UTC 1970")), // finishDate:Date
|
||||
BigDecimal(r.getOrElse("balance", "0.0")) // balance:BigDecimal
|
||||
))
|
||||
}
|
||||
|
||||
// Gets transactions identified by bankid, accountid and filtered by queryparams
|
||||
def getTransactions(bankId: BankId, accountId: AccountId, queryParams: OBPQueryParam*): Box[List[Transaction]] = {
|
||||
// Generate random uuid to be used as request-respose match id
|
||||
val reqId: String = UUID.randomUUID().toString
|
||||
|
||||
// Create Kafka producer, using list of brokers from Zookeeper
|
||||
val producer: KafkaProducer = new KafkaProducer()
|
||||
// Send request to Kafka, marked with reqId
|
||||
// so we can fetch the corresponding response
|
||||
val argList = Map( "bankId" -> bankId.toString,
|
||||
"accountId" -> accountId.toString,
|
||||
"queryParams" -> queryParams.toString )
|
||||
|
||||
producer.send(reqId, "getTransactions", argList, "1")
|
||||
|
||||
// Request sent, now we wait for response with the same reqId
|
||||
val consumer = new KafkaConsumer()
|
||||
// Create entry only for the first item on returned list
|
||||
val rList = consumer.getResponse(reqId)
|
||||
|
||||
// Loop through list of responses and create entry for each
|
||||
val res = { for ( r <- rList ) yield {
|
||||
|
||||
// helper for creating otherbankaccount
|
||||
def createOtherBankAccount(alreadyFoundMetadata : Option[OtherBankAccountMetadata]) = {
|
||||
// TODO Can we improve this? Remove duplication / have a more direct mapping of objects on the queue?
|
||||
new OtherBankAccount(
|
||||
id = alreadyFoundMetadata.map(_.metadataId).getOrElse(""),
|
||||
label = r.getOrElse("label", ""),
|
||||
nationalIdentifier = r.getOrElse("nationalIdentifier ", ""),
|
||||
swift_bic = Some(r.getOrElse("swift_bic", "")), //TODO: need to add this to the json/model
|
||||
iban = Some(r.getOrElse("iban", "")),
|
||||
number = r.getOrElse("number", ""),
|
||||
bankName = r.getOrElse("bankName", ""),
|
||||
kind = r.getOrElse("accountType", ""),
|
||||
originalPartyBankId = new BankId(r.getOrElse("bankId", "")),
|
||||
originalPartyAccountId = new AccountId(r.getOrElse("accountId", "")),
|
||||
alreadyFoundMetadata = alreadyFoundMetadata
|
||||
)
|
||||
}
|
||||
|
||||
//creates a dummy OtherBankAccount without an OtherBankAccountMetadata, which results in one being generated (in OtherBankAccount init)
|
||||
val dummyOtherBankAccount = createOtherBankAccount(None)
|
||||
//and create the proper OtherBankAccount with the correct "id" attribute set to the metadataId of the OtherBankAccountMetadata object
|
||||
//note: as we are passing in the OtherBankAccountMetadata we don't incur another db call to get it in OtherBankAccount init
|
||||
val otherAccount = createOtherBankAccount(Some(dummyOtherBankAccount.metadata))
|
||||
|
||||
new Transaction(
|
||||
TransactionId(r.getOrElse("accountId", "")).value, // uuid:String
|
||||
TransactionId(r.getOrElse("accountId", "")), // id:TransactionId
|
||||
getBankAccount(BankId(r.getOrElse("bankId", "")), AccountId(r.getOrElse("accountId", ""))).openOr(null), // thisAccount:BankAccount
|
||||
otherAccount, // otherAccount:OtherBankAccount
|
||||
r.getOrElse("transactionType", ""), // transactionType:String
|
||||
BigDecimal(r.getOrElse("amount", "0.0")), // val amount:BigDecimal
|
||||
r.getOrElse("currency", ""), // currency:String
|
||||
Some(r.getOrElse("description", "")), // description:Option[String]
|
||||
new SimpleDateFormat("EEE MMMM d HH:mm:ss z yyyy", Locale.ENGLISH).parse(r.getOrElse("startDate", "Thu Jan 1 00:00:00 UTC 1970")), // startDate:Date
|
||||
new SimpleDateFormat("EEE MMMM d HH:mm:ss z yyyy", Locale.ENGLISH).parse(r.getOrElse("finishDate", "Thu Jan 1 00:00:00 UTC 1970")), // finishDate:Date
|
||||
BigDecimal(r.getOrElse("balance", "0.0")) // balance:BigDecimal
|
||||
)
|
||||
}
|
||||
}
|
||||
Full(res)
|
||||
}
|
||||
|
||||
|
||||
def getBankAccountType(bankId: code.model.BankId, accountId: code.model.AccountId): net.liftweb.common.Box[code.bankconnectors.KafkaConnector.AccountType] = ???
|
||||
def accountExists(bankId: code.model.BankId,accountNumber: String): Boolean = ???
|
||||
def addCashTransactionAndUpdateBalance(account: code.bankconnectors.KafkaConnector.AccountType,cashTransaction: code.tesobe.CashTransaction): Unit = ???
|
||||
def createBankAndAccount(bankName: String,bankNationalIdentifier: String,accountNumber: String,accountHolderName: String): (code.model.Bank, code.model.BankAccount) = ???
|
||||
def createImportedTransaction(transaction: code.management.ImporterAPI.ImporterTransaction): net.liftweb.common.Box[code.model.Transaction] = ???
|
||||
def createSandboxBankAccount(bankId: code.model.BankId,accountId: code.model.AccountId,accountNumber: String,currency: String,initialBalance: BigDecimal,accountHolderName: String): net.liftweb.common.Box[code.model.BankAccount] = ???
|
||||
protected def createTransactionRequestImpl(transactionRequestId: code.model.TransactionRequestId,transactionRequestType: code.model.TransactionRequestType,fromAccount: code.model.BankAccount,counterparty: code.model.BankAccount,body: code.transactionrequests.TransactionRequests.TransactionRequestBody,status: String): net.liftweb.common.Box[code.transactionrequests.TransactionRequests.TransactionRequest] = ???
|
||||
def getAccountByUUID(uuid: String): net.liftweb.common.Box[code.bankconnectors.KafkaConnector.AccountType] = ???
|
||||
def getAccountHolders(bankId: code.model.BankId,accountID: code.model.AccountId): Set[code.model.User] = ???
|
||||
def getMatchingTransactionCount(bankNationalIdentifier: String,accountNumber: String,amount: String,completed: java.util.Date,otherAccountHolder: String): Int = ???
|
||||
def getOtherBankAccount(bankId: code.model.BankId,accountID: code.model.AccountId,otherAccountID: String): net.liftweb.common.Box[code.model.OtherBankAccount] = ???
|
||||
def getOtherBankAccounts(bankId: code.model.BankId,accountID: code.model.AccountId): List[code.model.OtherBankAccount] = ???
|
||||
def getPhysicalCards(user: code.model.User): Set[code.model.PhysicalCard] = ???
|
||||
def getPhysicalCardsForBank(bankId: code.model.BankId,user: code.model.User): Set[code.model.PhysicalCard] = ???
|
||||
protected def getTransactionRequestImpl(transactionRequestId: code.model.TransactionRequestId): net.liftweb.common.Box[code.transactionrequests.TransactionRequests.TransactionRequest] = ???
|
||||
protected def getTransactionRequestTypesImpl(fromAccount: code.model.BankAccount): net.liftweb.common.Box[List[code.model.TransactionRequestType]] = ???
|
||||
protected def getTransactionRequestsImpl(fromAccount: code.model.BankAccount): net.liftweb.common.Box[List[code.transactionrequests.TransactionRequests.TransactionRequest]] = ???
|
||||
protected def makePaymentImpl(fromAccount: code.bankconnectors.KafkaConnector.AccountType,toAccount: code.bankconnectors.KafkaConnector.AccountType,amt: BigDecimal,description: String): net.liftweb.common.Box[code.model.TransactionId] = ???
|
||||
def removeAccount(bankId: code.model.BankId,accountId: code.model.AccountId): Boolean = ???
|
||||
protected def saveTransactionRequestChallengeImpl(transactionRequestId: code.model.TransactionRequestId,challenge: code.transactionrequests.TransactionRequests.TransactionRequestChallenge): net.liftweb.common.Box[Boolean] = ???
|
||||
protected def saveTransactionRequestStatusImpl(transactionRequestId: code.model.TransactionRequestId,status: String): net.liftweb.common.Box[Boolean] = ???
|
||||
protected def saveTransactionRequestTransactionImpl(transactionRequestId: code.model.TransactionRequestId,transactionId: code.model.TransactionId): net.liftweb.common.Box[Boolean] = ???
|
||||
def setAccountHolder(bankAccountUID: code.model.BankAccountUID,user: code.model.User): Unit = ???
|
||||
def setBankAccountLastUpdated(bankNationalIdentifier: String,accountNumber: String,updateDate: java.util.Date): Boolean = ???
|
||||
def updateAccountBalance(bankId: code.model.BankId,accountId: code.model.AccountId,newBalance: BigDecimal): Boolean = ???
|
||||
def updateAccountLabel(bankId: code.model.BankId,accountId: code.model.AccountId,label: String): Boolean = ???
|
||||
}
|
||||
|
||||
@ -25,19 +25,13 @@ Berlin 13359, Germany
|
||||
|
||||
import java.util.{Properties, UUID}
|
||||
|
||||
import scala.concurrent.ops._
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import net.liftweb.util.Props
|
||||
|
||||
import kafka.utils.{ZkUtils, ZKStringSerializer}
|
||||
import org.I0Itec.zkclient.ZkClient
|
||||
import kafka.consumer.Consumer
|
||||
import kafka.consumer._
|
||||
import kafka.consumer.KafkaStream
|
||||
import kafka.consumer.{Consumer, _}
|
||||
import kafka.message._
|
||||
import kafka.producer.{KeyedMessage, Producer, ProducerConfig}
|
||||
|
||||
import kafka.utils.{Json, ZKStringSerializer, ZkUtils}
|
||||
import net.liftweb.json.DefaultFormats
|
||||
import net.liftweb.util.Props
|
||||
import org.I0Itec.zkclient.ZkClient
|
||||
|
||||
object ZooKeeperUtils {
|
||||
// gets brokers tracked by zookeeper
|
||||
@ -89,6 +83,7 @@ class KafkaConsumer(val zookeeper: String = Props.get("kafka.zookeeper_host")ope
|
||||
val config = new ConsumerConfig(props)
|
||||
config
|
||||
}
|
||||
|
||||
def getResponse(reqId: String): List[Map[String, String]] = {
|
||||
// create single stream for topic
|
||||
val topicCountMap = Map(topic -> 1)
|
||||
@ -107,12 +102,14 @@ class KafkaConsumer(val zookeeper: String = Props.get("kafka.zookeeper_host")ope
|
||||
if (key == reqId) {
|
||||
// disconnect from kafka
|
||||
shutdown()
|
||||
// split result if it contains multiple answers
|
||||
val msgList = msg.split("\\},\\{")
|
||||
// match '"<key>":"<value>"', with possible space after colon
|
||||
val p = """"([a-zA-Z0-9_-]*?)":"(.*?)"""".r
|
||||
val r = (for( m <- msgList) yield (for( p(k, v) <- p.findAllIn(m) ) yield (k -> v)).toMap[String, String]).toList
|
||||
return r;
|
||||
// Parse JSON message
|
||||
val json = Json.parseFull(msg)
|
||||
val r = json.get match {
|
||||
case l: List[Map[String,String]] => l.asInstanceOf[List[Map[String, String]]]
|
||||
case m: Map[String,String] => List(m.asInstanceOf[Map[String, String]])
|
||||
case _ => List(Map("error" -> "incorrect JSON format"))
|
||||
}
|
||||
return r
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -129,7 +126,7 @@ class KafkaConsumer(val zookeeper: String = Props.get("kafka.zookeeper_host")ope
|
||||
}
|
||||
}
|
||||
|
||||
import ZooKeeperUtils._
|
||||
import code.bankconnectors.ZooKeeperUtils._
|
||||
|
||||
case class KafkaProducer(
|
||||
topic: String = Props.get("kafka.request_topic").openOrThrowException("no kafka.request_topic set"),
|
||||
@ -170,11 +167,23 @@ case class KafkaProducer(
|
||||
}
|
||||
}
|
||||
|
||||
//case class Argument(name: String, value: String)
|
||||
|
||||
|
||||
case class Tweet(
|
||||
username: String,
|
||||
tweet: String,
|
||||
date: String
|
||||
)
|
||||
|
||||
|
||||
implicit val formats = DefaultFormats
|
||||
|
||||
def send(key: String, request: String, arguments: Map[String, String], partition: String = null): Unit = {
|
||||
// create string from named map of arguments
|
||||
val args = (for ( (k,v) <- arguments ) yield { s""""$k":"$v",""" }).mkString.replaceAll(",$", "")
|
||||
// create message using request and arguments strings
|
||||
val message = s"$request:{$args}"
|
||||
val reqArguments = arguments.map { args => Map(args._1 -> args._2) }
|
||||
val reqCommand = Map(request -> reqArguments)
|
||||
val message = Json.encode(reqCommand)
|
||||
// translate strings to utf8 before sending to kafka
|
||||
send(key.getBytes("UTF8"), message.getBytes("UTF8"), if (partition == null) null else partition.getBytes("UTF8"))
|
||||
}
|
||||
@ -189,4 +198,3 @@ case class KafkaProducer(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -106,6 +106,7 @@ object LocalMappedConnector extends Connector with Loggable {
|
||||
}
|
||||
}
|
||||
|
||||
// Question: Why is this called getBankAccountType? Why not getBankAccount?
|
||||
override def getBankAccountType(bankId: BankId, accountId: AccountId): Box[MappedBankAccount] = {
|
||||
MappedBankAccount.find(
|
||||
By(MappedBankAccount.bank, bankId.value),
|
||||
|
||||
@ -19,7 +19,15 @@ trait CustomerProvider {
|
||||
|
||||
def getUser(bankId : BankId, customerId : String) : Box[User]
|
||||
|
||||
def addCustomer(bankId : BankId, user : User, number : String, legalName : String, mobileNumber : String, email : String, faceImage: CustomerFaceImage) : Box[Customer]
|
||||
def addCustomer(bankId: BankId, user: User, number: String, legalName: String, mobileNumber: String, email: String, faceImage: CustomerFaceImage,
|
||||
dateOfBirth: Date,
|
||||
relationshipStatus: String,
|
||||
dependents: Int,
|
||||
dobOfDependents: List[Date],
|
||||
highestEducationAttained: String,
|
||||
employmentStatus: String,
|
||||
kycStatus: Boolean,
|
||||
lastOkDate: Date): Box[Customer]
|
||||
|
||||
}
|
||||
|
||||
@ -29,6 +37,14 @@ trait Customer {
|
||||
def mobileNumber : String
|
||||
def email : String
|
||||
def faceImage : CustomerFaceImage
|
||||
def dateOfBirth: Date
|
||||
def relationshipStatus: String
|
||||
def dependents: Int
|
||||
def dobOfDependents: List[Date]
|
||||
def highestEducationAttained: String
|
||||
def employmentStatus: String
|
||||
def kycStatus: Boolean
|
||||
def lastOkDate: Date
|
||||
}
|
||||
|
||||
trait CustomerFaceImage {
|
||||
|
||||
@ -23,12 +23,25 @@ object MappedCustomerProvider extends CustomerProvider {
|
||||
).flatMap(_.mUser.obj)
|
||||
}
|
||||
|
||||
override def addCustomer(bankId: BankId, user : User, number : String, legalName : String, mobileNumber : String, email : String, faceImage: CustomerFaceImage) : Box[Customer] = {
|
||||
override def addCustomer(bankId: BankId, user : User, number : String, legalName : String, mobileNumber : String, email : String, faceImage: CustomerFaceImage,
|
||||
dateOfBirth: Date,
|
||||
relationshipStatus: String,
|
||||
dependents: Int,
|
||||
dobOfDependents: List[Date],
|
||||
highestEducationAttained: String,
|
||||
employmentStatus: String,
|
||||
kycStatus: Boolean,
|
||||
lastOkDate: Date) : Box[Customer] = {
|
||||
|
||||
val createdCustomer = MappedCustomer.create
|
||||
.mBank(bankId.value).mEmail(email).mFaceImageTime(faceImage.date)
|
||||
.mFaceImageUrl(faceImage.url).mLegalName(legalName)
|
||||
.mMobileNumber(mobileNumber).mNumber(number).mUser(user.apiId.value).saveMe()
|
||||
.mMobileNumber(mobileNumber).mNumber(number).mUser(user.apiId.value)
|
||||
.mDateOfBirth(dateOfBirth).mRelationshipStatus(relationshipStatus)
|
||||
.mDependents(dependents)
|
||||
.mHighestEducationAttained(highestEducationAttained)
|
||||
.mEmploymentStatus(employmentStatus).mKycStatus(kycStatus)
|
||||
.mLastOkDate(lastOkDate).saveMe()
|
||||
|
||||
Some(createdCustomer)
|
||||
}
|
||||
@ -48,6 +61,13 @@ class MappedCustomer extends Customer with LongKeyedMapper[MappedCustomer] with
|
||||
object mEmail extends MappedEmail(this, 200)
|
||||
object mFaceImageUrl extends DefaultStringField(this)
|
||||
object mFaceImageTime extends MappedDateTime(this)
|
||||
object mDateOfBirth extends MappedDateTime(this)
|
||||
object mRelationshipStatus extends DefaultStringField(this)
|
||||
object mDependents extends MappedInt(this)
|
||||
object mHighestEducationAttained extends DefaultStringField(this)
|
||||
object mEmploymentStatus extends DefaultStringField(this)
|
||||
object mKycStatus extends MappedBoolean(this)
|
||||
object mLastOkDate extends MappedDateTime(this)
|
||||
|
||||
override def number: String = mNumber.get
|
||||
override def mobileNumber: String = mMobileNumber.get
|
||||
@ -57,6 +77,14 @@ class MappedCustomer extends Customer with LongKeyedMapper[MappedCustomer] with
|
||||
override def date: Date = mFaceImageTime.get
|
||||
override def url: String = mFaceImageUrl.get
|
||||
}
|
||||
override def dateOfBirth: Date = mDateOfBirth.get
|
||||
override def relationshipStatus: String = mRelationshipStatus.get
|
||||
override def dependents: Int = mDependents
|
||||
override def dobOfDependents: List[Date] = List(createdAt.get)
|
||||
override def highestEducationAttained: String = mHighestEducationAttained.get
|
||||
override def employmentStatus: String = mEmploymentStatus.get
|
||||
override def kycStatus: Boolean = mKycStatus
|
||||
override def lastOkDate: Date = mLastOkDate.get
|
||||
}
|
||||
|
||||
object MappedCustomer extends MappedCustomer with LongKeyedMetaMapper[MappedCustomer] {
|
||||
|
||||
32
src/main/scala/code/kyccheck/KycCheck.scala
Normal file
32
src/main/scala/code/kyccheck/KycCheck.scala
Normal file
@ -0,0 +1,32 @@
|
||||
package code.kycchecks
|
||||
|
||||
import java.util.Date
|
||||
import net.liftweb.util.SimpleInjector
|
||||
|
||||
|
||||
object KycChecks extends SimpleInjector {
|
||||
|
||||
val kycCheckProvider = new Inject(buildOne _) {}
|
||||
|
||||
def buildOne: KycCheckProvider = MappedKycChecksProvider
|
||||
|
||||
}
|
||||
|
||||
trait KycCheckProvider {
|
||||
|
||||
def getKycChecks(customerNumber: String) : List[KycCheck]
|
||||
|
||||
def addKycChecks(id: String, customerNumber: String, date: Date, how: String, staffUserId: String, mStaffName: String, mSatisfied: Boolean, comments: String) : Boolean
|
||||
|
||||
}
|
||||
|
||||
trait KycCheck {
|
||||
def idKycCheck : String
|
||||
def customerNumber : String
|
||||
def date : Date
|
||||
def how : String
|
||||
def staffUserId : String
|
||||
def staffName : String
|
||||
def satisfied: Boolean
|
||||
def comments : String
|
||||
}
|
||||
64
src/main/scala/code/kyccheck/MappedKycChecksProvider.scala
Normal file
64
src/main/scala/code/kyccheck/MappedKycChecksProvider.scala
Normal file
@ -0,0 +1,64 @@
|
||||
package code.kycchecks
|
||||
|
||||
import java.util.Date
|
||||
|
||||
import code.model.{BankId, User}
|
||||
import code.model.dataAccess.APIUser
|
||||
import code.util.{DefaultStringField}
|
||||
import net.liftweb.mapper._
|
||||
|
||||
object MappedKycChecksProvider extends KycCheckProvider {
|
||||
|
||||
override def getKycChecks(customerNumber: String): List[MappedKycCheck] = {
|
||||
MappedKycCheck.findAll(
|
||||
By(MappedKycCheck.mCustomerNumber, customerNumber),
|
||||
OrderBy(MappedKycCheck.updatedAt, Descending))
|
||||
}
|
||||
|
||||
|
||||
override def addKycChecks(id: String, customerNumber: String, date: Date, how: String, staffUserId: String, mStaffName: String, mSatisfied: Boolean, comments: String): Boolean = {
|
||||
MappedKycCheck.create
|
||||
.mId(id)
|
||||
.mCustomerNumber(customerNumber)
|
||||
.mDate(date)
|
||||
.mHow(how)
|
||||
.mStaffUserId(staffUserId)
|
||||
.mStaffName(mStaffName)
|
||||
.mSatisfied(mSatisfied)
|
||||
.mComments(comments)
|
||||
.save()
|
||||
}
|
||||
}
|
||||
|
||||
class MappedKycCheck extends KycCheck
|
||||
with LongKeyedMapper[MappedKycCheck] with IdPK with CreatedUpdated {
|
||||
|
||||
def getSingleton = MappedKycCheck
|
||||
|
||||
object user extends MappedLongForeignKey(this, APIUser)
|
||||
object bank extends DefaultStringField(this)
|
||||
|
||||
object mId extends DefaultStringField(this)
|
||||
object mCustomerNumber extends DefaultStringField(this)
|
||||
object mDate extends MappedDateTime(this)
|
||||
object mHow extends DefaultStringField(this)
|
||||
object mStaffUserId extends DefaultStringField(this)
|
||||
object mStaffName extends DefaultStringField(this)
|
||||
object mSatisfied extends MappedBoolean(this)
|
||||
object mComments extends DefaultStringField(this)
|
||||
|
||||
|
||||
|
||||
override def idKycCheck: String = mId.get
|
||||
override def customerNumber: String = mCustomerNumber.get
|
||||
override def date: Date = mDate.get
|
||||
override def how: String = mHow.get
|
||||
override def staffUserId: String = mStaffUserId.get
|
||||
override def staffName: String = mStaffName.get
|
||||
override def satisfied: Boolean = mSatisfied.get
|
||||
override def comments: String = mComments.get
|
||||
}
|
||||
|
||||
object MappedKycCheck extends MappedKycCheck with LongKeyedMetaMapper[MappedKycCheck] {
|
||||
override def dbIndexes = UniqueIndex(mId) :: super.dbIndexes
|
||||
}
|
||||
31
src/main/scala/code/kycdocuments/KycDocuments.scala
Normal file
31
src/main/scala/code/kycdocuments/KycDocuments.scala
Normal file
@ -0,0 +1,31 @@
|
||||
package code.kycdocuments
|
||||
|
||||
import java.util.Date
|
||||
import net.liftweb.util.SimpleInjector
|
||||
|
||||
|
||||
object KycDocuments extends SimpleInjector {
|
||||
|
||||
val kycDocumentProvider = new Inject(buildOne _) {}
|
||||
|
||||
def buildOne: KycDocumentProvider = MappedKycDocumentsProvider
|
||||
|
||||
}
|
||||
|
||||
trait KycDocumentProvider {
|
||||
|
||||
def getKycDocuments(customerNumber: String) : List[KycDocument]
|
||||
|
||||
def addKycDocuments(id: String, customerNumber: String, `type`: String, number: String, issueDate: Date, issuePlace: String, expiryDate: Date) : Boolean
|
||||
|
||||
}
|
||||
|
||||
trait KycDocument {
|
||||
def idKycDocument : String
|
||||
def customerNumber : String
|
||||
def `type` : String
|
||||
def number : String
|
||||
def issueDate : Date
|
||||
def issuePlace : String
|
||||
def expiryDate : Date
|
||||
}
|
||||
@ -0,0 +1,60 @@
|
||||
package code.kycdocuments
|
||||
|
||||
import java.util.Date
|
||||
|
||||
import code.model.{BankId, User}
|
||||
import code.model.dataAccess.APIUser
|
||||
import code.util.{DefaultStringField}
|
||||
import net.liftweb.mapper._
|
||||
|
||||
object MappedKycDocumentsProvider extends KycDocumentProvider {
|
||||
|
||||
override def getKycDocuments(customerNumber: String): List[MappedKycDocument] = {
|
||||
MappedKycDocument.findAll(
|
||||
By(MappedKycDocument.mCustomerNumber, customerNumber),
|
||||
OrderBy(MappedKycDocument.updatedAt, Descending))
|
||||
}
|
||||
|
||||
|
||||
override def addKycDocuments(id: String, customerNumber: String, `type`: String, number: String, issueDate: Date, issuePlace: String, expiryDate: Date): Boolean = {
|
||||
MappedKycDocument.create
|
||||
.mId(id)
|
||||
.mCustomerNumber(customerNumber)
|
||||
.mType(`type`)
|
||||
.mNumber(number)
|
||||
.mIssueDate(issueDate)
|
||||
.mIssuePlace(issuePlace)
|
||||
.mExpiryDate(expiryDate)
|
||||
.save()
|
||||
}
|
||||
}
|
||||
|
||||
class MappedKycDocument extends KycDocument
|
||||
with LongKeyedMapper[MappedKycDocument] with IdPK with CreatedUpdated {
|
||||
|
||||
def getSingleton = MappedKycDocument
|
||||
|
||||
object user extends MappedLongForeignKey(this, APIUser)
|
||||
object bank extends DefaultStringField(this)
|
||||
|
||||
object mId extends DefaultStringField(this)
|
||||
object mCustomerNumber extends DefaultStringField(this)
|
||||
object mType extends DefaultStringField(this)
|
||||
object mNumber extends DefaultStringField(this)
|
||||
object mIssueDate extends MappedDateTime(this)
|
||||
object mIssuePlace extends DefaultStringField(this)
|
||||
object mExpiryDate extends MappedDateTime(this)
|
||||
|
||||
|
||||
override def idKycDocument: String = mId.get
|
||||
override def customerNumber: String = mCustomerNumber.get
|
||||
override def `type`: String = mType.get
|
||||
override def number: String = mNumber.get
|
||||
override def issueDate: Date = mIssueDate.get
|
||||
override def issuePlace: String = mIssuePlace.get
|
||||
override def expiryDate: Date = mExpiryDate.get
|
||||
}
|
||||
|
||||
object MappedKycDocument extends MappedKycDocument with LongKeyedMetaMapper[MappedKycDocument] {
|
||||
override def dbIndexes = UniqueIndex(mId) :: super.dbIndexes
|
||||
}
|
||||
33
src/main/scala/code/kycmedia/KycMedia.scala
Normal file
33
src/main/scala/code/kycmedia/KycMedia.scala
Normal file
@ -0,0 +1,33 @@
|
||||
package code.kycmedias
|
||||
|
||||
import java.util.Date
|
||||
|
||||
import code.model.{BankId, User}
|
||||
import net.liftweb.util.SimpleInjector
|
||||
|
||||
|
||||
object KycMedias extends SimpleInjector {
|
||||
|
||||
val kycMediaProvider = new Inject(buildOne _) {}
|
||||
|
||||
def buildOne: KycMediaProvider = MappedKycMediasProvider
|
||||
|
||||
}
|
||||
|
||||
trait KycMediaProvider {
|
||||
|
||||
def getKycMedias(customerNumber: String) : List[KycMedia]
|
||||
|
||||
def addKycMedias(id: String, customerNumber: String, `type`: String, url: String, date: Date, relatesToKycDocumentId: String, relatesToKycCheckId: String) : Boolean
|
||||
|
||||
}
|
||||
|
||||
trait KycMedia {
|
||||
def idKycMedia : String
|
||||
def customerNumber : String
|
||||
def `type` : String
|
||||
def url : String
|
||||
def date : Date
|
||||
def relatesToKycDocumentId : String
|
||||
def relatesToKycCheckId : String
|
||||
}
|
||||
60
src/main/scala/code/kycmedia/MappedKycMediasProvider.scala
Normal file
60
src/main/scala/code/kycmedia/MappedKycMediasProvider.scala
Normal file
@ -0,0 +1,60 @@
|
||||
package code.kycmedias
|
||||
|
||||
import java.util.Date
|
||||
|
||||
import code.model.{BankId, User}
|
||||
import code.model.dataAccess.APIUser
|
||||
import code.util.{DefaultStringField}
|
||||
import net.liftweb.mapper._
|
||||
|
||||
object MappedKycMediasProvider extends KycMediaProvider {
|
||||
|
||||
override def getKycMedias(customerNumber: String): List[MappedKycMedia] = {
|
||||
MappedKycMedia.findAll(
|
||||
By(MappedKycMedia.mCustomerNumber, customerNumber),
|
||||
OrderBy(MappedKycMedia.updatedAt, Descending))
|
||||
}
|
||||
|
||||
|
||||
override def addKycMedias(id: String, customerNumber: String, `type`: String, url: String, date: Date, relatesToKycDocumentId: String, relatesToKycCheckId: String): Boolean = {
|
||||
MappedKycMedia.create
|
||||
.mId(id)
|
||||
.mCustomerNumber(customerNumber)
|
||||
.mType(`type`)
|
||||
.mUrl(url)
|
||||
.mDate(date)
|
||||
.mRelatesToKycDocumentId(relatesToKycDocumentId)
|
||||
.mRelatesToKycCheckId(relatesToKycCheckId)
|
||||
.save()
|
||||
}
|
||||
}
|
||||
|
||||
class MappedKycMedia extends KycMedia
|
||||
with LongKeyedMapper[MappedKycMedia] with IdPK with CreatedUpdated {
|
||||
|
||||
def getSingleton = MappedKycMedia
|
||||
|
||||
object user extends MappedLongForeignKey(this, APIUser)
|
||||
object bank extends DefaultStringField(this)
|
||||
|
||||
object mId extends DefaultStringField(this)
|
||||
object mCustomerNumber extends DefaultStringField(this)
|
||||
object mType extends DefaultStringField(this)
|
||||
object mUrl extends DefaultStringField(this)
|
||||
object mDate extends MappedDateTime(this)
|
||||
object mRelatesToKycDocumentId extends DefaultStringField(this)
|
||||
object mRelatesToKycCheckId extends DefaultStringField(this)
|
||||
|
||||
|
||||
override def idKycMedia: String = mId.get
|
||||
override def customerNumber: String = mCustomerNumber.get
|
||||
override def `type`: String = mType.get
|
||||
override def url: String = mUrl.get
|
||||
override def date: Date = mDate.get
|
||||
override def relatesToKycDocumentId: String = mRelatesToKycDocumentId.get
|
||||
override def relatesToKycCheckId: String = mRelatesToKycCheckId.get
|
||||
}
|
||||
|
||||
object MappedKycMedia extends MappedKycMedia with LongKeyedMetaMapper[MappedKycMedia] {
|
||||
override def dbIndexes = UniqueIndex(mId) :: super.dbIndexes
|
||||
}
|
||||
29
src/main/scala/code/kycstatus/KycStatus.scala
Normal file
29
src/main/scala/code/kycstatus/KycStatus.scala
Normal file
@ -0,0 +1,29 @@
|
||||
package code.kycstatuses
|
||||
|
||||
import java.util.Date
|
||||
|
||||
import code.model.{BankId, User}
|
||||
import net.liftweb.util.SimpleInjector
|
||||
|
||||
|
||||
object KycStatuses extends SimpleInjector {
|
||||
|
||||
val kycStatusProvider = new Inject(buildOne _) {}
|
||||
|
||||
def buildOne: KycStatusProvider = MappedKycStatusesProvider
|
||||
|
||||
}
|
||||
|
||||
trait KycStatusProvider {
|
||||
|
||||
def getKycStatuses(customerNumber: String) : List[KycStatus]
|
||||
|
||||
def addKycStatus(customerNumber: String, ok: Boolean, date: Date) : Boolean
|
||||
|
||||
}
|
||||
|
||||
trait KycStatus {
|
||||
def customerNumber : String
|
||||
def ok : Boolean
|
||||
def date : Date
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
package code.kycstatuses
|
||||
|
||||
import java.util.Date
|
||||
|
||||
import code.model.{BankId, User}
|
||||
import code.model.dataAccess.APIUser
|
||||
import code.util.{DefaultStringField}
|
||||
import net.liftweb.mapper._
|
||||
|
||||
object MappedKycStatusesProvider extends KycStatusProvider {
|
||||
|
||||
override def getKycStatuses(customerNumber: String): List[MappedKycStatus] = {
|
||||
MappedKycStatus.findAll(
|
||||
By(MappedKycStatus.mCustomerNumber, customerNumber),
|
||||
OrderBy(MappedKycStatus.updatedAt, Descending))
|
||||
}
|
||||
|
||||
|
||||
override def addKycStatus(customerNumber: String, ok: Boolean, date: Date): Boolean = {
|
||||
MappedKycStatus.create
|
||||
.mCustomerNumber(customerNumber)
|
||||
.mOk(ok)
|
||||
.mDate(date)
|
||||
.save()
|
||||
}
|
||||
}
|
||||
|
||||
class MappedKycStatus extends KycStatus
|
||||
with LongKeyedMapper[MappedKycStatus] with IdPK with CreatedUpdated {
|
||||
|
||||
def getSingleton = MappedKycStatus
|
||||
|
||||
object user extends MappedLongForeignKey(this, APIUser)
|
||||
object bank extends DefaultStringField(this)
|
||||
|
||||
object mCustomerNumber extends DefaultStringField(this)
|
||||
object mOk extends MappedBoolean(this)
|
||||
object mDate extends MappedDateTime(this)
|
||||
|
||||
|
||||
|
||||
override def customerNumber: String = mCustomerNumber.get
|
||||
override def ok: Boolean = mOk.get
|
||||
override def date: Date = mDate.get
|
||||
|
||||
}
|
||||
|
||||
object MappedKycStatus extends MappedKycStatus with LongKeyedMetaMapper[MappedKycStatus] {
|
||||
override def dbIndexes = UniqueIndex(mCustomerNumber) :: super.dbIndexes
|
||||
}
|
||||
@ -184,7 +184,7 @@ object ImporterAPI extends RestHelper with Loggable {
|
||||
savetransactions
|
||||
else
|
||||
errorJsonResponse("wrong secret", 401)
|
||||
case _ => errorJsonResponse("importer_secret not set")
|
||||
case _ => errorJsonResponse("importer_secret not set on the server.")
|
||||
}
|
||||
}
|
||||
case _ => errorJsonResponse("secret missing")
|
||||
|
||||
@ -104,6 +104,15 @@ object BankId {
|
||||
def unapply(id : String) = Some(BankId(id))
|
||||
}
|
||||
|
||||
// In preparation for use in Context (api links) To replace OtherAccountId
|
||||
case class CounterpartyId(val value : String) {
|
||||
override def toString = value
|
||||
}
|
||||
|
||||
object CounterpartyId {
|
||||
def unapply(id : String) = Some(CounterpartyId(id))
|
||||
}
|
||||
|
||||
trait Bank {
|
||||
def bankId: BankId
|
||||
def shortName : String
|
||||
|
||||
@ -142,15 +142,10 @@ object Consumer extends Consumer with LongKeyedMetaMapper[Consumer] with CRUDify
|
||||
//overridden to display extra stats above the table
|
||||
override def _showAllTemplate =
|
||||
<lift:crud.all>
|
||||
<div>
|
||||
<p>
|
||||
Total of {Consumer.count} applications from {recordsWithUniqueEmails.getOrElse("ERROR")} unique email addresses.
|
||||
{recordsWithUniqueAppNames.getOrElse("ERROR")} unique app names.
|
||||
</p>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
</div>
|
||||
<p id="admin-consumer-summary">
|
||||
Total of {Consumer.count} applications from {recordsWithUniqueEmails.getOrElse("ERROR")} unique email addresses. <br />
|
||||
{recordsWithUniqueAppNames.getOrElse("ERROR")} unique app names.
|
||||
</p>
|
||||
<table id={showAllId} class={showAllClass}>
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
@ -59,6 +59,7 @@ object Admin extends Admin with MetaMegaProtoUser[Admin]{
|
||||
locale, timezone, password)
|
||||
|
||||
// comment this line out to require email validations
|
||||
// TODO Get this from Props
|
||||
override def skipEmailValidation = true
|
||||
|
||||
//Keep track of the referer on login
|
||||
|
||||
@ -292,6 +292,74 @@ import net.liftweb.util.Helpers._
|
||||
S.error("login", S.?("Invalid Username or Password"))
|
||||
}
|
||||
|
||||
def getUserId(username: String, password: String): Long = {
|
||||
findUserByUserName(username) match {
|
||||
case Full(user) => {
|
||||
if (user.validated_? &&
|
||||
user.getProvider() == Props.get("hostname","") &&
|
||||
user.testPassword(Full(password)))
|
||||
{
|
||||
user.id.toLong
|
||||
}
|
||||
else {
|
||||
getExternalUser(username, password).get.id.toLong
|
||||
}
|
||||
}
|
||||
case _ => 0
|
||||
}
|
||||
}
|
||||
|
||||
def getExternalUser(username: String, password: String):Box[OBPUser] = {
|
||||
getUserViaKafka(username, password) match {
|
||||
case Full(SandboxUserImport(extEmail, extPassword, extDisplayName)) => {
|
||||
val preLoginState = capturePreLoginState()
|
||||
info("external user authenticated. login redir: " + loginRedirect.get)
|
||||
val redir = loginRedirect.get match {
|
||||
case Full(url) =>
|
||||
loginRedirect(Empty)
|
||||
url
|
||||
case _ =>
|
||||
homePage
|
||||
}
|
||||
|
||||
val dummyPassword = "nothingreallyjustdummypass"
|
||||
val extProvider = Props.get("connector").openOrThrowException("no connector set")
|
||||
|
||||
val user = findUserByUserName(username) match {
|
||||
// Check if the external user is already created locally
|
||||
case Full(user) if user.validated_? &&
|
||||
user.provider == extProvider => {
|
||||
// Return existing user if found
|
||||
info("external user already exists locally, using that one")
|
||||
user
|
||||
}
|
||||
|
||||
// If not found, create new user
|
||||
case _ => {
|
||||
// Create OBPUser using fetched data from Kafka
|
||||
// assuming that user's email is always validated
|
||||
info("external user does not exist locally, creating one")
|
||||
val newUser = OBPUser.create
|
||||
.firstName(extDisplayName)
|
||||
.email(extEmail)
|
||||
// No need to store password, so store dummy string instead
|
||||
.password(dummyPassword)
|
||||
.provider(extProvider)
|
||||
.validated(true)
|
||||
// Save the user in order to be able to log in
|
||||
newUser.save()
|
||||
// Return created user
|
||||
newUser
|
||||
}
|
||||
}
|
||||
Full(user)
|
||||
}
|
||||
case _ => {
|
||||
Empty
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//overridden to allow a redirection if login fails
|
||||
override def login = {
|
||||
if (S.post_?) {
|
||||
@ -326,67 +394,22 @@ import net.liftweb.util.Helpers._
|
||||
case _ => {
|
||||
// If not found locally, try to authenticate user via Kafka, if enabled in props
|
||||
if (Props.get("connector").openOrThrowException("no connector set") == "kafka") {
|
||||
S.param("username").
|
||||
flatMap(username => getUserViaKafka(username, S.param("password").openOr(""))) match {
|
||||
case Full(SandboxUserImport(extEmail, extPassword, extDisplayName)) => {
|
||||
val preLoginState = capturePreLoginState()
|
||||
info("external user authenticated. login redir: " + loginRedirect.get)
|
||||
val redir = loginRedirect.get match {
|
||||
case Full(url) =>
|
||||
loginRedirect(Empty)
|
||||
url
|
||||
case _ =>
|
||||
homePage
|
||||
}
|
||||
val preLoginState = capturePreLoginState()
|
||||
val user = getExternalUser(S.param("username").get, S.param("password").get)
|
||||
|
||||
val dummyPassword = "nothingreallyjustdummypass"
|
||||
val extProvider = Props.get("connector").openOrThrowException("no connector set")
|
||||
if (!user.isEmpty) {
|
||||
logUserIn(user.get, () => {
|
||||
S.notice(S.?("logged.in"))
|
||||
|
||||
val user = S.param("username").
|
||||
flatMap(username => findUserByUserName(username)) match {
|
||||
preLoginState()
|
||||
|
||||
// Check if the external user is already created locally
|
||||
case Full(user) if user.validated_? &&
|
||||
user.provider == extProvider => {
|
||||
// Return existing user if found
|
||||
info("external user already exists locally, using that one")
|
||||
user
|
||||
}
|
||||
|
||||
// If not found, create new user
|
||||
case _ => {
|
||||
// Create OBPUser using fetched data from Kafka
|
||||
// assuming that user's email is always validated
|
||||
info("external user does not exist locally, creating one")
|
||||
val newUser = OBPUser.create
|
||||
.firstName(extDisplayName)
|
||||
.email(extEmail)
|
||||
// No need to store password, so store dummy string instead
|
||||
.password(dummyPassword)
|
||||
.provider(extProvider)
|
||||
.validated(true)
|
||||
// Save the user in order to be able to log in
|
||||
newUser.save()
|
||||
// Return created user
|
||||
newUser
|
||||
}
|
||||
}
|
||||
|
||||
logUserIn(user, () => {
|
||||
S.notice(S.?("logged.in"))
|
||||
|
||||
preLoginState()
|
||||
|
||||
S.redirectTo(homePage)
|
||||
})
|
||||
|
||||
}
|
||||
case _ => {
|
||||
userLoginFailed
|
||||
}
|
||||
S.redirectTo(homePage)
|
||||
})
|
||||
} else {
|
||||
userLoginFailed
|
||||
}
|
||||
} else {
|
||||
userLoginFailed
|
||||
userLoginFailed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -487,7 +487,6 @@ trait OBPDataImport extends Loggable {
|
||||
* @return A full box if the import worked, or else a failure describing what went wrong
|
||||
*/
|
||||
def importData(data: SandboxDataImport) : Box[Unit] = {
|
||||
|
||||
logger.info(s"Hello from importData")
|
||||
for {
|
||||
banks <- createBanks(data)
|
||||
|
||||
@ -33,9 +33,10 @@ Berlin 13359, Germany
|
||||
package code.snippet
|
||||
|
||||
import code.model.dataAccess.OBPUser
|
||||
import net.liftweb.common.Loggable
|
||||
import scala.xml.NodeSeq
|
||||
import net.liftweb.util.Helpers._
|
||||
import net.liftweb.util.CssSel
|
||||
import net.liftweb.util.{Props, CssSel}
|
||||
import net.liftweb.http.S
|
||||
import code.model.dataAccess.Admin
|
||||
import net.liftweb.http.SHtml
|
||||
@ -70,7 +71,7 @@ class Login {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def adminLogout : CssSel = {
|
||||
if(Admin.loggedIn_?) {
|
||||
val current = Admin.currentUser
|
||||
@ -84,4 +85,19 @@ class Login {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Used to display custom message to users when they login.
|
||||
// For instance we can use it to display example login on a sandbox
|
||||
def customiseLogin : CssSel = {
|
||||
val specialLoginInstructions = scala.xml.Unparsed(Props.get("webui_login_page_special_instructions", ""))
|
||||
// In case we use Extraction.decompose
|
||||
implicit val formats = net.liftweb.json.DefaultFormats
|
||||
"#login_special_instructions *" #> specialLoginInstructions
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// End of class
|
||||
}
|
||||
@ -1,32 +1,37 @@
|
||||
/**
|
||||
Open Bank Project
|
||||
Open Bank Project - API
|
||||
Copyright (C) 2011 - 2015, TESOBE Ltd.
|
||||
|
||||
Copyright 2011,2012 TESOBE / Music Pictures Ltd.
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
This product includes software developed at
|
||||
TESOBE (http://www.tesobe.com/)
|
||||
by
|
||||
Simon Redfern : simon AT tesobe DOT com
|
||||
Stefan Bethge : stefan AT tesobe DOT com
|
||||
Everett Sochowski : everett AT tesobe DOT com
|
||||
Ayoub Benali: ayoub AT tesobe DOT com
|
||||
|
||||
Open Bank Project (http://www.openbankproject.com)
|
||||
Copyright 2011,2012 TESOBE / Music Pictures Ltd
|
||||
|
||||
This product includes software developed at
|
||||
TESOBE (http://www.tesobe.com/)
|
||||
TESOBE Ltd.
|
||||
Osloer Str. 16/17
|
||||
Berlin 13359, Germany
|
||||
Email: contact@tesobe.com
|
||||
|
||||
*/
|
||||
|
||||
by
|
||||
Simon Redfern : simon AT tesobe DOT com
|
||||
Everett Sochowski: everett AT tesobe DOT com
|
||||
Ayoub Benali : ayoub AT tesobe Dot com
|
||||
|
||||
*/
|
||||
package code.snippet
|
||||
|
||||
import code.util.Helper
|
||||
@ -168,7 +173,7 @@ object OAuthAuthorisation {
|
||||
|
||||
}
|
||||
|
||||
//looks for expired tokens and nonces and delete them
|
||||
//looks for expired tokens and nonces and deletes them
|
||||
def dataBaseCleaner: Unit = {
|
||||
import net.liftweb.util.Schedule
|
||||
import net.liftweb.mapper.By_<
|
||||
|
||||
@ -63,10 +63,27 @@ class WebUI extends Loggable{
|
||||
".api-explorer-link a [href]" #> scala.xml.Unparsed(Props.get("webui_api_explorer_url", ""))
|
||||
}
|
||||
|
||||
// Social Finance (Sofi)
|
||||
def sofiLink: CssSel = {
|
||||
".sofi-link a [href]" #> scala.xml.Unparsed(Props.get("webui_sofi_url", ""))
|
||||
}
|
||||
|
||||
|
||||
// Points to the documentation. Probably a sandbox specific link is good.
|
||||
def apiDocumentationLink: CssSel = {
|
||||
".api-documentation-link a [href]" #> scala.xml.Unparsed(Props.get("webui_api_documentation_url", "https://github.com/OpenBankProject/OBP-API/wiki"))
|
||||
}
|
||||
|
||||
// For example customers and credentials
|
||||
// This relies on the page for sandbox documentation having an anchor called example-customer-logins
|
||||
def exampleSandboxCredentialsLink: CssSel = {
|
||||
".example_sandbox_credentials_link a [href]" #> scala.xml.Unparsed(Props.get("webui_api_documentation_url", "") + "#example-customer-logins")
|
||||
}
|
||||
|
||||
// For link to OAuth Client SDKs
|
||||
def sdksLink: CssSel = {
|
||||
".sdks_link a [href]" #> scala.xml.Unparsed(Props.get("webui_sdks_url", "https://github.com/OpenBankProject/OBP-API/wiki/OAuth-Client-SDKS"))
|
||||
}
|
||||
|
||||
def mainStyleSheet: CssSel = {
|
||||
"#main_style_sheet [href]" #> scala.xml.Unparsed(Props.get("webui_main_style_sheet", "/media/css/website.css"))
|
||||
|
||||
@ -0,0 +1,52 @@
|
||||
package code.socialmedia
|
||||
|
||||
import java.util.Date
|
||||
import code.model.dataAccess.APIUser
|
||||
import code.util.{DefaultStringField}
|
||||
import net.liftweb.mapper._
|
||||
|
||||
object MappedSocialMediasProvider extends SocialMediaHandleProvider {
|
||||
|
||||
override def getSocialMedias(customerNumber: String): List[MappedSocialMedia] = {
|
||||
MappedSocialMedia.findAll(
|
||||
By(MappedSocialMedia.mCustomerNumber, customerNumber),
|
||||
OrderBy(MappedSocialMedia.updatedAt, Descending))
|
||||
}
|
||||
|
||||
|
||||
override def addSocialMedias(customerNumber: String, `type`: String, handle: String, dateAdded: Date, dateActivated: Date): Boolean = {
|
||||
MappedSocialMedia.create
|
||||
.mCustomerNumber(customerNumber)
|
||||
.mType(`type`)
|
||||
.mHandle(handle)
|
||||
.mDateAdded(dateAdded)
|
||||
.mDateActivated(dateActivated)
|
||||
.save()
|
||||
}
|
||||
}
|
||||
|
||||
class MappedSocialMedia extends SocialMedia
|
||||
with LongKeyedMapper[MappedSocialMedia] with IdPK with CreatedUpdated {
|
||||
|
||||
def getSingleton = MappedSocialMedia
|
||||
|
||||
object user extends MappedLongForeignKey(this, APIUser)
|
||||
object bank extends DefaultStringField(this)
|
||||
|
||||
object mCustomerNumber extends DefaultStringField(this)
|
||||
object mType extends DefaultStringField(this)
|
||||
object mHandle extends DefaultStringField(this)
|
||||
object mDateAdded extends MappedDateTime(this)
|
||||
object mDateActivated extends MappedDateTime(this)
|
||||
|
||||
|
||||
override def customerNumber: String = mCustomerNumber.get
|
||||
override def `type`: String = mType.get
|
||||
override def handle: String = mHandle.get
|
||||
override def dateAdded: Date = mDateAdded.get
|
||||
override def dateActivated: Date = mDateActivated.get
|
||||
}
|
||||
|
||||
object MappedSocialMedia extends MappedSocialMedia with LongKeyedMetaMapper[MappedSocialMedia] {
|
||||
override def dbIndexes = UniqueIndex(mCustomerNumber) :: super.dbIndexes
|
||||
}
|
||||
34
src/main/scala/code/socialmedia/SocialMedia.scala
Normal file
34
src/main/scala/code/socialmedia/SocialMedia.scala
Normal file
@ -0,0 +1,34 @@
|
||||
package code.socialmedia
|
||||
|
||||
import java.util.Date
|
||||
import net.liftweb.util.SimpleInjector
|
||||
|
||||
|
||||
// TODO Rename to SocialMediaHandle
|
||||
object SocialMediaHandle extends SimpleInjector {
|
||||
|
||||
val socialMediaHandleProvider = new Inject(buildOne _) {}
|
||||
|
||||
def buildOne: SocialMediaHandleProvider = MappedSocialMediasProvider
|
||||
|
||||
}
|
||||
|
||||
|
||||
// TODO Rename to SocialMediaHandlesProvider etc.
|
||||
trait SocialMediaHandleProvider {
|
||||
|
||||
def getSocialMedias(customerNumber: String) : List[SocialMedia]
|
||||
|
||||
def addSocialMedias(customerNumber: String, `type`: String, handle: String, dateAdded: Date, dateActivated: Date) : Boolean
|
||||
|
||||
}
|
||||
|
||||
|
||||
// TODO Rename to SocialMediaHandle
|
||||
trait SocialMedia {
|
||||
def customerNumber : String
|
||||
def `type` : String
|
||||
def handle : String
|
||||
def dateAdded : Date
|
||||
def dateActivated : Date
|
||||
}
|
||||
@ -243,6 +243,8 @@ private object MapperViews extends Views with Loggable {
|
||||
def getAllPublicAccounts() : List[BankAccount] = {
|
||||
//TODO: do this more efficiently
|
||||
|
||||
// An account is considered public if it contains a public view
|
||||
|
||||
val bankAndAccountIds : List[(BankId, AccountId)] =
|
||||
ViewImpl.findAll(By(ViewImpl.isPublic_, true)).map(v =>
|
||||
(v.bankId, v.accountId)
|
||||
|
||||
@ -37,18 +37,17 @@ Berlin 13359, Germany
|
||||
</div>
|
||||
</div>
|
||||
<div id="main-links">
|
||||
<a href="/consumer-registration">Get API keys</a>
|
||||
<a class="api-explorer-link" data-lift="WebUI.apiExplorerLink" href="">Use API Explorer</a>
|
||||
<a href="/consumer-registration">API keys</a>
|
||||
<a class="api-explorer-link" data-lift="WebUI.apiExplorerLink" href="">API Explorer</a>
|
||||
<a class="sofi-link" data-lift="WebUI.sofiLink" href="">Sofi</a>
|
||||
</div>
|
||||
<div id="main-sandbox">
|
||||
<h1>Get Started</h1>
|
||||
|
||||
<p class="about-text">
|
||||
<ol start="1">
|
||||
<li>Register / Login as a developer <a href="/user_mgt/sign_up">here</a></li>
|
||||
<li>Get one or more developer keys <a href="/consumer-registration">here</a></li>
|
||||
<li>Read the documentation <a class="api-documentation-link lift:WebUI.apiDocumentationLink" href="">here</a></li>
|
||||
<li>Explore the API <a class="api-explorer-link lift:WebUI.apiExplorerLink" href="">here</a></li>
|
||||
<li>Explore the API <a class="api-explorer-link" data-lift="WebUI.apiExplorerLink" href="">here</a> using the example customer logins <a class="example_sandbox_credentials_link" data-lift="WebUI.exampleSandboxCredentialsLink" href="">here</a>.</li>
|
||||
<li>Get <a href="/consumer-registration">API keys</a> and <a class="sdks_link" data-lift="WebUI.sdksLink" href="">SDKs</a>.</li>
|
||||
</ol>
|
||||
</p>
|
||||
|
||||
|
||||
3
src/main/webapp/media/css/overrides/enbdg.css
Normal file
3
src/main/webapp/media/css/overrides/enbdg.css
Normal file
@ -0,0 +1,3 @@
|
||||
#header-decoration {
|
||||
background-color: #646ca1;
|
||||
}
|
||||
84
src/main/webapp/media/css/overrides/uk.css
Normal file
84
src/main/webapp/media/css/overrides/uk.css
Normal file
@ -0,0 +1,84 @@
|
||||
h1 {
|
||||
color: white;
|
||||
font-family: "Nunito", "sans-serif";
|
||||
font-size: 36px;
|
||||
margin-bottom: 10px;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
#main #main-about {
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
|
||||
.top {
|
||||
background-color: #53C4EF;
|
||||
font-family: "Nunito", "sans-serif";
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
.intro {
|
||||
font-family: "Nunito", "sans-serif";
|
||||
margin: 0 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.content-big {
|
||||
font-family: "Nunito", "sans-serif";
|
||||
margin: 30px 50px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
font-family: "Nunito", "sans-serif";
|
||||
margin: 40px 0;
|
||||
text-align: center;
|
||||
}
|
||||
.buttons a {
|
||||
border: 2px solid white;
|
||||
border-radius: 24px;
|
||||
color: white;
|
||||
margin-bottom: 10px;
|
||||
font-family: "Nunito", "sans-serif";
|
||||
font-size: x-large;
|
||||
font-weight: bold;
|
||||
padding: 10px 50px;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.content-small {
|
||||
background-color: white;
|
||||
font-family: "Nunito", "sans-serif";
|
||||
margin: 20px 20px 0 20px;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.newsletter {
|
||||
background-color: black;
|
||||
font-family: "Nunito", "sans-serif";
|
||||
margin: 0 20px;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
.newsletter input {
|
||||
border: 1px solid black;
|
||||
border-top-left-radius: 24px;
|
||||
border-bottom-left-radius: 24px;
|
||||
font-family: "Nunito", "sans-serif";
|
||||
height: auto;
|
||||
padding: 10px 20px;
|
||||
}
|
||||
.newsletter button {
|
||||
border: 1px solid black;
|
||||
border-left: 0px;
|
||||
border-top-right-radius: 24px;
|
||||
border-bottom-right-radius: 24px;
|
||||
background-color: #50b165;
|
||||
color: white;
|
||||
font-family: "Nunito", "sans-serif";
|
||||
font-size: 17px;
|
||||
margin-left: -6px;
|
||||
padding: 10px 30px 10px 10px;
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
#main-about h1 {
|
||||
color: white;
|
||||
font-size: 35px;
|
||||
margin-bottom: 15px;
|
||||
margin-left: -5px;
|
||||
}
|
||||
|
||||
#main-about p {
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#main-about ul
|
||||
{
|
||||
list-style-type: initial;
|
||||
}
|
||||
|
||||
#main-about li
|
||||
{
|
||||
color: white;
|
||||
text-align: left;
|
||||
margin-left: 100px;
|
||||
}
|
||||
|
||||
#main-about img {
|
||||
margin-top: 30px;
|
||||
}
|
||||
@ -894,8 +894,21 @@ span#accountsMsg {
|
||||
}
|
||||
|
||||
#admin-logout {
|
||||
margin: 20px;
|
||||
padding: 10px;
|
||||
padding-left: 10px;
|
||||
border: 2px solid;
|
||||
border-color: #FF0000;
|
||||
margin-top:-6px;
|
||||
padding: 4px 0 3px 10px;
|
||||
}
|
||||
|
||||
#admin-consumer-summary {
|
||||
color: black;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
#login_special_instructions {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -1,4 +1,10 @@
|
||||
<div data-lift="Login.customiseLogin">
|
||||
|
||||
<form class="login" action="/user_mgt/login" method="post">
|
||||
|
||||
<div class="login_special_instructions" id="login_special_instructions">Special Instructions</div>
|
||||
|
||||
|
||||
<div class="field username">
|
||||
<input class="username" type="email" placeholder="Username" name="username" tabindex=1 autofocus />
|
||||
</div>
|
||||
@ -16,4 +22,6 @@
|
||||
<a href="/user_mgt/sign_up" class="signup" tabindex=5>Register</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
@ -1,6 +1,6 @@
|
||||
<!--
|
||||
Open Bank Project - API
|
||||
Copyright (C) 2011-2015, TESOBE / Music Pictures Ltd.
|
||||
Copyright (C) 2011-2015, TESOBE Ltd.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
@ -16,7 +16,7 @@ You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Email: contact@tesobe.com
|
||||
TESOBE / Music Pictures Ltd
|
||||
TESOBE Ltd.
|
||||
Osloerstrasse 16/17
|
||||
Berlin 13359, Germany
|
||||
|
||||
@ -122,7 +122,8 @@ Berlin 13359, Germany
|
||||
<a href="http://twitter.com/#!/OpenBankProject">Twitter</a> |
|
||||
<a href="https://github.com/OpenBankProject/OBP-API/">Github</a> |
|
||||
<a class="api-documentation-link lift:WebUI.apiDocumentationLink" href="">API Documentation</a> |
|
||||
<a class="api-explorer-link lift:WebUI.apiExplorerLink" href="">API Explorer</a>
|
||||
<a class="api-explorer-link lift:WebUI.apiExplorerLink" href="">API Explorer</a> |
|
||||
<a class="sofi-link lift:WebUI.sofiLink" href="">Sofi</a>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
@ -15,12 +15,17 @@ class CustomerTest extends V140ServerSetup with DefaultUsers {
|
||||
val mockBankId = BankId("testBank1")
|
||||
|
||||
case class MockFaceImage(date : Date, url : String) extends CustomerFaceImage
|
||||
case class MockCustomer(number : String, mobileNumber : String,
|
||||
legalName : String, email : String,
|
||||
faceImage : MockFaceImage) extends Customer
|
||||
|
||||
case class MockCustomer(number: String, mobileNumber: String,
|
||||
legalName: String, email: String,
|
||||
faceImage: MockFaceImage, dateOfBirth: Date,
|
||||
relationshipStatus: String, dependents: Int,
|
||||
dobOfDependents: List[Date], highestEducationAttained: String,
|
||||
employmentStatus: String, kycStatus: Boolean, lastOkDate: Date) extends Customer
|
||||
|
||||
val format = new java.text.SimpleDateFormat("dd/MM/yyyy")
|
||||
val mockCustomerFaceImage = MockFaceImage(new Date(1234000), "http://example.com/image1")
|
||||
val mockCustomer = MockCustomer("123", "3939", "Bob", "bob@example.com", mockCustomerFaceImage)
|
||||
val mockCustomer = MockCustomer("123", "3939", "Bob", "bob@example.com", mockCustomerFaceImage, new Date(1234000), "Single", 3, List(format.parse("30/03/2012"), format.parse("30/03/2012"), format.parse("30/03/2014")), "Bachelor’s Degree", "Employed", true, new Date(1234000))
|
||||
|
||||
object MockedCustomerProvider extends CustomerProvider {
|
||||
override def getCustomer(bankId: BankId, user: User): Box[Customer] = {
|
||||
@ -29,7 +34,15 @@ class CustomerTest extends V140ServerSetup with DefaultUsers {
|
||||
}
|
||||
|
||||
override def getUser(bankId: BankId, customerId: String): Box[User] = Empty
|
||||
override def addCustomer(bankId : BankId, user : User, number : String, legalName : String, mobileNumber : String, email : String, faceImage: CustomerFaceImage) : Box[Customer] = Empty
|
||||
override def addCustomer(bankId : BankId, user : User, number : String, legalName : String, mobileNumber : String, email : String, faceImage: CustomerFaceImage,
|
||||
dateOfBirth: Date,
|
||||
relationshipStatus: String,
|
||||
dependents: Int,
|
||||
dobOfDependents: List[Date],
|
||||
highestEducationAttained: String,
|
||||
employmentStatus: String,
|
||||
kycStatus: Boolean,
|
||||
lastOkDate: Date) : Box[Customer] = Empty
|
||||
}
|
||||
|
||||
override def beforeAll() {
|
||||
@ -92,8 +105,19 @@ class CustomerTest extends V140ServerSetup with DefaultUsers {
|
||||
And("We should get the right information back")
|
||||
|
||||
val info = response.body.extract[CustomerJson]
|
||||
val received = MockCustomer(info.customer_number, info.mobile_phone_number,
|
||||
info.legal_name, info.email, MockFaceImage(info.face_image.date, info.face_image.url))
|
||||
val received = MockCustomer(info.customer_number,
|
||||
info.mobile_phone_number,
|
||||
info.legal_name,
|
||||
info.email,
|
||||
MockFaceImage(info.face_image.date, info.face_image.url),
|
||||
info.date_of_birth,
|
||||
info.relationship_status,
|
||||
info.dependants,
|
||||
info.dob_of_dependants,
|
||||
info.highest_education_attained,
|
||||
info.employment_status,
|
||||
info.kyc_status,
|
||||
info.last_ok_date)
|
||||
|
||||
received should equal(mockCustomer)
|
||||
}
|
||||
|
||||
@ -48,7 +48,15 @@ class MappedCustomerMessagesTest extends V140ServerSetup with DefaultUsers {
|
||||
legal_name = "Someone",
|
||||
mobile_phone_number = "125245",
|
||||
email = "hello@hullo.com",
|
||||
face_image = CustomerFaceImageJson("www.example.com/person/123/image.png", exampleDate)
|
||||
face_image = CustomerFaceImageJson("www.example.com/person/123/image.png", exampleDate),
|
||||
date_of_birth = exampleDate,
|
||||
relationship_status = "Single",
|
||||
dependants = 1,
|
||||
dob_of_dependants = List(exampleDate),
|
||||
highest_education_attained = "Bachelor’s Degree",
|
||||
employment_status = "Employed",
|
||||
kyc_status = true,
|
||||
last_ok_date = exampleDate
|
||||
)
|
||||
var response = makePostRequest(request, write(customerJson))
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ package code.api.v1_4_0
|
||||
import code.api.DefaultUsers
|
||||
import code.api.test.{APIResponse, ServerSetupWithTestData, ServerSetup}
|
||||
import code.api.util.APIUtil.OAuth.{Token, Consumer}
|
||||
import code.api.v1_2_1.{TransactionsJSON, TransactionJSON, MakePaymentJson}
|
||||
import code.api.v1_2_1.{TransactionsJSON, TransactionJSON, MakePaymentJson, AmountOfMoneyJSON}
|
||||
import code.api.v1_4_0.JSONFactory1_4_0._
|
||||
import code.bankconnectors.Connector
|
||||
import code.model.{TransactionRequestId, AccountId, BankAccount}
|
||||
@ -74,12 +74,12 @@ class TransactionRequestsTest extends ServerSetupWithTestData with DefaultUsers
|
||||
|
||||
//call createTransactionRequest
|
||||
var request = (v1_4Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value /
|
||||
"owner" / "transaction-request-types" / "SANDBOX" / "transaction-requests").POST <@(user1)
|
||||
"owner" / "transaction-request-types" / "SANDBOX_TAN" / "transaction-requests").POST <@(user1)
|
||||
var response = makePostRequest(request, write(transactionRequestBody))
|
||||
Then("we should get a 201 created code")
|
||||
response.code should equal(201)
|
||||
|
||||
//created a transaction request, check some return values. As type is SANDBOX, we expect no challenge
|
||||
//created a transaction request, check some return values. As type is SANDBOX_TAN, we expect no challenge
|
||||
val transId: String = (response.body \ "id" \ "value") match {
|
||||
case JString(i) => i
|
||||
case _ => ""
|
||||
@ -141,36 +141,24 @@ class TransactionRequestsTest extends ServerSetupWithTestData with DefaultUsers
|
||||
}
|
||||
description should not equal ("")
|
||||
|
||||
//TODO: check that the balances have been properly decreased/increased (since we handle that logic for sandbox accounts at least)
|
||||
//check that the balances have been properly decreased/increased (since we handle that logic for sandbox accounts at least)
|
||||
//(do it here even though the payments test does test makePayment already)
|
||||
|
||||
/* val fromAccountTransAmt = transJson.details.value.amount
|
||||
//the from account transaction should have a negative value
|
||||
//since money left the account
|
||||
And("the json we receive back should have a transaction amount equal to the amount specified to pay")
|
||||
fromAccountTransAmt should equal((-amt).toString)
|
||||
val fromAccountBalance = getFromAccount.balance
|
||||
And("the from account should have a balance smaller by the amount specified to pay")
|
||||
fromAccountBalance should equal((beforeFromBalance - amt))
|
||||
|
||||
val expectedNewFromBalance = beforeFromBalance - amt
|
||||
And("the account sending the payment should have a new_balance amount equal to the previous balance minus the amount paid")
|
||||
transJson.details.new_balance.amount should equal(expectedNewFromBalance.toString)
|
||||
getFromAccount.balance should equal(expectedNewFromBalance)
|
||||
val toAccountTransactionsReq = getTransactions(toAccount.bankId.value, toAccount.accountId.value, view, user1)
|
||||
toAccountTransactionsReq.code should equal(200)
|
||||
val toAccountTransactions = toAccountTransactionsReq.body.extract[TransactionsJSON]
|
||||
val newestToAccountTransaction = toAccountTransactions.transactions(0)
|
||||
|
||||
//here amt should be positive (unlike in the transaction in the "from" account")
|
||||
/*
|
||||
And("the newest transaction for the account receiving the payment should have the proper amount")
|
||||
newestToAccountTransaction.details.value.amount should equal(amt.toString)
|
||||
*/
|
||||
|
||||
And("the account receiving the payment should have the proper balance")
|
||||
val expectedNewToBalance = beforeToBalance + amt
|
||||
newestToAccountTransaction.details.new_balance.amount should equal(expectedNewToBalance.toString)
|
||||
getToAccount.balance should equal(expectedNewToBalance)
|
||||
And("the account receiving the payment should have a new balance plus the amount paid")
|
||||
val toAccountBalance = getToAccount.balance
|
||||
toAccountBalance should equal(beforeToBalance + amt)
|
||||
|
||||
And("there should now be 2 new transactions in the database (one for the sender, one for the receiver")
|
||||
transactionCount(fromAccount, toAccount) should equal(totalTransactionsBefore + 2)
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
@ -216,12 +204,12 @@ class TransactionRequestsTest extends ServerSetupWithTestData with DefaultUsers
|
||||
|
||||
//call createTransactionRequest API method
|
||||
var request = (v1_4Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value /
|
||||
"owner" / "transaction-request-types" / "SANDBOX" / "transaction-requests").POST <@ (user1)
|
||||
"owner" / "transaction-request-types" / "SANDBOX_TAN" / "transaction-requests").POST <@ (user1)
|
||||
var response = makePostRequest(request, write(transactionRequestBody))
|
||||
Then("we should get a 201 created code")
|
||||
response.code should equal(201)
|
||||
|
||||
//ok, created a transaction request, check some return values. As type is SANDBOX but over 100€, we expect a challenge
|
||||
//ok, created a transaction request, check some return values. As type is SANDBOX_TAN but over 100€, we expect a challenge
|
||||
val transId: String = (response.body \ "id" \ "value") match {
|
||||
case JString(i) => i
|
||||
case _ => ""
|
||||
@ -272,7 +260,7 @@ class TransactionRequestsTest extends ServerSetupWithTestData with DefaultUsers
|
||||
//call answerTransactionRequestChallenge, give a false answer
|
||||
var answerJson = ChallengeAnswerJSON(id = challenge_id, answer = "hello") //wrong answer, not a number
|
||||
request = (v1_4Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value /
|
||||
"owner" / "transaction-request-types" / "sandbox" / "transaction-requests" / transId / "challenge").POST <@ (user1)
|
||||
"owner" / "transaction-request-types" / "SANDBOX_TAN" / "transaction-requests" / transId / "challenge").POST <@ (user1)
|
||||
response = makePostRequest(request, write(answerJson))
|
||||
Then("we should get a 400 bad request code")
|
||||
response.code should equal(400)
|
||||
@ -282,7 +270,7 @@ class TransactionRequestsTest extends ServerSetupWithTestData with DefaultUsers
|
||||
//call answerTransactionRequestChallenge again, give a good answer
|
||||
answerJson = ChallengeAnswerJSON(id = challenge_id, answer = "12345") //wrong answer, not a number
|
||||
request = (v1_4Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value /
|
||||
"owner" / "transaction-request-types" / "sandbox" / "transaction-requests" / transId / "challenge").POST <@ (user1)
|
||||
"owner" / "transaction-request-types" / "SANDBOX_TAN" / "transaction-requests" / transId / "challenge").POST <@ (user1)
|
||||
response = makePostRequest(request, write(answerJson))
|
||||
Then("we should get a 202 accepted code")
|
||||
response.code should equal(202)
|
||||
@ -318,6 +306,25 @@ class TransactionRequestsTest extends ServerSetupWithTestData with DefaultUsers
|
||||
|
||||
challenge = (response.body \ "challenge").children
|
||||
challenge.size should not equal(0)
|
||||
|
||||
//check that the balances have been properly decreased/increased (since we handle that logic for sandbox accounts at least)
|
||||
//(do it here even though the payments test does test makePayment already)
|
||||
|
||||
val fromAccountBalance = getFromAccount.balance
|
||||
And("the from account should have a balance smaller by the amount specified to pay")
|
||||
fromAccountBalance should equal((beforeFromBalance - amt))
|
||||
|
||||
/*
|
||||
And("the newest transaction for the account receiving the payment should have the proper amount")
|
||||
newestToAccountTransaction.details.value.amount should equal(amt.toString)
|
||||
*/
|
||||
|
||||
And("the account receiving the payment should have a new balance plus the amount paid")
|
||||
val toAccountBalance = getToAccount.balance
|
||||
toAccountBalance should equal(beforeToBalance + amt)
|
||||
|
||||
And("there should now be 2 new transactions in the database (one for the sender, one for the receiver")
|
||||
transactionCount(fromAccount, toAccount) should equal(totalTransactionsBefore + 2)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -42,17 +42,25 @@ import code.api.ObpJson.BarebonesAccountsJson
|
||||
case class CounterpartyJSONRecord(name: String, category: String, superCategory: String, logoUrl: String, homePageUrl: String, region: String)
|
||||
case class UserJSONRecord(email: String, password: String, display_name: String)
|
||||
|
||||
|
||||
// Import counterparty metadata
|
||||
// Instructions for using this:
|
||||
// Run a copy of the API somewhere (else)
|
||||
// Set the paths for users and counterparties. (remove the outer [] from the json)
|
||||
|
||||
|
||||
object ImportCounterpartyMetadata extends SendServerRequests {
|
||||
def main(args : Array[String]) {
|
||||
implicit val formats = DefaultFormats
|
||||
|
||||
//load json for counterpaties
|
||||
var path = "/Users/stefan/Downloads/OBP_sandbox_counterparties_pretty.json"
|
||||
var path = "/Users/simonredfern/Documents/OpenBankProject/DATA/API_sandbox/ulster_bank_2016/OBP_sandbox_counterparties_pretty_MOD.json"
|
||||
var records = JsonParser.parse(Source.fromFile(path) mkString)
|
||||
var counterparties = ListBuffer[CounterpartyJSONRecord]()
|
||||
|
||||
//collect counterparties records
|
||||
for(r <- records.children){
|
||||
//logger.info(s" extract counterparty records")
|
||||
val rec = r.extract[CounterpartyJSONRecord]
|
||||
//println (rec.name + "in region " + rec.region)
|
||||
counterparties.append(rec)
|
||||
@ -61,7 +69,7 @@ object ImportCounterpartyMetadata extends SendServerRequests {
|
||||
println("Got " + counterparties.length + " counterparty records")
|
||||
|
||||
//load sandbox users from json
|
||||
path = "/Users/stefan/Downloads/OBP_sandbox_pretty_load_002.json"
|
||||
path = "/Users/simonredfern/Documents/OpenBankProject/DATA/API_sandbox/ulster_bank_2016/loaded_30_jan_UB_OBP_sandbox_pretty.json"
|
||||
records = JsonParser.parse(Source.fromFile(path) mkString)
|
||||
val users = (records \ "users").children
|
||||
println("got " + users.length + " users")
|
||||
|
||||
Loading…
Reference in New Issue
Block a user