Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package code.api.util

import doobie._
import doobie.implicits._

/**
* Row from the v_account_access_with_views SQL view.
* This view joins accountaccess + resourceuser + viewdefinition in a single query.
*/
case class AccountAccessWithViewRow(
accountAccessId: Long,
bankId: String,
accountId: String,
viewId: String,
consumerId: String,
userId: String,
username: String,
email: Option[String],
provider: String,
resourceUserPrimaryKey: Long,
viewName: String,
viewDescription: Option[String],
metadataView: Option[String],
isSystem: Boolean,
isPublic: Boolean,
isFirehose: Boolean
)

/**
* Doobie queries against the v_account_access_with_views SQL view.
*
* These replace multiple Mapper queries (AccountAccess + ResourceUser + ViewDefinition)
* with a single SQL query, eliminating N+1 patterns and reducing round-trips.
*
* The SQL view is created by MigrationOfAccountAccessWithViewsView.
*/
object DoobieAccountAccessViewQueries {

private val baseSelect = fr"""
SELECT account_access_id, bank_id, account_id, view_id, consumer_id,
user_id, username, email, provider, resource_user_primary_key,
view_name, view_description, metadata_view, is_system, is_public, is_firehose
FROM v_account_access_with_views
"""

/**
* Filter for private views only, unless allowPublicViews is enabled.
*/
private def privateFilter: Fragment = {
if (APIUtil.allowPublicViews) fr""
else fr"AND is_public = ${false}"
}

/** Get all account access rows for a user (by userId UUID string). */
def getByUser(userId: String): List[AccountAccessWithViewRow] = {
val query = (baseSelect ++ fr"WHERE user_id = $userId" ++ privateFilter)
.query[AccountAccessWithViewRow].to[List]
DoobieUtil.runQuery(query)
}

/** Get account access rows for a user at a specific bank. */
def getByUserAndBank(userId: String, bankId: String): List[AccountAccessWithViewRow] = {
val query = (baseSelect ++ fr"WHERE user_id = $userId AND bank_id = $bankId" ++ privateFilter)
.query[AccountAccessWithViewRow].to[List]
DoobieUtil.runQuery(query)
}

/** Get account access rows for a user at a specific bank/account. */
def getByUserBankAccount(userId: String, bankId: String, accountId: String): List[AccountAccessWithViewRow] = {
val query = (baseSelect ++ fr"WHERE user_id = $userId AND bank_id = $bankId AND account_id = $accountId" ++ privateFilter)
.query[AccountAccessWithViewRow].to[List]
DoobieUtil.runQuery(query)
}

/** Get account access rows for a user filtered by view IDs. */
def getByUserAndViewIds(userId: String, viewIds: List[String]): List[AccountAccessWithViewRow] = {
if (viewIds.isEmpty) return Nil
val inClause = viewIds.map(v => fr"$v").reduceLeft((a, b) => a ++ fr"," ++ b)
val query = (baseSelect ++ fr"WHERE user_id = $userId AND view_id IN (" ++ inClause ++ fr")" ++ privateFilter)
.query[AccountAccessWithViewRow].to[List]
DoobieUtil.runQuery(query)
}

/** Get account access rows for a user at a specific bank through a specific view. */
def getByUserBankView(userId: String, bankId: String, viewId: String): List[AccountAccessWithViewRow] = {
val query = (baseSelect ++ fr"WHERE user_id = $userId AND bank_id = $bankId AND view_id = $viewId" ++ privateFilter)
.query[AccountAccessWithViewRow].to[List]
DoobieUtil.runQuery(query)
}

/** Get all account access rows for a bank/account (for permissions). */
def getByBankAccount(bankId: String, accountId: String): List[AccountAccessWithViewRow] = {
val query = (baseSelect ++ fr"WHERE bank_id = $bankId AND account_id = $accountId" ++ privateFilter)
.query[AccountAccessWithViewRow].to[List]
DoobieUtil.runQuery(query)
}
}
13 changes: 13 additions & 0 deletions obp-api/src/main/scala/code/api/util/migration/Migration.scala
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ object Migration extends MdcLoggable {
alterRoleNameLength()
alterConsentRequestColumnConsumerIdLength()
alterMappedConsentColumnConsumerIdLength()
addAccountAccessWithViewsView(startedBeforeSchemifier)
}

