package biz import ( "context" "time" ) type API struct { ID uint CreatedAt time.Time UpdatedAt time.Time Path string Description string APIGroup string Method string OrderKey string Desc bool StrictAll bool } type APIRepo interface { CreateAPI(context.Context, *API) error UpdateAPI(context.Context, *API) error DeleteAPIs(context.Context, []uint) error FindAPI(context.Context, uint) (*API, error) ListAPIs(context.Context, int, int, *API) ([]*API, int64, error) APIRoleIDs(context.Context, string, string) ([]uint, error) SetAPIRoles(context.Context, string, string, []uint) error CheckPolicyStore(context.Context) error Authorize(context.Context, uint, string, string) (bool, error) PolicyPaths(context.Context, uint) ([]*API, error) SetPolicyPaths(context.Context, uint, []*API) error IgnoredAPIs(context.Context) ([]*API, error) SetAPIIgnored(context.Context, string, string, bool) error ApplyAPISync(context.Context, []*API, []*API) error } type APIUsecase struct{ APIRepo } func NewAPIUsecase(repo APIRepo) *APIUsecase { return &APIUsecase{APIRepo: repo} } func (uc *APIUsecase) FreshCasbin(ctx context.Context) error { return uc.CheckPolicyStore(ctx) } // DeleteAPI preserves the single-delete contract used by the legacy admin: // the target is looked up first, so deleting a missing API returns the // repository's not-found error instead of silently succeeding on an empty // UPDATE/DELETE. Bulk deletion intentionally keeps separate semantics. func (uc *APIUsecase) DeleteAPI(ctx context.Context, id uint) error { if _, err := uc.FindAPI(ctx, id); err != nil { return err } return uc.DeleteAPIs(ctx, []uint{id}) } type APISyncDiff struct { Added []*API Deleted []*API Ignored []*API } func (uc *APIUsecase) SyncAPIs(ctx context.Context, routes []*API) (*APISyncDiff, error) { stored, _, err := uc.ListAPIs(ctx, 0, 0, nil) if err != nil { return nil, err } ignored, err := uc.IgnoredAPIs(ctx) if err != nil { return nil, err } key := func(value *API) string { return value.Method + " " + value.Path } ignoreSet := make(map[string]bool, len(ignored)) for _, item := range ignored { ignoreSet[key(item)] = true } routeSet := make(map[string]*API, len(routes)) for _, item := range routes { if !ignoreSet[key(item)] { routeSet[key(item)] = item } } storedSet := make(map[string]*API, len(stored)) for _, item := range stored { storedSet[key(item)] = item } // The sync API always returns JSON arrays for all three collections, // including when no routes were added or removed. diff := &APISyncDiff{ Added: make([]*API, 0), Deleted: make([]*API, 0), Ignored: ignored, } added := make(map[string]bool) for _, item := range routes { routeKey := key(item) if ignoreSet[routeKey] || added[routeKey] { continue } if storedSet[routeKey] == nil { diff.Added = append(diff.Added, item) added[routeKey] = true } } for _, item := range stored { storedKey := key(item) // Ignoring an API removes it from the in-memory route comparison. If it // already exists in sys_apis, the reference implementation therefore // returns it in deleteApis while also returning it in ignoreApis. if routeSet[storedKey] == nil { diff.Deleted = append(diff.Deleted, item) } } return diff, nil }