Skip to content

Commit 53f49c3

Browse files
authored
Merge pull request #2737 from simonredfern/develop
Using Doobie for Account Access queries . Adding SQL View
2 parents a9f6fb0 + 5dab2d0 commit 53f49c3

File tree

6 files changed

+320
-73
lines changed

6 files changed

+320
-73
lines changed
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package code.api.util
2+
3+
import doobie._
4+
import doobie.implicits._
5+
6+
/**
7+
* Row from the v_account_access_with_views SQL view.
8+
* This view joins accountaccess + resourceuser + viewdefinition in a single query.
9+
*/
10+
case class AccountAccessWithViewRow(
11+
accountAccessId: Long,
12+
bankId: String,
13+
accountId: String,
14+
viewId: String,
15+
consumerId: String,
16+
userId: String,
17+
username: String,
18+
email: Option[String],
19+
provider: String,
20+
resourceUserPrimaryKey: Long,
21+
viewName: String,
22+
viewDescription: Option[String],
23+
metadataView: Option[String],
24+
isSystem: Boolean,
25+
isPublic: Boolean,
26+
isFirehose: Boolean
27+
)
28+
29+
/**
30+
* Doobie queries against the v_account_access_with_views SQL view.
31+
*
32+
* These replace multiple Mapper queries (AccountAccess + ResourceUser + ViewDefinition)
33+
* with a single SQL query, eliminating N+1 patterns and reducing round-trips.
34+
*
35+
* The SQL view is created by MigrationOfAccountAccessWithViewsView.
36+
*/
37+
object DoobieAccountAccessViewQueries {
38+
39+
private val baseSelect = fr"""
40+
SELECT account_access_id, bank_id, account_id, view_id, consumer_id,
41+
user_id, username, email, provider, resource_user_primary_key,
42+
view_name, view_description, metadata_view, is_system, is_public, is_firehose
43+
FROM v_account_access_with_views
44+
"""
45+
46+
/**
47+
* Filter for private views only, unless allowPublicViews is enabled.
48+
*/
49+
private def privateFilter: Fragment = {
50+
if (APIUtil.allowPublicViews) fr""
51+
else fr"AND is_public = ${false}"
52+
}
53+
54+
/** Get all account access rows for a user (by userId UUID string). */
55+
def getByUser(userId: String): List[AccountAccessWithViewRow] = {
56+
val query = (baseSelect ++ fr"WHERE user_id = $userId" ++ privateFilter)
57+
.query[AccountAccessWithViewRow].to[List]
58+
DoobieUtil.runQuery(query)
59+
}
60+
61+
/** Get account access rows for a user at a specific bank. */
62+
def getByUserAndBank(userId: String, bankId: String): List[AccountAccessWithViewRow] = {
63+
val query = (baseSelect ++ fr"WHERE user_id = $userId AND bank_id = $bankId" ++ privateFilter)
64+
.query[AccountAccessWithViewRow].to[List]
65+
DoobieUtil.runQuery(query)
66+
}
67+
68+
/** Get account access rows for a user at a specific bank/account. */
69+
def getByUserBankAccount(userId: String, bankId: String, accountId: String): List[AccountAccessWithViewRow] = {
70+
val query = (baseSelect ++ fr"WHERE user_id = $userId AND bank_id = $bankId AND account_id = $accountId" ++ privateFilter)
71+
.query[AccountAccessWithViewRow].to[List]
72+
DoobieUtil.runQuery(query)
73+
}
74+
75+
/** Get account access rows for a user filtered by view IDs. */
76+
def getByUserAndViewIds(userId: String, viewIds: List[String]): List[AccountAccessWithViewRow] = {
77+
if (viewIds.isEmpty) return Nil
78+
val inClause = viewIds.map(v => fr"$v").reduceLeft((a, b) => a ++ fr"," ++ b)
79+
val query = (baseSelect ++ fr"WHERE user_id = $userId AND view_id IN (" ++ inClause ++ fr")" ++ privateFilter)
80+
.query[AccountAccessWithViewRow].to[List]
81+
DoobieUtil.runQuery(query)
82+
}
83+
84+
/** Get account access rows for a user at a specific bank through a specific view. */
85+
def getByUserBankView(userId: String, bankId: String, viewId: String): List[AccountAccessWithViewRow] = {
86+
val query = (baseSelect ++ fr"WHERE user_id = $userId AND bank_id = $bankId AND view_id = $viewId" ++ privateFilter)
87+
.query[AccountAccessWithViewRow].to[List]
88+
DoobieUtil.runQuery(query)
89+
}
90+
91+
/** Get all account access rows for a bank/account (for permissions). */
92+
def getByBankAccount(bankId: String, accountId: String): List[AccountAccessWithViewRow] = {
93+
val query = (baseSelect ++ fr"WHERE bank_id = $bankId AND account_id = $accountId" ++ privateFilter)
94+
.query[AccountAccessWithViewRow].to[List]
95+
DoobieUtil.runQuery(query)
96+
}
97+
}

