1use super::*;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
4pub enum AdminResourceKind {
5 Dashboard,
6 ResourceIndex,
7 DetailView,
8 Workflow,
9 Audit,
10 Settings,
11}
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14pub enum NavigationSection {
15 Overview,
16 Content,
17 Commerce,
18 Memberships,
19 Events,
20 Media,
21 System,
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum WidgetSlot {
26 Header,
27 Summary,
28 Sidebar,
29 Footer,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum BulkActionKind {
34 Publish,
35 Refund,
36 Cancel,
37 CheckIn,
38 Export,
39 Custom,
40}
41
42#[derive(Debug, Clone, PartialEq, Eq)]
43pub struct AccessibilityContract {
44 pub skip_link_target: String,
45 pub live_region_id: String,
46 pub error_summary_id: String,
47 pub table_caption_required: bool,
48 pub focus_restore_target: String,
49}
50
51impl AccessibilityContract {
52 pub fn standard() -> Self {
53 Self {
54 skip_link_target: "admin-main".to_string(),
55 live_region_id: "admin-status".to_string(),
56 error_summary_id: "admin-errors".to_string(),
57 table_caption_required: true,
58 focus_restore_target: "page-title".to_string(),
59 }
60 }
61}
62
63#[derive(Debug, Clone, PartialEq, Eq)]
64pub struct AdminResourceDescriptor {
65 pub id: AdminResourceId,
66 pub route: String,
67 pub title: String,
68 pub nav_label: String,
69 pub section: NavigationSection,
70 pub kind: AdminResourceKind,
71 pub required_capability: Capability,
72}
73
74impl AdminResourceDescriptor {
75 pub fn new(
76 id: AdminResourceId,
77 route: impl Into<String>,
78 title: impl Into<String>,
79 nav_label: impl Into<String>,
80 section: NavigationSection,
81 kind: AdminResourceKind,
82 required_capability: Capability,
83 ) -> Result<Self, AdminModelError> {
84 Ok(Self {
85 id,
86 route: validate_route("resource_route", route.into())?,
87 title: require_non_empty("resource_title", title.into())?,
88 nav_label: require_non_empty("resource_nav_label", nav_label.into())?,
89 section,
90 kind,
91 required_capability,
92 })
93 }
94
95 pub fn from_contribution(
96 contribution: &AdminResourceContribution,
97 ) -> Result<Self, AdminModelError> {
98 Self::new(
99 AdminResourceId::new(contribution.id.clone())?,
100 contribution.route.clone(),
101 contribution.title.clone(),
102 contribution.nav_label.clone(),
103 contribution.section.into(),
104 contribution.kind.into(),
105 contribution.required_capability,
106 )
107 }
108}
109
110impl From<CoreAdminNavigationSection> for NavigationSection {
111 fn from(value: CoreAdminNavigationSection) -> Self {
112 match value {
113 CoreAdminNavigationSection::Overview => Self::Overview,
114 CoreAdminNavigationSection::Content => Self::Content,
115 CoreAdminNavigationSection::Commerce => Self::Commerce,
116 CoreAdminNavigationSection::Memberships => Self::Memberships,
117 CoreAdminNavigationSection::Events => Self::Events,
118 CoreAdminNavigationSection::Media => Self::Media,
119 CoreAdminNavigationSection::System => Self::System,
120 }
121 }
122}
123
124impl From<CoreAdminContributionKind> for AdminResourceKind {
125 fn from(value: CoreAdminContributionKind) -> Self {
126 match value {
127 CoreAdminContributionKind::Dashboard => Self::Dashboard,
128 CoreAdminContributionKind::ResourceIndex => Self::ResourceIndex,
129 CoreAdminContributionKind::DetailView => Self::DetailView,
130 CoreAdminContributionKind::Workflow => Self::Workflow,
131 CoreAdminContributionKind::Audit => Self::Audit,
132 CoreAdminContributionKind::Settings => Self::Settings,
133 }
134 }
135}
136
137#[derive(Debug, Clone, PartialEq, Eq)]
138pub struct AdminWidgetDescriptor {
139 pub id: AdminWidgetId,
140 pub title: String,
141 pub slot: WidgetSlot,
142 pub required_capability: Option<Capability>,
143 pub resource_route: Option<String>,
144}
145
146impl AdminWidgetDescriptor {
147 pub fn new(
148 id: AdminWidgetId,
149 title: impl Into<String>,
150 slot: WidgetSlot,
151 required_capability: Option<Capability>,
152 resource_route: Option<String>,
153 ) -> Result<Self, AdminModelError> {
154 Ok(Self {
155 id,
156 title: require_non_empty("widget_title", title.into())?,
157 slot,
158 required_capability,
159 resource_route: resource_route
160 .map(|route| validate_route("widget_resource_route", route))
161 .transpose()?,
162 })
163 }
164}
165
166#[derive(Debug, Clone, PartialEq, Eq)]
167pub struct WorkflowAction {
168 pub id: WorkflowId,
169 pub title: String,
170 pub bulk_action: BulkActionKind,
171 pub required_capability: Capability,
172 pub success_message: String,
173}
174
175impl WorkflowAction {
176 pub fn new(
177 id: WorkflowId,
178 title: impl Into<String>,
179 bulk_action: BulkActionKind,
180 required_capability: Capability,
181 success_message: impl Into<String>,
182 ) -> Result<Self, AdminModelError> {
183 Ok(Self {
184 id,
185 title: require_non_empty("workflow_title", title.into())?,
186 bulk_action,
187 required_capability,
188 success_message: require_non_empty("workflow_success_message", success_message.into())?,
189 })
190 }
191
192 pub fn from_bulk_operation(
193 definition: &CoreBulkOperationDefinition,
194 ) -> Result<Self, AdminModelError> {
195 Self::new(
196 WorkflowId::new(definition.id.clone())?,
197 definition.title.clone(),
198 map_bulk_action_kind(definition.kind),
199 definition.required_capability,
200 format!("{} queued", definition.title),
201 )
202 }
203}
204
205#[derive(Debug, Clone, PartialEq, Eq)]
206pub struct AuditEntry {
207 pub id: AuditEntryId,
208 pub actor_id: String,
209 pub capability: Capability,
210 pub resource_kind: ResourceKind,
211 pub resource_id: String,
212 pub action: String,
213}
214
215impl AuditEntry {
216 pub fn new(
217 id: AuditEntryId,
218 actor_id: impl Into<String>,
219 capability: Capability,
220 resource_kind: ResourceKind,
221 resource_id: impl Into<String>,
222 action: impl Into<String>,
223 ) -> Result<Self, AdminModelError> {
224 Ok(Self {
225 id,
226 actor_id: require_non_empty("actor_id", actor_id.into())?,
227 capability,
228 resource_kind,
229 resource_id: require_non_empty("resource_id", resource_id.into())?,
230 action: require_non_empty("action", action.into())?,
231 })
232 }
233}
234
235#[derive(Debug, Clone, Default, PartialEq, Eq)]
236pub struct OperatorAccessContext {
237 capabilities: HashSet<Capability>,
238}
239
240impl OperatorAccessContext {
241 pub fn new() -> Self {
242 Self::default()
243 }
244
245 pub fn with_capability(mut self, capability: Capability) -> Self {
246 self.capabilities.insert(capability);
247 self
248 }
249
250 pub fn allows(&self, capability: Capability) -> bool {
251 self.capabilities.contains(&capability)
252 }
253}
254
255#[derive(Debug, Clone, PartialEq, Eq)]
256pub struct BulkActionPlan {
257 pub workflow_id: WorkflowId,
258 pub resource_count: usize,
259 pub message: String,
260}
261
262pub(super) fn map_bulk_action_kind(kind: CoreBulkOperationKind) -> BulkActionKind {
263 match kind {
264 CoreBulkOperationKind::Publish => BulkActionKind::Publish,
265 CoreBulkOperationKind::Unpublish => BulkActionKind::Custom,
266 CoreBulkOperationKind::Reindex => BulkActionKind::Custom,
267 CoreBulkOperationKind::Export => BulkActionKind::Export,
268 CoreBulkOperationKind::Cancel => BulkActionKind::Cancel,
269 CoreBulkOperationKind::CheckIn => BulkActionKind::CheckIn,
270 CoreBulkOperationKind::Custom => BulkActionKind::Custom,
271 }
272}
273
274pub(super) fn map_extension_widget_slot(surface: &str) -> WidgetSlot {
275 if surface.contains("header") {
276 WidgetSlot::Header
277 } else if surface.contains("sidebar") {
278 WidgetSlot::Sidebar
279 } else if surface.contains("footer") {
280 WidgetSlot::Footer
281 } else {
282 WidgetSlot::Summary
283 }
284}