diff --git a/lib/Pear/LocalLoop.pm b/lib/Pear/LocalLoop.pm index 32814cf..33742aa 100644 --- a/lib/Pear/LocalLoop.pm +++ b/lib/Pear/LocalLoop.pm @@ -149,6 +149,7 @@ sub startup { $api->post('/user/account')->to('api-user#post_account_update'); $api->post('/user-history')->to('api-user#post_user_history'); $api->post('/stats')->to('api-stats#post_index'); + $api->post('/stats/category')->to('api-categories#post_category_list'); $api->post('/stats/customer')->to('api-stats#post_customer'); $api->post('/stats/leaderboard')->to('api-stats#post_leaderboards'); $api->post('/stats/leaderboard/paged')->to('api-stats#post_leaderboards_paged'); diff --git a/lib/Pear/LocalLoop/Controller/Admin/Categories.pm b/lib/Pear/LocalLoop/Controller/Admin/Categories.pm index b37900d..529239c 100644 --- a/lib/Pear/LocalLoop/Controller/Admin/Categories.pm +++ b/lib/Pear/LocalLoop/Controller/Admin/Categories.pm @@ -57,6 +57,7 @@ sub read { sub update { my $c = shift; my $validation = $c->validation; + $validation->required('id'); $validation->required('category', 'trim')->like(qr/^[\w]*$/); my $id = $c->param('id'); @@ -67,10 +68,11 @@ sub update { $c->redirect_to( '/admin/categories/' . $id ); } elsif ( my $category = $c->result_set->find($id) ) { $category->update({ + id => $validation->param('id'), name => $validation->param('category'), }); $c->flash( success => 'Category Updated' ); - $c->redirect_to( '/admin/categories/' . $id ); + $c->redirect_to( '/admin/categories/' . $validation->param('id') ); } else { $c->flash( error => 'No Category found' ); $c->redirect_to( '/admin/categories' ); diff --git a/lib/Pear/LocalLoop/Controller/Api/Categories.pm b/lib/Pear/LocalLoop/Controller/Api/Categories.pm new file mode 100644 index 0000000..8e52d4b --- /dev/null +++ b/lib/Pear/LocalLoop/Controller/Api/Categories.pm @@ -0,0 +1,78 @@ +package Pear::LocalLoop::Controller::Api::Categories; +use Mojo::Base 'Mojolicious::Controller'; + +use List::Util qw/ max /; + +sub post_category_list { + my $c = shift; + + my $entity = $c->stash->{api_user}->entity; + + my $duration = DateTime::Duration->new( days => 28 ); + my $end = DateTime->today; + my $start = $end->clone->subtract_duration( $duration ); + + my $dtf = $c->schema->storage->datetime_parser; + my $driver = $c->schema->storage->dbh->{Driver}->{Name}; + my $month_transaction_rs = $c->schema->resultset('ViewQuantisedTransactionCategory' . $driver)->search( + { + purchase_time => { + -between => [ + $dtf->format_datetime($start), + $dtf->format_datetime($end), + ], + }, + buyer_id => $entity->id, + }, + { + columns => [ + { + quantised => 'quantised_weeks', + value => 'value', + category_id => 'category_id', + } + ], + group_by => [ qw/ category_id quantised_weeks / ], + order_by => { '-desc' => 'quantised_weeks' }, + } + ); + + my $data = {}; + + for ( $month_transaction_rs->all ) { + my $quantised = $c->db_datetime_parser->parse_datetime($_->get_column('quantised')); + my $days = $c->format_iso_date( $quantised ) || 0; + my $category = $_->get_column('category_id') || 0; + my $value = ($_->get_column('value') || 0) / 100000; + $data->{$days} = [] unless exists $data->{$days}; + push @{ $data->{$days} }, { + days => $days, + value => $value, + category => $category, + }; + } + + return $c->render( + json => { + success => Mojo::JSON->true, + data => $data, + } + ); +} + +sub pg_or_sqlite { + my ( $c, $pg_sql, $sqlite_sql ) = @_; + + my $driver = $c->schema->storage->dbh->{Driver}->{Name}; + + if ( $driver eq 'Pg' ) { + return \$pg_sql; + } elsif ( $driver eq 'SQLite' ) { + return \$sqlite_sql; + } else { + $c->app->log->warn('Unknown Driver Used'); + return undef; + } +} + +1; diff --git a/lib/Pear/LocalLoop/Schema/Result/ViewQuantisedTransactionCategoryPg.pm b/lib/Pear/LocalLoop/Schema/Result/ViewQuantisedTransactionCategoryPg.pm new file mode 100644 index 0000000..2ae0445 --- /dev/null +++ b/lib/Pear/LocalLoop/Schema/Result/ViewQuantisedTransactionCategoryPg.pm @@ -0,0 +1,26 @@ +package Pear::LocalLoop::Schema::Result::ViewQuantisedTransactionCategoryPg; + +use strict; +use warnings; + +use base 'DBIx::Class::Core'; + +__PACKAGE__->table_class('DBIx::Class::ResultSource::View'); +__PACKAGE__->table('view_quantised_transactions'); +__PACKAGE__->result_source_instance->is_virtual(1); + +__PACKAGE__->result_source_instance->view_definition( qq/ +SELECT "transactions.value", + "transactions.distance", + "transactions.purchase_time", + "transactions.buyer_id", + "transactions.seller_id", + "transaction_category.category_id", + DATE_TRUNC('hour', "transactions.purchase_time") AS "quantised_hours", + DATE_TRUNC('day', "transactions.purchase_time") AS "quantised_days", + DATE_TRUNC('week', "transactions.purchase_time") AS "quantised_weeks" + FROM "transactions" +LEFT JOIN "transaction_category" ON "transactions.id" = "transaction_category.transaction_id" +/); + +1; diff --git a/lib/Pear/LocalLoop/Schema/Result/ViewQuantisedTransactionCategorySQLite.pm b/lib/Pear/LocalLoop/Schema/Result/ViewQuantisedTransactionCategorySQLite.pm new file mode 100644 index 0000000..fcef5a2 --- /dev/null +++ b/lib/Pear/LocalLoop/Schema/Result/ViewQuantisedTransactionCategorySQLite.pm @@ -0,0 +1,26 @@ +package Pear::LocalLoop::Schema::Result::ViewQuantisedTransactionCategorySQLite; + +use strict; +use warnings; + +use base 'DBIx::Class::Core'; + +__PACKAGE__->table_class('DBIx::Class::ResultSource::View'); +__PACKAGE__->table('view_quantised_transactions'); +__PACKAGE__->result_source_instance->is_virtual(1); + +__PACKAGE__->result_source_instance->view_definition( qq/ +SELECT "transactions"."value", + "transactions"."distance", + "transactions"."purchase_time", + "transactions"."buyer_id", + "transactions"."seller_id", + "transaction_category"."category_id", + DATETIME(STRFTIME('%Y-%m-%d %H:00:00',"transactions"."purchase_time")) AS "quantised_hours", + DATETIME(STRFTIME('%Y-%m-%d 00:00:00',"transactions"."purchase_time")) AS "quantised_days", + DATETIME(STRFTIME('%Y-%m-%d 00:00:00',"transactions"."purchase_time", 'weekday 1')) AS "quantised_weeks" + FROM "transactions" +LEFT JOIN "transaction_category" ON "transactions"."id" = "transaction_category"."transaction_id" +/); + +1; diff --git a/t/api/categories.t b/t/api/categories.t new file mode 100644 index 0000000..fe2c751 --- /dev/null +++ b/t/api/categories.t @@ -0,0 +1,98 @@ +use Mojo::Base -strict; + +BEGIN { + use Test::MockTime qw/ set_absolute_time /; +} + +use FindBin qw/ $Bin /; + +use Test::More; +use Mojo::JSON; +use Test::Pear::LocalLoop; +use DateTime; + +my $framework = Test::Pear::LocalLoop->new( + etc_dir => "$Bin/../etc", +); +$framework->install_fixtures('users'); + +my $t = $framework->framework; +my $schema = $t->app->schema; + +set_absolute_time('2017-01-02T00:00:00Z'); + +my $start = DateTime->today->subtract( hours => 12 ); + +# create 40 days worth of data +for my $count ( 0 .. 28 ) { + my $trans_day = $start->clone->subtract( days => $count ); + + create_random_transaction( 'test1@example.com', $trans_day ); + if ( $count % 2 ) { + create_random_transaction( 'test1@example.com', $trans_day ); + } + if ( $count % 3 ) { + create_random_transaction( 'test1@example.com', $trans_day ); + } + if ( $count % 4 ) { + create_random_transaction( 'test1@example.com', $trans_day ); + } +} + +my $session_key = $framework->login({ + email => 'test1@example.com', + password => 'abc123', +}); + +$t->post_ok('/api/stats/category' => json => { + session_key => $session_key, + }) + ->status_is(200)->or($framework->dump_error) + ->json_is('/data', { + "2016-12-05" => [{ + days => "2016-12-05", + value => 10, + category => 1, + }], + "2016-12-12" => [{ + days => "2016-12-12", + value => 10, + category => 1, + }], + "2016-12-19" => [{ + days => "2016-12-19", + value => 10, + category => 1, + }], + "2016-12-26" => [{ + days => "2016-12-26", + value => 10, + category => 1, + }], + "2017-01-02" => [{ + days => "2017-01-02", + value => 10, + category => 1, + }] + })->or($framework->dump_error); + +sub create_random_transaction { + my $buyer = shift; + my $time = shift; + + my $buyer_result = $schema->resultset('User')->find({ email => $buyer })->entity; + my $seller_result = $schema->resultset('Organisation')->find({ name => 'Test Org' })->entity; + my $test_transaction = $schema->resultset('Transaction')->create({ + buyer => $buyer_result, + seller => $seller_result, + value => 10 * 100000, + proof_image => 'a', + purchase_time => $time, + }); + $schema->resultset('TransactionCategory')->create({ + category_id => 1, + transaction_id => $test_transaction->id, + }); +} + +done_testing; diff --git a/templates/admin/categories/read.html.ep b/templates/admin/categories/read.html.ep index 2cf8af0..5e32820 100644 --- a/templates/admin/categories/read.html.ep +++ b/templates/admin/categories/read.html.ep @@ -16,6 +16,11 @@ +
+ + +

Do not change the ID unless absolutely necessary.

+