Adding the first endpoint which allows to interact by http with the app.

The POST /developer endpoint is created using the openapi definition
to validate the fields. This will allow a way to create endpoints
and their doc automatically.
This commit is contained in:
sergiotarxz 2022-04-01 03:45:03 +02:00
parent 3d9724b144
commit 16ac140f9d
14 changed files with 607 additions and 20 deletions

View File

@ -21,6 +21,8 @@ my $build = Module::Build->new(
'JSON' => 0,
'YAML' => 0,
'Capture::Tiny' => 0,
'Email::Valid' => 0,
'Crypt::Bcrypt' => 0,
},
install_path => {
'templates' => "$HOME/.local/share/peace/template",

View File

@ -16,16 +16,21 @@
"prereqs" : {
"runtime" : {
"requires" : {
"Capture::Tiny" : "0",
"Crypt::Bcrypt" : "0",
"DBD::Mock" : "0",
"DBD::Pg" : "0",
"DBI" : "0",
"DateTime" : "0",
"DateTime::Format::Pg" : "0",
"Email::Valid" : "0",
"JSON" : "0",
"Mojolicious" : "0",
"Test::MockModule" : "0",
"Test::MockObject" : "0",
"Test::Most" : "0",
"Test::Pod::Coverage" : "0"
"Test::Pod::Coverage" : "0",
"YAML" : "0"
}
}
},

View File

@ -69,7 +69,7 @@ if ( !-e $config_path) {
path ($config_path)->spew_utf8(encode_json $config);
}
Peace::DB->dbh( config => Peace->new->config );
Peace::DB->dbh( config => Peace->new->peace_config );
@ARGV = ('daemon');
# Start command line interface for application

View File

@ -8,6 +8,9 @@
<li>
<a href="lib/Peace.pm.html">Peace</a>
</li>
<li>
<a href="lib/Peace/Controller/Developer.pm.html">Peace::Controller::Developer</a>
</li>
<li>
<a href="lib/Peace/Controller/User.pm.html">Peace::Controller::User</a>
</li>
@ -44,6 +47,9 @@
<li>
<a href="lib/Peace/Model/Release.pm.html">Peace::Model::Release</a>
</li>
<li>
<a href="lib/Peace/Swagger.pm.html">Peace::Swagger</a>
</li>
<li>
<a href="lib/Peace/Test/Mock/Model/Application.pm.html">Peace::Test::Mock::Model::Application</a>
</li>

View File

@ -18,6 +18,7 @@
<li><a href="#FUNCTIONS">FUNCTIONS</a>
<ul>
<li><a href="#startup">startup</a></li>
<li><a href="#peace_config">peace_config</a></li>
</ul>
</li>
</ul>
@ -42,6 +43,12 @@
<p>For internal usage from Mojolicious.</p>
<h2 id="peace_config">peace_config</h2>
<pre><code>my $config = Peace-&gt;new-&gt;peace_config;</code></pre>
<p>Retrieves the config for the peace application.</p>
</body>

View File

@ -0,0 +1,57 @@
<?xml version="1.0" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Peace::Controller::Developer - Developer&#39;s http endpoint.</title>
<meta content="text/html; charset=utf-8" http-equiv="content-type" />
<link href="mailto:Alpine@build-edge-aarch64.nonet" rev="made" />
</head>
<body>
<ul id="index">
<li><a href="#NAME">NAME</a></li>
<li><a href="#SYNOPSIS">SYNOPSIS</a></li>
<li><a href="#DESCRIPTION">DESCRIPTION</a></li>
<li><a href="#METHODS">METHODS</a>
<ul>
<li><a href="#post">post</a></li>
</ul>
</li>
<li><a href="#SEE-ALSO">SEE ALSO</a></li>
</ul>
<h1 id="NAME">NAME</h1>
<p>Peace::Controller::Developer - Developer&#39;s http endpoint.</p>
<h1 id="SYNOPSIS">SYNOPSIS</h1>
<pre><code># This object is used by mojolicious.</code></pre>
<h1 id="DESCRIPTION">DESCRIPTION</h1>
<p>Peace::Controller::Developer allows to interact using a json http api with the <a href="../Model/Developer.pm.html">Peace::Model::Developer</a> objects in Peace.</p>
<h1 id="METHODS">METHODS</h1>
<p>Peace::Controller::Developer implements the following methods:</p>
<h2 id="post">post</h2>
<pre><code># To be used by mojolicious.</code></pre>
<p>Creates a Developer in db with the data given by the user.</p>
<h1 id="SEE-ALSO">SEE ALSO</h1>
<p><a href="../Model/Developer.pm.html">Peace::Model::Developer</a>, <a href="../DAO/Developer.pm.html">Peace::DAO::Developer</a></p>
</body>
</html>

View File

@ -22,6 +22,7 @@
</li>
<li><a href="#METHODS">METHODS</a>
<ul>
<li><a href="#to_json">to_json</a></li>
<li><a href="#applications">applications</a></li>
<li><a href="#uuid">uuid</a></li>
<li><a href="#date_creation">date_creation</a></li>
@ -79,6 +80,12 @@
<p>Peace::Model::Developer implements the following methods:</p>
<h2 id="to_json">to_json</h2>
<pre><code>my $json = $developer-&gt;to_json;</code></pre>
<p>Renders the developer in a json like structure.</p>
<h2 id="applications">applications</h2>
<pre><code>my $applications = $developer-&gt;applications;</code></pre>

View File

@ -0,0 +1,90 @@
<?xml version="1.0" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Peace::Swagger - OpenAPI definitions for the Peace API.</title>
<meta content="text/html; charset=utf-8" http-equiv="content-type" />
<link href="mailto:Alpine@build-edge-aarch64.nonet" rev="made" />
</head>
<body>
<ul id="index">
<li><a href="#NAME">NAME</a></li>
<li><a href="#SYNOPSIS">SYNOPSIS</a></li>
<li><a href="#DESCRIPTION">DESCRIPTION</a></li>
<li><a href="#INSTANCE-METHODS">INSTANCE METHODS</a>
<ul>
<li><a href="#new">new</a></li>
</ul>
</li>
<li><a href="#METHODS">METHODS</a>
<ul>
<li><a href="#schema">schema</a></li>
<li><a href="#validate_request">validate_request</a></li>
<li><a href="#developer">developer</a></li>
<li><a href="#developer_post">developer_post</a></li>
</ul>
</li>
</ul>
<h1 id="NAME">NAME</h1>
<p>Peace::Swagger - OpenAPI definitions for the Peace API.</p>
<h1 id="SYNOPSIS">SYNOPSIS</h1>
<p>my $swagger = Peace::Swagger-&gt;new;</p>
<p>my $spec = $swagger-&gt;schema;</p>
<h1 id="DESCRIPTION">DESCRIPTION</h1>
<p>This module aims to help in the programmatic description of all API endpoints in a way that makes possible to reuse those schemas in the code and in the documentation.</p>
<h1 id="INSTANCE-METHODS">INSTANCE METHODS</h1>
<p>Peace::Swagger implements the following instance methods:</p>
<h2 id="new">new</h2>
<p>my $swagger = Peace::Swagger-&gt;new;</p>
<p>Instances a Peace::Swagger.</p>
<h1 id="METHODS">METHODS</h1>
<p>Peace::Swagger implements the following methods:</p>
<h2 id="schema">schema</h2>
<pre><code>my $schema = $swagger-&gt;schema;</code></pre>
<p>Returns the complete openapi schema.</p>
<h2 id="validate_request">validate_request</h2>
<pre><code>$swagger-&gt;validate_request(json =&gt; $json, spec =&gt; $spec);</code></pre>
<p>Validates the spec for a specific endpoint say developer_post against the json got from the user and dies if the check is unsuccesful.</p>
<h2 id="developer">developer</h2>
<pre><code>my $developer_schema = $swagger-&gt;developer;</code></pre>
<p>Returns the schemas associated with the <a href="Model/Developer.pm.html">Peace::Model::Developer</a> object.</p>
<h2 id="developer_post">developer_post</h2>
<pre><code>my $developer_post = $swagger-&gt;developer_post</code></pre>
<p>Returns the schema of the post request to the /developer enpoint.</p>
</body>
</html>

View File

@ -8,18 +8,22 @@ use Mojo::Base 'Mojolicious';
sub startup {
my $self = shift;
my $home = $ENV{HOME};
# Load configuration from config file
my $config =
$self->plugin( JSONConfig => { file => "$home/.config/peace/peace.conf" } );
my $config = $self->config;
# Configure the application
$self->secrets( $config->{secrets} );
# Router
my $r = $self->routes;
$r->post('/users')->to('user#post');
$r->post('/developer')->to('developer#post');
}
sub peace_config {
my $self = shift;
my $home = $ENV{HOME};
return $self->plugin(
JSONConfig => { file => "$home/.config/peace/peace.conf" } );
}
1;
@ -46,4 +50,10 @@ Peace implements the following functions:
For internal usage from Mojolicious.
=head2 peace_config
my $config = Peace->new->peace_config;
Retrieves the config for the peace application.
=cut

View File

@ -0,0 +1,90 @@
package Peace::Controller::Developer;
use Mojo::Base 'Mojolicious::Controller';
use v5.30.0;
use strict;
use warnings;
use Data::Dumper;
use Const::Fast;
use Peace::Swagger;
use Crypt::Bcrypt qw/bcrypt/;
use Crypt::URandom qw/urandom/;
use Peace;
use Peace::DB;
use Peace::Model::Developer;
use Peace::DAO::Developer;
const my $swagger => Peace::Swagger->new;
sub post {
my $self = shift;
my $json = $self->req->json;
my $peace = Peace->new;
my $config = $peace->peace_config;
print Data::Dumper::Dumper $config;
my $dbh = Peace::DB->dbh( config => $config );
my $developer_dao = Peace::DAO::Developer->new( dbh => $dbh );
unless ( defined $json ) {
$self->render( text => 'body is not a json', status => '400' );
return;
}
my $spec = $swagger->developer_post;
eval { $swagger->validate_request( json => $json, spec => $spec, ); };
if ($@) {
$self->render( text => $@, status => '400' );
return;
}
my $secret = delete $json->{secret};
my $secret_bcrypt = bcrypt( $secret, '2b', 12, urandom(16) );
$json->{secret_bcrypt} = $secret_bcrypt;
$json->{verified} = 0;
my $developer = Peace::Model::Developer->new(%$json);
eval {
$developer_dao->create( developer => $developer );
};
if ($@) {
$self->render( status => 400, text => $@ );
return;
}
$self->render( json => $developer->to_json() );
}
1;
=encoding utf8
=head1 NAME
Peace::Controller::Developer - Developer's http endpoint.
=head1 SYNOPSIS
# This object is used by mojolicious.
=head1 DESCRIPTION
Peace::Controller::Developer allows to interact using
a json http api with the L<Peace::Model::Developer> objects
in Peace.
=head1 METHODS
Peace::Controller::Developer implements the following methods:
=head2 post
# To be used by mojolicious.
Creates a Developer in db with the data given by the user.
=head1 SEE ALSO
L<Peace::Model::Developer>, L<Peace::DAO::Developer>
=cut

View File

@ -44,11 +44,20 @@ INSERT INTO developers (secret_bcrypt, name, surname, email, country, verified)
VALUES (?, ?, ?, ?, ?, ?)
RETURNING uuid;
EOF
my $result = $dbh->selectrow_hashref(
$insert, undef, $developer->secret_bcrypt,
$developer->name, $developer->surname, $developer->email,
$developer->country, $developer->verified
);
my $result;
eval {
$result = $dbh->selectrow_hashref(
$insert, undef, $developer->secret_bcrypt,
$developer->name, $developer->surname, $developer->email,
$developer->country, $developer->verified
);
};
if ($@) {
if ($@ =~ /duplicate key value/) {
die "Email already registered.";
}
die $@;
}
my $uuid = $result->{uuid};
$developer->uuid($uuid);
my $new_developer = $self->recover_by_uuid( uuid => $uuid );

View File

@ -30,7 +30,7 @@ my @migrations = (
secret_bcrypt TEXT NOT NULL,
name TEXT NOT NULL,
surname TEXT NOT NULL,
email TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
stripe_id TEXT,
country TEXT NOT NULL,
verified BOOL DEFAULT false,
@ -93,7 +93,7 @@ my @migrations = (
my %params = $validator->(@_);
my $config = $params{config};
my $db_config = $config->{db_config};
my $dbname = $db_config->{dbname} or die 'No dbnabe in db_config';
my $dbname = $db_config->{dbname} or die 'No dbname in db_config';
my $host = $db_config->{host} or die 'No host in db_config';
my $username = $db_config->{username} or die 'No username in db_config';
my $password = $db_config->{password} or die 'No password in db_config';

View File

@ -35,15 +35,30 @@ use Peace::DAO::Application;
}
}
sub to_json {
my $self = shift;
return {
uuid => $self->uuid,
date_creation => '' . $self->date_creation,
name => $self->name,
surname => $self->surname,
email => $self->email,
country => $self->country,
verified => $self->verified,
};
}
sub applications {
my $self = shift;
if (!defined $self->{applications}) {
my $dbh = $self->_dbh;
if (!defined $dbh) {
die "There is no database handle, so no chance to recover applications from the developer.";
my $self = shift;
if ( !defined $self->{applications} ) {
my $dbh = $self->_dbh;
if ( !defined $dbh ) {
die
"There is no database handle, so no chance to recover applications from the developer.";
}
my $application_dao = Peace::DAO::Application->new( dbh => $dbh );
$self->{applications} = $application_dao->recover_by_developer(developer => $self);
$self->{applications} =
$application_dao->recover_by_developer( developer => $self );
}
return $self->{applications};
@ -181,6 +196,7 @@ sub _dbh {
return $self->{dbh};
}
1;
=encoding utf8
=head1 NAME
@ -226,6 +242,12 @@ Peace::Model::Developer implements the following instance methods:
Peace::Model::Developer implements the following methods:
=head2 to_json
my $json = $developer->to_json;
Renders the developer in a json like structure.
=head2 applications
my $applications = $developer->applications;

282
lib/Peace/Swagger.pm Normal file
View File

@ -0,0 +1,282 @@
package Peace::Swagger;
use v5.30.0;
use strict;
use warnings;
use Params::ValidationCompiler qw/validation_for/;
use Types::Standard qw/ArrayRef Str HashRef/;
use Const::Fast;
use Email::Valid;
const my $EMAIL_VALIDATOR => Type::Tiny->new(
name => 'Email',
constraint => sub {
return Email::Valid->address($_);
},
message => sub { return "$_ is not an email address."; },
);
sub new {
my $class = shift;
my $self = bless {}, $class;
}
sub schema {
return {
openapi => "3.1.0",
info => {
title => 'Peace API',
version => '0.0.1',
license => {
name => 'AGPLv3',
url => 'https://www.gnu.org/licenses/agpl-3.0.en.html',
}
},
paths => { developer(), }
};
}
{
my $validator = validation_for(
params => {
type => { type => Str },
}
);
sub _type_to_type_tiny {
my $self = shift;
my %params = $validator->(@_);
my ($type) = $params{type};
return Str if $type eq 'string';
die 'No such type declared.';
}
}
{
my $validator = validation_for(
params => {
format => { type => Str },
}
);
sub _format_to_type_tiny {
my $self = shift;
my %params = $validator->(@_);
my ($format) = $params{format};
return $EMAIL_VALIDATOR if $format eq 'email';
die "No such format declared.";
}
}
sub developer {
return (
"/developer" => {
summary => "Allows to work with developers.",
post => developer_post(),
}
);
}
{
my $validator = validation_for(
params => {
json => { type => HashRef },
spec => { type => HashRef },
}
);
sub validate_request {
my $self = shift;
my %params = $validator->(@_);
my $json = $params{json};
my $spec = $params{spec};
my $spec_parameters = $spec->{parameters};
for my $parameter_spec (@$spec_parameters) {
$self->_validate_parameter(
parameter_spec => $parameter_spec,
json => $json,
);
}
}
}
{
my $validator = validation_for(
params => {
parameter_spec => { type => HashRef },
json => { type => HashRef },
}
);
sub _validate_parameter {
my $self = shift;
my %params = $validator->(@_);
my ( $parameter_spec, $json ) = @params{
qw/
parameter_spec json/
};
my $name = $parameter_spec->{name};
my $schema = $parameter_spec->{schema};
my $required = $parameter_spec->{required};
my $type = $schema->{type};
my $format = $schema->{format};
my $pattern = $schema->{pattern};
if ( !exists $json->{$name} ) {
die "$name is required." if $required;
return;
}
my $value = $json->{$name};
unless ( $self->_type_to_type_tiny( type => $type )->check($value) ) {
die "$value is not $type.";
}
if ( defined $format ) {
my $type_format = $self->_format_to_type_tiny( format => $format );
unless ( $type_format->check( $json->{$name} ) ) {
die "$value is not $format.";
}
}
if ( defined $pattern ) {
die "$value doesn\'t match $pattern." if $value !~ /^$pattern$/;
}
}
}
sub developer_post {
return {
summary => 'Creates a developer',
parameters => [
{
name => 'secret',
required => 1,
in => 'query',
description =>
'Access credential for Authenticate latter as the created developer.',
schema => {
type => "string",
# Bcrypt limit is 72 characters.
pattern => ".{10,72}"
}
},
{
name => 'name',
required => 1,
in => 'query',
description => 'Developer\'s real name',
schema => {
type => "string",
}
},
{
name => 'surname',
required => 1,
in => 'query',
description => 'Developer\'s real surname',
schema => {
type => "string",
}
},
{
name => 'surname',
required => 1,
in => 'query',
description => 'Developer\'s real surname',
schema => {
type => "string",
}
},
{
name => 'email',
required => 1,
in => 'query',
description => 'Developer\'s email',
schema => {
type => "string",
format => 'email',
}
},
{
name => 'country',
description => 'Developer\'s country in iso3166format',
required => 1,
in => 'query',
schema => {
type => "string",
pattern => '[A-Z]{2}',
}
}
]
};
}
1;
=encoding utf8
=head1 NAME
Peace::Swagger - OpenAPI definitions for the Peace API.
=head1 SYNOPSIS
my $swagger = Peace::Swagger->new;
my $spec = $swagger->schema;
=head1 DESCRIPTION
This module aims to help in the programmatic description
of all API endpoints in a way that makes possible to
reuse those schemas in the code and in the documentation.
=head1 INSTANCE METHODS
Peace::Swagger implements the following instance methods:
=head2 new
my $swagger = Peace::Swagger->new;
Instances a Peace::Swagger.
=head1 METHODS
Peace::Swagger implements the following methods:
=head2 schema
my $schema = $swagger->schema;
Returns the complete openapi schema.
=head2 validate_request
$swagger->validate_request(json => $json, spec => $spec);
Validates the spec for a specific endpoint say
developer_post against the json got from the
user and dies if the check is unsuccesful.
=head2 developer
my $developer_schema = $swagger->developer;
Returns the schemas associated with the
L<Peace::Model::Developer> object.
=head2 developer_post
my $developer_post = $swagger->developer_post
Returns the schema of the post request
to the /developer enpoint.
=cut