private def dummyScript(): Boolean = {
Expand Down Expand Up @@ -569,6 +570,18 @@ object Migration extends MdcLoggable {
MigrationOfMappedConsent.alterColumnConsumerIdLength(name)
}
}

private def addAccountAccessWithViewsView(startedBeforeSchemifier: Boolean): Boolean = {
if(startedBeforeSchemifier == true) {
logger.warn(s"Migration.database.addAccountAccessWithViewsView(true) cannot be run before Schemifier.")
true
} else {
val name = nameOf(addAccountAccessWithViewsView(startedBeforeSchemifier))
runOnce(name) {
MigrationOfAccountAccessWithViewsView.addAccountAccessWithViewsView(name)
}
}
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package code.api.util.migration

import code.api.util.APIUtil
import code.api.util.migration.Migration.{DbFunction, saveLog}
import code.views.system.AccountAccess
import net.liftweb.mapper.Schemifier

object MigrationOfAccountAccessWithViewsView {

def addAccountAccessWithViewsView(name: String): Boolean = {
DbFunction.tableExists(AccountAccess) match {
case true =>
val startDate = System.currentTimeMillis()
val commitId: String = APIUtil.gitCommit
var isSuccessful = false

val executedSql =
DbFunction.maybeWrite(true, Schemifier.infoF _) {
APIUtil.getPropsValue("db.driver") openOr("org.h2.Driver") match {
case value if value.contains("com.microsoft.sqlserver.jdbc.SQLServerDriver") =>
() =>
"""
|CREATE OR ALTER VIEW v_account_access_with_views AS
|SELECT
| aa.id AS account_access_id,
| aa.bank_id AS bank_id,
| aa.account_id AS account_id,
| aa.view_id AS view_id,
| aa.consumer_id AS consumer_id,
| ru.userid_ AS user_id,
| ru.name_ AS username,
| ru.email AS email,
| ru.provider_ AS provider,
| ru.id AS resource_user_primary_key,
| vd.name_ AS view_name,
| vd.description_ AS view_description,
| vd.metadataview_ AS metadata_view,
| vd.issystem_ AS is_system,
| vd.ispublic_ AS is_public,
| vd.isfirehose_ AS is_firehose
|FROM accountaccess aa
|JOIN resourceuser ru ON ru.id = aa.user_fk
|JOIN viewdefinition vd ON (
| (vd.issystem_ = 1 AND vd.view_id = aa.view_id)
| OR
| (vd.issystem_ = 0 AND vd.bank_id = aa.bank_id
| AND vd.account_id = aa.account_id
| AND vd.view_id = aa.view_id)
|);
|""".stripMargin
case _ =>
() =>
"""
|CREATE OR REPLACE VIEW v_account_access_with_views AS
|SELECT
| aa.id AS account_access_id,
| aa.bank_id AS bank_id,
| aa.account_id AS account_id,
| aa.view_id AS view_id,
| aa.consumer_id AS consumer_id,
| ru.userid_ AS user_id,
| ru.name_ AS username,
| ru.email AS email,
| ru.provider_ AS provider,
| ru.id AS resource_user_primary_key,
| vd.name_ AS view_name,
| vd.description_ AS view_description,
| vd.metadataview_ AS metadata_view,
| vd.issystem_ AS is_system,
| vd.ispublic_ AS is_public,
| vd.isfirehose_ AS is_firehose
|FROM accountaccess aa
|JOIN resourceuser ru ON ru.id = aa.user_fk
|JOIN viewdefinition vd ON (
| (vd.issystem_ = true AND vd.view_id = aa.view_id)
| OR
| (vd.issystem_ = false AND vd.bank_id = aa.bank_id
| AND vd.account_id = aa.account_id
| AND vd.view_id = aa.view_id)
|);
|""".stripMargin
}
}

val endDate = System.currentTimeMillis()
val comment: String =
s"""Executed SQL:
|$executedSql
|""".stripMargin
isSuccessful = true
saveLog(name, commitId, isSuccessful, startDate, endDate, comment)
isSuccessful

case false =>
val startDate = System.currentTimeMillis()
val commitId: String = APIUtil.gitCommit
val isSuccessful = false
val endDate = System.currentTimeMillis()
val comment: String =
s"""${AccountAccess._dbTableNameLC} table does not exist""".stripMargin
saveLog(name, commitId, isSuccessful, startDate, endDate, comment)
isSuccessful
}
}
}
16 changes: 8 additions & 8 deletions obp-api/src/main/scala/code/api/v6_0_0/APIMethods600.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5000,7 +5000,7 @@ trait APIMethods600 {
bank_id = "",
account_id = "",
view_id = "owner",
short_name = "Owner",
view_name = "Owner",
description = "The owner of the account",
metadata_view = "owner",
is_public = false,
Expand Down Expand Up @@ -5059,7 +5059,7 @@ trait APIMethods600 {
bank_id = "",
account_id = "",
view_id = "owner",
short_name = "Owner",
view_name = "Owner",
description = "The owner of the account. Has full privileges.",
metadata_view = "owner",
is_public = false,
Expand Down Expand Up @@ -5121,7 +5121,7 @@ trait APIMethods600 {
// EmptyBody,
// ViewJsonV600(
// view_id = "owner",
// short_name = "Owner",
// view_name = "Owner",
// description = "The owner of the account. Has full privileges.",
// metadata_view = "owner",
// is_public = false,
Expand Down Expand Up @@ -5198,7 +5198,7 @@ trait APIMethods600 {
bank_id = "",
account_id = "",
view_id = "owner",
short_name = "Owner",
view_name = "Owner",
description = "This is the owner view",
metadata_view = "owner",
is_public = false,
Expand Down Expand Up @@ -5456,7 +5456,7 @@ trait APIMethods600 {
bank_id = ExampleValue.bankIdExample.value,
account_id = ExampleValue.accountIdExample.value,
view_id = "_work",
short_name = "Work",
view_name = "Work",
description = "A custom view for work-related transactions.",
metadata_view = "_work",
is_public = false,
Expand Down Expand Up @@ -11257,7 +11257,7 @@ trait APIMethods600 {
|* Owners
|* Type
|* Balance
|* Available views (sorted by short_name)
|* Available views (sorted by view_name)
|
|More details about the data moderation by the view [here](#1_2_1-getViewsForBankAccount).
|
Expand All @@ -11278,7 +11278,7 @@ trait APIMethods600 {
bank_id = "",
account_id = "",
view_id = "owner",
short_name = "Owner",
view_name = "Owner",
description = "The owner of the account",
metadata_view = "owner",
is_public = false,
Expand Down Expand Up @@ -11332,7 +11332,7 @@ trait APIMethods600 {
BankIdAccountId(account.bankId, account.accountId)
)
val viewsAvailable =
availableViews.map(JSONFactory600.createViewJsonV600).sortBy(_.short_name)
availableViews.map(JSONFactory600.createViewJsonV600).sortBy(_.view_name)
val tags = Tags.tags.vend.getTagsOnAccount(bankId, accountId)(viewId)
(
createBankAccountJSON600(
Expand Down
4 changes: 2 additions & 2 deletions obp-api/src/main/scala/code/api/v6_0_0/JSONFactory6.0.0.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1787,7 +1787,7 @@ object JSONFactory600 extends CustomJsonFormats with MdcLoggable {
bank_id: String,
account_id: String,
view_id: String,
short_name: String,
view_name: String,
description: String,
metadata_view: String,
is_public: Boolean,
Expand Down Expand Up @@ -1841,7 +1841,7 @@ object JSONFactory600 extends CustomJsonFormats with MdcLoggable {
bank_id = view.bankId.value,
account_id = view.accountId.value,
view_id = view.viewId.value,
short_name = view.name,
view_name = view.name,
description = view.description,
metadata_view = view.metadataView,
is_public = view.isPublic,
Expand Down
Loading
Loading