From f6aead62bb79859f10bc4325657f503e889a3eec Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 4 Nov 2025 18:07:22 +0800 Subject: [PATCH] Refactor scope loading to dynamically retrieve first-level subdirectories - Introduced a new function to get first-level subdirectories in the scopes directory, enhancing the flexibility of scope definition loading. - Updated the loadScopeDefinitions method to utilize this new function, replacing hardcoded subdirectory names with a dynamic approach. - Improved the description of the invite verification scope in the user scope initialization for clarity. --- openapi/oauth/acl/scope.go | 49 +++++++++++++++++++++++++++++++------- openapi/user/user.go | 5 ++-- seed/seed_reset_test.go | 32 ++++++++++++------------- 3 files changed, 58 insertions(+), 28 deletions(-) diff --git a/openapi/oauth/acl/scope.go b/openapi/oauth/acl/scope.go index fafdc8ba..abe1d31d 100644 --- a/openapi/oauth/acl/scope.go +++ b/openapi/oauth/acl/scope.go @@ -223,22 +223,53 @@ func (m *ScopeManager) expandAlias(alias string, visited map[string]bool) ([]str return expanded, nil } +// getFirstLevelSubdirs returns all first-level subdirectories in a given directory +func getFirstLevelSubdirs(baseDir string) ([]string, error) { + var subdirs []string + + err := application.App.Walk(baseDir, func(root, filename string, isdir bool) error { + // Only process directories + if !isdir { + return nil + } + + // Skip the root directory itself + if filename == baseDir { + return nil + } + + // Get relative path from baseDir + relPath := strings.TrimPrefix(filename, baseDir) + relPath = strings.TrimPrefix(relPath, string(filepath.Separator)) + + // Only include first-level directories (no nested paths) + if !strings.Contains(relPath, string(filepath.Separator)) && relPath != "" { + subdirs = append(subdirs, relPath) + } + + return nil + }, "") + + if err != nil { + return nil, err + } + + return subdirs, nil +} + // loadScopeDefinitions loads scope definitions from subdirectories func (m *ScopeManager) loadScopeDefinitions() error { scopesDir := filepath.Join("openapi", "scopes") - // Subdirectories to scan - subDirs := []string{"kb", "job", "file", "user"} + // Get all subdirectories in the scopes directory + subDirs, err := getFirstLevelSubdirs(scopesDir) + if err != nil { + return fmt.Errorf("failed to scan scopes directory: %w", err) + } + // Load scope definitions from each subdirectory for _, subDir := range subDirs { dirPath := filepath.Join(scopesDir, subDir) - exists, err := application.App.Exists(dirPath) - if err != nil { - return err - } - if !exists { - continue - } // Walk through all .yml files in the directory err = application.App.Walk(dirPath, func(root, filename string, isdir bool) error { diff --git a/openapi/user/user.go b/openapi/user/user.go index e7fb84c4..358f7ecf 100644 --- a/openapi/user/user.go +++ b/openapi/user/user.go @@ -35,12 +35,11 @@ func init() { "GET /file/:uploaderID/:fileID/content", }, }, - // Invite verification scope - allows users to accept team invitations + // Invite verification scope - allows users to view invitation details before accepting &acl.ScopeDefinition{ Name: ScopeInviteVerification, - Description: "Invite verification - temporary access for accepting team invitations", + Description: "Invite verification - temporary access for viewing invitation details", Endpoints: []string{ - "POST /user/teams/invitations/:invitation_id/accept", "GET /user/teams/invitations/:invitation_id", }, }, diff --git a/seed/seed_reset_test.go b/seed/seed_reset_test.go index fc0f1553..157c735f 100644 --- a/seed/seed_reset_test.go +++ b/seed/seed_reset_test.go @@ -28,7 +28,7 @@ func TestSeedImportDuplicateUpdateAfterClear(t *testing.T) { // Step 1: Clear and import data (initial import) _, _ = mod.DestroyWhere(model.QueryParam{}) - + p1 := process.New("seeds.import", "roles.csv", "__yao.role", map[string]interface{}{ "chunk_size": 100, "duplicate": "update", @@ -39,7 +39,7 @@ func TestSeedImportDuplicateUpdateAfterClear(t *testing.T) { assert.True(t, ok) assert.Greater(t, resultMap1.Success, 0, "First import should succeed") assert.Equal(t, 0, resultMap1.Failure, "First import should have no failures") - + firstCount := resultMap1.Success // Step 2: Clear all data (simulate Reset scenario) @@ -63,15 +63,15 @@ func TestSeedImportDuplicateUpdateAfterClear(t *testing.T) { result2 := p2.Run() resultMap2, ok := result2.(*ImportResult) assert.True(t, ok) - - t.Logf("Second import result: Total=%d, Success=%d, Failure=%d, Ignore=%d", + + t.Logf("Second import result: Total=%d, Success=%d, Failure=%d, Ignore=%d", resultMap2.Total, resultMap2.Success, resultMap2.Failure, resultMap2.Ignore) - + // Print errors if any if len(resultMap2.Errors) > 0 { t.Logf("Import errors: %+v", resultMap2.Errors) } - + // Critical assertions: data should be imported successfully assert.Greater(t, resultMap2.Success, 0, "Second import should succeed (CREATE new records)") assert.Equal(t, 0, resultMap2.Failure, "Second import should have no failures") @@ -101,7 +101,7 @@ func TestSeedImportDuplicateUpdateMixedScenario(t *testing.T) { // Step 1: Clear and initial import _, _ = mod.DestroyWhere(model.QueryParam{}) - + p1 := process.New("seeds.import", "roles.csv", "__yao.role", map[string]interface{}{ "duplicate": "update", "mode": "each", @@ -117,7 +117,7 @@ func TestSeedImportDuplicateUpdateMixedScenario(t *testing.T) { allRoles, _ := mod.Get(model.QueryParam{ Select: []interface{}{"id", "role_id"}, }) - + if len(allRoles) > 0 { // Delete the first role roleID := allRoles[0].Get("id") @@ -140,29 +140,29 @@ func TestSeedImportDuplicateUpdateMixedScenario(t *testing.T) { }) result2 := p2.Run() resultMap2 := result2.(*ImportResult) - - t.Logf("Second import result: Total=%d, Success=%d, Failure=%d, Ignore=%d", + + t.Logf("Second import result: Total=%d, Success=%d, Failure=%d, Ignore=%d", resultMap2.Total, resultMap2.Success, resultMap2.Failure, resultMap2.Ignore) - + // Print errors if any if len(resultMap2.Errors) > 0 { for i, err := range resultMap2.Errors { t.Logf("Error %d: Row=%d, Message=%s", i+1, err.Row, err.Message) } } - + // Should import all records assert.Greater(t, resultMap2.Success, 0, "Should import successfully") - + // Note: Some failures may occur if CSV has duplicate IDs or validation issues // The important thing is that deleted record should be recreated - + // Verify count increased (deleted record was recreated) finalRoles, err := mod.Get(model.QueryParam{}) assert.Nil(t, err) t.Logf("Final roles count: %d (expected %d)", len(finalRoles), initialCount) - + // At minimum, should have more records than before re-import - assert.GreaterOrEqual(t, len(finalRoles), len(remainingRoles), + assert.GreaterOrEqual(t, len(finalRoles), len(remainingRoles), "Should have at least as many records as before (deleted record recreated)") }