Dancer2

 view release on metacpan or  search on metacpan

Releasing-Dancer2.md  view on Meta::CPAN

- Commit `Dancer2.pm` and the new `README` file to the repo (after
  which, you need to `git push`)

After this, you must `git push` to push the updated release files to
GitHub.

The test suite *must* pass before a release can be done. This is your
last chance to resolve any test failures.

Provided the tests pass, you will be prompted to continue the release
process. Provided you have your PAUSE credentials set up, this will
upload the new dist to PAUSE. If you don't have a credential file set
up, you can manually update the new release via the PAUSE web interface.

### Send out release announcements

Historically, release announcements have been sent out via the following
channels:

- dancer-users mailing list
- Twitter/X

lib/Dancer2/Manual/Config.pod  view on Meta::CPAN


Dancer2 will first look for the file F<config.EXT> (where F<EXT> is the
type of configuration file you are using; e.g. F<ini> or F<json> or
F<yml>) in the root directory of your application. This is considered
your global Dancer2 config file. If you do not care to have separate
settings for production and development environments (B<not> a
recommended practice!), then this file is all you need.

Next, Dancer2 will look for a file called F<config_local.EXT>. This file
is typically useful for deployment-specific configuration that should
not be checked into source control. For instance, database credentials
could be stored in this file.  Any settings in this file are merged into
the existing configuration such that those with the same name in your
local configuration file will take precedence over those settings in
the global file.

Next, Dancer2 will look in the F<environments> directory for a configuration
file specific to the platform you are deploying to (F<production.EXT> or
F<development.EXT>, for example).  Again, the configuration from the environment
is merged with the existing configuration with the deployment config taking
precedence.

lib/Dancer2/Manual/Cookbook.pod  view on Meta::CPAN


=back

Our bookstore lookup application can now be started using the built-in server:

    # start the web application
    plackup bin/app.psgi

=head1 Authentication Recipes

Writing a form for authentication is simple: we check the user credentials
on a request and decide whether to continue or redirect them to a form.
The form allows them to submit their username and password; we save that
and create a session for them so when they try the original request,
we recognize the user and allow them in.

=head2 Basic Application

The application is fairly simple. We have a route that needs authentication,
we have a route for showing the login page, and we have a route for posting
login information and creating a session.

lib/Dancer2/Manual/Tutorial.pod  view on Meta::CPAN

    requires "DBIx::Class::Schema::Loader";
    requires "DateTime::Format::SQLite";

And then add it to the top of F<lib/DLBlog.pm> after C<use Dancer2;>:

    use Dancer2::Plugin::DBIx::Class;

We need to add configuration to tell our plugin where to find the SQLite
database. For this project, it's sufficient to put configuration for
the database in F<config.yml>. In a production application, you'd
have different database credentials in your development and staging
environments than you would in your production environment (we'd hope you
would anyhow!). And this is where environment config files are handy.

By default, Dancer2 runs your application in the development environment.
To that end, we'll add plugin configuration appropriately. Add the following
to your F<environments/development.yml> file:

    plugins:
      DBIx::Class:
        default:
          dsn: "dbi:SQLite:dbname=db/dlblog.db"
          schema_class: "DLBlog::Schema"
          dbi_params:
            RaiseError: 1
            AutoCommit: 1

Note that we only provided C<DBIx::Class> for the plugin name; Dancer2
automatically infers the C<Dancer2::Plugin::> prefix.

