55use Cachet \Actions \Component \CreateComponent ;
66use Cachet \Actions \Component \DeleteComponent ;
77use Cachet \Actions \Component \UpdateComponent ;
8+ use Cachet \Concerns \ChecksApiAuthentication ;
89use Cachet \Concerns \GuardsApiAbilities ;
910use Cachet \Data \Requests \Component \CreateComponentRequestData ;
1011use Cachet \Data \Requests \Component \UpdateComponentRequestData ;
1112use Cachet \Enums \ComponentStatusEnum ;
1213use Cachet \Filters \MetaFilter ;
1314use Cachet \Http \Resources \Component as ComponentResource ;
1415use Cachet \Models \Component ;
16+ use Cachet \Models \ComponentGroup ;
17+ use Cachet \Models \Incident ;
1518use Dedoc \Scramble \Attributes \Group ;
1619use Dedoc \Scramble \Attributes \QueryParameter ;
20+ use Illuminate \Database \Eloquent \Builder ;
21+ use Illuminate \Database \Eloquent \Relations \BelongsToMany ;
1722use Illuminate \Http \Request ;
1823use Illuminate \Http \Response ;
1924use Illuminate \Routing \Controller ;
25+ use Illuminate \Support \Collection ;
2026use Illuminate \Support \Number ;
2127use Spatie \QueryBuilder \AllowedFilter ;
28+ use Spatie \QueryBuilder \AllowedInclude ;
2229use Spatie \QueryBuilder \QueryBuilder ;
2330
2431#[Group('Components ' , weight: 1 )]
2532class ComponentController extends Controller
2633{
34+ use ChecksApiAuthentication;
2735 use GuardsApiAbilities;
2836
29- /**
30- * The list of allowed includes.
31- */
32- public const ALLOWED_INCLUDES = [
33- 'group ' ,
34- 'incidents ' ,
35- 'meta ' ,
36- ];
37-
3837 /**
3938 * List Components
4039 */
@@ -47,12 +46,12 @@ class ComponentController extends Controller
4746 #[QueryParameter('page ' , 'Which page to show. ' , type: 'int ' , example: 2 )]
4847 public function index (Request $ request )
4948 {
50- $ components = QueryBuilder::for (Component::class )
51- ->allowedIncludes (self :: ALLOWED_INCLUDES )
49+ $ components = QueryBuilder::for ($ this -> visibleComponents () )
50+ ->allowedIncludes ($ this -> allowedIncludes () )
5251 ->allowedFilters ([
5352 'name ' ,
5453 AllowedFilter::exact ('status ' ),
55- AllowedFilter::exact ('enabled ' ),
54+ AllowedFilter::exact ('enabled ' )-> default ( true ) ,
5655 AllowedFilter::custom ('meta ' , new MetaFilter ),
5756 ])
5857 ->allowedSorts (['name ' , 'order ' , 'id ' ])
@@ -61,6 +60,44 @@ public function index(Request $request)
6160 return ComponentResource::collection ($ components );
6261 }
6362
63+ /**
64+ * The list of allowed includes, scoped to the current caller.
65+ *
66+ * @return array<int, string|Collection<int, AllowedInclude>>
67+ */
68+ protected function allowedIncludes (): array
69+ {
70+ return [
71+ 'group ' ,
72+ AllowedInclude::callback ('incidents ' , function (BelongsToMany $ query ): void {
73+ /** @var BelongsToMany<Incident, Component> $query */
74+ $ query ->visible ($ this ->isAuthenticated ());
75+ }),
76+ 'meta ' ,
77+ ];
78+ }
79+
80+ /**
81+ * Base query scoping components to those visible to the current caller.
82+ *
83+ * Components have no visibility of their own; they inherit it from their
84+ * group. Ungrouped components are always public and disabled components
85+ * are hidden from guests, matching the status page.
86+ *
87+ * @return Builder<Component>
88+ */
89+ protected function visibleComponents (): Builder
90+ {
91+ $ visibleGroups = ComponentGroup::query ()->visible ($ this ->isAuthenticated ())->select ('id ' );
92+
93+ return Component::query ()
94+ ->unless ($ this ->isAuthenticated (), fn (Builder $ query ) => $ query ->enabled ())
95+ ->where (function ($ query ) use ($ visibleGroups ): void {
96+ $ query ->whereNull ('component_group_id ' )
97+ ->orWhereIn ('component_group_id ' , $ visibleGroups );
98+ });
99+ }
100+
64101 /**
65102 * Create Component
66103 */
@@ -81,10 +118,9 @@ public function store(CreateComponentRequestData $data, CreateComponent $createC
81118 #[QueryParameter('include ' , 'Include related data (group, incidents, meta). ' , example: 'meta ' )]
82119 public function show (Component $ component )
83120 {
84-
85- $ componentQuery = QueryBuilder::for (Component::class)
86- ->allowedIncludes (self ::ALLOWED_INCLUDES )
87- ->find ($ component ->id );
121+ $ componentQuery = QueryBuilder::for ($ this ->visibleComponents ())
122+ ->allowedIncludes ($ this ->allowedIncludes ())
123+ ->findOrFail ($ component ->id );
88124
89125 return ComponentResource::make ($ componentQuery )
90126 ->response ()
0 commit comments