@@ -6,6 +6,7 @@ import code.api.Constant._
66import code .api .util .APIUtil ._
77import code .api .util .ErrorMessages ._
88import code .api .util .{APIUtil , CallContext }
9+ import code .model .dataAccess .ResourceUser
910import code .util .Helper .MdcLoggable
1011import code .views .system .ViewDefinition .create
1112import code .views .system .{AccountAccess , ViewDefinition , ViewPermission }
@@ -61,18 +62,28 @@ object MapperViews extends Views with MdcLoggable {
6162 // 1. Separate system vs custom view access
6263 val (systemAccessList, customAccessList) = accountAccessList.partition(a => isValidSystemViewId(a.view_id.get))
6364
64- // 2. Batch-load system views: one query per distinct view_id (small fixed set)
65+ // 2. Batch-load system views: one query per distinct view_id (small fixed set).
66+ // We cache which view_ids exist to avoid repeated DB lookups, but we must call
67+ // findSystemView per access record because the returned ViewDefinition is mutable
68+ // and we need to set bank_id/account_id per access record without cross-contamination.
6569 val distinctSystemViewIds = systemAccessList.map(_.view_id.get).distinct
66- val systemViewMap : Map [String , ViewDefinition ] = distinctSystemViewIds
67- .flatMap (vid => ViewDefinition .findSystemView(vid).toList.map(v => vid -> v) )
68- .toMap
70+ val validSystemViewIds : Set [String ] = distinctSystemViewIds
71+ .filter (vid => ViewDefinition .findSystemView(vid).isDefined )
72+ .toSet
6973
7074 val systemPairs : List [(AccountAccess , ViewDefinition )] = systemAccessList.flatMap { aa =>
71- systemViewMap.get(aa.view_id.get).map { v =>
72- // Clone and set bank_id/account_id per access record (system views don't store these)
73- val viewWithContext = v.bank_id(aa.bank_id.get).account_id(aa.account_id.get)
74- (aa, viewWithContext)
75- }
75+ // Skip view_ids we already know don't exist in the DB (avoids pointless queries)
76+ if (validSystemViewIds.contains(aa.view_id.get)) {
77+ // We must call findSystemView per access record (not cache the ViewDefinition) because
78+ // ViewDefinition is a mutable Mapper object. v.bank_id(...) is a setter that mutates v
79+ // in place — caching one instance and reusing it across access records would cause each
80+ // call to overwrite the previous bank_id/account_id, corrupting earlier results.
81+ // System views are stored without bank_id/account_id (they're generic), so we stamp
82+ // each fresh instance with the specific account's context from the access record.
83+ ViewDefinition .findSystemView(aa.view_id.get).toList.map { v =>
84+ (aa, v.bank_id(aa.bank_id.get).account_id(aa.account_id.get))
85+ }
86+ } else Nil
7687 }
7788
7889 // 3. Batch-load custom views: one query using ByList on view_id, then filter in memory
@@ -100,29 +111,35 @@ object MapperViews extends Views with MdcLoggable {
100111 }
101112
102113 private def getViewsCommonPart (accountAccessList : List [AccountAccess ]): List [View ] = {
103- // we need to get views from accountAccess
104- val views : List [ViewDefinition ] = accountAccessList.flatMap(getViewFromAccountAccess).filter(
105- v =>
106- if (allowPublicViews) {
107- true // All views
108- } else {
109- v.isPrivate == true // Only private views
110- }
111- )
112- views
114+ batchLoadViewsForAccountAccess(accountAccessList).map(_._2)
113115 }
114116
115117 def permissions (account : BankIdAccountId ) : List [Permission ] = {
116-
117- val users = AccountAccess .findAll(
118+ // 1. Single query: get all AccountAccess for this account
119+ val allAccountAccess = AccountAccess .findAll(
118120 By (AccountAccess .bank_id, account.bankId.value),
119121 By (AccountAccess .account_id, account.accountId.value)
120- ).flatMap(_.user_fk.obj.toList).distinct
121-
122- for {
123- user <- users
124- } yield {
125- Permission (user, getViewsForUserAndAccount(user, account))
122+ )
123+
124+ // 2. Batch-load users: one query using ByList instead of N individual FK lookups
125+ val distinctUserFks = allAccountAccess.map(_.user_fk.get).distinct
126+ val usersMap : Map [Long , ResourceUser ] = if (distinctUserFks.nonEmpty) {
127+ ResourceUser .findAll(ByList (ResourceUser .id, distinctUserFks))
128+ .map(u => u.id.get -> u).toMap
129+ } else Map .empty
130+
131+ // 3. Batch-load views for all access records
132+ val viewPairs = batchLoadViewsForAccountAccess(allAccountAccess)
133+
134+ // 4. Group views by user FK and build Permission objects
135+ val viewsByUserFk : Map [Long , List [View ]] = viewPairs
136+ .groupBy(_._1.user_fk.get)
137+ .map { case (userFk, pairs) => userFk -> pairs.map(_._2: View ) }
138+
139+ distinctUserFks.flatMap { userFk =>
140+ usersMap.get(userFk).map { user =>
141+ Permission (user, viewsByUserFk.getOrElse(userFk, Nil ))
142+ }
126143 }
127144 }
128145
@@ -601,33 +618,33 @@ object MapperViews extends Views with MdcLoggable {
601618 }
602619
603620 def privateViewsUserCanAccess (user : User ): (List [View ], List [AccountAccess ]) = {
604- val allAccess = AccountAccess .findAllByUserPrimaryKey(user.userPrimaryKey)
605- val pairs = batchLoadViewsForAccountAccess(allAccess )
621+ val allAccountAccess = AccountAccess .findAllByUserPrimaryKey(user.userPrimaryKey)
622+ val pairs = batchLoadViewsForAccountAccess(allAccountAccess )
606623 (pairs.map(_._2).distinct, pairs.map(_._1))
607624 }
608625 def privateViewsUserCanAccess (user : User , viewIds : List [ViewId ]): (List [View ], List [AccountAccess ]) = {
609- val allAccess = AccountAccess .findAll(
626+ val allAccountAccess = AccountAccess .findAll(
610627 By (AccountAccess .user_fk, user.userPrimaryKey.value),
611628 ByList (AccountAccess .view_id, viewIds.map(_.value))
612629 )
613- val pairs = batchLoadViewsForAccountAccess(allAccess )
630+ val pairs = batchLoadViewsForAccountAccess(allAccountAccess )
614631 (pairs.map(_._2), pairs.map(_._1))
615632 }
616633 def privateViewsUserCanAccessAtBank (user : User , bankId : BankId ): (List [View ], List [AccountAccess ]) = {
617- val allAccess = AccountAccess .findAll(
634+ val allAccountAccess = AccountAccess .findAll(
618635 By (AccountAccess .user_fk, user.userPrimaryKey.value),
619636 By (AccountAccess .bank_id, bankId.value)
620637 )
621- val pairs = batchLoadViewsForAccountAccess(allAccess )
638+ val pairs = batchLoadViewsForAccountAccess(allAccountAccess )
622639 (pairs.map(_._2), pairs.map(_._1))
623640 }
624641 def getAccountAccessAtBankThroughView (user : User , bankId : BankId , viewId : ViewId ): (List [View ], List [AccountAccess ]) = {
625- val allAccess = AccountAccess .findAll(
642+ val allAccountAccess = AccountAccess .findAll(
626643 By (AccountAccess .user_fk, user.userPrimaryKey.value),
627644 By (AccountAccess .bank_id, bankId.value),
628645 By (AccountAccess .view_id, viewId.value)
629646 )
630- val pairs = batchLoadViewsForAccountAccess(allAccess )
647+ val pairs = batchLoadViewsForAccountAccess(allAccountAccess )
631648 (pairs.map(_._2), pairs.map(_._1))
632649 }
633650
0 commit comments