diff --git a/cpanfile b/cpanfile index 6531d21..6e21422 100644 --- a/cpanfile +++ b/cpanfile @@ -20,3 +20,4 @@ requires 'Try::Tiny'; requires 'MooX::Options::Actions'; requires 'Module::Runtime'; requires 'DBIx::Class::DeploymentHandler'; +requires 'DBIx::Class::Fixtures'; diff --git a/doc/Fixtures/Leaderboards.md b/doc/Fixtures/Leaderboards.md new file mode 100644 index 0000000..f3c7548 --- /dev/null +++ b/doc/Fixtures/Leaderboards.md @@ -0,0 +1,32 @@ +# Leaderboard Fixtures + +* Fixture Name: `leaderboards` + +## Users: + +* Test User1 + * email: test1@example.com + * password: abc123 +* Test User2 + * email: test2@example.com + * password: abc123 +* Test User3 + * email: test3@example.com + * password: abc123 +* Test User4 + * email: test4@example.com + * password: abc123 +* Test Org + * email: test5@example.com + * password: abc123 + +## Transactions + +Uses the same transactions as the `Transactions` fixtures set. + +## Leaderboards + +Pre calculated leaderboards for all normal leaderboard types. + +* Daily Total/Count + * Daily from May 3rd 2017 until July 31st 2017 diff --git a/doc/Fixtures/Transactions.md b/doc/Fixtures/Transactions.md new file mode 100644 index 0000000..444bd30 --- /dev/null +++ b/doc/Fixtures/Transactions.md @@ -0,0 +1,26 @@ +# Leaderboard Fixtures + +* Fixture Name: `transactions` + +## Users: + +* Test User1 + * email: test1@example.com + * password: abc123 +* Test User2 + * email: test2@example.com + * password: abc123 +* Test User3 + * email: test3@example.com + * password: abc123 +* Test User4 + * email: test4@example.com + * password: abc123 +* Test Org + * email: test5@example.com + * password: abc123 + +## Transactions: + +One transaction every 10 minutes, starting at August 1st 2017 and going back in +time, for each user. All transactions go to Test Org. diff --git a/doc/Leaderboards.md b/doc/Leaderboards.md new file mode 100644 index 0000000..a01069d --- /dev/null +++ b/doc/Leaderboards.md @@ -0,0 +1,16 @@ +# Leaderboards + +## Calculation + +The leaderboards are calculated for the previous range - so Daily leaderboards +are calculated for the whole of the day before, Weeks from the week before, +etc. - The only exception is all time, which is calculated from 00:00 on the +current day. + +## Recalculation + +Leaderboard recalculation only affects the latest two leaderboards for any set, +so just need to recalculate the last one and the current one, in that order. +This can be done during the regular leaderboard calculation cronjob, so +verified transactions will show up in the leaderboards the next day. + diff --git a/lib/Pear/LocalLoop.pm b/lib/Pear/LocalLoop.pm index 5134e95..f06b1c0 100644 --- a/lib/Pear/LocalLoop.pm +++ b/lib/Pear/LocalLoop.pm @@ -50,7 +50,7 @@ sub startup { return $user->id; } } - return undef; + return; }, }); @@ -155,6 +155,12 @@ sub startup { $api->post('/stats')->to('api-stats#post_index'); $api->post('/stats/leaderboard')->to('api-stats#post_leaderboards'); + my $api_v1 = $api->under('/v1'); + + my $api_v1_org = $api_v1->under('/organisation')->to('api-v1-organisation#auth'); + + $api_v1_org->post('/graphs')->to('api-v1-organisation-graphs#index'); + my $admin_routes = $r->under('/admin')->to('admin#under'); $admin_routes->get('/home')->to('admin#home'); @@ -169,10 +175,15 @@ sub startup { $admin_routes->get('/users/:id')->to('admin-users#read'); $admin_routes->post('/users/:id')->to('admin-users#update'); $admin_routes->post('/users/:id/delete')->to('admin-users#delete'); + $admin_routes->post('/users/:id/edit')->to('admin-users#edit'); $admin_routes->get('/organisations')->to('admin-organisations#list'); + $admin_routes->get('/organisations/add')->to('admin-organisations#add_org'); + $admin_routes->post('/organisations/add/submit')->to('admin-organisations#add_org_submit'); $admin_routes->get('/organisations/valid/:id')->to('admin-organisations#valid_read'); + $admin_routes->post('/organisations/valid/:id/edit')->to('admin-organisations#valid_edit'); $admin_routes->get('/organisations/pending/:id')->to('admin-organisations#pending_read'); + $admin_routes->post('/organisations/pending/:id/edit')->to('admin-organisations#pending_edit'); $admin_routes->get('/organisations/pending/:id/approve')->to('admin-organisations#pending_approve'); $admin_routes->get('/feedback')->to('admin-feedback#index'); diff --git a/lib/Pear/LocalLoop/Command/recalc_leaderboards.pm b/lib/Pear/LocalLoop/Command/recalc_leaderboards.pm new file mode 100644 index 0000000..2573f67 --- /dev/null +++ b/lib/Pear/LocalLoop/Command/recalc_leaderboards.pm @@ -0,0 +1,26 @@ +package Pear::LocalLoop::Command::recalc_leaderboards; +use Mojo::Base 'Mojolicious::Command'; + +use Mojo::Util 'getopt'; + +has description => 'Build All leaderboards'; + +has usage => sub { shift->extract_usage }; + +sub run { + my ( $self, @args ) = @_; + + my $leaderboard_rs = $self->app->schema->resultset('Leaderboard'); + + $leaderboard_rs->recalculate_all; +} + +=head1 SYNOPSIS + + Usage: APPLICATION recalc_leaderboards + + Recalculates ALL leaderboards. + +=cut + +1; diff --git a/lib/Pear/LocalLoop/Controller/Admin/Organisations.pm b/lib/Pear/LocalLoop/Controller/Admin/Organisations.pm index 1dffdc9..f382a5e 100644 --- a/lib/Pear/LocalLoop/Controller/Admin/Organisations.pm +++ b/lib/Pear/LocalLoop/Controller/Admin/Organisations.pm @@ -2,6 +2,7 @@ package Pear::LocalLoop::Controller::Admin::Organisations; use Mojo::Base 'Mojolicious::Controller'; use Try::Tiny; +use Data::Dumper; sub list { my $c = shift; @@ -15,6 +16,48 @@ sub list { ); } +sub add_org { + my $c = shift; +} + +sub add_org_submit { + my $c = shift; + + my $validation = $c->validation; + + $validation->required('name'); + $validation->optional('street_name'); + $validation->required('town'); + $validation->optional('sector'); + $validation->optional('postcode')->postcode; + + if ( $validation->has_error ) { + $c->flash( error => 'The validation has failed' ); + $c->app->log->warn(Dumper $validation); + return $c->redirect_to( '/admin/organisations/add/' ); + } + + my $organisation; + + try { + $organisation = $c->schema->resultset('Organisation')->create({ + name => $validation->param('name'), + street_name => $validation->param('street_name'), + town => $validation->param('town'), + sector => $validation->param('sector'), + postcode => $validation->param('postcode'), + }); + } finally { + if ( @_ ) { + $c->flash( error => 'Something went wrong Adding the Organisation' ); + $c->app->log->warn(Dumper @_); + } else { + $c->flash( success => 'Added Organisation' ); + } + }; + $c->redirect_to( '/admin/organisations/add/' ); +} + sub valid_read { my $c = shift; my $valid_org = $c->schema->resultset('Organisation')->find( $c->param('id') ); @@ -22,6 +65,7 @@ sub valid_read { undef, { page => $c->param('page') || 1, rows => 10, + order_by => { -desc => 'submitted_at' }, }, ); $c->stash( @@ -30,6 +74,45 @@ sub valid_read { ); } +sub valid_edit { + my $c = shift; + + my $validation = $c->validation; + $validation->required('name'); + $validation->required('street_name'); + $validation->required('town'); + $validation->optional('sector'); + $validation->required('postcode')->postcode; + + if ( $validation->has_error ) { + $c->flash( error => 'The validation has failed' ); + $c->app->log->warn(Dumper $validation); + return $c->redirect_to( '/admin/organisations/valid/' . $c->param('id') ); + } + + my $valid_org = $c->schema->resultset('Organisation')->find( $c->param('id') ); + + try { + $c->schema->storage->txn_do( sub { + $valid_org->update({ + name => $validation->param('name'), + street_name => $validation->param('street_name'), + town => $validation->param('town'), + sector => $validation->param('sector'), + postcode => $validation->param('postcode'), + }); + } ); + } finally { + if ( @_ ) { + $c->flash( error => 'Something went wrong Updating the Organisation' ); + $c->app->log->warn(Dumper @_); + } else { + $c->flash( success => 'Updated Organisation' ); + } + }; + $c->redirect_to( '/admin/organisations/valid/' . $valid_org->id ); +} + sub pending_read { my $c = shift; my $pending_org = $c->schema->resultset('PendingOrganisation')->find( $c->param('id') ); @@ -45,6 +128,43 @@ sub pending_read { ); } +sub pending_edit { + my $c = shift; + + my $validation = $c->validation; + $validation->required('name'); + $validation->required('street_name'); + $validation->required('town'); + $validation->required('postcode')->postcode; + + if ( $validation->has_error ) { + $c->flash( error => 'The validation has failed' ); + $c->app->log->warn(Dumper $validation); + return $c->redirect_to( '/admin/organisations/pending/' . $c->param('id') ); + } + + my $pending_org = $c->schema->resultset('PendingOrganisation')->find( $c->param('id') ); + + try { + $c->schema->storage->txn_do( sub { + $pending_org->update({ + name => $validation->param('name'), + street_name => $validation->param('street_name'), + town => $validation->param('town'), + postcode => $validation->param('postcode'), + }); + } ); + } finally { + if ( @_ ) { + $c->flash( error => 'Something went wrong Updating the Organisation' ); + $c->app->log->warn(Dumper @_); + } else { + $c->flash( success => 'Updated Organisation' ); + } + }; + $c->redirect_to( '/admin/organisations/pending/' . $pending_org->id ); +} + sub pending_approve { my $c = shift; my $pending_org = $c->schema->resultset('PendingOrganisation')->find( $c->param('id') ); diff --git a/lib/Pear/LocalLoop/Controller/Admin/Users.pm b/lib/Pear/LocalLoop/Controller/Admin/Users.pm index 2fbdd17..064ffba 100644 --- a/lib/Pear/LocalLoop/Controller/Admin/Users.pm +++ b/lib/Pear/LocalLoop/Controller/Admin/Users.pm @@ -1,15 +1,28 @@ package Pear::LocalLoop::Controller::Admin::Users; use Mojo::Base 'Mojolicious::Controller'; -has result_set => sub { +use Try::Tiny; +use Data::Dumper; + +has user_result_set => sub { my $c = shift; return $c->schema->resultset('User'); }; +has customer_result_set => sub { + my $c = shift; + return $c->schema->resultset('Customer'); +}; + +has organisation_result_set => sub { + my $c = shift; + return $c->schema->resultset('Organisation'); +}; + sub index { my $c = shift; - my $user_rs = $c->result_set; + my $user_rs = $c->user_result_set; $user_rs->result_class('DBIx::Class::ResultClass::HashRefInflator'); $c->stash( users => [ $user_rs->all ] ); } @@ -19,7 +32,7 @@ sub read { my $id = $c->param('id'); - if ( my $user = $c->result_set->find($id) ) { + if ( my $user = $c->user_result_set->find($id) ) { $c->stash( user => $user ); } else { $c->flash( error => 'No User found' ); @@ -27,6 +40,95 @@ sub read { } } +sub edit { + my $c = shift; + + my $id = $c->param('id'); + + my $user; + + unless ( $user = $c->user_result_set->find($id) ) { + $c->flash( error => 'No User found' ); + return $c->redirect_to( '/admin/users/' . $id ); + } + + my $validation = $c->validation; + + my $not_myself_user_rs = $c->user_result_set->search({ + id => { "!=" => $user->id }, + }); + $validation->required('email')->email->not_in_resultset( 'email', $not_myself_user_rs ); + $validation->required('postcode')->postcode; + $validation->optional('new_password'); + + if ( defined $user->customer_id ) { + $validation->required('display_name'); + $validation->required('full_name'); + } elsif ( defined $user->organisation_id ) { + $validation->required('name'); + $validation->required('street_name'); + $validation->required('town'); + $validation->optional('sector'); + } + + if ( $validation->has_error ) { + $c->flash( error => 'The validation has failed' ); + $c->app->log->warn(Dumper $validation); + return $c->redirect_to( '/admin/users/' . $id ); + } + + if ( defined $user->customer_id ){ + + try { + $c->schema->txn_do( sub { + $user->customer->update({ + full_name => $validation->param('full_name'), + display_name => $validation->param('display_name'), + postcode => $validation->param('postcode'), + }); + $user->update({ + email => $validation->param('email'), + ( defined $validation->param('new_password') ? ( password => $validation->param('new_password') ) : () ), + }); + }); + } finally { + if ( @_ ) { + $c->flash( error => 'Something went wrong Updating the User' ); + $c->app->log->warn(Dumper @_); + } else { + $c->flash( success => 'Updated User' ); + }; + } + } + elsif ( defined $user->organisation_id ) { + + try { + $c->schema->txn_do( sub { + $user->organisation->update({ + name => $validation->param('name'), + street_name => $validation->param('street_name'), + town => $validation->param('town'), + sector => $validation->param('sector'), + postcode => $validation->param('postcode'), + }); + $user->update({ + email => $validation->param('email'), + ( defined $validation->param('new_password') ? ( password => $validation->param('new_password') ) : () ), + }); + }); + } finally { + if ( @_ ) { + $c->flash( error => 'Something went wrong Updating the User' ); + $c->app->log->warn(Dumper @_); + } else { + $c->flash( success => 'Updated User' ); + } + } + }; + + $c->redirect_to( '/admin/users/' . $id ); +} + sub update { my $c = shift; $c->redirect_to( '/admin/users' ); diff --git a/lib/Pear/LocalLoop/Controller/Api/Auth.pm b/lib/Pear/LocalLoop/Controller/Api/Auth.pm index d64bf09..82cfb60 100644 --- a/lib/Pear/LocalLoop/Controller/Api/Auth.pm +++ b/lib/Pear/LocalLoop/Controller/Api/Auth.pm @@ -79,20 +79,12 @@ sub post_login { if ( defined $user_result ) { if ( $user_result->check_password($password) ) { my $session_key = $user_result->generate_session; - my $display_name; - - if ( defined $user_result->customer_id ) { - $display_name = $user_result->customer->display_name; - } elsif ( defined $user_result->organisation_id ) { - $display_name = $user_result->organisation->name; - } else { - return undef; - } return $c->render( json => { success => Mojo::JSON->true, session_key => $session_key, - display_name => $display_name, + display_name => $user_result->name, + user_type => $user_result->type, }); } } diff --git a/lib/Pear/LocalLoop/Controller/Api/Register.pm b/lib/Pear/LocalLoop/Controller/Api/Register.pm index ee760ee..dfff302 100644 --- a/lib/Pear/LocalLoop/Controller/Api/Register.pm +++ b/lib/Pear/LocalLoop/Controller/Api/Register.pm @@ -75,6 +75,7 @@ sub post_register{ $validation->required('name'); $validation->required('street_name'); $validation->required('town'); + $validation->required('sector'); } return $c->api_validation_error if $validation->has_error; @@ -102,7 +103,6 @@ sub post_register{ } elsif ($usertype eq 'organisation') { - my $fullAddress = $validation->param('fulladdress'); $c->schema->txn_do( sub { $c->schema->resultset('AccountToken')->find({ @@ -113,6 +113,7 @@ sub post_register{ name => $validation->param('name'), street_name => $validation->param('street_name'), town => $validation->param('town'), + sector => $validation->param('sector'), postcode => $validation->param('postcode'), }); $c->schema->resultset('User')->create({ diff --git a/lib/Pear/LocalLoop/Controller/Api/Upload.pm b/lib/Pear/LocalLoop/Controller/Api/Upload.pm index faec57b..08db6cc 100644 --- a/lib/Pear/LocalLoop/Controller/Api/Upload.pm +++ b/lib/Pear/LocalLoop/Controller/Api/Upload.pm @@ -93,7 +93,7 @@ sub post_upload { my $validation = $c->validation; # Test for file before loading the JSON in to the validator - $validation->required('file')->upload->filetype('image/jpeg'); + $validation->optional('file')->upload->filetype('image/jpeg'); $validation->input( $c->stash->{api_json} ); @@ -162,14 +162,14 @@ sub post_upload { my $upload = $validation->param('file'); my $purchase_time = $c->parse_iso_datetime($validation->param('purchase_time') || ''); $purchase_time ||= DateTime->now(); - my $file = $c->store_file_from_upload( $upload ); + my $file = defined $upload ? $c->store_file_from_upload( $upload ) : undef; my $new_transaction = $organisation->create_related( 'transactions', { buyer => $user, value => $transaction_value, - proof_image => $file, + ( defined $file ? ( proof_image => $file ) : ( proof_image => 'a' ) ), purchase_time => $c->format_db_datetime($purchase_time), } ); diff --git a/lib/Pear/LocalLoop/Controller/Api/User.pm b/lib/Pear/LocalLoop/Controller/Api/User.pm index ef9ec89..f9b46cb 100644 --- a/lib/Pear/LocalLoop/Controller/Api/User.pm +++ b/lib/Pear/LocalLoop/Controller/Api/User.pm @@ -72,7 +72,7 @@ sub post_account { } elsif ( defined $user_result->organisation_id ) { $display_name = $user_result->organisation->name; } else { - return undef; + return; } return $c->render( json => { @@ -124,10 +124,11 @@ sub post_account_update { if ( defined $user->customer_id ) { $validation->required('display_name'); $validation->required('full_name'); - } elsif ( defined $user->customer_id ) { + } elsif ( defined $user->organisation_id ) { $validation->required('name'); $validation->required('street_name'); $validation->required('town'); + $validation->required('sector'); } return $c->api_validation_error if $validation->has_error; @@ -148,13 +149,13 @@ sub post_account_update { } elsif ( defined $user->organisation_id ) { - my $fullAddress = $validation->param('fulladdress'); $c->schema->txn_do( sub { $user->organisation->update({ name => $validation->param('name'), street_name => $validation->param('street_name'), town => $validation->param('town'), + sector => $validation->param('sector'), postcode => $validation->param('postcode'), }); $user->update({ diff --git a/lib/Pear/LocalLoop/Controller/Api/V1/Organisation.pm b/lib/Pear/LocalLoop/Controller/Api/V1/Organisation.pm new file mode 100644 index 0000000..29a3ccb --- /dev/null +++ b/lib/Pear/LocalLoop/Controller/Api/V1/Organisation.pm @@ -0,0 +1,21 @@ +package Pear::LocalLoop::Controller::Api::V1::Organisation; +use Mojo::Base 'Mojolicious::Controller'; + +sub auth { + my $c = shift; + + return 1 if $c->stash->{api_user}->type eq 'organisation'; + + $c->render( + json => { + success => Mojo::JSON->false, + message => 'Not an Organisation', + error => 'user_not_org', + }, + status => 403, + ); + + return 0; +} + +1; diff --git a/lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Graphs.pm b/lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Graphs.pm new file mode 100644 index 0000000..bb807ba --- /dev/null +++ b/lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Graphs.pm @@ -0,0 +1,87 @@ +package Pear::LocalLoop::Controller::Api::V1::Organisation::Graphs; +use Mojo::Base 'Mojolicious::Controller'; + +has error_messages => sub { + return { + graph => { + required => { message => 'Must request graph type', status => 400 }, + in => { message => 'Unrecognised graph type', status => 400 }, + }, + }; +}; + +sub index { + my $c = shift; + + my $validation = $c->validation; + $validation->input( $c->stash->{api_json} ); + $validation->required('graph')->in( qw/ + customers_last_7_days + customers_last_30_days + / ); + + return $c->api_validation_error if $validation->has_error; + + my $graph_sub = "graph_" . $validation->param('graph'); + + unless ( $c->can($graph_sub) ) { + # Secondary catch in case a mistake has been made + return $c->render( + json => { + success => Mojo::JSON->false, + message => $c->error_messages->{graph}->{in}->{message}, + error => 'in', + }, + status => $c->error_messages->{graph}->{in}->{status}, + ); + } + + return $c->$graph_sub; +} + +sub graph_customers_last_7_days { + my $c = shift; + + my $duration = DateTime::Duration->new( days => 7 ); + return $c->_customers_last_duration( $duration ); +} + +sub graph_customers_last_30_days { + my $c = shift; + + my $duration = DateTime::Duration->new( days => 30 ); + return $c->_customers_last_duration( $duration ); +} + +sub _customers_last_duration { + my ( $c, $duration ) = @_; + + my $org = $c->stash->{api_user}->organisation; + + my $data = { day => [], count => [] }; + + my $start = DateTime->today; + my $end = $start->clone->subtract_duration( $duration ); + + my $dtf = $c->schema->storage->datetime_parser; + + while ( $end < $start ) { + my $moving_end = $end->clone->add( days => 1 ); + my $transactions = $c->schema->resultset('Transaction')->search({ + seller_id => $org->id, + purchase_time => { '-between' => [ $dtf->format_datetime($end), $dtf->format_datetime($moving_end) ] }, + })->count; + push @{$data->{day}}, $end->day_name; + push @{$data->{count}}, $transactions; + $end->add( days => 1 ); + } + + return $c->render( + json => { + success => Mojo::JSON->true, + graph => $data, + } + ); +} + +1; diff --git a/lib/Pear/LocalLoop/Schema.pm b/lib/Pear/LocalLoop/Schema.pm index 6c3cecc..739cc56 100644 --- a/lib/Pear/LocalLoop/Schema.pm +++ b/lib/Pear/LocalLoop/Schema.pm @@ -6,7 +6,7 @@ use warnings; use base 'DBIx::Class::Schema'; -our $VERSION = 4; +our $VERSION = 5; __PACKAGE__->load_namespaces; diff --git a/lib/Pear/LocalLoop/Schema/Result/Leaderboard.pm b/lib/Pear/LocalLoop/Schema/Result/Leaderboard.pm index e296d3f..480aaa4 100644 --- a/lib/Pear/LocalLoop/Schema/Result/Leaderboard.pm +++ b/lib/Pear/LocalLoop/Schema/Result/Leaderboard.pm @@ -184,7 +184,7 @@ sub _create_total_all_time { my @leaderboard; while ( my $user_result = $user_rs->next ) { - my $transaction_rs = $user_result->transactions; + my $transaction_rs = $user_result->transactions->search_before( $end ); my $transaction_sum = $transaction_rs->get_column('value')->sum; @@ -215,7 +215,7 @@ sub _create_count_all_time { my @leaderboard; while ( my $user_result = $user_rs->next ) { - my $transaction_rs = $user_result->transactions; + my $transaction_rs = $user_result->transactions->search_before( $end ); my $transaction_count = $transaction_rs->count; diff --git a/lib/Pear/LocalLoop/Schema/Result/Organisation.pm b/lib/Pear/LocalLoop/Schema/Result/Organisation.pm index e5a30a3..5967644 100644 --- a/lib/Pear/LocalLoop/Schema/Result/Organisation.pm +++ b/lib/Pear/LocalLoop/Schema/Result/Organisation.pm @@ -34,6 +34,11 @@ __PACKAGE__->add_columns( size => 16, is_nullable => 1, }, + sector => { + data_type => "varchar", + size => 1, + is_nullable => 1, + }, ); __PACKAGE__->set_primary_key('id'); diff --git a/lib/Pear/LocalLoop/Schema/Result/User.pm b/lib/Pear/LocalLoop/Schema/Result/User.pm index 151075d..75a1dde 100644 --- a/lib/Pear/LocalLoop/Schema/Result/User.pm +++ b/lib/Pear/LocalLoop/Schema/Result/User.pm @@ -149,8 +149,17 @@ sub name { } elsif ( defined $self->organisation_id ) { return $self->organisation->name; } else { - return undef; + return; } } +sub type { + my $self = shift; + + if ( defined $self->customer_id ) { + return "customer"; + } + return "organisation"; +} + 1; diff --git a/lib/Pear/LocalLoop/Schema/ResultSet/Leaderboard.pm b/lib/Pear/LocalLoop/Schema/ResultSet/Leaderboard.pm index d8fd9c6..a5ba33b 100644 --- a/lib/Pear/LocalLoop/Schema/ResultSet/Leaderboard.pm +++ b/lib/Pear/LocalLoop/Schema/ResultSet/Leaderboard.pm @@ -5,6 +5,8 @@ use warnings; use base 'DBIx::Class::ResultSet'; +use DateTime; + sub get_latest { my $self = shift; my $type = shift; @@ -39,4 +41,54 @@ sub find_by_type { return $self->find({ type => $type }); } +sub recalculate_all { + my $self = shift; + + for my $leaderboard_result ( $self->all ) { + my $lb_type = $leaderboard_result->type; + if ( $lb_type =~ /^daily/ ) { + + # Recalculating a daily set. This is calculated from the start of the + # day, so we need yesterdays date: + my $date = DateTime->today->subtract( days => 1 ); + $self->_recalculate_leaderboard( $leaderboard_result, $date, 'days' ); + + } elsif ( $lb_type =~ /^weekly/ ) { + + # Recalculating a weekly set. This is calculated from a Monday, of the + # week before. + my $date = DateTime->today->truncate( to => 'week' )->subtract( weeks => 1 ); + $self->_recalculate_leaderboard( $leaderboard_result, $date, 'weeks' ); + + } elsif ( $lb_type =~ /^monthly/ ) { + + # Recalculate a monthly set. This is calculated from the first of the + # month, for the month before. + my $date = DateTime->today->truncate( to => 'month' )->subtract( months => 1); + $self->_recalculate_leaderboard( $leaderboard_result, $date, 'months' ); + + } elsif ( $lb_type =~ /^all_time/ ) { + + # Recalculate for an all time set. This is calculated similarly to + # daily, but is calculated from an end time. + my $date = DateTime->today; + $self->_recalculate_leaderboard( $leaderboard_result, $date, 'days' ); + + } else { + warn "Unrecognised Set"; + } + } +} + +sub _recalculate_leaderboard { + my ( $self, $lb_result, $date, $diff ) = @_; + + $self->result_source->schema->txn_do( sub { + $lb_result->sets->related_resultset('values')->delete_all; + $lb_result->sets->delete_all; + $lb_result->create_new($date->clone->subtract( $diff => 1 )); + $lb_result->create_new($date); + }); +} + 1; diff --git a/lib/Pear/LocalLoop/Schema/ResultSet/Transaction.pm b/lib/Pear/LocalLoop/Schema/ResultSet/Transaction.pm index 05d264d..822abf2 100644 --- a/lib/Pear/LocalLoop/Schema/ResultSet/Transaction.pm +++ b/lib/Pear/LocalLoop/Schema/ResultSet/Transaction.pm @@ -21,6 +21,15 @@ sub search_between { }); } +sub search_before { + my ( $self, $date ) = @_; + + my $dtf = $self->result_source->schema->storage->datetime_parser; + return $self->search({ + purchase_time => { '<' => $dtf->format_datetime( $date ) }, + }); +} + sub today_rs { my ( $self ) = @_; diff --git a/lib/Test/Pear/LocalLoop.pm b/lib/Test/Pear/LocalLoop.pm index 12758fe..5fb6fae 100644 --- a/lib/Test/Pear/LocalLoop.pm +++ b/lib/Test/Pear/LocalLoop.pm @@ -5,6 +5,7 @@ use Test::More; use File::Temp; use Test::Mojo; use DateTime::Format::Strptime; +use DBIx::Class::Fixtures; has config => sub { my $file = File::Temp->new; @@ -21,30 +22,47 @@ END return $file; }; -has framework => sub { +has mojo => sub { my $self = shift; $ENV{MOJO_CONFIG} = $self->config->filename; my $t = Test::Mojo->new('Pear::LocalLoop'); - my $schema = $t->app->schema; - $schema->deploy; - - $schema->resultset('Leaderboard')->populate([ - [ qw/ name type / ], - [ 'Daily Total', 'daily_total' ], - [ 'Daily Count', 'daily_count' ], - [ 'Weekly Total', 'weekly_total' ], - [ 'Weekly Count', 'weekly_count' ], - [ 'Monthly Total', 'monthly_total' ], - [ 'Monthly Count', 'monthly_count' ], - [ 'All Time Total', 'all_time_total' ], - [ 'All Time Count', 'all_time_count' ], - ]); + $t->app->schema->deploy; return $t; }; +has _deployed => sub { 0 }; + +sub framework { + my $self = shift; + my $no_populate = shift; + + my $t = $self->mojo; + my $schema = $t->app->schema; + + unless ( $no_populate || $self->_deployed ) { + $schema->resultset('Leaderboard')->populate([ + [ qw/ name type / ], + [ 'Daily Total', 'daily_total' ], + [ 'Daily Count', 'daily_count' ], + [ 'Weekly Total', 'weekly_total' ], + [ 'Weekly Count', 'weekly_count' ], + [ 'Monthly Total', 'monthly_total' ], + [ 'Monthly Count', 'monthly_count' ], + [ 'All Time Total', 'all_time_total' ], + [ 'All Time Count', 'all_time_count' ], + ]); + } + + $self->_deployed(1); + + return $t; +}; + +has etc_dir => sub { die "etc dir not set" }; + sub dump_error { return sub { my $self = shift; @@ -91,6 +109,16 @@ sub login { return $self->framework->tx->res->json->{session_key}; } +sub logout { + my $self = shift; + my $session_key = shift; + + $self->framework->post_ok('/api/logout' => json => { session_key => $session_key }) + ->status_is(200) + ->json_is('/success', Mojo::JSON->true) + ->json_like('/message', qr/Logged Out/); +} + sub gen_upload { my ( $self, $args ) = @_; @@ -106,4 +134,19 @@ sub gen_upload { }; } +sub install_fixtures { + my ( $self, $fixture_name ) = @_; + + my $fixtures = DBIx::Class::Fixtures->new({ + config_dir => File::Spec->catdir( $self->etc_dir, 'fixtures', 'config'), + }); + + my $t = $self->framework(1); + $fixtures->populate({ + directory => File::Spec->catdir( $self->etc_dir, 'fixtures', 'data', $fixture_name ), + no_deploy => 1, + schema => $t->app->schema, + }); +} + 1; diff --git a/script/daily_leaderboards b/script/daily_leaderboards deleted file mode 100755 index 7860702..0000000 --- a/script/daily_leaderboards +++ /dev/null @@ -1,11 +0,0 @@ -#! /bin/bash - -eval $(perl -I ~/perl5/lib/perl5/ -Mlocal::lib) - -YESTERDAY=`date -d "yesterday" +%F` - - -MOJO_MODE=production ./script/pear-local_loop leaderboard -t daily_total -d $YESTERDAY -MOJO_MODE=production ./script/pear-local_loop leaderboard -t daily_count -d $YESTERDAY -MOJO_MODE=production ./script/pear-local_loop leaderboard -t all_time_total -d $YESTERDAY -MOJO_MODE=production ./script/pear-local_loop leaderboard -t all_time_count -d $YESTERDAY diff --git a/script/monthly_leaderboards b/script/monthly_leaderboards deleted file mode 100755 index 5aa101c..0000000 --- a/script/monthly_leaderboards +++ /dev/null @@ -1,9 +0,0 @@ -#! /bin/bash - -eval $(perl -I ~/perl5/lib/perl5/ -Mlocal::lib) - -YESTERDAY=`date -d "1 month ago" +%F` - - -MOJO_MODE=production ./script/pear-local_loop leaderboard -t monthly_total -d $YESTERDAY -MOJO_MODE=production ./script/pear-local_loop leaderboard -t monthly_count -d $YESTERDAY diff --git a/script/recalc_leaderboards b/script/recalc_leaderboards new file mode 100644 index 0000000..73ce46c --- /dev/null +++ b/script/recalc_leaderboards @@ -0,0 +1,5 @@ +#! /bin/bash + +eval $(perl -I ~/perl5/lib/perl5/ -Mlocal::lib) + +MOJO_MODE=production ./script/pear-local_loop recalc_leaderboards diff --git a/script/weekly_leaderboards b/script/weekly_leaderboards deleted file mode 100755 index 28da9fc..0000000 --- a/script/weekly_leaderboards +++ /dev/null @@ -1,9 +0,0 @@ -#! /bin/bash - -eval $(perl -I ~/perl5/lib/perl5/ -Mlocal::lib) - -YESTERDAY=`date -d "1 week ago" +%F` - - -MOJO_MODE=production ./script/pear-local_loop leaderboard -t weekly_total -d $YESTERDAY -MOJO_MODE=production ./script/pear-local_loop leaderboard -t weekly_count -d $YESTERDAY diff --git a/share/ddl/PostgreSQL/deploy/5/001-auto-__VERSION.sql b/share/ddl/PostgreSQL/deploy/5/001-auto-__VERSION.sql new file mode 100644 index 0000000..24fbed9 --- /dev/null +++ b/share/ddl/PostgreSQL/deploy/5/001-auto-__VERSION.sql @@ -0,0 +1,18 @@ +-- +-- Created by SQL::Translator::Producer::PostgreSQL +-- Created on Fri Aug 25 15:32:15 2017 +-- +; +-- +-- Table: dbix_class_deploymenthandler_versions +-- +CREATE TABLE "dbix_class_deploymenthandler_versions" ( + "id" serial NOT NULL, + "version" character varying(50) NOT NULL, + "ddl" text, + "upgrade_sql" text, + PRIMARY KEY ("id"), + CONSTRAINT "dbix_class_deploymenthandler_versions_version" UNIQUE ("version") +); + +; diff --git a/share/ddl/PostgreSQL/deploy/5/001-auto.sql b/share/ddl/PostgreSQL/deploy/5/001-auto.sql new file mode 100644 index 0000000..be85282 --- /dev/null +++ b/share/ddl/PostgreSQL/deploy/5/001-auto.sql @@ -0,0 +1,250 @@ +-- +-- Created by SQL::Translator::Producer::PostgreSQL +-- Created on Fri Aug 25 15:32:15 2017 +-- +; +-- +-- Table: account_tokens +-- +CREATE TABLE "account_tokens" ( + "id" serial NOT NULL, + "name" text NOT NULL, + "used" integer DEFAULT 0 NOT NULL, + PRIMARY KEY ("id"), + CONSTRAINT "account_tokens_name" UNIQUE ("name") +); + +; +-- +-- Table: customers +-- +CREATE TABLE "customers" ( + "id" serial NOT NULL, + "display_name" character varying(255) NOT NULL, + "full_name" character varying(255) NOT NULL, + "year_of_birth" integer NOT NULL, + "postcode" character varying(16) NOT NULL, + PRIMARY KEY ("id") +); + +; +-- +-- Table: leaderboards +-- +CREATE TABLE "leaderboards" ( + "id" serial NOT NULL, + "name" character varying(255) NOT NULL, + "type" character varying(255) NOT NULL, + PRIMARY KEY ("id"), + CONSTRAINT "leaderboards_type" UNIQUE ("type") +); + +; +-- +-- Table: organisations +-- +CREATE TABLE "organisations" ( + "id" serial NOT NULL, + "name" character varying(255) NOT NULL, + "street_name" text, + "town" character varying(255) NOT NULL, + "postcode" character varying(16), + "sector" character varying(1), + PRIMARY KEY ("id") +); + +; +-- +-- Table: leaderboard_sets +-- +CREATE TABLE "leaderboard_sets" ( + "id" serial NOT NULL, + "leaderboard_id" integer NOT NULL, + "date" timestamp NOT NULL, + PRIMARY KEY ("id") +); +CREATE INDEX "leaderboard_sets_idx_leaderboard_id" on "leaderboard_sets" ("leaderboard_id"); + +; +-- +-- Table: users +-- +CREATE TABLE "users" ( + "id" serial NOT NULL, + "customer_id" integer, + "organisation_id" integer, + "email" text NOT NULL, + "join_date" timestamp NOT NULL, + "password" character varying(100) NOT NULL, + PRIMARY KEY ("id"), + CONSTRAINT "users_customer_id" UNIQUE ("customer_id"), + CONSTRAINT "users_email" UNIQUE ("email"), + CONSTRAINT "users_organisation_id" UNIQUE ("organisation_id") +); +CREATE INDEX "users_idx_customer_id" on "users" ("customer_id"); +CREATE INDEX "users_idx_organisation_id" on "users" ("organisation_id"); + +; +-- +-- Table: administrators +-- +CREATE TABLE "administrators" ( + "user_id" integer NOT NULL, + PRIMARY KEY ("user_id") +); + +; +-- +-- Table: feedback +-- +CREATE TABLE "feedback" ( + "id" serial NOT NULL, + "user_id" integer NOT NULL, + "submitted_at" timestamp NOT NULL, + "feedbacktext" text NOT NULL, + "app_name" character varying(255) NOT NULL, + "package_name" character varying(255) NOT NULL, + "version_code" character varying(255) NOT NULL, + "version_number" character varying(255) NOT NULL, + PRIMARY KEY ("id") +); +CREATE INDEX "feedback_idx_user_id" on "feedback" ("user_id"); + +; +-- +-- Table: pending_organisations +-- +CREATE TABLE "pending_organisations" ( + "id" serial NOT NULL, + "name" character varying(255) NOT NULL, + "street_name" text, + "town" character varying(255) NOT NULL, + "postcode" character varying(16), + "submitted_by_id" integer NOT NULL, + "submitted_at" timestamp NOT NULL, + PRIMARY KEY ("id") +); +CREATE INDEX "pending_organisations_idx_submitted_by_id" on "pending_organisations" ("submitted_by_id"); + +; +-- +-- Table: session_tokens +-- +CREATE TABLE "session_tokens" ( + "id" serial NOT NULL, + "token" character varying(255) NOT NULL, + "user_id" integer NOT NULL, + PRIMARY KEY ("id"), + CONSTRAINT "session_tokens_token" UNIQUE ("token") +); +CREATE INDEX "session_tokens_idx_user_id" on "session_tokens" ("user_id"); + +; +-- +-- Table: transactions +-- +CREATE TABLE "transactions" ( + "id" serial NOT NULL, + "buyer_id" integer NOT NULL, + "seller_id" integer NOT NULL, + "value" numeric(16,2) NOT NULL, + "proof_image" text NOT NULL, + "submitted_at" timestamp NOT NULL, + "purchase_time" timestamp NOT NULL, + PRIMARY KEY ("id") +); +CREATE INDEX "transactions_idx_buyer_id" on "transactions" ("buyer_id"); +CREATE INDEX "transactions_idx_seller_id" on "transactions" ("seller_id"); + +; +-- +-- Table: pending_transactions +-- +CREATE TABLE "pending_transactions" ( + "id" serial NOT NULL, + "buyer_id" integer NOT NULL, + "seller_id" integer NOT NULL, + "value" numeric(16,2) NOT NULL, + "proof_image" text NOT NULL, + "submitted_at" timestamp NOT NULL, + "purchase_time" timestamp NOT NULL, + PRIMARY KEY ("id") +); +CREATE INDEX "pending_transactions_idx_buyer_id" on "pending_transactions" ("buyer_id"); +CREATE INDEX "pending_transactions_idx_seller_id" on "pending_transactions" ("seller_id"); + +; +-- +-- Table: leaderboard_values +-- +CREATE TABLE "leaderboard_values" ( + "id" serial NOT NULL, + "user_id" integer NOT NULL, + "set_id" integer NOT NULL, + "position" integer NOT NULL, + "value" numeric(16,2) NOT NULL, + "trend" integer DEFAULT 0 NOT NULL, + PRIMARY KEY ("id"), + CONSTRAINT "leaderboard_values_user_id_set_id" UNIQUE ("user_id", "set_id") +); +CREATE INDEX "leaderboard_values_idx_set_id" on "leaderboard_values" ("set_id"); +CREATE INDEX "leaderboard_values_idx_user_id" on "leaderboard_values" ("user_id"); + +; +-- +-- Foreign Key Definitions +-- + +; +ALTER TABLE "leaderboard_sets" ADD CONSTRAINT "leaderboard_sets_fk_leaderboard_id" FOREIGN KEY ("leaderboard_id") + REFERENCES "leaderboards" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION; + +; +ALTER TABLE "users" ADD CONSTRAINT "users_fk_customer_id" FOREIGN KEY ("customer_id") + REFERENCES "customers" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION; + +; +ALTER TABLE "users" ADD CONSTRAINT "users_fk_organisation_id" FOREIGN KEY ("organisation_id") + REFERENCES "organisations" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION; + +; +ALTER TABLE "administrators" ADD CONSTRAINT "administrators_fk_user_id" FOREIGN KEY ("user_id") + REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION; + +; +ALTER TABLE "feedback" ADD CONSTRAINT "feedback_fk_user_id" FOREIGN KEY ("user_id") + REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION; + +; +ALTER TABLE "pending_organisations" ADD CONSTRAINT "pending_organisations_fk_submitted_by_id" FOREIGN KEY ("submitted_by_id") + REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION; + +; +ALTER TABLE "session_tokens" ADD CONSTRAINT "session_tokens_fk_user_id" FOREIGN KEY ("user_id") + REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION; + +; +ALTER TABLE "transactions" ADD CONSTRAINT "transactions_fk_buyer_id" FOREIGN KEY ("buyer_id") + REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION; + +; +ALTER TABLE "transactions" ADD CONSTRAINT "transactions_fk_seller_id" FOREIGN KEY ("seller_id") + REFERENCES "organisations" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION; + +; +ALTER TABLE "pending_transactions" ADD CONSTRAINT "pending_transactions_fk_buyer_id" FOREIGN KEY ("buyer_id") + REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION; + +; +ALTER TABLE "pending_transactions" ADD CONSTRAINT "pending_transactions_fk_seller_id" FOREIGN KEY ("seller_id") + REFERENCES "pending_organisations" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION; + +; +ALTER TABLE "leaderboard_values" ADD CONSTRAINT "leaderboard_values_fk_set_id" FOREIGN KEY ("set_id") + REFERENCES "leaderboard_sets" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION; + +; +ALTER TABLE "leaderboard_values" ADD CONSTRAINT "leaderboard_values_fk_user_id" FOREIGN KEY ("user_id") + REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION; + +; diff --git a/share/ddl/PostgreSQL/upgrade/4-5/001-auto.sql b/share/ddl/PostgreSQL/upgrade/4-5/001-auto.sql new file mode 100644 index 0000000..2d09d8a --- /dev/null +++ b/share/ddl/PostgreSQL/upgrade/4-5/001-auto.sql @@ -0,0 +1,12 @@ +-- Convert schema 'share/ddl/_source/deploy/4/001-auto.yml' to 'share/ddl/_source/deploy/5/001-auto.yml':; + +; +BEGIN; + +; +ALTER TABLE organisations ADD COLUMN sector character varying(1); + +; + +COMMIT; + diff --git a/share/ddl/SQLite/deploy/5/001-auto-__VERSION.sql b/share/ddl/SQLite/deploy/5/001-auto-__VERSION.sql new file mode 100644 index 0000000..959fcd8 --- /dev/null +++ b/share/ddl/SQLite/deploy/5/001-auto-__VERSION.sql @@ -0,0 +1,18 @@ +-- +-- Created by SQL::Translator::Producer::SQLite +-- Created on Fri Aug 25 15:32:15 2017 +-- + +; +BEGIN TRANSACTION; +-- +-- Table: dbix_class_deploymenthandler_versions +-- +CREATE TABLE dbix_class_deploymenthandler_versions ( + id INTEGER PRIMARY KEY NOT NULL, + version varchar(50) NOT NULL, + ddl text, + upgrade_sql text +); +CREATE UNIQUE INDEX dbix_class_deploymenthandler_versions_version ON dbix_class_deploymenthandler_versions (version); +COMMIT; diff --git a/share/ddl/SQLite/deploy/5/001-auto.sql b/share/ddl/SQLite/deploy/5/001-auto.sql new file mode 100644 index 0000000..2d7798b --- /dev/null +++ b/share/ddl/SQLite/deploy/5/001-auto.sql @@ -0,0 +1,170 @@ +-- +-- Created by SQL::Translator::Producer::SQLite +-- Created on Fri Aug 25 15:32:15 2017 +-- + +; +BEGIN TRANSACTION; +-- +-- Table: account_tokens +-- +CREATE TABLE account_tokens ( + id INTEGER PRIMARY KEY NOT NULL, + name text NOT NULL, + used integer NOT NULL DEFAULT 0 +); +CREATE UNIQUE INDEX account_tokens_name ON account_tokens (name); +-- +-- Table: customers +-- +CREATE TABLE customers ( + id INTEGER PRIMARY KEY NOT NULL, + display_name varchar(255) NOT NULL, + full_name varchar(255) NOT NULL, + year_of_birth integer NOT NULL, + postcode varchar(16) NOT NULL +); +-- +-- Table: leaderboards +-- +CREATE TABLE leaderboards ( + id INTEGER PRIMARY KEY NOT NULL, + name varchar(255) NOT NULL, + type varchar(255) NOT NULL +); +CREATE UNIQUE INDEX leaderboards_type ON leaderboards (type); +-- +-- Table: organisations +-- +CREATE TABLE organisations ( + id INTEGER PRIMARY KEY NOT NULL, + name varchar(255) NOT NULL, + street_name text, + town varchar(255) NOT NULL, + postcode varchar(16), + sector varchar(1) +); +-- +-- Table: leaderboard_sets +-- +CREATE TABLE leaderboard_sets ( + id INTEGER PRIMARY KEY NOT NULL, + leaderboard_id integer NOT NULL, + date datetime NOT NULL, + FOREIGN KEY (leaderboard_id) REFERENCES leaderboards(id) ON DELETE NO ACTION ON UPDATE NO ACTION +); +CREATE INDEX leaderboard_sets_idx_leaderboard_id ON leaderboard_sets (leaderboard_id); +-- +-- Table: users +-- +CREATE TABLE users ( + id INTEGER PRIMARY KEY NOT NULL, + customer_id integer, + organisation_id integer, + email text NOT NULL, + join_date datetime NOT NULL, + password varchar(100) NOT NULL, + FOREIGN KEY (customer_id) REFERENCES customers(id) ON DELETE NO ACTION ON UPDATE NO ACTION, + FOREIGN KEY (organisation_id) REFERENCES organisations(id) ON DELETE NO ACTION ON UPDATE NO ACTION +); +CREATE INDEX users_idx_customer_id ON users (customer_id); +CREATE INDEX users_idx_organisation_id ON users (organisation_id); +CREATE UNIQUE INDEX users_customer_id ON users (customer_id); +CREATE UNIQUE INDEX users_email ON users (email); +CREATE UNIQUE INDEX users_organisation_id ON users (organisation_id); +-- +-- Table: administrators +-- +CREATE TABLE administrators ( + user_id INTEGER PRIMARY KEY NOT NULL, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE NO ACTION ON UPDATE NO ACTION +); +-- +-- Table: feedback +-- +CREATE TABLE feedback ( + id INTEGER PRIMARY KEY NOT NULL, + user_id integer NOT NULL, + submitted_at datetime NOT NULL, + feedbacktext text NOT NULL, + app_name varchar(255) NOT NULL, + package_name varchar(255) NOT NULL, + version_code varchar(255) NOT NULL, + version_number varchar(255) NOT NULL, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE NO ACTION ON UPDATE NO ACTION +); +CREATE INDEX feedback_idx_user_id ON feedback (user_id); +-- +-- Table: pending_organisations +-- +CREATE TABLE pending_organisations ( + id INTEGER PRIMARY KEY NOT NULL, + name varchar(255) NOT NULL, + street_name text, + town varchar(255) NOT NULL, + postcode varchar(16), + submitted_by_id integer NOT NULL, + submitted_at datetime NOT NULL, + FOREIGN KEY (submitted_by_id) REFERENCES users(id) ON DELETE NO ACTION ON UPDATE NO ACTION +); +CREATE INDEX pending_organisations_idx_submitted_by_id ON pending_organisations (submitted_by_id); +-- +-- Table: session_tokens +-- +CREATE TABLE session_tokens ( + id INTEGER PRIMARY KEY NOT NULL, + token varchar(255) NOT NULL, + user_id integer NOT NULL, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE NO ACTION ON UPDATE NO ACTION +); +CREATE INDEX session_tokens_idx_user_id ON session_tokens (user_id); +CREATE UNIQUE INDEX session_tokens_token ON session_tokens (token); +-- +-- Table: transactions +-- +CREATE TABLE transactions ( + id INTEGER PRIMARY KEY NOT NULL, + buyer_id integer NOT NULL, + seller_id integer NOT NULL, + value decimal(16,2) NOT NULL, + proof_image text NOT NULL, + submitted_at datetime NOT NULL, + purchase_time datetime NOT NULL, + FOREIGN KEY (buyer_id) REFERENCES users(id) ON DELETE NO ACTION ON UPDATE NO ACTION, + FOREIGN KEY (seller_id) REFERENCES organisations(id) ON DELETE NO ACTION ON UPDATE NO ACTION +); +CREATE INDEX transactions_idx_buyer_id ON transactions (buyer_id); +CREATE INDEX transactions_idx_seller_id ON transactions (seller_id); +-- +-- Table: pending_transactions +-- +CREATE TABLE pending_transactions ( + id INTEGER PRIMARY KEY NOT NULL, + buyer_id integer NOT NULL, + seller_id integer NOT NULL, + value decimal(16,2) NOT NULL, + proof_image text NOT NULL, + submitted_at datetime NOT NULL, + purchase_time datetime NOT NULL, + FOREIGN KEY (buyer_id) REFERENCES users(id) ON DELETE NO ACTION ON UPDATE NO ACTION, + FOREIGN KEY (seller_id) REFERENCES pending_organisations(id) ON DELETE NO ACTION ON UPDATE NO ACTION +); +CREATE INDEX pending_transactions_idx_buyer_id ON pending_transactions (buyer_id); +CREATE INDEX pending_transactions_idx_seller_id ON pending_transactions (seller_id); +-- +-- Table: leaderboard_values +-- +CREATE TABLE leaderboard_values ( + id INTEGER PRIMARY KEY NOT NULL, + user_id integer NOT NULL, + set_id integer NOT NULL, + position integer NOT NULL, + value decimal(16,2) NOT NULL, + trend integer NOT NULL DEFAULT 0, + FOREIGN KEY (set_id) REFERENCES leaderboard_sets(id) ON DELETE NO ACTION ON UPDATE NO ACTION, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE NO ACTION ON UPDATE NO ACTION +); +CREATE INDEX leaderboard_values_idx_set_id ON leaderboard_values (set_id); +CREATE INDEX leaderboard_values_idx_user_id ON leaderboard_values (user_id); +CREATE UNIQUE INDEX leaderboard_values_user_id_set_id ON leaderboard_values (user_id, set_id); +COMMIT; diff --git a/share/ddl/SQLite/upgrade/4-5/001-auto.sql b/share/ddl/SQLite/upgrade/4-5/001-auto.sql new file mode 100644 index 0000000..915202a --- /dev/null +++ b/share/ddl/SQLite/upgrade/4-5/001-auto.sql @@ -0,0 +1,12 @@ +-- Convert schema 'share/ddl/_source/deploy/4/001-auto.yml' to 'share/ddl/_source/deploy/5/001-auto.yml':; + +; +BEGIN; + +; +ALTER TABLE organisations ADD COLUMN sector varchar(1); + +; + +COMMIT; + diff --git a/share/ddl/_source/deploy/5/001-auto-__VERSION.yml b/share/ddl/_source/deploy/5/001-auto-__VERSION.yml new file mode 100644 index 0000000..907f443 --- /dev/null +++ b/share/ddl/_source/deploy/5/001-auto-__VERSION.yml @@ -0,0 +1,91 @@ +--- +schema: + procedures: {} + tables: + dbix_class_deploymenthandler_versions: + constraints: + - deferrable: 1 + expression: '' + fields: + - id + match_type: '' + name: '' + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: PRIMARY KEY + - deferrable: 1 + expression: '' + fields: + - version + match_type: '' + name: dbix_class_deploymenthandler_versions_version + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: UNIQUE + fields: + ddl: + data_type: text + default_value: ~ + is_nullable: 1 + is_primary_key: 0 + is_unique: 0 + name: ddl + order: 3 + size: + - 0 + id: + data_type: int + default_value: ~ + is_auto_increment: 1 + is_nullable: 0 + is_primary_key: 1 + is_unique: 0 + name: id + order: 1 + size: + - 0 + upgrade_sql: + data_type: text + default_value: ~ + is_nullable: 1 + is_primary_key: 0 + is_unique: 0 + name: upgrade_sql + order: 4 + size: + - 0 + version: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 1 + name: version + order: 2 + size: + - 50 + indices: [] + name: dbix_class_deploymenthandler_versions + options: [] + order: 1 + triggers: {} + views: {} +translator: + add_drop_table: 0 + filename: ~ + no_comments: 0 + parser_args: + sources: + - __VERSION + parser_type: SQL::Translator::Parser::DBIx::Class + producer_args: {} + producer_type: SQL::Translator::Producer::YAML + show_warnings: 0 + trace: 0 + version: 0.11021 diff --git a/share/ddl/_source/deploy/5/001-auto.yml b/share/ddl/_source/deploy/5/001-auto.yml new file mode 100644 index 0000000..0e54aa4 --- /dev/null +++ b/share/ddl/_source/deploy/5/001-auto.yml @@ -0,0 +1,1257 @@ +--- +schema: + procedures: {} + tables: + account_tokens: + constraints: + - deferrable: 1 + expression: '' + fields: + - id + match_type: '' + name: '' + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: PRIMARY KEY + - deferrable: 1 + expression: '' + fields: + - name + match_type: '' + name: account_tokens_name + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: UNIQUE + fields: + id: + data_type: integer + default_value: ~ + is_auto_increment: 1 + is_nullable: 0 + is_primary_key: 1 + is_unique: 0 + name: id + order: 1 + size: + - 0 + name: + data_type: text + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 1 + name: name + order: 2 + size: + - 0 + used: + data_type: integer + default_value: 0 + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: used + order: 3 + size: + - 0 + indices: [] + name: account_tokens + options: [] + order: 1 + administrators: + constraints: + - deferrable: 1 + expression: '' + fields: + - user_id + match_type: '' + name: '' + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: PRIMARY KEY + - deferrable: 0 + expression: '' + fields: + - user_id + match_type: '' + name: administrators_fk_user_id + on_delete: NO ACTION + on_update: NO ACTION + options: [] + reference_fields: + - id + reference_table: users + type: FOREIGN KEY + fields: + user_id: + data_type: integer + default_value: ~ + is_nullable: 0 + is_primary_key: 1 + is_unique: 0 + name: user_id + order: 1 + size: + - 0 + indices: [] + name: administrators + options: [] + order: 7 + customers: + constraints: + - deferrable: 1 + expression: '' + fields: + - id + match_type: '' + name: '' + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: PRIMARY KEY + fields: + display_name: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: display_name + order: 2 + size: + - 255 + full_name: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: full_name + order: 3 + size: + - 255 + id: + data_type: integer + default_value: ~ + is_auto_increment: 1 + is_nullable: 0 + is_primary_key: 1 + is_unique: 0 + name: id + order: 1 + size: + - 0 + postcode: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: postcode + order: 5 + size: + - 16 + year_of_birth: + data_type: integer + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: year_of_birth + order: 4 + size: + - 0 + indices: [] + name: customers + options: [] + order: 2 + feedback: + constraints: + - deferrable: 1 + expression: '' + fields: + - id + match_type: '' + name: '' + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: PRIMARY KEY + - deferrable: 0 + expression: '' + fields: + - user_id + match_type: '' + name: feedback_fk_user_id + on_delete: NO ACTION + on_update: NO ACTION + options: [] + reference_fields: + - id + reference_table: users + type: FOREIGN KEY + fields: + app_name: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: app_name + order: 5 + size: + - 255 + feedbacktext: + data_type: text + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: feedbacktext + order: 4 + size: + - 0 + id: + data_type: integer + default_value: ~ + is_auto_increment: 1 + is_nullable: 0 + is_primary_key: 1 + is_unique: 0 + name: id + order: 1 + size: + - 0 + package_name: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: package_name + order: 6 + size: + - 255 + submitted_at: + data_type: datetime + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: submitted_at + order: 3 + size: + - 0 + user_id: + data_type: integer + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: user_id + order: 2 + size: + - 0 + version_code: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: version_code + order: 7 + size: + - 255 + version_number: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: version_number + order: 8 + size: + - 255 + indices: + - fields: + - user_id + name: feedback_idx_user_id + options: [] + type: NORMAL + name: feedback + options: [] + order: 8 + leaderboard_sets: + constraints: + - deferrable: 1 + expression: '' + fields: + - id + match_type: '' + name: '' + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: PRIMARY KEY + - deferrable: 0 + expression: '' + fields: + - leaderboard_id + match_type: '' + name: leaderboard_sets_fk_leaderboard_id + on_delete: NO ACTION + on_update: NO ACTION + options: [] + reference_fields: + - id + reference_table: leaderboards + type: FOREIGN KEY + fields: + date: + data_type: datetime + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: date + order: 3 + size: + - 0 + id: + data_type: integer + default_value: ~ + is_auto_increment: 1 + is_nullable: 0 + is_primary_key: 1 + is_unique: 0 + name: id + order: 1 + size: + - 0 + leaderboard_id: + data_type: integer + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: leaderboard_id + order: 2 + size: + - 0 + indices: + - fields: + - leaderboard_id + name: leaderboard_sets_idx_leaderboard_id + options: [] + type: NORMAL + name: leaderboard_sets + options: [] + order: 5 + leaderboard_values: + constraints: + - deferrable: 1 + expression: '' + fields: + - id + match_type: '' + name: '' + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: PRIMARY KEY + - deferrable: 1 + expression: '' + fields: + - user_id + - set_id + match_type: '' + name: leaderboard_values_user_id_set_id + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: UNIQUE + - deferrable: 0 + expression: '' + fields: + - set_id + match_type: '' + name: leaderboard_values_fk_set_id + on_delete: NO ACTION + on_update: NO ACTION + options: [] + reference_fields: + - id + reference_table: leaderboard_sets + type: FOREIGN KEY + - deferrable: 0 + expression: '' + fields: + - user_id + match_type: '' + name: leaderboard_values_fk_user_id + on_delete: NO ACTION + on_update: NO ACTION + options: [] + reference_fields: + - id + reference_table: users + type: FOREIGN KEY + fields: + id: + data_type: integer + default_value: ~ + is_auto_increment: 1 + is_nullable: 0 + is_primary_key: 1 + is_unique: 0 + name: id + order: 1 + size: + - 0 + position: + data_type: integer + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: position + order: 4 + size: + - 0 + set_id: + data_type: integer + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 1 + name: set_id + order: 3 + size: + - 0 + trend: + data_type: integer + default_value: 0 + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: trend + order: 6 + size: + - 0 + user_id: + data_type: integer + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 1 + name: user_id + order: 2 + size: + - 0 + value: + data_type: decimal + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: value + order: 5 + size: + - 16 + - 2 + indices: + - fields: + - set_id + name: leaderboard_values_idx_set_id + options: [] + type: NORMAL + - fields: + - user_id + name: leaderboard_values_idx_user_id + options: [] + type: NORMAL + name: leaderboard_values + options: [] + order: 13 + leaderboards: + constraints: + - deferrable: 1 + expression: '' + fields: + - id + match_type: '' + name: '' + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: PRIMARY KEY + - deferrable: 1 + expression: '' + fields: + - type + match_type: '' + name: leaderboards_type + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: UNIQUE + fields: + id: + data_type: integer + default_value: ~ + is_auto_increment: 1 + is_nullable: 0 + is_primary_key: 1 + is_unique: 0 + name: id + order: 1 + size: + - 0 + name: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: name + order: 2 + size: + - 255 + type: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 1 + name: type + order: 3 + size: + - 255 + indices: [] + name: leaderboards + options: [] + order: 3 + organisations: + constraints: + - deferrable: 1 + expression: '' + fields: + - id + match_type: '' + name: '' + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: PRIMARY KEY + fields: + id: + data_type: integer + default_value: ~ + is_auto_increment: 1 + is_nullable: 0 + is_primary_key: 1 + is_unique: 0 + name: id + order: 1 + size: + - 0 + name: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: name + order: 2 + size: + - 255 + postcode: + data_type: varchar + default_value: ~ + is_nullable: 1 + is_primary_key: 0 + is_unique: 0 + name: postcode + order: 5 + size: + - 16 + sector: + data_type: varchar + default_value: ~ + is_nullable: 1 + is_primary_key: 0 + is_unique: 0 + name: sector + order: 6 + size: + - 1 + street_name: + data_type: text + default_value: ~ + is_nullable: 1 + is_primary_key: 0 + is_unique: 0 + name: street_name + order: 3 + size: + - 0 + town: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: town + order: 4 + size: + - 255 + indices: [] + name: organisations + options: [] + order: 4 + pending_organisations: + constraints: + - deferrable: 1 + expression: '' + fields: + - id + match_type: '' + name: '' + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: PRIMARY KEY + - deferrable: 0 + expression: '' + fields: + - submitted_by_id + match_type: '' + name: pending_organisations_fk_submitted_by_id + on_delete: NO ACTION + on_update: NO ACTION + options: [] + reference_fields: + - id + reference_table: users + type: FOREIGN KEY + fields: + id: + data_type: integer + default_value: ~ + is_auto_increment: 1 + is_nullable: 0 + is_primary_key: 1 + is_unique: 0 + name: id + order: 1 + size: + - 0 + name: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: name + order: 2 + size: + - 255 + postcode: + data_type: varchar + default_value: ~ + is_nullable: 1 + is_primary_key: 0 + is_unique: 0 + name: postcode + order: 5 + size: + - 16 + street_name: + data_type: text + default_value: ~ + is_nullable: 1 + is_primary_key: 0 + is_unique: 0 + name: street_name + order: 3 + size: + - 0 + submitted_at: + data_type: datetime + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: submitted_at + order: 7 + size: + - 0 + submitted_by_id: + data_type: integer + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: submitted_by_id + order: 6 + size: + - 0 + town: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: town + order: 4 + size: + - 255 + indices: + - fields: + - submitted_by_id + name: pending_organisations_idx_submitted_by_id + options: [] + type: NORMAL + name: pending_organisations + options: [] + order: 9 + pending_transactions: + constraints: + - deferrable: 1 + expression: '' + fields: + - id + match_type: '' + name: '' + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: PRIMARY KEY + - deferrable: 0 + expression: '' + fields: + - buyer_id + match_type: '' + name: pending_transactions_fk_buyer_id + on_delete: NO ACTION + on_update: NO ACTION + options: [] + reference_fields: + - id + reference_table: users + type: FOREIGN KEY + - deferrable: 0 + expression: '' + fields: + - seller_id + match_type: '' + name: pending_transactions_fk_seller_id + on_delete: NO ACTION + on_update: NO ACTION + options: [] + reference_fields: + - id + reference_table: pending_organisations + type: FOREIGN KEY + fields: + buyer_id: + data_type: integer + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: buyer_id + order: 2 + size: + - 0 + id: + data_type: integer + default_value: ~ + is_auto_increment: 1 + is_nullable: 0 + is_primary_key: 1 + is_unique: 0 + name: id + order: 1 + size: + - 0 + proof_image: + data_type: text + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: proof_image + order: 5 + size: + - 0 + purchase_time: + data_type: datetime + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: purchase_time + order: 7 + size: + - 0 + seller_id: + data_type: integer + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: seller_id + order: 3 + size: + - 0 + submitted_at: + data_type: datetime + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: submitted_at + order: 6 + size: + - 0 + value: + data_type: decimal + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: value + order: 4 + size: + - 16 + - 2 + indices: + - fields: + - buyer_id + name: pending_transactions_idx_buyer_id + options: [] + type: NORMAL + - fields: + - seller_id + name: pending_transactions_idx_seller_id + options: [] + type: NORMAL + name: pending_transactions + options: [] + order: 12 + session_tokens: + constraints: + - deferrable: 1 + expression: '' + fields: + - id + match_type: '' + name: '' + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: PRIMARY KEY + - deferrable: 1 + expression: '' + fields: + - token + match_type: '' + name: session_tokens_token + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: UNIQUE + - deferrable: 0 + expression: '' + fields: + - user_id + match_type: '' + name: session_tokens_fk_user_id + on_delete: NO ACTION + on_update: NO ACTION + options: [] + reference_fields: + - id + reference_table: users + type: FOREIGN KEY + fields: + id: + data_type: integer + default_value: ~ + is_auto_increment: 1 + is_nullable: 0 + is_primary_key: 1 + is_unique: 0 + name: id + order: 1 + size: + - 0 + token: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 1 + name: token + order: 2 + size: + - 255 + user_id: + data_type: integer + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: user_id + order: 3 + size: + - 0 + indices: + - fields: + - user_id + name: session_tokens_idx_user_id + options: [] + type: NORMAL + name: session_tokens + options: [] + order: 10 + transactions: + constraints: + - deferrable: 1 + expression: '' + fields: + - id + match_type: '' + name: '' + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: PRIMARY KEY + - deferrable: 0 + expression: '' + fields: + - buyer_id + match_type: '' + name: transactions_fk_buyer_id + on_delete: NO ACTION + on_update: NO ACTION + options: [] + reference_fields: + - id + reference_table: users + type: FOREIGN KEY + - deferrable: 0 + expression: '' + fields: + - seller_id + match_type: '' + name: transactions_fk_seller_id + on_delete: NO ACTION + on_update: NO ACTION + options: [] + reference_fields: + - id + reference_table: organisations + type: FOREIGN KEY + fields: + buyer_id: + data_type: integer + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: buyer_id + order: 2 + size: + - 0 + id: + data_type: integer + default_value: ~ + is_auto_increment: 1 + is_nullable: 0 + is_primary_key: 1 + is_unique: 0 + name: id + order: 1 + size: + - 0 + proof_image: + data_type: text + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: proof_image + order: 5 + size: + - 0 + purchase_time: + data_type: datetime + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: purchase_time + order: 7 + size: + - 0 + seller_id: + data_type: integer + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: seller_id + order: 3 + size: + - 0 + submitted_at: + data_type: datetime + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: submitted_at + order: 6 + size: + - 0 + value: + data_type: decimal + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: value + order: 4 + size: + - 16 + - 2 + indices: + - fields: + - buyer_id + name: transactions_idx_buyer_id + options: [] + type: NORMAL + - fields: + - seller_id + name: transactions_idx_seller_id + options: [] + type: NORMAL + name: transactions + options: [] + order: 11 + users: + constraints: + - deferrable: 1 + expression: '' + fields: + - id + match_type: '' + name: '' + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: PRIMARY KEY + - deferrable: 1 + expression: '' + fields: + - customer_id + match_type: '' + name: users_customer_id + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: UNIQUE + - deferrable: 1 + expression: '' + fields: + - email + match_type: '' + name: users_email + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: UNIQUE + - deferrable: 1 + expression: '' + fields: + - organisation_id + match_type: '' + name: users_organisation_id + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: UNIQUE + - deferrable: 0 + expression: '' + fields: + - customer_id + match_type: '' + name: users_fk_customer_id + on_delete: NO ACTION + on_update: NO ACTION + options: [] + reference_fields: + - id + reference_table: customers + type: FOREIGN KEY + - deferrable: 0 + expression: '' + fields: + - organisation_id + match_type: '' + name: users_fk_organisation_id + on_delete: NO ACTION + on_update: NO ACTION + options: [] + reference_fields: + - id + reference_table: organisations + type: FOREIGN KEY + fields: + customer_id: + data_type: integer + default_value: ~ + is_nullable: 1 + is_primary_key: 0 + is_unique: 1 + name: customer_id + order: 2 + size: + - 0 + email: + data_type: text + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 1 + name: email + order: 4 + size: + - 0 + id: + data_type: integer + default_value: ~ + is_auto_increment: 1 + is_nullable: 0 + is_primary_key: 1 + is_unique: 0 + name: id + order: 1 + size: + - 0 + join_date: + data_type: datetime + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: join_date + order: 5 + size: + - 0 + organisation_id: + data_type: integer + default_value: ~ + is_nullable: 1 + is_primary_key: 0 + is_unique: 1 + name: organisation_id + order: 3 + size: + - 0 + password: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: password + order: 6 + size: + - 100 + indices: + - fields: + - customer_id + name: users_idx_customer_id + options: [] + type: NORMAL + - fields: + - organisation_id + name: users_idx_organisation_id + options: [] + type: NORMAL + name: users + options: [] + order: 6 + triggers: {} + views: {} +translator: + add_drop_table: 0 + filename: ~ + no_comments: 0 + parser_args: + sources: + - AccountToken + - Administrator + - Customer + - Feedback + - Leaderboard + - LeaderboardSet + - LeaderboardValue + - Organisation + - PendingOrganisation + - PendingTransaction + - SessionToken + - Transaction + - User + parser_type: SQL::Translator::Parser::DBIx::Class + producer_args: {} + producer_type: SQL::Translator::Producer::YAML + show_warnings: 0 + trace: 0 + version: 0.11021 diff --git a/t/admin/organisation.t b/t/admin/organisation.t new file mode 100644 index 0000000..8ac7af6 --- /dev/null +++ b/t/admin/organisation.t @@ -0,0 +1,100 @@ +use Mojo::Base -strict; + +use Test::More; +use Mojo::JSON; +use Test::Pear::LocalLoop; + +my $framework = Test::Pear::LocalLoop->new; +my $t = $framework->framework; +my $schema = $t->app->schema; + +my $user = $schema->resultset('User')->create({ + email => 'admin@example.com', + password => 'abc123', + administrator => {}, +}); + +is $schema->resultset('Administrator')->count, 1, 'Admin Created'; + +$schema->resultset('Organisation')->create({ + id => 1, + name => 'Shinra Electric Power Company', + street_name => 'Sector 0, Midgar, Eastern Continent', + town => 'Gaia', + sector => 'A', + postcode => 'WC1E 6AD', +}); + +$schema->resultset('PendingOrganisation')->create({ + id => 2, + name => '7th Heaven', + street_name => 'Slums, Sector 7', + town => 'Midgar', + postcode => 'WC1E 6AD', + submitted_by_id => $user->id, +}); + +#login to admin +$t->ua->max_redirects(10); +$t->post_ok('/admin', form => { + email => 'admin@example.com', + password => 'abc123', +})->status_is(200); + +#Read approved organisation +$t->get_ok('/admin/organisations/valid/1/') + ->status_is(200); + +#Read pending organisation +$t->get_ok('/admin/organisations/pending/2/') + ->status_is(200); + +#Valid approved organisation update +$t->post_ok('/admin/organisations/valid/1/edit', form => { + name => 'Shinra Electric Power Company', + street_name => 'Sector 0, Midgar, Eastern Continent', + town => 'Gaia', + sector => 'A', + postcode => 'WC1E 6AD', +})->status_is(200)->content_like(qr/Updated Organisation/); + +#Failed validation on approved organisation +$t->post_ok('/admin/organisations/valid/1/edit', form => { + name => 'Shinra Electric Power Company', + street_name => 'Sector 0, Midgar, Eastern Continent', + sector => 'A', + postcode => 'WC1E 6AD', +})->content_like(qr/The validation has failed/); + +#Valid pending organisation update +$t->post_ok('/admin/organisations/pending/2/edit', form => { + name => '7th Heaven', + street_name => 'Slums, Sector 7', + town => 'Midgar', + postcode => 'WC1E 6AD', +})->status_is(200)->content_like(qr/Updated Organisation/); + +#Failed validation on pending organisation +$t->post_ok('/admin/organisations/pending/2/edit', form => { + name => '7th Heaven', + street_name => 'Slums, Sector 7', + postcode => 'WC1E 6AD', +})->content_like(qr/The validation has failed/); + +#Valid adding organisation +$t->post_ok('/admin/organisations/add/submit', form => { + name => 'Wall Market', + street_name => 'Slums, Sector 6', + town => 'Midgar', + sector => 'A', + postcode => 'TN35 5AQ', +})->status_is(200)->content_like(qr/Added Organisation/); + +#Failed validation on adding organisation +$t->post_ok('/admin/organisations/add/submit', form => { + name => 'Wall Market', + street_name => 'Slums, Sector 6', + postcode => 'TN35 5AQ', +})->content_like(qr/The validation has failed/); + +done_testing(); diff --git a/t/admin/user.t b/t/admin/user.t new file mode 100644 index 0000000..129dce6 --- /dev/null +++ b/t/admin/user.t @@ -0,0 +1,124 @@ +use Mojo::Base -strict; + +use Test::More; +use Mojo::JSON; +use Test::Pear::LocalLoop; + +my $framework = Test::Pear::LocalLoop->new; +my $t = $framework->framework; +my $schema = $t->app->schema; + +my $user = $schema->resultset('User')->create({ + email => 'admin@example.com', + password => 'abc123', + administrator => {}, +}); + +is $schema->resultset('Administrator')->count, 1, 'Admin Created'; + +my $user1 = { + token => 'a', + full_name => 'Test User1', + display_name => 'Test User1', + email => 'test1@example.com', + postcode => 'LA1 1AA', + password => 'abc123', + year_of_birth => 2006, +}; + +my $org = { + token => 'e', + email => 'test50@example.com', + name => '7th Heaven', + street_name => 'Slums, Sector 7', + town => 'Midgar', + sector => 'A', + postcode => 'WC1E 6AD', + password => 'abc123', +}; + +$schema->resultset('AccountToken')->create({ name => $_->{token} }) + for ( $user1, $org ); + +$framework->register_customer($user1); + +$framework->register_organisation($org); + +#login to admin +$t->ua->max_redirects(10); +$t->post_ok('/admin', form => { + email => 'admin@example.com', + password => 'abc123', +})->status_is(200); + +#Read customer user +$t->get_ok('/admin/users/2/') + ->status_is(200); + +#Read organisation user +$t->get_ok('/admin/users/3/') + ->status_is(200); + +#Valid customer user update +$t->post_ok('/admin/users/2/edit', form => { + email => 'test12@example.com', + new_password => 'abc123', + full_name => 'Test User1', + display_name => 'Test User1', + town => 'Midgar', + sector => 'A', + postcode => 'WC1E 6AD', +})->status_is(200)->content_like(qr/Updated User/); + +#Failed validation on customer user from no postcode +$t->post_ok('/admin/users/2/edit', form => { + email => 'test12@example.com', + new_password => 'abc123', + full_name => 'Test User1', + display_name => 'Test User1', + town => 'Midgar', + sector => 'A', +})->content_like(qr/The validation has failed/); + +#Failed validation on customer user from no display name +$t->post_ok('/admin/users/2/edit', form => { + email => 'test12@example.com', + new_password => 'abc123', + full_name => 'Test User1', + town => 'Midgar', + postcode => 'WC1E 6AD', + sector => 'A', +})->content_like(qr/The validation has failed/); + +#Valid organisation user update +$t->post_ok('/admin/users/3/edit', form => { + email => 'test51@example.com', + new_password => 'abc123', + name => '7th Heaven', + street_name => 'Slums, Sector 7', + town => 'Midgar', + sector => 'A', + postcode => 'WC1E 6AD', +})->status_is(200)->content_like(qr/Updated User/); + +#Failed validation on organisation user from no postcode +$t->post_ok('/admin/users/3/edit', form => { + email => 'test50@example.com', + new_password => 'abc123', + name => '7th Heaven', + street_name => 'Slums, Sector 7', + town => 'Midgar', + sector => 'A', +})->content_like(qr/The validation has failed/); + +#Failed validation on organisation user from no street name +$t->post_ok('/admin/users/3/edit', form => { + email => 'test50@example.com', + new_password => 'abc123', + name => '7th Heaven', + town => 'Midgar', + sector => 'A', + postcode => 'WC1E 6AD', +})->content_like(qr/The validation has failed/); + +done_testing(); diff --git a/t/api/login.t b/t/api/login.t index 1521378..08cf959 100644 --- a/t/api/login.t +++ b/t/api/login.t @@ -8,28 +8,22 @@ my $framework = Test::Pear::LocalLoop->new; my $t = $framework->framework; my $schema = $t->app->schema; -my $account_token = 'a'; -my $email = 'rufus@shinra.energy'; -my $password = 'MakoGold'; - -$schema->resultset('AccountToken')->create({ - name => $account_token -}); - -my $test_json = { - 'usertype' => 'customer', - 'token' => $account_token, - 'display_name' => 'RufusShinra', - 'full_name' => 'RufusShinra', - 'email' => $email, - 'postcode' => 'LA1 1AA', - 'password' => $password, - year_of_birth => 2006 +my $user = { + token => 'a', + usertype => 'customer', + display_name => 'Display Guy', + full_name => 'Real Name', + email => 'test@example.com', + postcode => 'LA1 1AA', + password => 'testerising', + year_of_birth => 2006, }; -$t->post_ok('/api/register' => json => $test_json) + +$schema->resultset('AccountToken')->create({ name => $user->{token} }); + +$t->post_ok('/api/register' => json => $user) ->status_is(200) ->json_is('/success', Mojo::JSON->true); -use Data::Dumper; is $schema->resultset('User')->count, 1, 'found a user'; @@ -48,11 +42,13 @@ $t->post_ok('/api/login' => json => { ->json_is('/success', Mojo::JSON->false); $t->post_ok('/api/login' => json => { - email => $email, - password => $password, + email => $user->{email}, + password => $user->{password}, }) ->status_is(200) ->json_is('/success', Mojo::JSON->true) + ->json_is('/display_name', 'Display Guy') + ->json_is('/user_type', 'customer') ->json_has('/session_key'); my $session_key = $t->tx->res->json->{session_key}; diff --git a/t/api/register.t b/t/api/register.t index 40020ac..203cde4 100644 --- a/t/api/register.t +++ b/t/api/register.t @@ -92,12 +92,12 @@ $t->post_ok('/api/register' => json => $testJson) #Blank name $testJson = { 'usertype' => 'customer', - 'token' => 'a', - 'display_name' => 'test name', - 'full_name' => '', - 'email' => 'a@b.com', - 'postcode' => 'LA1 1AA', - 'password' => 'Meh', + 'token' => 'a', + 'display_name' => 'test name', + 'full_name' => '', + 'email' => 'a@b.com', + 'postcode' => 'LA1 1AA', + 'password' => 'Meh', 'year_of_birth' => 2005 }; $t->post_ok('/api/register' => json => $testJson) @@ -108,12 +108,12 @@ $t->post_ok('/api/register' => json => $testJson) #Blank name $testJson = { 'usertype' => 'customer', - 'token' => 'a', - 'display_name' => '', - 'full_name' => 'test name', - 'email' => 'a@b.com', - 'postcode' => 'LA1 1AA', - 'password' => 'Meh', + 'token' => 'a', + 'display_name' => '', + 'full_name' => 'test name', + 'email' => 'a@b.com', + 'postcode' => 'LA1 1AA', + 'password' => 'Meh', 'year_of_birth' => 2005 }; $t->post_ok('/api/register' => json => $testJson) @@ -125,13 +125,13 @@ $t->post_ok('/api/register' => json => $testJson) #Valid customer $testJson = { - 'usertype' => 'customer', - 'token' => 'a', - 'full_name' => 'test name', - 'display_name' => 'test name', - 'email' => 'a@b.com', - 'postcode' => 'LA1 1AA', - 'password' => 'Meh', + 'usertype' => 'customer', + 'token' => 'a', + 'full_name' => 'test name', + 'display_name' => 'test name', + 'email' => 'a@b.com', + 'postcode' => 'LA1 1AA', + 'password' => 'Meh', 'year_of_birth' => 2005 }; $t->post_ok('/api/register' => json => $testJson) @@ -140,35 +140,35 @@ $t->post_ok('/api/register' => json => $testJson) #Valid customer2 $testJson = { - 'usertype' => 'customer', - 'token' => 'b', - 'full_name' => 'test name', - 'display_name' => 'test name', - 'email' => 'b@c.com', - 'postcode' => 'LA1 1AA', - 'password' => 'Meh', + 'usertype' => 'customer', + 'token' => 'b', + 'full_name' => 'test name', + 'display_name' => 'test name', + 'email' => 'b@c.com', + 'postcode' => 'LA1 1AA', + 'password' => 'Meh', 'year_of_birth' => 2005 }; $t->post_ok('/api/register' => json => $testJson) ->or($dump_error) - ->status_is(200) + ->status_is(200) ->or($dump_error) ->json_is('/success', Mojo::JSON->true) ->or($dump_error); #Valid customer3 $testJson = { - 'usertype' => 'customer', - 'token' => 'c', - 'full_name' => 'test name', - 'display_name' => 'test name', - 'email' => 'c@d.com', - 'postcode' => 'LA1 1AA', - 'password' => 'Meh', + 'usertype' => 'customer', + 'token' => 'c', + 'full_name' => 'test name', + 'display_name' => 'test name', + 'email' => 'c@d.com', + 'postcode' => 'LA1 1AA', + 'password' => 'Meh', 'year_of_birth' => 2005 }; $t->post_ok('/api/register' => json => $testJson) - ->status_is(200) + ->status_is(200) ->json_is('/success', Mojo::JSON->true); #email missing JSON @@ -186,53 +186,53 @@ $t->post_ok('/api/register' => json => $testJson) ->json_is('/success', Mojo::JSON->false) ->content_like(qr/no email sent/i); -#invalid email 1 +#invalid email 1 $testJson = { 'usertype' => 'customer', - 'token' => 'd', - 'full_name' => 'test name', - 'display_name' => 'test name', - 'email' => 'dfsd@.com', - 'postcode' => 'LA1 1AA', - 'password' => 'Meh', + 'token' => 'd', + 'full_name' => 'test name', + 'display_name' => 'test name', + 'email' => 'dfsd@.com', + 'postcode' => 'LA1 1AA', + 'password' => 'Meh', 'year_of_birth' => 2006 }; $t->post_ok('/api/register' => json => $testJson) - ->status_is(400) + ->status_is(400) ->json_is('/success', Mojo::JSON->false) ->content_like(qr/email/i) ->content_like(qr/invalid/i); #invalid email 2 $testJson = { - 'usertype' => 'customer', - 'token' => 'd', - 'full_name' => 'test name', - 'display_name' => 'test name', - 'email' => 'dfsd@com', - 'postcode' => 'LA1 1AA', - 'password' => 'Meh', + 'usertype' => 'customer', + 'token' => 'd', + 'full_name' => 'test name', + 'display_name' => 'test name', + 'email' => 'dfsd@com', + 'postcode' => 'LA1 1AA', + 'password' => 'Meh', 'year_of_birth' => 2006 }; $t->post_ok('/api/register' => json => $testJson) - ->status_is(400) + ->status_is(400) ->json_is('/success', Mojo::JSON->false) ->content_like(qr/email/i) ->content_like(qr/invalid/i); #Email exists $testJson = { - 'usertype' => 'customer', - 'token' => 'd', - 'full_name' => 'test name', - 'display_name' => 'test name', - 'email' => 'a@b.com', - 'postcode' => 'LA1 1AA', - 'password' => 'Meh', + 'usertype' => 'customer', + 'token' => 'd', + 'full_name' => 'test name', + 'display_name' => 'test name', + 'email' => 'a@b.com', + 'postcode' => 'LA1 1AA', + 'password' => 'Meh', 'year_of_birth' => 2006 }; $t->post_ok('/api/register' => json => $testJson) - ->status_is(403) + ->status_is(403) ->json_is('/success', Mojo::JSON->false) ->content_like(qr/email/i) ->content_like(qr/exists/i); @@ -288,16 +288,15 @@ $t->post_ok('/api/register' => json => $testJson) #Invalid user type $testJson = { - 'usertype' => 'organisation1', - 'token' => 'f', - 'name' => 'test name', - 'email' => 'org@org.com', - 'postcode' => 'LA1 1AA', - 'password' => 'Meh', - 'fulladdress' => 'mary lane testing....' + 'usertype' => 'organisation1', + 'token' => 'f', + 'name' => 'test name', + 'email' => 'org@org.com', + 'postcode' => 'LA1 1AA', + 'password' => 'Meh', }; $t->post_ok('/api/register' => json => $testJson) - ->status_is(400) + ->status_is(400) ->json_is('/success', Mojo::JSON->false) ->content_like(qr/usertype/i) ->content_like(qr/invalid/i); @@ -320,29 +319,30 @@ $t->post_ok('/api/register' => json => $testJson) #Age is invalid $testJson = { - 'usertype' => 'customer', - 'token' => 'f', - 'full_name' => 'test name', - 'display_name' => 'test name', - 'email' => 'test@example.com', - 'postcode' => 'LA1 1AA', - 'password' => 'Meh', + 'usertype' => 'customer', + 'token' => 'f', + 'full_name' => 'test name', + 'display_name' => 'test name', + 'email' => 'test@example.com', + 'postcode' => 'LA1 1AA', + 'password' => 'Meh', 'year_of_birth' => 'invalid' }; $t->post_ok('/api/register' => json => $testJson) - ->status_is(400) + ->status_is(400) ->json_is('/success', Mojo::JSON->false) ->content_like(qr/year_of_birth/i) ->content_like(qr/invalid/i); #full address missing JSON $testJson = { - 'usertype' => 'organisation', - 'token' => 'f', - 'name' => 'test org', - 'email' => 'org@org.com', - 'postcode' => 'LA1 1AA', - 'password' => 'Meh', + 'usertype' => 'organisation', + 'token' => 'f', + 'name' => 'test org', + 'email' => 'org@org.com', + 'postcode' => 'LA1 1AA', + 'password' => 'Meh', + 'sector' => 'A', }; $t->post_ok('/api/register' => json => $testJson) ->status_is(400) @@ -353,17 +353,18 @@ $t->post_ok('/api/register' => json => $testJson) #Organisation valid $testJson = { - 'usertype' => 'organisation', - 'token' => 'f', - 'name' => 'org name', - 'email' => 'org@org.com', - 'postcode' => 'LA1 1AA', - 'password' => 'Meh', + 'usertype' => 'organisation', + 'token' => 'f', + 'name' => 'org name', + 'email' => 'org@org.com', + 'postcode' => 'LA1 1AA', + 'password' => 'Meh', 'street_name' => 'mary lane testing....', 'town' => 'Lancaster', + 'sector' => 'A', }; $t->post_ok('/api/register' => json => $testJson) - ->status_is(200) + ->status_is(200) ->json_is('/success', Mojo::JSON->true); is $t->app->schema->resultset('User')->count, 4, 'Correct user count'; diff --git a/t/api/search.t b/t/api/search.t index ada3c0a..dbc688d 100644 --- a/t/api/search.t +++ b/t/api/search.t @@ -55,6 +55,7 @@ $testJson = { 'password' => $passwordBilly, 'street_name' => 'Market St', 'town' => 'Lancaster', + 'sector' => 'A', }; $t->post_ok('/api/register' => json => $testJson) ->status_is(200) diff --git a/t/api/stats.t b/t/api/stats.t index d12bcbe..2a6d889 100644 --- a/t/api/stats.t +++ b/t/api/stats.t @@ -28,6 +28,7 @@ my $org = { town => 'Lancaster', postcode => 'LA1 1AA', password => 'abc123', + sector => 'A', }; $schema->resultset('AccountToken')->create({ name => $user->{token} }); diff --git a/t/api/stats_leaderboards.t b/t/api/stats_leaderboards.t index 3b494ff..11003c2 100644 --- a/t/api/stats_leaderboards.t +++ b/t/api/stats_leaderboards.t @@ -56,6 +56,7 @@ my $org = { name => 'Test Org', street_name => 'Test Street', town => 'Lancaster', + sector => 'A', postcode => 'LA1 1AA', password => 'abc123', }; diff --git a/t/api/upload.t b/t/api/upload.t index 0cac28b..ca925cf 100644 --- a/t/api/upload.t +++ b/t/api/upload.t @@ -81,6 +81,7 @@ $testJson = { 'password' => $passwordBilly, 'street_name' => 'Chocobo Farm, Eastern Continent', 'town' => 'Gaia', + 'sector' => 'A', }; $t->post_ok('/api/register' => json => $testJson) ->status_is(200) @@ -201,9 +202,10 @@ $json = { }; $upload = {json => Mojo::JSON::encode_json($json)}; $t->post_ok('/api/upload' => form => $upload ) - ->status_is(400) - ->json_is('/success', Mojo::JSON->false) - ->content_like(qr/no file uploaded/i); +->status_is(200) +->json_is('/success', Mojo::JSON->true) +->json_like('/message', qr/Upload Successful/); +is $schema->resultset('Transaction')->count, 1, "1 transaction"; print "test 13 - organisation_id missing (type 1: already validated)\n"; $json = { @@ -234,7 +236,7 @@ $t->post_ok('/api/upload' => form => $upload ) ->content_like(qr/organisation_id does not exist in the database/i); print "test 15 - valid addition. (type 1: already validated)\n"; -is $schema->resultset('Transaction')->count, 0, "no transactions"; +is $schema->resultset('Transaction')->count, 1, "1 transaction"; $json = { transaction_value => 10, transaction_type => 1, @@ -247,7 +249,7 @@ $t->post_ok('/api/upload' => form => $upload ) ->status_is(200) ->json_is('/success', Mojo::JSON->true) ->json_like('/message', qr/Upload Successful/); -is $schema->resultset('Transaction')->count, 1, "1 transaction"; +is $schema->resultset('Transaction')->count, 2, "2 transaction"; # Add type 3 (new organisation) checking. @@ -444,7 +446,7 @@ $t->post_ok('/api/login' => json => $testJson) $session_key = $t->tx->res->json('/session_key'); print "test 30 - organisation buy from another organisation\n"; -is $schema->resultset('Transaction')->count, 1, "1 transaction"; +is $schema->resultset('Transaction')->count, 2, "2 transaction"; $json = { transaction_value => 100000, transaction_type => 1, @@ -457,6 +459,6 @@ $t->post_ok('/api/upload' => form => $upload ) ->status_is(200) ->json_is('/success', Mojo::JSON->true) ->json_like('/message', qr/Upload Successful/); -is $schema->resultset('Transaction')->count, 2, "2 transaction"; +is $schema->resultset('Transaction')->count, 3, "3 transaction"; done_testing(); diff --git a/t/api/v1/organisation/graphs.t b/t/api/v1/organisation/graphs.t new file mode 100644 index 0000000..d6885f3 --- /dev/null +++ b/t/api/v1/organisation/graphs.t @@ -0,0 +1,89 @@ +use Mojo::Base -strict; + +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; + +my $start = DateTime->today->subtract( hours => 12 ); + +# create 30 days worth of data +for my $count ( 0 .. 29 ) { + my $trans_day = $start->clone->subtract( days => $count ); + + create_random_transaction( 'test1@example.com', $trans_day ); + if ( $count % 2 ) { + create_random_transaction( 'test2@example.com', $trans_day ); + } + if ( $count % 3 ) { + create_random_transaction( 'test3@example.com', $trans_day ); + } + if ( $count % 4 ) { + create_random_transaction( 'test4@example.com', $trans_day ); + } +} + +my $session_key = $framework->login({ + email => 'org@example.com', + password => 'abc123', +}); + +$t->post_ok('/api/v1/organisation/graphs' => json => { + session_key => $session_key, + graph => 'customers_last_7_days', + }) + ->status_is(200)->or($framework->dump_error) + ->json_is('/graph', { + day => [ map { $start->clone->subtract( days => $_ )->day_name } reverse ( 0 .. 6 ) ], + count => [ 2, 4, 2, 3, 3, 4, 1 ], + }); + +$t->post_ok('/api/v1/organisation/graphs' => json => { + session_key => $session_key, + graph => 'customers_last_30_days', + }) + ->status_is(200)->or($framework->dump_error) + ->json_is('/graph', { + day => [ map { $start->clone->subtract( days => $_ )->day_name } reverse ( 0 .. 29 ) ], + count => [ 4, 2, 3, 3, 4, 1, 4, 3, 3, 2, 4, 2, 4, 2, 3, 3, 4, 1, 4, 3, 3, 2, 4, 2, 4, 2, 3, 3, 4, 1 ], + }); + +$framework->logout( $session_key ); + +$session_key = $framework->login({ + email => 'test1@example.com', + password => 'abc123', +}); + +$t->post_ok('/api/v1/organisation/graphs' => json => { + session_key => $session_key, + graph => 'customers_last_7_days', + }) + ->status_is(403) + ->json_is('/success', Mojo::JSON->false) + ->json_is('/error', 'user_not_org'); + +sub create_random_transaction { + my $buyer = shift; + my $time = shift; + + $schema->resultset('Transaction')->create({ + buyer => { email => $buyer }, + seller => { name => 'Test Org' }, + value => ( int( rand( 10000 ) ) / 100 ), + proof_image => 'a', + purchase_time => $time, + }); +} + +done_testing; diff --git a/t/etc/fixtures/config/leaderboards.pl b/t/etc/fixtures/config/leaderboards.pl new file mode 100644 index 0000000..9a0c600 --- /dev/null +++ b/t/etc/fixtures/config/leaderboards.pl @@ -0,0 +1,109 @@ +#! /usr/bin/env perl + +use strict; +use warnings; + +use 5.020; + +use DBIx::Class::Fixtures; +use FindBin qw/ $Bin /; +use lib "$Bin/../../../../lib"; +use Pear::LocalLoop::Schema; +use DateTime; +use Devel::Dwarn; + +my $fixtures = DBIx::Class::Fixtures->new({ + config_dir => "$Bin", +}); + +my $schema = Pear::LocalLoop::Schema->connect('dbi:SQLite::memory:'); + +$schema->deploy; + +$fixtures->populate({ + directory => "$Bin/../data/transactions", + no_deploy => 1, + schema => $schema, +}); + +my $trans_rs = $schema->resultset('Transaction')->search( undef, { order_by => { '-asc' => 'purchase_time' } } ); + +my $first = $trans_rs->first->purchase_time; + +# Start with the first monday after this transaction +my $beginning_of_week = $first->clone->truncate( to => 'week' ); + +# Start with the first month after this transaction +my $beginning_of_month = $first->clone->truncate( to => 'month' ); + +say "First Entry"; +say $first->iso8601; +say "First Week"; +say $beginning_of_week->iso8601; +say "First Month"; +say $beginning_of_month->iso8601; + +$trans_rs = $schema->resultset('Transaction')->search( undef, { order_by => { '-desc' => 'purchase_time' } } ); + +my $last = $trans_rs->first->purchase_time->subtract( days => 1 ); + +my $end_week = $last->clone->truncate( to => 'week' )->subtract( weeks => 1 ); + +my $end_month = $last->clone->truncate( to => 'month' ); + +say "Last Entry"; +say $last->iso8601; +say "Last Week"; +say $end_week->iso8601; +say "Last Month"; +say $end_month->iso8601; + +say "Calculating Daily Leaderboards from " . $first->iso8601 . " to " . $last->iso8601; + +my $leaderboard_rs = $schema->resultset('Leaderboard'); +my $daily_date = $first->clone; + +while ( $daily_date <= $last ) { + say "Creating Daily Total for " . $daily_date->iso8601; + $leaderboard_rs->create_new( 'daily_total', $daily_date ); + say "Creating Daily Count for " . $daily_date->iso8601; + $leaderboard_rs->create_new( 'daily_count', $daily_date ); + $daily_date->add( days => 1 ); +} + +say "Created " . $leaderboard_rs->find({ type => 'daily_total' })->sets->count . " Daily Total boards"; +say "Created " . $leaderboard_rs->find({ type => 'daily_count' })->sets->count . " Daily Count boards"; + +my $weekly_date = $beginning_of_week->clone; + +while ( $weekly_date <= $end_week ) { + say "Creating Weekly Total for " . $weekly_date->iso8601; + $leaderboard_rs->create_new( 'weekly_total', $weekly_date ); + say "Creating Weekly Count for " . $weekly_date->iso8601; + $leaderboard_rs->create_new( 'weekly_count', $weekly_date ); + $weekly_date->add( weeks => 1 ); +} + +say "Created " . $leaderboard_rs->find({ type => 'weekly_total' })->sets->count . " Weekly Total boards"; +say "Created " . $leaderboard_rs->find({ type => 'weekly_count' })->sets->count . " Weekly Count boards"; + +my $monthly_date = $beginning_of_month->clone; + +while ( $monthly_date <= $end_month ) { + say "Creating Monthly Total for " . $monthly_date->iso8601; + $leaderboard_rs->create_new( 'monthly_total', $monthly_date ); + say "Creating Monthly Count for " . $monthly_date->iso8601; + $leaderboard_rs->create_new( 'monthly_count', $monthly_date ); + $monthly_date->add( months => 1 ); +} + +say "Created " . $leaderboard_rs->find({ type => 'monthly_total' })->sets->count . " Monthly Total boards"; +say "Created " . $leaderboard_rs->find({ type => 'monthly_count' })->sets->count . " Monthly Count boards"; + +my $data_set = 'leaderboards'; + +$fixtures->dump({ + all => 1, + schema => $schema, + directory => "$Bin/../data/" . $data_set, +}); diff --git a/t/etc/fixtures/config/transactions.pl b/t/etc/fixtures/config/transactions.pl new file mode 100644 index 0000000..f98fddd --- /dev/null +++ b/t/etc/fixtures/config/transactions.pl @@ -0,0 +1,128 @@ +#! /usr/bin/env perl + +use strict; +use warnings; + +use DBIx::Class::Fixtures; +use FindBin qw/ $Bin /; +use lib "$Bin/../../../../lib"; +use Pear::LocalLoop::Schema; +use DateTime; +use Devel::Dwarn; + +my $fixtures = DBIx::Class::Fixtures->new({ + config_dir => "$Bin", +}); + +my $schema = Pear::LocalLoop::Schema->connect('dbi:SQLite::memory:'); + +$schema->deploy; + +$schema->resultset('Leaderboard')->populate([ + [ qw/ name type / ], + [ 'Daily Total', 'daily_total' ], + [ 'Daily Count', 'daily_count' ], + [ 'Weekly Total', 'weekly_total' ], + [ 'Weekly Count', 'weekly_count' ], + [ 'Monthly Total', 'monthly_total' ], + [ 'Monthly Count', 'monthly_count' ], + [ 'All Time Total', 'all_time_total' ], + [ 'All Time Count', 'all_time_count' ], +]); + +my $user1 = { + customer => { + full_name => 'Test User1', + display_name => 'Test User1', + postcode => 'LA1 1AA', + year_of_birth => 2006, + }, + email => 'test1@example.com', + password => 'abc123', +}; + +my $user2 = { + customer => { + full_name => 'Test User2', + display_name => 'Test User2', + postcode => 'LA1 1AA', + year_of_birth => 2006, + }, + email => 'test2@example.com', + password => 'abc123', +}; + +my $user3 = { + customer => { + full_name => 'Test User3', + display_name => 'Test User3', + postcode => 'LA1 1AA', + year_of_birth => 2006, + }, + email => 'test3@example.com', + password => 'abc123', +}; + +my $user4 = { + customer => { + full_name => 'Test User4', + display_name => 'Test User4', + postcode => 'LA1 1AA', + year_of_birth => 2006, + }, + email => 'test4@example.com', + password => 'abc123', +}; + +my $org = { + organisation => { + name => 'Test Org', + street_name => 'Test Street', + town => 'Lancaster', + postcode => 'LA1 1AA', + }, + email => 'test5@example.com', + password => 'abc123', +}; + +$schema->resultset('User')->create( $_ ) + for ( $user1, $user2, $user3, $user4, $org ); + +my $org_result = $schema->resultset('Organisation')->find({ name => $org->{organisation}{name} }); + +my $dtf = $schema->storage->datetime_parser; + +# Number of hours in 90 days +my $time_count = 24 * 90; + +for my $user ( $user1, $user2, $user3, $user4 ) { + + my $start = DateTime->new( + year => 2017, + month => 8, + day => 1, + hour => 0, + minute => 0, + second => 0, + time_zone => 'UTC', + ); + + my $user_result = $schema->resultset('User')->find({ email => $user->{email} }); + for ( 0 .. $time_count ) { + $user_result->create_related( 'transactions', { + seller_id => $org_result->id, + value => ( int( rand( 10000 ) ) / 100 ), + proof_image => 'a', + purchase_time => $start->clone->subtract( hours => $_ ), + }); + } +} + +my $data_set = 'transactions'; + +$fixtures->dump({ + all => 1, + schema => $schema, + directory => "$Bin/../data/" . $data_set, +}); + diff --git a/t/etc/fixtures/config/users.pl b/t/etc/fixtures/config/users.pl new file mode 100644 index 0000000..676592b --- /dev/null +++ b/t/etc/fixtures/config/users.pl @@ -0,0 +1,97 @@ +#! /usr/bin/env perl + +use strict; +use warnings; + +use DBIx::Class::Fixtures; +use FindBin qw/ $Bin /; +use lib "$Bin/../../../../lib"; +use Pear::LocalLoop::Schema; +use DateTime; + +my $fixtures = DBIx::Class::Fixtures->new({ + config_dir => "$Bin", +}); + +my $schema = Pear::LocalLoop::Schema->connect('dbi:SQLite::memory:'); + +$schema->deploy; + +$schema->resultset('Leaderboard')->populate([ + [ qw/ name type / ], + [ 'Daily Total', 'daily_total' ], + [ 'Daily Count', 'daily_count' ], + [ 'Weekly Total', 'weekly_total' ], + [ 'Weekly Count', 'weekly_count' ], + [ 'Monthly Total', 'monthly_total' ], + [ 'Monthly Count', 'monthly_count' ], + [ 'All Time Total', 'all_time_total' ], + [ 'All Time Count', 'all_time_count' ], +]); + +my $user1 = { + customer => { + full_name => 'Test User1', + display_name => 'Test User1', + postcode => 'LA1 1AA', + year_of_birth => 2006, + }, + email => 'test1@example.com', + password => 'abc123', +}; + +my $user2 = { + customer => { + full_name => 'Test User2', + display_name => 'Test User2', + postcode => 'LA1 1AA', + year_of_birth => 2006, + }, + email => 'test2@example.com', + password => 'abc123', +}; + +my $user3 = { + customer => { + full_name => 'Test User3', + display_name => 'Test User3', + postcode => 'LA1 1AA', + year_of_birth => 2006, + }, + email => 'test3@example.com', + password => 'abc123', +}; + +my $user4 = { + customer => { + full_name => 'Test User4', + display_name => 'Test User4', + postcode => 'LA1 1AA', + year_of_birth => 2006, + }, + email => 'test4@example.com', + password => 'abc123', +}; + +my $org = { + organisation => { + name => 'Test Org', + street_name => 'Test Street', + town => 'Lancaster', + postcode => 'LA1 1AA', + }, + email => 'org@example.com', + password => 'abc123', +}; + +$schema->resultset('User')->create( $_ ) + for ( $user1, $user2, $user3, $user4, $org ); + +my $data_set = 'users'; + +$fixtures->dump({ + all => 1, + schema => $schema, + directory => "$Bin/../data/" . $data_set, +}); + diff --git a/t/etc/fixtures/data/users/_config_set b/t/etc/fixtures/data/users/_config_set new file mode 100644 index 0000000..f2242db --- /dev/null +++ b/t/etc/fixtures/data/users/_config_set @@ -0,0 +1,65 @@ +$VAR1 = { + 'has_many' => { + 'fetch' => 0 + }, + 'belongs_to' => { + 'fetch' => 0 + }, + 'might_have' => { + 'fetch' => 0 + }, + 'sets' => [ + { + 'class' => 'Leaderboard', + 'quantity' => 'all' + }, + { + 'class' => 'AccountToken', + 'quantity' => 'all' + }, + { + 'class' => 'LeaderboardValue', + 'quantity' => 'all' + }, + { + 'quantity' => 'all', + 'class' => 'SessionToken' + }, + { + 'class' => 'Administrator', + 'quantity' => 'all' + }, + { + 'class' => 'LeaderboardSet', + 'quantity' => 'all' + }, + { + 'quantity' => 'all', + 'class' => 'Feedback' + }, + { + 'class' => 'PendingOrganisation', + 'quantity' => 'all' + }, + { + 'class' => 'User', + 'quantity' => 'all' + }, + { + 'class' => 'Transaction', + 'quantity' => 'all' + }, + { + 'quantity' => 'all', + 'class' => 'Organisation' + }, + { + 'quantity' => 'all', + 'class' => 'Customer' + }, + { + 'quantity' => 'all', + 'class' => 'PendingTransaction' + } + ] + }; diff --git a/t/etc/fixtures/data/users/_dumper_version b/t/etc/fixtures/data/users/_dumper_version new file mode 100644 index 0000000..8d0321e --- /dev/null +++ b/t/etc/fixtures/data/users/_dumper_version @@ -0,0 +1 @@ +1.001036 \ No newline at end of file diff --git a/t/etc/fixtures/data/users/customers/1.fix b/t/etc/fixtures/data/users/customers/1.fix new file mode 100644 index 0000000..16d0da6 --- /dev/null +++ b/t/etc/fixtures/data/users/customers/1.fix @@ -0,0 +1,7 @@ +$HASH1 = { + display_name => 'Test User1', + full_name => 'Test User1', + id => 1, + postcode => 'LA1 1AA', + year_of_birth => 2006 + }; diff --git a/t/etc/fixtures/data/users/customers/2.fix b/t/etc/fixtures/data/users/customers/2.fix new file mode 100644 index 0000000..3c31701 --- /dev/null +++ b/t/etc/fixtures/data/users/customers/2.fix @@ -0,0 +1,7 @@ +$HASH1 = { + display_name => 'Test User2', + full_name => 'Test User2', + id => 2, + postcode => 'LA1 1AA', + year_of_birth => 2006 + }; diff --git a/t/etc/fixtures/data/users/customers/3.fix b/t/etc/fixtures/data/users/customers/3.fix new file mode 100644 index 0000000..b434d5c --- /dev/null +++ b/t/etc/fixtures/data/users/customers/3.fix @@ -0,0 +1,7 @@ +$HASH1 = { + display_name => 'Test User3', + full_name => 'Test User3', + id => 3, + postcode => 'LA1 1AA', + year_of_birth => 2006 + }; diff --git a/t/etc/fixtures/data/users/customers/4.fix b/t/etc/fixtures/data/users/customers/4.fix new file mode 100644 index 0000000..d5f881b --- /dev/null +++ b/t/etc/fixtures/data/users/customers/4.fix @@ -0,0 +1,7 @@ +$HASH1 = { + display_name => 'Test User4', + full_name => 'Test User4', + id => 4, + postcode => 'LA1 1AA', + year_of_birth => 2006 + }; diff --git a/t/etc/fixtures/data/users/leaderboards/1.fix b/t/etc/fixtures/data/users/leaderboards/1.fix new file mode 100644 index 0000000..597a843 --- /dev/null +++ b/t/etc/fixtures/data/users/leaderboards/1.fix @@ -0,0 +1,5 @@ +$HASH1 = { + id => 1, + name => 'Daily Total', + type => 'daily_total' + }; diff --git a/t/etc/fixtures/data/users/leaderboards/2.fix b/t/etc/fixtures/data/users/leaderboards/2.fix new file mode 100644 index 0000000..08fef2c --- /dev/null +++ b/t/etc/fixtures/data/users/leaderboards/2.fix @@ -0,0 +1,5 @@ +$HASH1 = { + id => 2, + name => 'Daily Count', + type => 'daily_count' + }; diff --git a/t/etc/fixtures/data/users/leaderboards/3.fix b/t/etc/fixtures/data/users/leaderboards/3.fix new file mode 100644 index 0000000..4166f18 --- /dev/null +++ b/t/etc/fixtures/data/users/leaderboards/3.fix @@ -0,0 +1,5 @@ +$HASH1 = { + id => 3, + name => 'Weekly Total', + type => 'weekly_total' + }; diff --git a/t/etc/fixtures/data/users/leaderboards/4.fix b/t/etc/fixtures/data/users/leaderboards/4.fix new file mode 100644 index 0000000..feb773c --- /dev/null +++ b/t/etc/fixtures/data/users/leaderboards/4.fix @@ -0,0 +1,5 @@ +$HASH1 = { + id => 4, + name => 'Weekly Count', + type => 'weekly_count' + }; diff --git a/t/etc/fixtures/data/users/leaderboards/5.fix b/t/etc/fixtures/data/users/leaderboards/5.fix new file mode 100644 index 0000000..d0522b2 --- /dev/null +++ b/t/etc/fixtures/data/users/leaderboards/5.fix @@ -0,0 +1,5 @@ +$HASH1 = { + id => 5, + name => 'Monthly Total', + type => 'monthly_total' + }; diff --git a/t/etc/fixtures/data/users/leaderboards/6.fix b/t/etc/fixtures/data/users/leaderboards/6.fix new file mode 100644 index 0000000..f7bb145 --- /dev/null +++ b/t/etc/fixtures/data/users/leaderboards/6.fix @@ -0,0 +1,5 @@ +$HASH1 = { + id => 6, + name => 'Monthly Count', + type => 'monthly_count' + }; diff --git a/t/etc/fixtures/data/users/leaderboards/7.fix b/t/etc/fixtures/data/users/leaderboards/7.fix new file mode 100644 index 0000000..b2aadcd --- /dev/null +++ b/t/etc/fixtures/data/users/leaderboards/7.fix @@ -0,0 +1,5 @@ +$HASH1 = { + id => 7, + name => 'All Time Total', + type => 'all_time_total' + }; diff --git a/t/etc/fixtures/data/users/leaderboards/8.fix b/t/etc/fixtures/data/users/leaderboards/8.fix new file mode 100644 index 0000000..df58698 --- /dev/null +++ b/t/etc/fixtures/data/users/leaderboards/8.fix @@ -0,0 +1,5 @@ +$HASH1 = { + id => 8, + name => 'All Time Count', + type => 'all_time_count' + }; diff --git a/t/etc/fixtures/data/users/organisations/1.fix b/t/etc/fixtures/data/users/organisations/1.fix new file mode 100644 index 0000000..035a3ce --- /dev/null +++ b/t/etc/fixtures/data/users/organisations/1.fix @@ -0,0 +1,10 @@ +$HASH1 = { + id => 1, + name => 'Test Org', + postcode + => 'LA1 1AA', + sector => undef, + street_name + => 'Test Street', + town => 'Lancaster' + }; diff --git a/t/etc/fixtures/data/users/users/1.fix b/t/etc/fixtures/data/users/users/1.fix new file mode 100644 index 0000000..0946374 --- /dev/null +++ b/t/etc/fixtures/data/users/users/1.fix @@ -0,0 +1,11 @@ +$HASH1 = { + customer_id + => 1, + email => 'test1@example.com', + id => 1, + join_date + => '2017-08-25 15:36:11', + organisation_id + => undef, + password => '$2a$08$yCau6xDkRFZINg80iVvMh.M3JnLq2g.LJ7GMJL5KQvO45pDL.D/Rq' + }; diff --git a/t/etc/fixtures/data/users/users/2.fix b/t/etc/fixtures/data/users/users/2.fix new file mode 100644 index 0000000..9b35ef4 --- /dev/null +++ b/t/etc/fixtures/data/users/users/2.fix @@ -0,0 +1,11 @@ +$HASH1 = { + customer_id + => 2, + email => 'test2@example.com', + id => 2, + join_date + => '2017-08-25 15:36:11', + organisation_id + => undef, + password => '$2a$08$BZAsbHSW8TN/jlL2DFGDoeKFRKzj2dQTBwatxb0p/maefEcWcziom' + }; diff --git a/t/etc/fixtures/data/users/users/3.fix b/t/etc/fixtures/data/users/users/3.fix new file mode 100644 index 0000000..f3ad391 --- /dev/null +++ b/t/etc/fixtures/data/users/users/3.fix @@ -0,0 +1,11 @@ +$HASH1 = { + customer_id + => 3, + email => 'test3@example.com', + id => 3, + join_date + => '2017-08-25 15:36:11', + organisation_id + => undef, + password => '$2a$08$xdkD/OA5izrOX9cvJDa4i..8T3YGmfVSo/G87wDRoGWnQmlC0gxOW' + }; diff --git a/t/etc/fixtures/data/users/users/4.fix b/t/etc/fixtures/data/users/users/4.fix new file mode 100644 index 0000000..47d83c3 --- /dev/null +++ b/t/etc/fixtures/data/users/users/4.fix @@ -0,0 +1,11 @@ +$HASH1 = { + customer_id + => 4, + email => 'test4@example.com', + id => 4, + join_date + => '2017-08-25 15:36:11', + organisation_id + => undef, + password => '$2a$08$svjdm.Syn3f062pDIN3/ROTUU7W16n0zJFm9/sm3x7pfbMLZFV.5G' + }; diff --git a/t/etc/fixtures/data/users/users/5.fix b/t/etc/fixtures/data/users/users/5.fix new file mode 100644 index 0000000..50e56c9 --- /dev/null +++ b/t/etc/fixtures/data/users/users/5.fix @@ -0,0 +1,11 @@ +$HASH1 = { + customer_id + => undef, + email => 'org@example.com', + id => 5, + join_date + => '2017-08-25 15:36:11', + organisation_id + => 1, + password => '$2a$08$3PkZF7D9FiOq8hgU7cJ6puY86Fkl34bQj6dZeJRXPU8hhJIMZtge2' + }; diff --git a/t/schema/leaderboard.t b/t/schema/leaderboard.t index 9f0e72f..5915760 100644 --- a/t/schema/leaderboard.t +++ b/t/schema/leaderboard.t @@ -58,6 +58,7 @@ my $org = { town => 'Lancaster', postcode => 'LA1 1AA', password => 'abc123', + sector => 'A', }; $schema->resultset('AccountToken')->create({ name => $_->{token} }) @@ -215,10 +216,10 @@ test_leaderboard( 'all_time_total', $now, [ - { user_id => 4, value => 980 }, - { user_id => 3, value => 940 }, - { user_id => 2, value => 900 }, - { user_id => 1, value => 860 }, + { user_id => 4, value => 885 }, + { user_id => 3, value => 855 }, + { user_id => 2, value => 825 }, + { user_id => 1, value => 795 }, ] ); @@ -227,10 +228,10 @@ test_leaderboard( 'all_time_count', $now, [ - { user_id => 1, value => 40 }, - { user_id => 2, value => 40 }, - { user_id => 3, value => 40 }, - { user_id => 4, value => 40 }, + { user_id => 1, value => 30 }, + { user_id => 2, value => 30 }, + { user_id => 3, value => 30 }, + { user_id => 4, value => 30 }, ] ); diff --git a/t/schema/leaderboard_trend.t b/t/schema/leaderboard_trend.t index 0674e68..33e7ea9 100644 --- a/t/schema/leaderboard_trend.t +++ b/t/schema/leaderboard_trend.t @@ -58,6 +58,7 @@ my $org = { town => 'Lancaster', postcode => 'LA1 1AA', password => 'abc123', + sector => 'A', }; $schema->resultset('AccountToken')->create({ name => $_->{token} }) diff --git a/t/schema/resultset_leaderboard.t b/t/schema/resultset_leaderboard.t index 7245283..92b1120 100644 --- a/t/schema/resultset_leaderboard.t +++ b/t/schema/resultset_leaderboard.t @@ -58,6 +58,7 @@ my $org = { town => 'Lancaster', postcode => 'LA1 1AA', password => 'abc123', + sector => 'A', }; $schema->resultset('AccountToken')->create({ name => $_->{token} }) @@ -217,10 +218,10 @@ test_leaderboard( 'all_time_total', $now, [ - { user_id => 4, value => 980, position => 1 }, - { user_id => 3, value => 940, position => 2 }, - { user_id => 2, value => 900, position => 3 }, - { user_id => 1, value => 860, position => 4 }, + { user_id => 4, value => 885, position => 1 }, + { user_id => 3, value => 855, position => 2 }, + { user_id => 2, value => 825, position => 3 }, + { user_id => 1, value => 795, position => 4 }, ] ); @@ -229,10 +230,10 @@ test_leaderboard( 'all_time_count', $now, [ - { user_id => 1, value => 40, position => 1 }, - { user_id => 2, value => 40, position => 2 }, - { user_id => 3, value => 40, position => 3 }, - { user_id => 4, value => 40, position => 4 }, + { user_id => 1, value => 30, position => 1 }, + { user_id => 2, value => 30, position => 2 }, + { user_id => 3, value => 30, position => 3 }, + { user_id => 4, value => 30, position => 4 }, ] ); diff --git a/templates/admin/organisations/add_org.html.ep b/templates/admin/organisations/add_org.html.ep new file mode 100644 index 0000000..6e7702e --- /dev/null +++ b/templates/admin/organisations/add_org.html.ep @@ -0,0 +1,33 @@ +% layout 'admin'; +% title 'Organisations'; +% content_for javascript => begin +% end +% if ( my $error = flash 'error' ) { + +% } elsif ( my $success = flash 'success' ) { + +% } +
+

