NAME
Concierge::Base - Records-store base class for Concierge component modules
VERSION
v0.7.0
SYNOPSIS
package Concierge::Organizations;
use parent 'Concierge::Base';
sub new ($class, $config_file) {
my $self = bless {}, $class;
$self->{config_file} = $config_file;
return $self;
}
sub setup ($self, $config) {
# Initialize storage from desk config block
# $config is the hashref for your component in concierge.conf
return { success => 1, message => 'Organizations ready' };
}
sub add_record ($self, $id, $data) {
# Persist a new organization record
# ...
return { success => 1, message => "Organization '$id' added", id => $id };
}
# ... implement remaining methods ...
1;
DESCRIPTION
Concierge::Base is an abstract base class for records-store components that integrate with Concierge desks. It documents the method contract that Concierge expects from any additional component -- such as Concierge::Organizations, Concierge::Assets, or similar -- and provides stub implementations that die informatively if a subclass omits a required method.
Concierge::Base does not depend on any of the identity core modules (Concierge::Auth, Concierge::Sessions, Concierge::Users) and does not need to be used alongside them. It is purely a contract-documentation and safety-net class.
Return Convention
All methods in a conforming subclass should return a hashref:
# Success, no payload beyond confirmation
{ success => 1, message => 'Record added' }
# Success with payload
{ success => 1, message => 'Record found', record => \%data }
# Failure
{ success => 0, message => 'Record not found' }
The success key is always 0 or 1. The message key is always a human-readable string. Additional payload keys (record, records, ids, etc.) may be included as appropriate to the method.
Concierge itself follows this convention throughout; adopting it in additional components makes error handling uniform across the entire desk.
METHODS
new
my $component = Concierge::Organizations->new($config_file);
Constructor. Receives the path to the component's configuration file (or undef if the component does not use a separate file). The subclass is responsible for initializing any storage handles or caches it needs.
Subclasses must override this method.
setup
my $result = $component->setup($config);
One-time setup called during desk initialization (e.g., from Concierge::Setup). $config is a hashref containing the component's block from the desk configuration -- whatever key/value pairs your component needs (storage path, backend type, field schema, etc.).
Returns a { success = 1|0, message => '...' }> hashref.
Subclasses must override this method.
add_record
my $result = $component->add_record($id, \%data);
Creates a new record identified by $id with the fields in %data.
Returns { success = 1|0, message => '...', id => $id }> on success, or { success = 0, message => '...' }> on failure (e.g., duplicate ID).
Subclasses must override this method.
remove_record
my $result = $component->remove_record($id);
Deletes the record identified by $id.
Returns { success = 1|0, message => '...' }>.
Subclasses must override this method.
get_record
# All fields
my $result = $component->get_record($id);
# Selected fields only
my $result = $component->get_record($id, qw(name status));
Retrieves the record identified by $id. If @fields is supplied, returns only those fields; otherwise returns all fields.
Returns { success = 1, message => '...', record => \%data }> on success, or { success = 0, message => '...' }> if the record is not found.
Subclasses must override this method.
update_record
my $result = $component->update_record($id, \%updates);
Applies %updates to the existing record identified by $id. Fields not present in %updates are left unchanged.
Returns { success = 1|0, message => '...' }>.
Subclasses must override this method.
list_records
# All records
my $result = $component->list_records();
# With filter string and options
my $result = $component->list_records('active', { include_data => 1 });
Enumerates records. $filter is an optional string whose interpretation is left to the subclass (e.g., a status value or SQL WHERE fragment). %opts is an optional hashref for pagination, field selection, or other backend-specific options.
Returns { success = 1, ids => \@ids, count => $n }> at minimum, with optional records = \%data> when include_data is requested.
Subclasses must override this method.
INTEGRATING WITH CONCIERGE
Error Return Convention
Concierge itself and all its component modules return { success = 1|0, message => '...' }> from every method. Conforming to this convention in your component lets application code use a single, uniform error-handling idiom:
my $result = $concierge->some_operation(...);
unless ($result->{success}) {
# handle $result->{message}
}
Desk Config Block
During desk setup and at open_desk() time, Concierge reads concierge.conf (a JSON file in the desk directory). To integrate a new component, add a key for it in that file:
{
"sessions_dir": "/path/to/desk",
"users_config_file": "/path/to/desk/users.conf",
"auth_file": "/path/to/desk/auth.json",
"organizations_config": {
"backend": "sqlite",
"db_file": "/path/to/desk/orgs.db"
}
}
Your component's setup() method receives the value of that key as $config:
sub setup ($self, $config) {
my $db = $config->{db_file};
# initialize storage ...
}
open_desk Hook
To load your component automatically when a desk is opened, the recommended pattern is to subclass or monkey-patch Concierge::open_desk(), or to provide a thin wrapper around it in your application:
my $result = Concierge->open_desk($desk_dir);
my $c = $result->{concierge};
# Load your component
my $orgs = Concierge::Organizations->new(
$desk_config->{organizations_config}{config_file}
);
$orgs->setup($desk_config->{organizations_config});
$c->{organizations} = $orgs;
Alternatively, a future version of Concierge may provide a hook point for additional components. See the EXTENSIBILITY section in Concierge for the current recommended approach.
SUBCLASSING
A minimal subclass skeleton:
package Concierge::Organizations;
use v5.36;
use parent 'Concierge::Base';
use Carp qw<croak>;
our $VERSION = 'v0.1.0';
sub new ($class, $config_file=undef) {
bless { config_file => $config_file, records => {} }, $class;
}
sub setup ($self, $config) {
# $config comes from the desk's concierge.conf
$self->{storage_path} = $config->{storage_path}
or return { success => 0, message => 'storage_path required' };
# ... initialize backend ...
return { success => 1, message => 'Organizations initialized' };
}
sub add_record ($self, $id, $data) {
return { success => 0, message => 'id is required' }
unless defined $id && length $id;
return { success => 0, message => "Record '$id' already exists" }
if exists $self->{records}{$id};
$self->{records}{$id} = $data;
return { success => 1, message => "Organization '$id' added", id => $id };
}
sub remove_record ($self, $id) {
return { success => 0, message => "Record '$id' not found" }
unless exists $self->{records}{$id};
delete $self->{records}{$id};
return { success => 1, message => "Organization '$id' removed" };
}
sub get_record ($self, $id, @fields) {
return { success => 0, message => "Record '$id' not found" }
unless exists $self->{records}{$id};
my $data = $self->{records}{$id};
if (@fields) {
my %selected = map { $_ => $data->{$_} }
grep { exists $data->{$_} } @fields;
return { success => 1, record => \%selected };
}
return { success => 1, record => $data };
}
sub update_record ($self, $id, $updates) {
return { success => 0, message => "Record '$id' not found" }
unless exists $self->{records}{$id};
$self->{records}{$id} = { %{$self->{records}{$id}}, %$updates };
return { success => 1, message => "Organization '$id' updated" };
}
sub list_records ($self, $filter='', $opts={}) {
my @ids = sort keys %{$self->{records}};
return { success => 1, ids => \@ids, count => scalar @ids }
unless $opts->{include_data};
my %records = map { $_ => $self->{records}{$_} } @ids;
return { success => 1, ids => \@ids, records => \%records, count => scalar @ids };
}
1;
SEE ALSO
Concierge -- main orchestrator; see its EXTENSIBILITY section for the component substitution and addition pattern.
Concierge::Setup -- desk creation and configuration.
Concierge::Users -- the identity core records-store component, which provides a production example of a records-store component integrated with a Concierge desk.
AUTHOR
Bruce Van Allen <bva@cruzio.com>
LICENSE
This module is free software; you can redistribute it and/or modify it under the terms of the Artistic License 2.0.