@@ -25,10 +25,26 @@ import (
2525 "github.com/koderover/zadig/v2/pkg/microservice/user/core/repository"
2626 usermodels "github.com/koderover/zadig/v2/pkg/microservice/user/core/repository/models"
2727 "github.com/koderover/zadig/v2/pkg/microservice/user/core/repository/orm"
28+ permissionservice "github.com/koderover/zadig/v2/pkg/microservice/user/core/service/permission"
2829 "github.com/koderover/zadig/v2/pkg/setting"
2930 internalhandler "github.com/koderover/zadig/v2/pkg/shared/handler"
31+ pkgtypes "github.com/koderover/zadig/v2/pkg/types"
3032)
3133
34+ type permissionActionSeed430 struct {
35+ Name string
36+ Action string
37+ Resource string
38+ Scope int
39+ }
40+
41+ var businessDirectoryActionSeeds430 = []permissionActionSeed430 {
42+ {Name : "查看" , Action : permissionservice .VerbGetBusinessDirectory , Resource : "BusinessDirectory" , Scope : pkgtypes .DBSystemScope },
43+ {Name : "新建" , Action : permissionservice .VerbCreateBusinessDirectory , Resource : "BusinessDirectory" , Scope : pkgtypes .DBSystemScope },
44+ {Name : "编辑" , Action : permissionservice .VerbEditBusinessDirectory , Resource : "BusinessDirectory" , Scope : pkgtypes .DBSystemScope },
45+ {Name : "删除" , Action : permissionservice .VerbDeleteBusinessDirectory , Resource : "BusinessDirectory" , Scope : pkgtypes .DBSystemScope },
46+ }
47+
3248func init () {
3349 upgradepath .RegisterHandler ("4.2.1" , "4.3.0" , V421ToV430 )
3450 upgradepath .RegisterHandler ("4.3.0" , "4.2.1" , V430ToV421 )
@@ -90,6 +106,16 @@ func migrateGlobalReadOnlyRole(_ *internalhandler.Context, migrationInfo *intern
90106 if err != nil {
91107 return err
92108 }
109+ // Ensure business-directory actions exist for upgraded instances.
110+ if err := ensureBusinessDirectoryActions430 (); err != nil {
111+ return err
112+ }
113+ // Fallback backfill:
114+ // - if a role already has get_business_directory, append create/edit/delete
115+ // - otherwise append the full business-directory action set.
116+ if err := backfillBusinessDirectoryRolePermissions430 (); err != nil {
117+ return err
118+ }
93119
94120 _ = internalmongodb .NewMigrationColl ().UpdateMigrationStatus (migrationInfo .ID , map [string ]interface {}{
95121 getMigrationFieldBsonTag (migrationInfo , & migrationInfo .Migration430GlobalReadOnlyRole ): true ,
@@ -98,6 +124,136 @@ func migrateGlobalReadOnlyRole(_ *internalhandler.Context, migrationInfo *intern
98124 return nil
99125}
100126
127+ func ensureBusinessDirectoryActions430 () error {
128+ tx := repository .DB .Begin ()
129+ if tx .Error != nil {
130+ return fmt .Errorf ("failed to begin tx for business directory action migration, err: %s" , tx .Error )
131+ }
132+
133+ for _ , seed := range businessDirectoryActionSeeds430 {
134+ action , err := orm .GetActionByVerb (seed .Action , tx )
135+ if err != nil {
136+ tx .Rollback ()
137+ return fmt .Errorf ("failed to query action %s, err: %s" , seed .Action , err )
138+ }
139+ if action != nil && action .ID != 0 {
140+ continue
141+ }
142+
143+ action = & usermodels.Action {
144+ Name : seed .Name ,
145+ Action : seed .Action ,
146+ Resource : seed .Resource ,
147+ Scope : seed .Scope ,
148+ }
149+ if err := orm .CreateAction (action , tx ); err != nil {
150+ tx .Rollback ()
151+ return fmt .Errorf ("failed to create action %s, err: %s" , seed .Action , err )
152+ }
153+ }
154+
155+ if err := tx .Commit ().Error ; err != nil {
156+ return fmt .Errorf ("failed to commit business directory action migration tx, err: %s" , err )
157+ }
158+ return nil
159+ }
160+
161+ // backfillBusinessDirectoryRolePermissions430 provides a migration fallback for
162+ // historical system roles:
163+ // 1) If a role already has get_business_directory, only append write verbs.
164+ // 2) If a role does not have get_business_directory, append the full set.
165+ // global-read-only role is skipped to keep readonly semantics.
166+ func backfillBusinessDirectoryRolePermissions430 () error {
167+ tx := repository .DB .Begin ()
168+ if tx .Error != nil {
169+ return fmt .Errorf ("failed to begin tx for business directory permission backfill, err: %s" , tx .Error )
170+ }
171+
172+ actionIDMap := make (map [string ]uint , len (businessDirectoryActionSeeds430 ))
173+ for _ , seed := range businessDirectoryActionSeeds430 {
174+ action , err := orm .GetActionByVerb (seed .Action , tx )
175+ if err != nil {
176+ tx .Rollback ()
177+ return fmt .Errorf ("failed to query action %s for backfill, err: %s" , seed .Action , err )
178+ }
179+ if action == nil || action .ID == 0 {
180+ tx .Rollback ()
181+ return fmt .Errorf ("action %s is missing while backfilling business directory permissions" , seed .Action )
182+ }
183+ actionIDMap [seed .Action ] = action .ID
184+ }
185+
186+ roles , err := orm .ListRoleByNamespace (permissionservice .GeneralNamespace , tx )
187+ if err != nil {
188+ tx .Rollback ()
189+ return fmt .Errorf ("failed to list system roles for business directory backfill, err: %s" , err )
190+ }
191+
192+ for _ , role := range roles {
193+ if role == nil || role .ID == 0 {
194+ continue
195+ }
196+ // Keep global-read-only role as readonly.
197+ if role .GlobalReadOnly {
198+ continue
199+ }
200+
201+ actions , err := orm .ListActionByRole (role .ID , tx )
202+ if err != nil {
203+ tx .Rollback ()
204+ return fmt .Errorf ("failed to list actions for role %d during business directory backfill, err: %s" , role .ID , err )
205+ }
206+
207+ existingVerbs := map [string ]struct {}{}
208+ for _ , action := range actions {
209+ if action == nil {
210+ continue
211+ }
212+ existingVerbs [action .Action ] = struct {}{}
213+ }
214+
215+ // Fallback strategy:
216+ // - has get_business_directory: only backfill create/edit/delete
217+ // - does not have get_business_directory: backfill get/create/edit/delete
218+ targetVerbs := []string {
219+ permissionservice .VerbGetBusinessDirectory ,
220+ permissionservice .VerbCreateBusinessDirectory ,
221+ permissionservice .VerbEditBusinessDirectory ,
222+ permissionservice .VerbDeleteBusinessDirectory ,
223+ }
224+ if _ , hasGet := existingVerbs [permissionservice .VerbGetBusinessDirectory ]; hasGet {
225+ targetVerbs = []string {
226+ permissionservice .VerbCreateBusinessDirectory ,
227+ permissionservice .VerbEditBusinessDirectory ,
228+ permissionservice .VerbDeleteBusinessDirectory ,
229+ }
230+ }
231+
232+ missingActionIDs := make ([]uint , 0 )
233+ for _ , verb := range targetVerbs {
234+ if _ , ok := existingVerbs [verb ]; ok {
235+ continue
236+ }
237+ if actionID , ok := actionIDMap [verb ]; ok {
238+ missingActionIDs = append (missingActionIDs , actionID )
239+ }
240+ }
241+
242+ if len (missingActionIDs ) == 0 {
243+ continue
244+ }
245+ if err := orm .BulkCreateRoleActionBindings (role .ID , missingActionIDs , tx ); err != nil {
246+ tx .Rollback ()
247+ return fmt .Errorf ("failed to backfill business directory permissions for role %d, err: %s" , role .ID , err )
248+ }
249+ }
250+
251+ if err := tx .Commit ().Error ; err != nil {
252+ return fmt .Errorf ("failed to commit business directory permission backfill tx, err: %s" , err )
253+ }
254+ return nil
255+ }
256+
101257// backfill global read only role
102258func backfillGlobalReadOnlyRole () error {
103259 tx := repository .DB .Begin ()
0 commit comments