+ Add an Organisation +

+ +
diff --git a/templates/admin/organisations/list.html.ep b/templates/admin/organisations/list.html.ep index 75e7670..dad10cf 100644 --- a/templates/admin/organisations/list.html.ep +++ b/templates/admin/organisations/list.html.ep @@ -16,6 +16,7 @@

Valid Organisations + Add Organisation

% for my $valid_org ($valid_orgs_rs->all) { diff --git a/templates/admin/organisations/pending_read.html.ep b/templates/admin/organisations/pending_read.html.ep index 13995ff..bbf99e0 100644 --- a/templates/admin/organisations/pending_read.html.ep +++ b/templates/admin/organisations/pending_read.html.ep @@ -16,10 +16,12 @@ %= $pending_org->name
diff --git a/templates/admin/organisations/valid_read.html.ep b/templates/admin/organisations/valid_read.html.ep index 17099b2..ffb7bb2 100644 --- a/templates/admin/organisations/valid_read.html.ep +++ b/templates/admin/organisations/valid_read.html.ep @@ -16,9 +16,18 @@ %= $valid_org->name
@@ -33,6 +42,8 @@
From: <%= $transaction->buyer->name %>
To: <%= $transaction->seller->name %>
Value: <%= $transaction->value %>
+
Submitted At: <%= $transaction->submitted_at %>
+
Purchase Time: <%= $transaction->purchase_time %>
diff --git a/templates/admin/users/read.html.ep b/templates/admin/users/read.html.ep index eaca969..8e6cb47 100644 --- a/templates/admin/users/read.html.ep +++ b/templates/admin/users/read.html.ep @@ -11,20 +11,78 @@ Success! <%= $success %> % } -
-
- - -
-
- - -
-
- - -
-
- -
+ +

+ User Details +

+
+ + +
+
+ + +
+
+ + +
+
+ + +

Leave blank unless you want to change their password

+
+ % if ( my $customer_rs = $user->customer ) { +

+ Customer Details +

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ % } elsif ( my $org_rs = $user->organisation ) { +

+ Organisation Details +

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ % } else { +

+ User is not a customer or an organisation +

+ % } + +
+ +
diff --git a/templates/partials/sector_options.html.ep b/templates/partials/sector_options.html.ep new file mode 100644 index 0000000..625e89f --- /dev/null +++ b/templates/partials/sector_options.html.ep @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + +