Skip to content

Commit 05fcf1e

Browse files
committed
Allow scaffolding of new apps from tutorial
1 parent b75c932 commit 05fcf1e

30 files changed

+862
-1
lines changed

lib/Dancer2/CLI/Gen.pm

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,7 @@ sub _build_file_list {
325325

326326
sub _copy_templates {
327327
my ( $self, $files, $vars, $overwrite ) = @_;
328+
my $app_name = $vars->{ appname };
328329

329330
foreach my $pair (@$files) {
330331
my ( $from, $to ) = @{$pair};
@@ -338,6 +339,7 @@ sub _copy_templates {
338339
next unless ( $res eq 'y' ) or ( $res eq 'a' );
339340
}
340341

342+
$to =~ s/AppFile/$app_name/;
341343
my $to_dir = path( $to )->parent;
342344
if ( ! $to_dir->is_dir ) {
343345
print "+ $to_dir\n";
@@ -359,7 +361,7 @@ sub _copy_templates {
359361
close $fh;
360362
}
361363

362-
if( $from !~ m/\.(ico|jpg|png|css|eot|map|swp|ttf|svg|woff|woff2|js)$/ ) {
364+
if( $from !~ m/\.(db|ico|jpg|png|css|eot|map|swp|ttf|svg|woff|woff2|js)$/ ) {
363365
$content = $self->_process_template($content, $vars);
364366
}
365367

share/skel/tutorial/bin/+app.psgi

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#!/usr/bin/env perl
2+
3+
use strict;
4+
use warnings;
5+
use FindBin;
6+
use lib "$FindBin::Bin/../lib";
7+
8+
9+
# use this block if you don't need middleware, and only have a single target Dancer app to run here
10+
use [d2% appname %2d];
11+
12+
[d2% appname %2d]->to_app;
13+
14+
=begin comment
15+
# use this block if you want to include middleware such as Plack::Middleware::Deflater
16+
17+
use [d2% appname %2d];
18+
use Plack::Builder;
19+
20+
builder {
21+
enable 'Deflater';
22+
[d2% appname %2d]->to_app;
23+
}
24+
25+
=end comment
26+
27+
=cut
28+
29+
=begin comment
30+
# use this block if you want to mount several applications on different path
31+
32+
use [d2% appname %2d];
33+
use [d2% appname %2d]_admin;
34+
35+
use Plack::Builder;
36+
37+
builder {
38+
mount '/' => [d2% appname %2d]->to_app;
39+
mount '/admin' => [d2% appname %2d]_admin->to_app;
40+
}
41+
42+
=end comment
43+
44+
=cut
45+

share/skel/tutorial/config.yml

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# This is the main configuration file of your Dancer2 app
2+
# env-related settings should go to environments/$env.yml
3+
# all the settings in this file will be loaded at Dancer's startup.
4+
5+
# === Basic configuration ===
6+
7+
# Your application's name
8+
appname: "[d2% appname %2d]"
9+
10+
# The default layout to use for your application (located in
11+
# views/layouts/main.tt)
12+
layout: "main"
13+
14+
# when the charset is set to UTF-8 Dancer2 will handle for you
15+
# all the magic of encoding and decoding. You should not care
16+
# about unicode within your app when this setting is set (recommended).
17+
charset: "UTF-8"
18+
19+
# === Engines ===
20+
#
21+
# NOTE: All the engine configurations need to be under a single "engines:"
22+
# key. If you uncomment engine configurations below, make sure to delete
23+
# all "engines:" lines except the first. Otherwise, only the last
24+
# "engines:" block will take effect.
25+
26+
# template engine
27+
# simple: default and very basic template engine
28+
# template_toolkit: TT
29+
30+
#template: "simple"
31+
32+
template: "template_toolkit"
33+
session: "YAML"
34+
engines:
35+
template:
36+
template_toolkit:
37+
# Note: start_tag and end_tag are regexes
38+
start_tag: '<%'
39+
end_tag: '%>'
40+
session:
41+
YAML:
42+
cookie_name: [d2% appname %2d].session
43+
#is_secure: 1
44+
#is_http_only: 1
45+
46+
# session engine
47+
#
48+
# Simple: in-memory session store - Dancer2::Session::Simple
49+
# YAML: session stored in YAML files - Dancer2::Session::YAML
50+
#
51+
# Check out metacpan for other session storage options:
52+
# https://metacpan.org/search?q=Dancer2%3A%3ASession&search_type=modules
53+
#
54+
# Default value for 'cookie_name' is 'dancer.session'. If you run multiple
55+
# Dancer apps on the same host then you will need to make sure 'cookie_name'
56+
# is different for each app.
57+
#
58+
#engines:
59+
# session:
60+
# Simple:
61+
# cookie_name: testapp.session
62+
#
63+
#engines:
64+
# session:
65+
# YAML:
66+
# cookie_name: eshop.session
67+
# is_secure: 1
68+
# is_http_only: 1

share/skel/tutorial/cpanfile

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
requires "Dancer2" => "1.1.0";
2+
requires "DBD::SQLite";
3+
requires "Dancer2::Plugin::DBIx::Class";
4+
requires "DBIx::Class::Schema::Loader";
5+
requires "DateTime::Format::SQLite";
6+
requires "Feature::Compat::Try";
7+
requires "Dancer2::Plugin::CryptPassphrase";
8+
requires "Dancer2::Plugin::Auth::Tiny";
9+
requires "Starman";
10+
11+
recommends "YAML" => "0";
12+
recommends "URL::Encode::XS" => "0";
13+
recommends "CGI::Deurl::XS" => "0";
14+
recommends "CBOR::XS" => "0";
15+
recommends "YAML::XS" => "0";
16+
recommends "Class::XSAccessor" => "0";
17+
recommends "Crypt::URandom" => "0";
18+
recommends "HTTP::XSCookies" => "0";
19+
recommends "HTTP::XSHeaders" => "0";
20+
recommends "Math::Random::ISAAC::XS" => "0";
21+
recommends "MooX::TypeTiny" => "0";
22+
recommends "Type::Tiny::XS" => "0";
23+
recommends "Unicode::UTF8" => "0";
24+
25+
feature 'accelerate', 'Accelerate Dancer2 app performance with XS modules' => sub {
26+
requires "URL::Encode::XS" => "0";
27+
requires "CGI::Deurl::XS" => "0";
28+
requires "YAML::XS" => "0";
29+
requires "Class::XSAccessor" => "0";
30+
requires "Cpanel::JSON::XS" => "0";
31+
requires "Crypt::URandom" => "0";
32+
requires "HTTP::XSCookies" => "0";
33+
requires "HTTP::XSHeaders" => "0";
34+
requires "Math::Random::ISAAC::XS" => "0";
35+
requires "MooX::TypeTiny" => "0";
36+
requires "Type::Tiny::XS" => "0";
37+
requires "Unicode::UTF8" => "0";
38+
};
39+
40+
on "test" => sub {
41+
requires "Test::More" => "0";
42+
requires "Test::WWW::Mechanize::PSGI" => "0";
43+
requires "HTTP::Request::Common" => "0";
44+
};
45+

share/skel/tutorial/db/blog.db

20 KB
Binary file not shown.

share/skel/tutorial/db/entries.sql

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
CREATE TABLE entries (
2+
id INTEGER PRIMARY KEY AUTOINCREMENT,
3+
title TEXT NOT NULL,
4+
summary TEXT NOT NULL,
5+
content TEXT NOT NULL,
6+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
7+
);
8+

share/skel/tutorial/db/users.sql

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
CREATE TABLE users (
2+
id INTEGER PRIMARY KEY AUTOINCREMENT,
3+
username VARCHAR NOT NULL UNIQUE,
4+
password VARCHAR NOT NULL
5+
);
6+
7+
INSERT INTO users (username, password)
8+
VALUES ('admin', '$argon2id$v=19$m=262144,t=3,p=1$07krd3DaNn3b9JplNPSjnA$CiFKqjqgecDiYoV0qq0QsZn2GXmORkia2YIhgn/dbBo'); -- admin/test

share/skel/tutorial/lib/AppFile.pm

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
package [d2% appname %2d];
2+
use Dancer2;
3+
use Dancer2::Plugin::DBIx::Class;
4+
use Dancer2::Plugin::Auth::Tiny;
5+
use Dancer2::Plugin::CryptPassphrase;
6+
use Feature::Compat::Try;
7+
8+
get '/' => sub {
9+
# Give us the most recent first
10+
my @entries = resultset('Entry')->search(
11+
{},
12+
{ order_by => { -desc => 'created_at' } },
13+
)->all;
14+
template 'index', { entries => \@entries };
15+
};
16+
17+
get '/entry/:id[Int]' => sub {
18+
my $id = route_parameters->get('id');
19+
my $entry = resultset( 'Entry' )->find( $id );
20+
template 'entry', { entry => $entry };
21+
};
22+
23+
get '/create' => needs login => sub {
24+
# Vars are passed to templates automatically
25+
template 'create_update', { post_to => uri_for '/create' };
26+
};
27+
28+
post '/create' => needs login => sub {
29+
my $params = body_parameters();
30+
var $_ => $params->{ $_ } foreach qw< title summary content >;
31+
32+
my @missing = grep { $params->{$_} eq '' } qw< title summary content >;
33+
if( @missing ) {
34+
var missing => join ",", @missing;
35+
warning "Missing parameters: " . var 'missing';
36+
forward '/create', {}, { method => 'GET' };
37+
}
38+
39+
my $entry = do {
40+
try {
41+
resultset('Entry')->create( $params->as_hashref );
42+
}
43+
catch( $e ) {
44+
error "Database error: $e";
45+
var error_message => 'A database error occurred; your entry could not be created',
46+
forward '/create', {}, { method => 'GET' };
47+
}
48+
};
49+
50+
debug 'Created entry ' . $entry->id . ' for "' . $entry->title . '"';
51+
redirect uri_for "/entry/" . $entry->id; # redirect does not need a return
52+
};
53+
54+
get '/update/:id[Int]' => needs login => sub {
55+
my $id = route_parameters->get('id');
56+
my $entry = resultset( 'Entry' )->find( $id );
57+
var $_ => $entry->$_ foreach qw< title summary content >;
58+
template 'create_update', { post_to => uri_for "/update/$id" };
59+
};
60+
61+
post '/update/:id[Int]' => needs login => sub {
62+
my $id = route_parameters->get('id');
63+
my $entry = resultset( 'Entry' )->find( $id );
64+
if( !$entry ) {
65+
status 'not_found';
66+
return "Attempt to update non-existent entry $id";
67+
}
68+
69+
my $params = body_parameters();
70+
var $_ => $params->{ $_ } foreach qw< title summary content >;
71+
my @missing = grep { $params->{$_} eq '' } qw< title summary content >;
72+
if( @missing ) {
73+
var missing => join ",", @missing;
74+
warning "Missing parameters: " . var 'missing';
75+
forward "/update/$id", {}, { method => 'GET' };
76+
}
77+
78+
try {
79+
$entry->update( $params->as_hashref );
80+
}
81+
catch( $e ) {
82+
error "Database error: $e";
83+
var error_message => 'A database error occurred; your entry could not be updated',
84+
forward "/update/$id", {}, { method => 'GET' };
85+
}
86+
87+
debug 'Updated entry ' . $entry->id . ' for "' . $entry->title . '"';
88+
redirect uri_for "/entry/" . $entry->id; # redirect does not need a return
89+
};
90+
91+
get '/delete/:id[Int]' => needs login => sub {
92+
my $id = route_parameters->get('id');
93+
my $entry = resultset( 'Entry' )->find( $id );
94+
template 'delete', { entry => $entry };
95+
};
96+
97+
post '/delete/:id[Int]' => needs login => sub {
98+
my $id = route_parameters->get('id');
99+
my $entry = resultset( 'Entry' )->find( $id );
100+
if( !$entry ) {
101+
status 'not_found';
102+
return "Attempt to delete non-existent entry $id";
103+
}
104+
105+
# Always default to not destroying data
106+
my $delete_it = body_parameters->get('delete_it') // 0;
107+
108+
if( $delete_it ) {
109+
$entry->delete;
110+
redirect uri_for "/";
111+
} else {
112+
# Display our entry again
113+
redirect uri_for "/entry/$id";
114+
}
115+
};
116+
117+
get '/login' => sub {
118+
template 'login' => { return_url => params->{ return_url } };
119+
};
120+
121+
post '/login' => sub {
122+
my $username = body_parameters->get('username');
123+
my $password = body_parameters->get('password');
124+
my $return_url = body_parameters->get('return_url');
125+
126+
my $user = resultset( 'User' )->find({ username => $username });
127+
if ( $user and verify_password( $password, $user->password) ) {
128+
app->change_session_id;
129+
session user => $username;
130+
info "$username successfully logged in";
131+
return redirect $return_url || '/';
132+
}
133+
else {
134+
warning "Failed login attempt for $username";
135+
template 'login' => { login_error => 1, return_url => $return_url };
136+
}
137+
};
138+
139+
get '/logout' => sub {
140+
app->destroy_session;
141+
redirect uri_for "/";
142+
};
143+
144+
true;
145+
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
use utf8;
2+
package [d2% appname %2d]::Schema;
3+
4+
# Created by DBIx::Class::Schema::Loader
5+
# DO NOT MODIFY THE FIRST PART OF THIS FILE
6+
7+
use strict;
8+
use warnings;
9+
10+
use base 'DBIx::Class::Schema';
11+
12+
__PACKAGE__->load_namespaces;
13+
14+
15+
# Created by DBIx::Class::Schema::Loader v0.07052 @ 2025-01-17 22:29:56
16+
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:lxoqFoUlG8ltcbtq4b0ehA
17+
18+
19+
# You can replace this text with custom code or comments, and it will be preserved on regeneration
20+
1;

0 commit comments

Comments
 (0)