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.
This commit is contained in:
Max 2025-11-04 18:07:22 +08:00
parent e1405a5fa2
commit f6aead62bb
3 changed files with 58 additions and 28 deletions

View file

@ -223,22 +223,53 @@ func (m *ScopeManager) expandAlias(alias string, visited map[string]bool) ([]str
return expanded, nil 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 // loadScopeDefinitions loads scope definitions from subdirectories
func (m *ScopeManager) loadScopeDefinitions() error { func (m *ScopeManager) loadScopeDefinitions() error {
scopesDir := filepath.Join("openapi", "scopes") scopesDir := filepath.Join("openapi", "scopes")
// Subdirectories to scan // Get all subdirectories in the scopes directory
subDirs := []string{"kb", "job", "file", "user"} 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 { for _, subDir := range subDirs {
dirPath := filepath.Join(scopesDir, subDir) 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 // Walk through all .yml files in the directory
err = application.App.Walk(dirPath, func(root, filename string, isdir bool) error { err = application.App.Walk(dirPath, func(root, filename string, isdir bool) error {

View file

@ -35,12 +35,11 @@ func init() {
"GET /file/:uploaderID/:fileID/content", "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{ &acl.ScopeDefinition{
Name: ScopeInviteVerification, Name: ScopeInviteVerification,
Description: "Invite verification - temporary access for accepting team invitations", Description: "Invite verification - temporary access for viewing invitation details",
Endpoints: []string{ Endpoints: []string{
"POST /user/teams/invitations/:invitation_id/accept",
"GET /user/teams/invitations/:invitation_id", "GET /user/teams/invitations/:invitation_id",
}, },
}, },

View file

@ -28,7 +28,7 @@ func TestSeedImportDuplicateUpdateAfterClear(t *testing.T) {
// Step 1: Clear and import data (initial import) // Step 1: Clear and import data (initial import)
_, _ = mod.DestroyWhere(model.QueryParam{}) _, _ = mod.DestroyWhere(model.QueryParam{})
p1 := process.New("seeds.import", "roles.csv", "__yao.role", map[string]interface{}{ p1 := process.New("seeds.import", "roles.csv", "__yao.role", map[string]interface{}{
"chunk_size": 100, "chunk_size": 100,
"duplicate": "update", "duplicate": "update",
@ -39,7 +39,7 @@ func TestSeedImportDuplicateUpdateAfterClear(t *testing.T) {
assert.True(t, ok) assert.True(t, ok)
assert.Greater(t, resultMap1.Success, 0, "First import should succeed") assert.Greater(t, resultMap1.Success, 0, "First import should succeed")
assert.Equal(t, 0, resultMap1.Failure, "First import should have no failures") assert.Equal(t, 0, resultMap1.Failure, "First import should have no failures")
firstCount := resultMap1.Success firstCount := resultMap1.Success
// Step 2: Clear all data (simulate Reset scenario) // Step 2: Clear all data (simulate Reset scenario)
@ -63,15 +63,15 @@ func TestSeedImportDuplicateUpdateAfterClear(t *testing.T) {
result2 := p2.Run() result2 := p2.Run()
resultMap2, ok := result2.(*ImportResult) resultMap2, ok := result2.(*ImportResult)
assert.True(t, ok) 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) resultMap2.Total, resultMap2.Success, resultMap2.Failure, resultMap2.Ignore)
// Print errors if any // Print errors if any
if len(resultMap2.Errors) > 0 { if len(resultMap2.Errors) > 0 {
t.Logf("Import errors: %+v", resultMap2.Errors) t.Logf("Import errors: %+v", resultMap2.Errors)
} }
// Critical assertions: data should be imported successfully // Critical assertions: data should be imported successfully
assert.Greater(t, resultMap2.Success, 0, "Second import should succeed (CREATE new records)") 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") 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 // Step 1: Clear and initial import
_, _ = mod.DestroyWhere(model.QueryParam{}) _, _ = mod.DestroyWhere(model.QueryParam{})
p1 := process.New("seeds.import", "roles.csv", "__yao.role", map[string]interface{}{ p1 := process.New("seeds.import", "roles.csv", "__yao.role", map[string]interface{}{
"duplicate": "update", "duplicate": "update",
"mode": "each", "mode": "each",
@ -117,7 +117,7 @@ func TestSeedImportDuplicateUpdateMixedScenario(t *testing.T) {
allRoles, _ := mod.Get(model.QueryParam{ allRoles, _ := mod.Get(model.QueryParam{
Select: []interface{}{"id", "role_id"}, Select: []interface{}{"id", "role_id"},
}) })
if len(allRoles) > 0 { if len(allRoles) > 0 {
// Delete the first role // Delete the first role
roleID := allRoles[0].Get("id") roleID := allRoles[0].Get("id")
@ -140,29 +140,29 @@ func TestSeedImportDuplicateUpdateMixedScenario(t *testing.T) {
}) })
result2 := p2.Run() result2 := p2.Run()
resultMap2 := result2.(*ImportResult) 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) resultMap2.Total, resultMap2.Success, resultMap2.Failure, resultMap2.Ignore)
// Print errors if any // Print errors if any
if len(resultMap2.Errors) > 0 { if len(resultMap2.Errors) > 0 {
for i, err := range resultMap2.Errors { for i, err := range resultMap2.Errors {
t.Logf("Error %d: Row=%d, Message=%s", i+1, err.Row, err.Message) t.Logf("Error %d: Row=%d, Message=%s", i+1, err.Row, err.Message)
} }
} }
// Should import all records // Should import all records
assert.Greater(t, resultMap2.Success, 0, "Should import successfully") assert.Greater(t, resultMap2.Success, 0, "Should import successfully")
// Note: Some failures may occur if CSV has duplicate IDs or validation issues // Note: Some failures may occur if CSV has duplicate IDs or validation issues
// The important thing is that deleted record should be recreated // The important thing is that deleted record should be recreated
// Verify count increased (deleted record was recreated) // Verify count increased (deleted record was recreated)
finalRoles, err := mod.Get(model.QueryParam{}) finalRoles, err := mod.Get(model.QueryParam{})
assert.Nil(t, err) assert.Nil(t, err)
t.Logf("Final roles count: %d (expected %d)", len(finalRoles), initialCount) t.Logf("Final roles count: %d (expected %d)", len(finalRoles), initialCount)
// At minimum, should have more records than before re-import // 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)") "Should have at least as many records as before (deleted record recreated)")
} }