obp-api/src/main/scala/code/api/util/migration/Migration.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ object Migration extends MdcLoggable {
109109
alterRoleNameLength()
110110
alterConsentRequestColumnConsumerIdLength()
111111
alterMappedConsentColumnConsumerIdLength()
112+
addAccountAccessWithViewsView(startedBeforeSchemifier)
112113
}
113114

114115
private def dummyScript(): Boolean = {
@@ -569,6 +570,18 @@ object Migration extends MdcLoggable {
569570
MigrationOfMappedConsent.alterColumnConsumerIdLength(name)
570571
}
571572
}
573+
574+
private def addAccountAccessWithViewsView(startedBeforeSchemifier: Boolean): Boolean = {
575+
if(startedBeforeSchemifier == true) {
576+
logger.warn(s"Migration.database.addAccountAccessWithViewsView(true) cannot be run before Schemifier.")
577+
true
578+
} else {
579+
val name = nameOf(addAccountAccessWithViewsView(startedBeforeSchemifier))
580+
runOnce(name) {
581+
MigrationOfAccountAccessWithViewsView.addAccountAccessWithViewsView(name)
582+
}
583+
}
584+
}
572585
}
573586

574587
/**
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package code.api.util.migration
2+
3+
import code.api.util.APIUtil
4+
import code.api.util.migration.Migration.{DbFunction, saveLog}
5+
import code.views.system.AccountAccess
6+
import net.liftweb.mapper.Schemifier
7+
8+
object MigrationOfAccountAccessWithViewsView {
9+
10+
def addAccountAccessWithViewsView(name: String): Boolean = {
11+
DbFunction.tableExists(AccountAccess) match {
12+
case true =>
13+
val startDate = System.currentTimeMillis()
14+
val commitId: String = APIUtil.gitCommit
15+
var isSuccessful = false
16+
17+
val executedSql =
18+
DbFunction.maybeWrite(true, Schemifier.infoF _) {
19+
APIUtil.getPropsValue("db.driver") openOr("org.h2.Driver") match {
20+
case value if value.contains("com.microsoft.sqlserver.jdbc.SQLServerDriver") =>
21+
() =>
22+
"""
23+
|CREATE OR ALTER VIEW v_account_access_with_views AS
24+
|SELECT
25+
| aa.id AS account_access_id,
26+
| aa.bank_id AS bank_id,
27+
| aa.account_id AS account_id,
28+
| aa.view_id AS view_id,
29+
| aa.consumer_id AS consumer_id,
30+
| ru.userid_ AS user_id,
31+
| ru.name_ AS username,
32+
| ru.email AS email,
33+
| ru.provider_ AS provider,
34+
| ru.id AS resource_user_primary_key,
35+
| vd.name_ AS view_name,
36+
| vd.description_ AS view_description,
37+
| vd.metadataview_ AS metadata_view,
38+
| vd.issystem_ AS is_system,
39+
| vd.ispublic_ AS is_public,
40+
| vd.isfirehose_ AS is_firehose
41+
|FROM accountaccess aa
42+
|JOIN resourceuser ru ON ru.id = aa.user_fk
43+
|JOIN viewdefinition vd ON (
44+
| (vd.issystem_ = 1 AND vd.view_id = aa.view_id)
45+
| OR
46+
| (vd.issystem_ = 0 AND vd.bank_id = aa.bank_id
47+
| AND vd.account_id = aa.account_id
48+
| AND vd.view_id = aa.view_id)
49+
|);
50+
|""".stripMargin
51+
case _ =>
52+
() =>
53+
"""
54+
|CREATE OR REPLACE VIEW v_account_access_with_views AS
55+
|SELECT
56+
| aa.id AS account_access_id,
57+
| aa.bank_id AS bank_id,
58+
| aa.account_id AS account_id,
59+
| aa.view_id AS view_id,
60+
| aa.consumer_id AS consumer_id,
61+
| ru.userid_ AS user_id,
62+
| ru.name_ AS username,
63+
| ru.email AS email,
64+
| ru.provider_ AS provider,
65+
| ru.id AS resource_user_primary_key,
66+
| vd.name_ AS view_name,
67+
| vd.description_ AS view_description,
68+
| vd.metadataview_ AS metadata_view,
69+
| vd.issystem_ AS is_system,
70+
| vd.ispublic_ AS is_public,
71+
| vd.isfirehose_ AS is_firehose
72+
|FROM accountaccess aa
73+
|JOIN resourceuser ru ON ru.id = aa.user_fk
74+
|JOIN viewdefinition vd ON (
75+
| (vd.issystem_ = true AND vd.view_id = aa.view_id)
76+
| OR
77+
| (vd.issystem_ = false AND vd.bank_id = aa.bank_id
78+
| AND vd.account_id = aa.account_id
79+
| AND vd.view_id = aa.view_id)
80+
|);
81+
|""".stripMargin
82+
}
83+
}
84+
85+
val endDate = System.currentTimeMillis()
86+
val comment: String =
87+
s"""Executed SQL:
88+
|$executedSql
89+
|""".stripMargin
90+
isSuccessful = true
91+
saveLog(name, commitId, isSuccessful, startDate, endDate, comment)
92+
isSuccessful
93+
94+
case false =>
95+
val startDate = System.currentTimeMillis()
96+
val commitId: String = APIUtil.gitCommit
97+
val isSuccessful = false
98+
val endDate = System.currentTimeMillis()
99+
val comment: String =
100+
s"""${AccountAccess._dbTableNameLC} table does not exist""".stripMargin
101+
saveLog(name, commitId, isSuccessful, startDate, endDate, comment)
102+
isSuccessful
103+
}
104+
}
105+
}

obp-api/src/main/scala/code/api/v6_0_0/APIMethods600.scala

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5000,7 +5000,7 @@ trait APIMethods600 {
50005000
bank_id = "",
50015001
account_id = "",
50025002
view_id = "owner",
5003-
short_name = "Owner",
5003+
view_name = "Owner",
50045004
description = "The owner of the account",
50055005
metadata_view = "owner",
50065006
is_public = false,
@@ -5059,7 +5059,7 @@ trait APIMethods600 {
50595059
bank_id = "",
50605060
account_id = "",
50615061
view_id = "owner",
5062-
short_name = "Owner",
5062+
view_name = "Owner",
50635063
description = "The owner of the account. Has full privileges.",
50645064
metadata_view = "owner",
50655065
is_public = false,
@@ -5121,7 +5121,7 @@ trait APIMethods600 {
51215121
// EmptyBody,
51225122
// ViewJsonV600(
51235123
// view_id = "owner",
5124-
// short_name = "Owner",
5124+
// view_name = "Owner",
51255125
// description = "The owner of the account. Has full privileges.",
51265126
// metadata_view = "owner",
51275127
// is_public = false,
@@ -5198,7 +5198,7 @@ trait APIMethods600 {
51985198
bank_id = "",
51995199
account_id = "",
52005200
view_id = "owner",
5201-
short_name = "Owner",
5201+
view_name = "Owner",
52025202
description = "This is the owner view",
52035203
metadata_view = "owner",
52045204
is_public = false,
@@ -5456,7 +5456,7 @@ trait APIMethods600 {
54565456
bank_id = ExampleValue.bankIdExample.value,
54575457
account_id = ExampleValue.accountIdExample.value,
54585458
view_id = "_work",
5459-
short_name = "Work",
5459+
view_name = "Work",
54605460
description = "A custom view for work-related transactions.",
54615461
metadata_view = "_work",
54625462
is_public = false,
@@ -11257,7 +11257,7 @@ trait APIMethods600 {
1125711257
|* Owners
1125811258
|* Type
1125911259
|* Balance
11260-
|* Available views (sorted by short_name)
11260+
|* Available views (sorted by view_name)
1126111261
|
1126211262
|More details about the data moderation by the view [here](#1_2_1-getViewsForBankAccount).
1126311263
|
@@ -11278,7 +11278,7 @@ trait APIMethods600 {
1127811278
bank_id = "",
1127911279
account_id = "",
1128011280
view_id = "owner",
11281-
short_name = "Owner",
11281+
view_name = "Owner",
1128211282
description = "The owner of the account",
1128311283
metadata_view = "owner",
1128411284
is_public = false,
@@ -11332,7 +11332,7 @@ trait APIMethods600 {
1133211332
BankIdAccountId(account.bankId, account.accountId)
1133311333
)
1133411334
val viewsAvailable =
11335-
availableViews.map(JSONFactory600.createViewJsonV600).sortBy(_.short_name)
11335+
availableViews.map(JSONFactory600.createViewJsonV600).sortBy(_.view_name)
1133611336
val tags = Tags.tags.vend.getTagsOnAccount(bankId, accountId)(viewId)
1133711337
(
1133811338
createBankAccountJSON600(

obp-api/src/main/scala/code/api/v6_0_0/JSONFactory6.0.0.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1787,7 +1787,7 @@ object JSONFactory600 extends CustomJsonFormats with MdcLoggable {
17871787
bank_id: String,
17881788
account_id: String,
17891789
view_id: String,
1790-
short_name: String,
1790+
view_name: String,
17911791
description: String,
17921792
metadata_view: String,
17931793
is_public: Boolean,
@@ -1841,7 +1841,7 @@ object JSONFactory600 extends CustomJsonFormats with MdcLoggable {
18411841
bank_id = view.bankId.value,
18421842
account_id = view.accountId.value,
18431843
view_id = view.viewId.value,
1844-
short_name = view.name,
1844+
view_name = view.name,
18451845
description = view.description,
18461846
metadata_view = view.metadataView,
18471847
is_public = view.isPublic,

0 commit comments

Comments
 (0)