As SQLite databases are a local file, we don't need to provide login
credentials for the database. The two settings in the C<dbi_params>
section tell L<DBIx::Class> to raise an error automatically to our code
(should one occur), and to automatically manage transactions for us (so
we don't have to).

=head1 Generating Schema Classes

L<DBIx::Class> relies on class definitions to map database tables to
Perl constructs. Thankfully, L<DBIx::Class::Schema::Loader> can do much
of this work for us.

lib/Dancer2/Manual/Tutorial.pod  view on Meta::CPAN

    dbicdump -o dump_directory=./lib \
        -o components='["InflateColumn::DateTime"]' \
        DLBlog::Schema dbi:SQLite:db/dlblog.db '{ quote_char => "\"" }'

You should have an additional source file in your project directory now,
F<lib/DLBlog/Schema/Result/User.pm>.

=head2 Password management with Dancer2::Plugin::CryptPassphrase

It is best practice to store passwords encrypted, less someone with database
access look at your C<users> table and steal account credentials. Rather than
roll our own, we'll use one of the many great options on CPAN.

L<Dancer2::Plugin::CryptPassphrase> provides convenient access to
L<Crypt::Passphrase> in your Dancer2 applications. We'll use the latter
to generate a password hash for any new users we create.

Install the above modules:

    cpanm Dancer2::Plugin::CryptPassphrase

lib/Dancer2/Manual/Tutorial.pod  view on Meta::CPAN

    my $mech = Test::WWW::Mechanize::PSGI->new(
        app => DLBlog->to_app,
    );

    $mech->get_ok('/create', 'Got /create while not logged in');
    $mech->content_contains('Password', '...and was presented with a login page');
    $mech->submit_form_ok({
        fields => {
            username => 'admin',
            password => 'foobar',
        }}, '...which we gave invalid credentials');
    $mech->content_contains('Invalid username or password', '...and gave us an appropriate error');
    $mech->submit_form_ok({
        fields => {
            username => 'admin',
            password => 'test',
        }}, '...so we give it real credentials');
    $mech->content_contains('form', '...and get something that looks like the create form' );
    $mech->content_contains('Content', 'Confirmed this is the create form');

    done_testing;

We load two test modules: L<Test::More>, which provides a basic set of test
functionality, and L<Test::WWW::Mechanize::PSGI>, which will do all our heavy
lifting.

To start, we need to create an instance of a Mechanize object:

lib/Dancer2/Manual/Tutorial.pod  view on Meta::CPAN

in users. Let's test this:

    $mech->get_ok('/create', 'Got /create while not logged in');
    $mech->content_contains('Password', '...and was presented with a login page');

This tests the C<needs login> condition on the C</create> route. We should
be taken to a login page if we haven't logged in. C<get_ok> ensures the
route is accessible, and C<content_contains> looks for a password field.

We should get an error message for a failed login attempt. Let's stuff
the form with invalid credentials and verify that:

    $mech->submit_form_ok({
        fields => {
            username => 'admin',
            password => 'foobar',
        }}, '...which we gave invalid credentials');
    $mech->content_contains('Invalid username or password', '...and gave us an appropriate error');

C<submit_form_ok> takes a hashref of fields and puts the specified values
into them, then clicks the appropriate submit button. We then check the
resulting page content to confirm that we do, in fact, see the invalid
username/password error message.

We know that login handles failed attempts ok now. How about a login with
valid credentials>

    $mech->submit_form_ok({
        fields => {
            username => 'admin',
            password => 'test',
        }}, '...so we give it real credentials');
    $mech->content_contains('form', '...and get something that looks like the create form' );
    $mech->content_contains('Content', 'Confirmed this is the create form');

We pass the default admin/test credentials, then look at the page we're
taken to. The create page should have a form, and one of the fields should
be named Content. C<content_contains> looks for both of these on the
resulting page, and passes if they are present.

Finally, we need to say we're done running tests:

    done_testing;

Now that it's done, let's run just this one test. From your shell:

    DANCER_ENVIRONMENT=test prove -lv t/003_login.t

And you should see the following output:

    t/003_login.t ..
    ok 1 - Got /create while not logged in
    ok 2 - ...and was presented with a login page
    [DLBlog:2287783] warning @2025-02-06 22:34:25> Failed login attempt for admin in /path/to/DLBlog/lib/DLBlog.pm l. 134
    ok 3 - ...which we gave invalid credentials
    ok 4 - ...and gave us an appropriate error
    ok 5 - ...so we give it real credentials
    ok 6 - ...and get something that looks like the create form
    ok 7 - Confirmed this is the create form
    1..7
    ok
    All tests successful.
    Files=1, Tests=7,  1 wallclock secs ( 0.02 usr  0.00 sys +  0.92 cusr  0.10 csys =  1.04 CPU)
    Result: PASS

The labels help us see which tests are running, making it easier to examine
failures when they happen. You'll see a log message produced when our login

lib/Dancer2/Manual/Tutorial.pod  view on Meta::CPAN


=head2 Creating Production Configuration

The default Dancer2 configuration provides a lot of information to the
developer to assist in debugging while creating an application. In a
production environment, there's too much information being given that can
be used by someone trying to compromise your application. Let's create
an environment specifically for production to turn the level of detail down.

If you were using a database server instead of SQLite, you'd want to update
database credentials in your production configuration to point to your
production database server.

Replace your F<environments/production.yml> file with the following:

    # configuration file for production environment
    behind_proxy: 1

    # only log info, warning and error messsages
    log: "info"

share/skel/tutorial/t/003_login.t  view on Meta::CPAN

my $mech = Test::WWW::Mechanize::PSGI->new(
    app => [d2% appname %2d]->to_app,
);

$mech->get_ok('/create', 'Got /create while not logged in');
$mech->content_contains('Password', '...and was presented with a login page');
$mech->submit_form_ok({
    fields => {
        username => 'admin',
        password => 'foobar',
    }}, '...which we gave invalid credentials');
$mech->content_contains('Invalid username or password', '...and gave us an appropriate error');
$mech->submit_form_ok({
    fields => {
        username => 'admin',
        password => 'test',
    }}, '...so we give it real credentials');
$mech->content_contains('form', '...and get something that looks like the create form' );
$mech->content_contains('Content', 'Confirmed this is the create form');

done_testing;



( run in 0.606 second using v1.01-cache-2.11-cpan-0ffa90cfd1c )