Adding endpoint to create applications.

This commit is contained in:
sergiotarxz 2022-04-21 02:46:11 +02:00
parent 16ac140f9d
commit 85aea0378c
7 changed files with 375 additions and 23 deletions

View File

@ -17,6 +17,8 @@ sub startup {
# Router
my $r = $self->routes;
$r->post('/developer')->to('developer#post');
$r->post('/developer/#identifier/application')
->to('application#developer_application_post');
}
sub peace_config {

View File

@ -0,0 +1,97 @@
package Peace::Controller::Application;
use Mojo::Base 'Mojolicious::Controller';
use v5.30.0;
use strict;
use warnings;
use Peace;
use Peace::Swagger;
use Peace::DB;
use Peace::DAO::Developer;
use Peace::Model::Application;
use Peace::DAO::Application;
sub developer_application_post {
my $self = shift;
my $json = $self->req->json;
my $peace = Peace->new;
my $config = $peace->peace_config;
my $dbh = Peace::DB->dbh( config => $config );
my $swagger = Peace::Swagger->new;
my $headers = $self->req->headers->to_hash;
unless ( defined $json ) {
$self->render( text => 'body is not a json', status => '400' );
return;
}
my $spec = $swagger->developer_application_post;
eval {
$swagger->validate_request(
json => $json,
stash => $self->stash,
headers => $headers,
spec => $spec,
);
};
if ($@) {
$self->render( text => $@, status => '400' );
return;
}
my $api_key = $headers->{api_key};
my $identifier = $self->stash->{identifier};
my $developer_dao = Peace::DAO::Developer->new( dbh => $dbh );
my $developer = $developer_dao->recover_by_identifier( identifier => $identifier );
unless (defined $developer || $developer->login($api_key)) {
$self->render( text => 'Incorrect user or password', status => 401 );
return;
}
unless ($developer->verified) {
$self->render( text => 'Developer is still not verified', status => 401 );
return;
}
my $application = Peace::Model::Application->new(%$json, verified => 0, developer => $developer);
my $application_dao = Peace::DAO::Application->new( dbh => $dbh );
eval {
$application_dao->create( application => $application );
};
if ($@) {
$self->render( status => 400, text => $@ );
return;
}
$self->render( json => $application->to_json() );
}
1;
=encoding utf8
=head1 NAME
Peace::Controller::Application - Application's http endpoint.
=head1 SYNOPSIS
# This object is used by mojolicious.
=head1 DESCRIPTION
Peace::Controller::Application allows to interact using
a json http api with the L<Peace::Model::Application> objects
in Peace.
=head1 METHODS
Peace::Controller::Developer implements the following methods:
=head2 developer_application_post
# To be used by mojolicious.
Creates a application in db with the data given by the user.
=head1 SEE ALSO
L<Peace::Model::Application>, L<Peace::DAO::Application>
=cut

View File

@ -28,7 +28,6 @@ sub post {
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 );
@ -37,7 +36,7 @@ sub post {
return;
}
my $spec = $swagger->developer_post;
eval { $swagger->validate_request( json => $json, spec => $spec, ); };
eval { $swagger->validate_request( json => $json, stash => $self->stash, headers=>$self->req->headers->to_hash, spec => $spec, ); };
if ($@) {
$self->render( text => $@, status => '400' );
return;

View File

@ -84,6 +84,9 @@ SELECT uuid, date_creation, secret_bcrypt, name, surname, email, stripe_id, coun
FROM developers
WHERE uuid = ?;
EOF
if (!defined $result) {
die "No such developer $uuid.";
}
for my $key (keys %$result) {
delete $result->{$key} unless defined $result->{$key};
}
@ -99,6 +102,40 @@ EOF
}
}
{
my $validator = validation_for(
params => {
identifier => { type => Str },
}
);
sub recover_by_identifier {
my $self = shift;
my %params = $validator->(@_);
my $identifier =$params{identifier};
my $dbh = $self->_dbh;
my $result = $dbh->selectrow_hashref( <<'EOF', undef, $identifier );
SELECT uuid, date_creation, secret_bcrypt, name, surname, email, stripe_id, country, verified
FROM developers
WHERE email = $1 or uuid::text = $1;
EOF
if (!defined $result) {
die "No such developer $identifier.";
}
for my $key (keys %$result) {
delete $result->{$key} unless defined $result->{$key};
}
if ( exists $result->{date_creation} ) {
my $iso8601 = DateTime::Format::Pg->new;
$result->{date_creation} =
$iso8601->parse_datetime( $result->{date_creation} );
}
my $developer = Peace::Model::Developer->new(%$result);
return $developer;
}
}
sub _dbh {
my $self = shift;
return $self->{dbh};
@ -153,6 +190,12 @@ in the passed object.
Recovers the L<Peace::Model::Developer> associated from an uuid from database.
=head2 recover_by_identifier
my $developer = $developer_dao->recover_by_identifier( identifier => $identifier );
Recovers the L<Peace::Model::Developer> associated from an identifier from database.
=head1 SEE ALSO
L<Peace::DB>, L<Peace::Model::Developer>

View File

@ -44,6 +44,24 @@ use DateTime;
}
}
sub to_json {
my $self = shift;
return {
uuid => $self->uuid,
date_creation => $self->date_creation,
name => $self->name,
description => $self->description,
url => $self->url,
developer => $self->developer->uuid,
app_id => $self->app_id,
price => $self->price,
git_repo => $self->git_repo,
flatpak_builder_file => $self->flatpak_builder_file,
flatpak_repo => $self->flatpak_repo,
verified => $self->verified,
};
}
{
my $validator =
validation_for( params => [ { type => Str, optional => 1 } ] );
@ -282,6 +300,12 @@ Peace::Model::Application implements the following instance methods:
Peace::Model::Application implements the following methods:
=head2 to_json
my $json = $application->to_json;
Retrieves the json representation of the application.
=head2 uuid
my $uuid = $application->uuid;

View File

@ -8,6 +8,7 @@ use Params::ValidationCompiler qw/validation_for/;
use Types::Standard qw/Str InstanceOf Bool HasMethods/;
use DateTime;
use Crypt::Bcrypt qw/bcrypt_check/;
use Peace::DAO::Application;
@ -48,6 +49,21 @@ sub to_json {
};
}
{
my $validator = validation_for(
params => {
secret => { type => Str },
}
);
sub login {
my $self = shift;
my %params = $validator->(@_);
my $secret = $params{secret};
my $secret_bcrypt = $self->secret_bcrypt;
return bcrypt_check($secret, $secret_bcrypt);
}
}
sub applications {
my $self = shift;
if ( !defined $self->{applications} ) {
@ -248,6 +264,13 @@ Peace::Model::Developer implements the following methods:
Renders the developer in a json like structure.
=head2 login
my $logged = $developer->login( secret => $secret );
Returns true if the login is successful, false
otherwise.
=head2 applications
my $applications = $developer->applications;

View File

@ -5,8 +5,10 @@ use v5.30.0;
use strict;
use warnings;
use Data::Dumper;
use Params::ValidationCompiler qw/validation_for/;
use Types::Standard qw/ArrayRef Str HashRef/;
use Types::Standard qw/ArrayRef Str HashRef Int/;
use Const::Fast;
use Email::Valid;
@ -14,11 +16,29 @@ use Email::Valid;
const my $EMAIL_VALIDATOR => Type::Tiny->new(
name => 'Email',
constraint => sub {
return Email::Valid->address($_);
return Email::Valid->rfc822($_);
},
message => sub { return "$_ is not an email address."; },
);
const my $UUID_VALIDATOR => Type::Tiny->new(
name => 'Uuid',
constraint => sub {
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
},
message => sub { return "$_ is not an uuid."; },
);
const my $DEVELOPER_IDENTIFIER_VALIDATOR => Type::Tiny->new(
name => 'DeveloperIdentifier',
constraint => sub {
return 1 if $EMAIL_VALIDATOR->check($_);
return 1 if $UUID_VALIDATOR->check($_);
return 0;
},
message => sub { return "$_ is not a developer identifier."; },
);
sub new {
my $class = shift;
my $self = bless {}, $class;
@ -51,6 +71,7 @@ sub schema {
my %params = $validator->(@_);
my ($type) = $params{type};
return Str if $type eq 'string';
return Int if $type eq 'integer';
die 'No such type declared.';
}
}
@ -67,6 +88,8 @@ sub schema {
my %params = $validator->(@_);
my ($format) = $params{format};
return $EMAIL_VALIDATOR if $format eq 'email';
return $DEVELOPER_IDENTIFIER_VALIDATOR
if $format eq 'developer_identifier';
die "No such format declared.";
}
}
@ -76,6 +99,11 @@ sub developer {
"/developer" => {
summary => "Allows to work with developers.",
post => developer_post(),
},
"/developer/{identifier}/application" => {
summary =>
"Allows to work with applications with developer's permissions.",
post => developer_application_post(),
}
);
}
@ -83,23 +111,37 @@ sub developer {
{
my $validator = validation_for(
params => {
json => { type => HashRef },
spec => { type => HashRef },
json => { type => HashRef },
spec => { type => HashRef },
headers => { type => HashRef },
stash => { type => HashRef },
}
);
sub validate_request {
my $self = shift;
my %params = $validator->(@_);
my $json = $params{json};
my $spec = $params{spec};
my $spec_parameters = $spec->{parameters};
my $self = shift;
my %params = $validator->(@_);
my $json = $params{json};
my $spec = $params{spec};
my $headers = $params{headers};
my $stash = $params{stash};
my $spec_parameters = $spec->{parameters};
my $validated_json_keys = {};
for my $parameter_spec (@$spec_parameters) {
$self->_validate_parameter(
parameter_spec => $parameter_spec,
json => $json,
parameter_spec => $parameter_spec,
json => $json,
headers => $headers,
stash => $stash,
validated_json_keys => $validated_json_keys,
);
}
## Avoiding fancy exploits.
my @keys_to_remove =
grep { !defined $validated_json_keys->{$_} } keys %$json;
for my $key (@keys_to_remove) {
delete $json->{$key};
}
}
}
@ -107,30 +149,43 @@ sub developer {
{
my $validator = validation_for(
params => {
parameter_spec => { type => HashRef },
json => { type => HashRef },
parameter_spec => { type => HashRef },
json => { type => HashRef },
headers => { type => HashRef },
stash => { type => HashRef },
validated_json_keys => { type => HashRef },
}
);
sub _validate_parameter {
my $self = shift;
my %params = $validator->(@_);
my ( $parameter_spec, $json ) = @params{
my ( $parameter_spec, $json, $headers, $stash, $validated_json_keys ) =
@params{
qw/
parameter_spec json/
};
parameter_spec json headers stash validated_json_keys/
};
my $name = $parameter_spec->{name};
my $schema = $parameter_spec->{schema};
my $required = $parameter_spec->{required};
my $in = $parameter_spec->{in};
my $type = $schema->{type};
my $format = $schema->{format};
my $pattern = $schema->{pattern};
if ( !exists $json->{$name} ) {
my $value;
if ( $in eq 'query' ) {
$value = $json->{$name};
$validated_json_keys->{$name} = 1;
}
print Data::Dumper::Dumper $headers;
$value = $headers->{$name} if $in eq 'header';
$value = $stash->{$name} if $in eq 'path';
if ( !defined $value ) {
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.";
@ -138,7 +193,7 @@ sub developer {
if ( defined $format ) {
my $type_format = $self->_format_to_type_tiny( format => $format );
unless ( $type_format->check( $json->{$name} ) ) {
unless ( $type_format->check($value) ) {
die "$value is not $format.";
}
}
@ -149,6 +204,108 @@ sub developer {
}
}
sub developer_application_post {
return {
summary => 'Creates a application.',
parameters => [
{
name => 'identifier',
description => 'Developer\'s email or uuid.',
required => 1,
in => 'path',
schema => {
type => "string",
format => "developer_identifier",
}
},
{
name => 'api_key',
description => '<secret>',
required => 1,
in => 'header',
schema => {
type => "string",
}
},
{
name => 'name',
description => 'Project\'s name.',
required => 1,
in => 'query',
schema => {
type => "string",
}
},
{
name => 'description',
required => 1,
in => 'query',
description => 'Application\'s description.',
schema => {
type => "string",
}
},
{
name => 'url',
required => 1,
in => 'query',
description => 'Project\'s url.',
pattern => '^https?://',
schema => {
type => "string",
}
},
{
name => 'price',
required => 1,
in => 'query',
description => 'Price in dollar cents.',
schema => {
type => "integer",
}
},
{
name => 'git_repo',
required => 1,
in => 'query',
description => 'Project\'s git repository.',
schema => {
type => "string",
pattern => 'https?://.*'
}
},
{
name => 'app_id',
required => 1,
in => 'query',
description => 'Project\'s application id in flatpak format.',
schema => {
type => "string",
}
},
{
name => 'flatpak_builder_file',
required => 1,
in => 'query',
description => 'Project\'s path to the flatpak builder file.',
schema => {
type => "string",
}
},
{
name => 'flatpak_repo',
required => 1,
in => 'query',
description =>
'Flatpak repo to be attached to the .flatpak file.',
schema => {
type => "string",
}
}
]
};
}
sub developer_post {
return {
summary => 'Creates a developer',
@ -274,9 +431,16 @@ L<Peace::Model::Developer> object.
=head2 developer_post
my $developer_post = $swagger->developer_post
my $developer_post = $swagger->developer_post;
Returns the schema of the post request
to the /developer enpoint.
=head2 developer_application_post
my $developer_application_post = $swagger->developer_application_post;
Returns the schema of the post request
to the /developer/:identifier/application endpoint.
=cut