File Coverage

lib/Concierge/Setup.pm
Criterion Covered Total %
statement 48 100 48.0
branch 8 52 15.3
condition 4 52 7.6
subroutine 10 12 83.3
pod 3 3 100.0
total 73 219 33.3


line stmt bran cond sub pod time code
1             package Concierge::Setup v0.6.1;
2 7     7   1383100 use v5.36;
  7         26  
3              
4             our $VERSION = 'v0.6.1';
5              
6             # ABSTRACT: Setup and configuration for Concierge desk initialization
7              
8 7     7   33 use Carp qw;
  7         21  
  7         426  
9 7     7   37 use File::Spec;
  7         14  
  7         199  
10 7     7   44 use File::Path qw/make_path/;
  7         29  
  7         428  
11 7     7   4965 use JSON::PP qw< encode_json decode_json >;
  7         126342  
  7         636  
12 7     7   3444 use Concierge;
  7         23  
  7         314  
13              
14             # === COMPONENT MODULES ===
15 7     7   46 use Concierge::Auth;
  7         13  
  7         281  
16 7     7   29 use Concierge::Sessions;
  7         11  
  7         155  
17 7     7   26 use Concierge::Users;
  7         11  
  7         8749  
18              
19             # =============================================================================
20             # SIMPLE SETUP - Opinionated defaults for quick start
21             # =============================================================================
22              
23 13     13 1 1283653 sub build_quick_desk ($storage_dir, $app_fields=[]) {
  13         35  
  13         29  
  13         103  
24             # Simple, opinionated setup with reasonable defaults:
25             # - Database sessions backend (SQLite via Concierge::Sessions)
26             # - Database users backend (SQLite via Concierge::Users)
27             # - All standard user fields included
28             # - All storage co-located in $storage_dir
29              
30 13 100       69 return { success => 0, message => 'desk_location is required' }
31             unless defined $storage_dir;
32              
33             # Safety: Convert '.' or empty string to './desk' to avoid cluttering app root
34 12 50 33     121 if (!$storage_dir || $storage_dir eq '.' || $storage_dir eq './') {
      33        
35 0         0 $storage_dir = './desk';
36             }
37              
38             # Ensure storage directory exists
39 12 100       224 unless (-d $storage_dir) {
40 1         5 eval { make_path($storage_dir) };
  1         283  
41 1 50       7 croak "Cannot create storage directory '$storage_dir': $@" if $@;
42             }
43              
44             # Create minimal Concierge object for internal operations
45 12         87 my $concierge = bless {}, 'Concierge';
46              
47             # Initialize Sessions component
48 12         143 $concierge->{sessions} = Concierge::Sessions->new(
49             storage_dir => $storage_dir, # Uses database backend (SQLite) by default
50             );
51              
52             # Initialize Auth component
53 12         761660 my $auth_file = File::Spec->catfile($storage_dir, 'auth.pwd');
54 12         224 $concierge->{auth} = Concierge::Auth->new({
55             file => $auth_file
56             });
57              
58             # Setup Users component
59 12         124681 my $users_setup = Concierge::Users->setup({
60             storage_dir => $storage_dir,
61             backend => 'database', # Database backend (SQLite)
62             include_standard_fields => 'all', # All standard fields
63             field_overrides => [], # No overrides
64             app_fields => $app_fields,
65             });
66 12 50       806847 unless ($users_setup->{success}) {
67             return {
68             success => 0,
69             message => "Failed to setup Users: " . $users_setup->{message}
70 0         0 };
71             }
72              
73             # Build configuration to store in concierge session
74             my $full_config = {
75             users_config_file => $users_setup->{config_file},
76 12         160 storage_dir => $storage_dir,
77             sessions_dir => $storage_dir,
78             users_dir => $storage_dir,
79             auth_file => $auth_file,
80             sessions_backend => 'database',
81             users_backend => 'database',
82             };
83             # Encode to JSON with pretty formatting and write with trailing newline
84 12         172 my $json = JSON::PP->new->utf8->pretty->encode($full_config) . "\n";
85            
86 12         6809 my $concierge_conf_file = File::Spec->catfile($storage_dir, 'concierge.conf');
87            
88 12         44 my $fh;
89 12 50 33     5625 open $fh, ">", $concierge_conf_file
      33        
90             and
91             print $fh $json
92             and
93             close $fh
94             or return { success => 0, message => "Cannot write to concierge config file: $!" };
95              
96             return {
97 12         685 success => 1,
98             message => "Concierge desk built successfully",
99             desk => $storage_dir,
100             };
101             }
102              
103             # =============================================================================
104             # ADVANCED SETUP - Full control with custom configuration
105             # =============================================================================
106              
107 0     0 1   sub build_desk ($config) {
  0            
  0            
108             # Advanced setup with full configuration options:
109             # - Separate storage directories per component
110             # - Full Users.pm field configuration (include_standard_fields, field_overrides, etc.)
111             # - Custom backend selection for Sessions and Users
112             # - No assumptions or defaults
113              
114             # Validate required top-level config
115 0 0         return { success => 0, message => 'Configuration must be a hash reference' }
116             unless ref $config eq 'HASH';
117              
118             return { success => 0, message => 'Missing storage.base_dir' }
119 0 0 0       unless $config->{storage} && $config->{storage}{base_dir};
120              
121             # Safety: Convert '.' or empty string to './desk' to avoid cluttering app root
122 0           my $base_dir = $config->{storage}{base_dir};
123 0 0 0       if (!$base_dir || $base_dir eq '.' || $base_dir eq './') {
      0        
124 0           $base_dir = './desk';
125 0           $config->{storage}{base_dir} = $base_dir;
126             }
127              
128             # Determine storage locations (support separate dirs or single base_dir)
129 0   0       my $sessions_dir = $config->{storage}{sessions_dir} || $base_dir;
130 0   0       my $users_dir = $config->{storage}{users_dir} || $base_dir;
131 0   0       my $auth_file = $config->{auth}{file} || File::Spec->catfile($base_dir, 'auth.pwd');
132              
133             # Create directories if needed
134 0           for my $dir ($base_dir, $sessions_dir, $users_dir) {
135 0 0         next if -d $dir;
136 0           eval { make_path($dir) };
  0            
137             return {
138 0 0         success => 0,
139             message => "Failed to create directory '$dir': $@"
140             } if $@;
141             }
142              
143             # Create minimal Concierge object for internal operations
144 0           my $concierge = bless {}, 'Concierge';
145              
146             # Initialize Sessions component with specified backend
147 0   0       my $sessions_backend = $config->{sessions}{backend} || 'database';
148 0           $concierge->{sessions} = Concierge::Sessions->new(
149             backend => $sessions_backend,
150             storage_dir => $sessions_dir,
151             );
152              
153             # Initialize Auth component
154 0           $concierge->{auth} = Concierge::Auth->new({
155             file => $auth_file
156             });
157              
158             # Build Users setup configuration
159             my $users_config = {
160             storage_dir => $users_dir,
161 0   0       backend => $config->{users}{backend} || 'database',
162             };
163              
164             # Add Users-specific configuration options
165             $users_config->{include_standard_fields} = $config->{users}{include_standard_fields}
166 0 0         if exists $config->{users}{include_standard_fields};
167              
168             $users_config->{app_fields} = $config->{users}{app_fields}
169 0 0         if exists $config->{users}{app_fields};
170              
171             $users_config->{field_overrides} = $config->{users}{field_overrides}
172 0 0         if exists $config->{users}{field_overrides};
173              
174             # Setup Users component
175 0           my $users_setup = Concierge::Users->setup($users_config);
176 0 0         unless ($users_setup->{success}) {
177             return {
178             success => 0,
179             message => "Failed to setup Users: " . $users_setup->{message}
180 0           };
181             }
182              
183             # Build configuration to store in concierge session
184             my $full_config = {
185             users_config_file => $users_setup->{config_file},
186             storage_dir => $base_dir,
187             sessions_dir => $sessions_dir,
188             users_dir => $users_dir,
189             auth_file => $auth_file,
190             sessions_backend => $sessions_backend,
191             users_backend => $users_config->{backend},
192 0           };
193 0           my $json = JSON::PP->new->utf8->pretty->encode($full_config) . "\n";
194              
195 0           my $config_location = $full_config->{storage_dir};
196 0           my $concierge_conf_file = File::Spec->catfile($config_location, 'concierge.conf');
197 0           my $fh;
198 0 0 0       open $fh, ">", $concierge_conf_file
      0        
199             and
200             print $fh $json
201             and
202             close $fh
203             or return { success => 0, message => "Cannot write to concierge config file: $!" };
204              
205             return {
206 0           success => 1,
207             message => "Custom Concierge desk built successfully",
208             desk => $base_dir,
209             config => $full_config,
210             };
211             }
212              
213              
214             # =============================================================================
215             # HELPER METHODS
216             # =============================================================================
217              
218             # Validate setup configuration before executing
219 0     0 1   sub validate_setup_config ($config) {
  0            
  0            
220 0           my @errors;
221              
222             # Check required fields
223             push @errors, "Missing storage.base_dir"
224 0 0 0       unless $config->{storage} && $config->{storage}{base_dir};
225              
226             push @errors, "Missing auth.file"
227 0 0 0       unless $config->{auth} && $config->{auth}{file};
228              
229             push @errors, "Missing sessions.backend"
230 0 0 0       unless $config->{sessions} && $config->{sessions}{backend};
231              
232             push @errors, "Missing users.backend"
233 0 0 0       unless $config->{users} && $config->{users}{backend};
234              
235             # Validate backend values
236 0 0         if ($config->{sessions}{backend}) {
237 0           my $backend = lc $config->{sessions}{backend};
238 0 0         push @errors, "Invalid sessions.backend: must be 'database' or 'file'"
239             unless $backend =~ /^(database|file)$/;
240             }
241              
242 0 0         if ($config->{users}{backend}) {
243 0           my $backend = lc $config->{users}{backend};
244 0 0         push @errors, "Invalid users.backend: must be 'database', 'yaml', or 'file'"
245             unless $backend =~ /^(database|yaml|file)$/;
246             }
247              
248             return {
249 0 0         success => @errors ? 0 : 1,
    0          
250             (@errors ? (errors => \@errors) : ()),
251             };
252             }
253              
254             1;
255              
256             __END__