From 8473fc839a549d1647b61a2c32331104521ddbcd Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Thu, 17 Aug 2017 15:29:00 +0100 Subject: [PATCH 01/33] Initial docs on Leaderboard concepts --- doc/Leaderboards.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 doc/Leaderboards.md diff --git a/doc/Leaderboards.md b/doc/Leaderboards.md new file mode 100644 index 0000000..8902337 --- /dev/null +++ b/doc/Leaderboards.md @@ -0,0 +1,12 @@ +# Leaderboards + +## Recalculation + +To figure out if a Leaderboard needs recalculation, the easiest way is to get +the transaction set that it corresponds to, and compare either the sum or count +of that result set to the sum of the leaderboard values. + +This can be done at any time, but if recalculation is needed then ALL +leaderboards newer than one that doesnt match (of the same type) will need +recalculating due to possible position changes, and therefore the trend +changing. From db2dc9fc0f3289cb687b834c33a3a0f5600cc41c Mon Sep 17 00:00:00 2001 From: Finn Date: Fri, 18 Aug 2017 12:56:14 +0100 Subject: [PATCH 02/33] Can now edit valid and pending organisations --- lib/Pear/LocalLoop.pm | 2 + .../Controller/Admin/Organisations.pm | 75 +++++++++++++++++++ .../admin/organisations/pending_read.html.ep | 10 ++- .../admin/organisations/valid_read.html.ep | 10 ++- 4 files changed, 90 insertions(+), 7 deletions(-) diff --git a/lib/Pear/LocalLoop.pm b/lib/Pear/LocalLoop.pm index 5134e95..c8d154b 100644 --- a/lib/Pear/LocalLoop.pm +++ b/lib/Pear/LocalLoop.pm @@ -172,7 +172,9 @@ sub startup { $admin_routes->get('/organisations')->to('admin-organisations#list'); $admin_routes->get('/organisations/valid/:id')->to('admin-organisations#valid_read'); + $admin_routes->get('/organisations/valid/:id/edit')->to('admin-organisations#valid_edit'); $admin_routes->get('/organisations/pending/:id')->to('admin-organisations#pending_read'); + $admin_routes->get('/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/Controller/Admin/Organisations.pm b/lib/Pear/LocalLoop/Controller/Admin/Organisations.pm index 1dffdc9..84eef3f 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; @@ -30,6 +31,43 @@ sub valid_read { ); } +sub valid_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/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'), + 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 +83,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/valid/' . $pending_org->id ); +} + sub pending_approve { my $c = shift; my $pending_org = $c->schema->resultset('PendingOrganisation')->find( $c->param('id') ); diff --git a/templates/admin/organisations/pending_read.html.ep b/templates/admin/organisations/pending_read.html.ep index 13995ff..aa0bec9 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..961f5b2 100644 --- a/templates/admin/organisations/valid_read.html.ep +++ b/templates/admin/organisations/valid_read.html.ep @@ -16,9 +16,13 @@ %= $valid_org->name
    -
  • Street Name: <%= $valid_org->street_name %>
  • -
  • Town: <%= $valid_org->town %>
  • -
  • Postcode: <%= $valid_org->postcode %>
  • + +
  • +
  • +
  • +
  • +
  • +
From 36a5fdf2e30112cf90c8b1a3a71e71a0fc995c4e Mon Sep 17 00:00:00 2001 From: Finn Date: Fri, 18 Aug 2017 16:46:49 +0100 Subject: [PATCH 03/33] Fixed code and added test for reading and editing Organisations --- lib/Pear/LocalLoop.pm | 4 +- .../Controller/Admin/Organisations.pm | 2 +- t/admin/update.t | 81 +++++++++++++++++++ .../admin/organisations/pending_read.html.ep | 4 +- .../admin/organisations/valid_read.html.ep | 2 +- 5 files changed, 87 insertions(+), 6 deletions(-) create mode 100644 t/admin/update.t diff --git a/lib/Pear/LocalLoop.pm b/lib/Pear/LocalLoop.pm index c8d154b..a04a9bc 100644 --- a/lib/Pear/LocalLoop.pm +++ b/lib/Pear/LocalLoop.pm @@ -172,9 +172,9 @@ sub startup { $admin_routes->get('/organisations')->to('admin-organisations#list'); $admin_routes->get('/organisations/valid/:id')->to('admin-organisations#valid_read'); - $admin_routes->get('/organisations/valid/:id/edit')->to('admin-organisations#valid_edit'); + $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->get('/organisations/pending/:id/edit')->to('admin-organisations#pending_edit'); + $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/Controller/Admin/Organisations.pm b/lib/Pear/LocalLoop/Controller/Admin/Organisations.pm index 84eef3f..0b3e4c5 100644 --- a/lib/Pear/LocalLoop/Controller/Admin/Organisations.pm +++ b/lib/Pear/LocalLoop/Controller/Admin/Organisations.pm @@ -117,7 +117,7 @@ sub pending_edit { $c->flash( success => 'Updated Organisation' ); } }; - $c->redirect_to( '/admin/organisations/valid/' . $pending_org->id ); + $c->redirect_to( '/admin/organisations/pending/' . $pending_org->id ); } sub pending_approve { diff --git a/t/admin/update.t b/t/admin/update.t new file mode 100644 index 0000000..84fc3e6 --- /dev/null +++ b/t/admin/update.t @@ -0,0 +1,81 @@ +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', + 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', + 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', + 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/); + +done_testing(); diff --git a/templates/admin/organisations/pending_read.html.ep b/templates/admin/organisations/pending_read.html.ep index aa0bec9..bbf99e0 100644 --- a/templates/admin/organisations/pending_read.html.ep +++ b/templates/admin/organisations/pending_read.html.ep @@ -16,8 +16,8 @@ %= $pending_org->name
    -
    -
  • + +
  • diff --git a/templates/admin/organisations/valid_read.html.ep b/templates/admin/organisations/valid_read.html.ep index 961f5b2..9fd3e97 100644 --- a/templates/admin/organisations/valid_read.html.ep +++ b/templates/admin/organisations/valid_read.html.ep @@ -16,7 +16,7 @@ %= $valid_org->name
      - +
    • From a1b6dd8d2aece1c4c2f45f5a4a131571aa5544f4 Mon Sep 17 00:00:00 2001 From: Finn Date: Mon, 21 Aug 2017 15:10:33 +0100 Subject: [PATCH 04/33] Add Organisation Code --- lib/Pear/LocalLoop.pm | 2 + .../Controller/Admin/Organisations.pm | 40 +++++++++++++++++++ templates/admin/organisations/add_org.html.ep | 28 +++++++++++++ templates/admin/organisations/list.html.ep | 1 + 4 files changed, 71 insertions(+) create mode 100644 templates/admin/organisations/add_org.html.ep diff --git a/lib/Pear/LocalLoop.pm b/lib/Pear/LocalLoop.pm index a04a9bc..4813064 100644 --- a/lib/Pear/LocalLoop.pm +++ b/lib/Pear/LocalLoop.pm @@ -171,6 +171,8 @@ sub startup { $admin_routes->post('/users/:id/delete')->to('admin-users#delete'); $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'); diff --git a/lib/Pear/LocalLoop/Controller/Admin/Organisations.pm b/lib/Pear/LocalLoop/Controller/Admin/Organisations.pm index 0b3e4c5..178d9f7 100644 --- a/lib/Pear/LocalLoop/Controller/Admin/Organisations.pm +++ b/lib/Pear/LocalLoop/Controller/Admin/Organisations.pm @@ -16,6 +16,46 @@ 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('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'), + 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') ); diff --git a/templates/admin/organisations/add_org.html.ep b/templates/admin/organisations/add_org.html.ep new file mode 100644 index 0000000..07cf699 --- /dev/null +++ b/templates/admin/organisations/add_org.html.ep @@ -0,0 +1,28 @@ +% layout 'admin'; +% title 'Organisations'; +% content_for javascript => begin +% end +% if ( my $error = flash 'error' ) { + +% } elsif ( my $success = flash 'success' ) { + +% } +
      +

      + Add an Organisation +

      +
        + +
      • Only 'Organisation Name' and 'Town' are required entries.
      • +
      • +
      • +
      • +
      • +
      • + +
      +
      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) { From 573c1ad50a3d863fdf5a94e061ed2f08cbf6b41c Mon Sep 17 00:00:00 2001 From: Finn Date: Mon, 21 Aug 2017 15:18:34 +0100 Subject: [PATCH 05/33] Test renamed and add organisation rest added --- t/admin/{update.t => organisation.t} | 15 +++++++++++++++ 1 file changed, 15 insertions(+) rename t/admin/{update.t => organisation.t} (82%) diff --git a/t/admin/update.t b/t/admin/organisation.t similarity index 82% rename from t/admin/update.t rename to t/admin/organisation.t index 84fc3e6..720415a 100644 --- a/t/admin/update.t +++ b/t/admin/organisation.t @@ -78,4 +78,19 @@ $t->post_ok('/admin/organisations/pending/2/edit', form => { 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', + 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(); From e49d8063087827f626adbd2ef965af8cc074c881 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Mon, 21 Aug 2017 15:03:52 +0100 Subject: [PATCH 06/33] Added Fixtures to cpanfile --- cpanfile | 1 + 1 file changed, 1 insertion(+) 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'; From 6c35bc36e0922148856554edf9404b9a9c22abc2 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Mon, 21 Aug 2017 15:11:05 +0100 Subject: [PATCH 07/33] Added basic docs for leaderboards, and fixtures --- doc/Fixtures/Leaderboards.md | 32 ++++++++++++++++++++++++++++++++ doc/Fixtures/Transactions.md | 26 ++++++++++++++++++++++++++ doc/Leaderboards.md | 18 +++++++++++------- 3 files changed, 69 insertions(+), 7 deletions(-) create mode 100644 doc/Fixtures/Leaderboards.md create mode 100644 doc/Fixtures/Transactions.md 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 index 8902337..a01069d 100644 --- a/doc/Leaderboards.md +++ b/doc/Leaderboards.md @@ -1,12 +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 -To figure out if a Leaderboard needs recalculation, the easiest way is to get -the transaction set that it corresponds to, and compare either the sum or count -of that result set to the sum of the leaderboard values. +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. -This can be done at any time, but if recalculation is needed then ALL -leaderboards newer than one that doesnt match (of the same type) will need -recalculating due to possible position changes, and therefore the trend -changing. From 3f743f0b5ff5460e6838cebea62c2116702e4130 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Mon, 21 Aug 2017 15:16:29 +0100 Subject: [PATCH 08/33] Fixed all_day calculating from the wrong time --- .../LocalLoop/Schema/ResultSet/Transaction.pm | 9 +++++++++ t/schema/leaderboard.t | 16 ++++++++-------- t/schema/resultset_leaderboard.t | 16 ++++++++-------- 3 files changed, 25 insertions(+), 16 deletions(-) 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/t/schema/leaderboard.t b/t/schema/leaderboard.t index 9f0e72f..5d09595 100644 --- a/t/schema/leaderboard.t +++ b/t/schema/leaderboard.t @@ -215,10 +215,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 +227,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/resultset_leaderboard.t b/t/schema/resultset_leaderboard.t index 7245283..c1ab6b1 100644 --- a/t/schema/resultset_leaderboard.t +++ b/t/schema/resultset_leaderboard.t @@ -217,10 +217,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 +229,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 }, ] ); From 9b9b2bd3225f5a18b72dfddb9602314108b3e68a Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Mon, 21 Aug 2017 15:19:39 +0100 Subject: [PATCH 09/33] Enabled recalculation of leaderboards at any point --- .../LocalLoop/Command/recalc_leaderboards.pm | 26 ++++++++++ .../LocalLoop/Schema/Result/Leaderboard.pm | 4 +- .../LocalLoop/Schema/ResultSet/Leaderboard.pm | 52 +++++++++++++++++++ 3 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 lib/Pear/LocalLoop/Command/recalc_leaderboards.pm 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/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/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; From 2dfce6afd2d38fc672dd1cc5b116868948ede17d Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Mon, 21 Aug 2017 15:32:02 +0100 Subject: [PATCH 10/33] Added scripts for generating fixtures - no data stored for now --- t/etc/fixtures/config/leaderboards.pl | 109 ++++++++++++++++++++++ t/etc/fixtures/config/transactions.pl | 128 ++++++++++++++++++++++++++ 2 files changed, 237 insertions(+) create mode 100644 t/etc/fixtures/config/leaderboards.pl create mode 100644 t/etc/fixtures/config/transactions.pl 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, +}); + From e907ad37d44d5ed7b4894e37c62da673e2b811a6 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Mon, 21 Aug 2017 16:51:35 +0100 Subject: [PATCH 11/33] Replace all leaderboard scripts with the single recalc script --- script/daily_leaderboards | 11 ----------- script/monthly_leaderboards | 9 --------- script/recalc_leaderboards | 5 +++++ script/weekly_leaderboards | 9 --------- 4 files changed, 5 insertions(+), 29 deletions(-) delete mode 100755 script/daily_leaderboards delete mode 100755 script/monthly_leaderboards create mode 100644 script/recalc_leaderboards delete mode 100755 script/weekly_leaderboards 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 From 4d299cee1044fa22d226adbbc919cb2049f7c855 Mon Sep 17 00:00:00 2001 From: Finn Date: Wed, 23 Aug 2017 16:25:08 +0100 Subject: [PATCH 12/33] Fixed validation for organisation edit --- lib/Pear/LocalLoop/Controller/Api/User.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Pear/LocalLoop/Controller/Api/User.pm b/lib/Pear/LocalLoop/Controller/Api/User.pm index ef9ec89..c552933 100644 --- a/lib/Pear/LocalLoop/Controller/Api/User.pm +++ b/lib/Pear/LocalLoop/Controller/Api/User.pm @@ -124,7 +124,7 @@ 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'); From 6f01bf26617dea7b194dac5767a0111b12941826 Mon Sep 17 00:00:00 2001 From: Finn Date: Wed, 23 Aug 2017 16:47:29 +0100 Subject: [PATCH 13/33] Fixed validation removing redundant code --- lib/Pear/LocalLoop/Controller/Api/Register.pm | 1 - lib/Pear/LocalLoop/Controller/Api/User.pm | 1 - t/api/register.t | 175 +++++++++--------- 3 files changed, 87 insertions(+), 90 deletions(-) diff --git a/lib/Pear/LocalLoop/Controller/Api/Register.pm b/lib/Pear/LocalLoop/Controller/Api/Register.pm index ee760ee..da6805a 100644 --- a/lib/Pear/LocalLoop/Controller/Api/Register.pm +++ b/lib/Pear/LocalLoop/Controller/Api/Register.pm @@ -102,7 +102,6 @@ sub post_register{ } elsif ($usertype eq 'organisation') { - my $fullAddress = $validation->param('fulladdress'); $c->schema->txn_do( sub { $c->schema->resultset('AccountToken')->find({ diff --git a/lib/Pear/LocalLoop/Controller/Api/User.pm b/lib/Pear/LocalLoop/Controller/Api/User.pm index ef9ec89..cb2396f 100644 --- a/lib/Pear/LocalLoop/Controller/Api/User.pm +++ b/lib/Pear/LocalLoop/Controller/Api/User.pm @@ -148,7 +148,6 @@ sub post_account_update { } elsif ( defined $user->organisation_id ) { - my $fullAddress = $validation->param('fulladdress'); $c->schema->txn_do( sub { $user->organisation->update({ diff --git a/t/api/register.t b/t/api/register.t index 40020ac..a1a20ec 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,29 @@ $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', }; $t->post_ok('/api/register' => json => $testJson) ->status_is(400) @@ -353,17 +352,17 @@ $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', }; $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'; From 4c7836c9f6f7ca4ab3c56071b45c1f9c51dd63eb Mon Sep 17 00:00:00 2001 From: Finn Date: Wed, 23 Aug 2017 16:50:04 +0100 Subject: [PATCH 14/33] Initial server code added for updating user account --- lib/Pear/LocalLoop.pm | 1 + lib/Pear/LocalLoop/Controller/Admin/Users.pm | 67 ++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/lib/Pear/LocalLoop.pm b/lib/Pear/LocalLoop.pm index 4813064..c41371d 100644 --- a/lib/Pear/LocalLoop.pm +++ b/lib/Pear/LocalLoop.pm @@ -169,6 +169,7 @@ 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'); diff --git a/lib/Pear/LocalLoop/Controller/Admin/Users.pm b/lib/Pear/LocalLoop/Controller/Admin/Users.pm index 2fbdd17..296a0cf 100644 --- a/lib/Pear/LocalLoop/Controller/Admin/Users.pm +++ b/lib/Pear/LocalLoop/Controller/Admin/Users.pm @@ -27,6 +27,73 @@ sub read { } } +sub edit { + my $c = shift; + + my $id = $c->param('id'); + + if ( my $user = $c->result_set->find($id) ) { + $c->stash( user => $user ); + } else { + $c->flash( error => 'No User found' ); + $c->redirect_to( '/admin/users/' . $id ); + } + + my $validation = $c->validation; + + $validation->required('email')->not_in_resultset( 'email', $user->id ); + $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'); + } + + 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 ){ + + $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') ) : () ), + }); + }); + + } + elsif ( defined $user->organisation_id ) { + + $c->schema->txn_do( sub { + $user->organisation->update({ + name => $validation->param('name'), + street_name => $validation->param('street_name'), + town => $validation->param('town'), + postcode => $validation->param('postcode'), + }); + $user->update({ + email => $validation->param('email'), + ( defined $validation->param('new_password') ? ( password => $validation->param('new_password') ) : () ), + }); + }); + } + + $c->redirect_to( '/admin/users/' . $id ); +} + sub update { my $c = shift; $c->redirect_to( '/admin/users' ); From 9865130666757adc4b70b4fc2b11c88f9e9c43a2 Mon Sep 17 00:00:00 2001 From: Finn Date: Wed, 23 Aug 2017 18:42:19 +0100 Subject: [PATCH 15/33] Added user account stuff test and some fixes --- lib/Pear/LocalLoop/Controller/Admin/Users.pm | 78 +++++++++------ t/admin/user.t | 99 ++++++++++++++++++++ 2 files changed, 149 insertions(+), 28 deletions(-) create mode 100644 t/admin/user.t diff --git a/lib/Pear/LocalLoop/Controller/Admin/Users.pm b/lib/Pear/LocalLoop/Controller/Admin/Users.pm index 296a0cf..d48e259 100644 --- a/lib/Pear/LocalLoop/Controller/Admin/Users.pm +++ b/lib/Pear/LocalLoop/Controller/Admin/Users.pm @@ -1,6 +1,8 @@ package Pear::LocalLoop::Controller::Admin::Users; use Mojo::Base 'Mojolicious::Controller'; +use Data::Dumper; + has result_set => sub { my $c = shift; return $c->schema->resultset('User'); @@ -32,16 +34,19 @@ sub edit { my $id = $c->param('id'); - if ( my $user = $c->result_set->find($id) ) { - $c->stash( user => $user ); - } else { + my $user; + + unless ( $user = $c->result_set->find($id) ) { $c->flash( error => 'No User found' ); - $c->redirect_to( '/admin/users/' . $id ); + return $c->redirect_to( '/admin/users/' . $id ); } my $validation = $c->validation; - $validation->required('email')->not_in_resultset( 'email', $user->id ); + my $not_myself_user_rs = $c->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'); @@ -62,34 +67,51 @@ sub edit { if ( defined $user->customer_id ){ - $c->schema->txn_do( sub { - $user->customer->update({ - full_name => $validation->param('full_name'), - display_name => $validation->param('display_name'), - postcode => $validation->param('postcode'), + 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') ) : () ), + }); }); - $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 ) { - $c->schema->txn_do( sub { - $user->organisation->update({ - name => $validation->param('name'), - street_name => $validation->param('street_name'), - town => $validation->param('town'), - postcode => $validation->param('postcode'), + try { + $c->schema->txn_do( sub { + $user->organisation->update({ + name => $validation->param('name'), + street_name => $validation->param('street_name'), + town => $validation->param('town'), + postcode => $validation->param('postcode'), + }); + $user->update({ + email => $validation->param('email'), + ( defined $validation->param('new_password') ? ( password => $validation->param('new_password') ) : () ), + }); }); - $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 ); } diff --git a/t/admin/user.t b/t/admin/user.t new file mode 100644 index 0000000..86386e0 --- /dev/null +++ b/t/admin/user.t @@ -0,0 +1,99 @@ +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', + 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/1/') + ->status_is(200); + +#Read organisation user +$t->get_ok('/admin/users/2/') + ->status_is(200); + +#Valid organisation user update +$t->post_ok('/admin/users/1/edit', form => { + email => 'test51@example.com', + new_password => 'abc123', + name => '7th Heaven', + street_name => 'Slums, Sector 7', + town => 'Midgar', + postcode => 'WC1E 6AD', +})->status_is(200)->content_like(qr/Updated User/); + +#Failed validation on organisation user from wrong email +$t->post_ok('/admin/users/1/edit', form => { + email => 'test55@example.com', + new_password => 'abc123', + name => '7th Heaven', + street_name => 'Slums, Sector 7', + town => 'Midgar', + postcode => 'WC1E 6AD', +})->content_like(qr/The validation has failed/); + +#Failed validation on organisation user from no postcode +$t->post_ok('/admin/users/1/edit', form => { + email => 'test50@example.com', + new_password => 'abc123', + name => '7th Heaven', + street_name => 'Slums, Sector 7', + town => 'Midgar', +})->content_like(qr/The validation has failed/); + +#Failed validation on organisation user from no street name +$t->post_ok('/admin/users/1/edit', form => { + email => 'test50@example.com', + new_password => 'abc123', + name => '7th Heaven', + town => 'Midgar', + postcode => 'WC1E 6AD', +})->content_like(qr/The validation has failed/); + +done_testing(); From db45f710c327186ab62ad5a40344dca20bc9b911 Mon Sep 17 00:00:00 2001 From: Finn Date: Thu, 24 Aug 2017 12:37:53 +0100 Subject: [PATCH 16/33] Code fixed, now functioning account edit --- lib/Pear/LocalLoop/Controller/Admin/Users.pm | 21 +++-- templates/admin/users/read.html.ep | 85 ++++++++++++++++---- 2 files changed, 85 insertions(+), 21 deletions(-) diff --git a/lib/Pear/LocalLoop/Controller/Admin/Users.pm b/lib/Pear/LocalLoop/Controller/Admin/Users.pm index d48e259..2fbc110 100644 --- a/lib/Pear/LocalLoop/Controller/Admin/Users.pm +++ b/lib/Pear/LocalLoop/Controller/Admin/Users.pm @@ -1,17 +1,28 @@ package Pear::LocalLoop::Controller::Admin::Users; use Mojo::Base 'Mojolicious::Controller'; +use Try::Tiny; use Data::Dumper; -has result_set => sub { +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 ] ); } @@ -21,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' ); @@ -36,14 +47,14 @@ sub edit { my $user; - unless ( $user = $c->result_set->find($id) ) { + 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->result_set->search({ + 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 ); diff --git a/templates/admin/users/read.html.ep b/templates/admin/users/read.html.ep index eaca969..9cf6740 100644 --- a/templates/admin/users/read.html.ep +++ b/templates/admin/users/read.html.ep @@ -11,20 +11,73 @@ Success! <%= $success %>
      % } -
      -
      - - -
      -
      - - -
      -
      - - -
      -
      - -
      + +

      + User Details +

      +
      + + +
      +
      + + +
      +
      + + +
      +
      + + +
      + % 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 +

      + % } + +
      + +
      From 7c25ecccfdce93a88595d50f1f3fa891a7c850e5 Mon Sep 17 00:00:00 2001 From: Finn Date: Thu, 24 Aug 2017 12:48:32 +0100 Subject: [PATCH 17/33] Entry field and test fixed --- t/admin/user.t | 52 ++++++++++++++++++++---------- templates/admin/users/read.html.ep | 2 +- 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/t/admin/user.t b/t/admin/user.t index 86386e0..a531600 100644 --- a/t/admin/user.t +++ b/t/admin/user.t @@ -51,15 +51,43 @@ $t->post_ok('/admin', form => { })->status_is(200); #Read customer user -$t->get_ok('/admin/users/1/') - ->status_is(200); - -#Read organisation 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', + 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', +})->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', +})->content_like(qr/The validation has failed/); + #Valid organisation user update -$t->post_ok('/admin/users/1/edit', form => { +$t->post_ok('/admin/users/3/edit', form => { email => 'test51@example.com', new_password => 'abc123', name => '7th Heaven', @@ -68,18 +96,8 @@ $t->post_ok('/admin/users/1/edit', form => { postcode => 'WC1E 6AD', })->status_is(200)->content_like(qr/Updated User/); -#Failed validation on organisation user from wrong email -$t->post_ok('/admin/users/1/edit', form => { - email => 'test55@example.com', - new_password => 'abc123', - name => '7th Heaven', - street_name => 'Slums, Sector 7', - town => 'Midgar', - postcode => 'WC1E 6AD', -})->content_like(qr/The validation has failed/); - #Failed validation on organisation user from no postcode -$t->post_ok('/admin/users/1/edit', form => { +$t->post_ok('/admin/users/3/edit', form => { email => 'test50@example.com', new_password => 'abc123', name => '7th Heaven', @@ -88,7 +106,7 @@ $t->post_ok('/admin/users/1/edit', form => { })->content_like(qr/The validation has failed/); #Failed validation on organisation user from no street name -$t->post_ok('/admin/users/1/edit', form => { +$t->post_ok('/admin/users/3/edit', form => { email => 'test50@example.com', new_password => 'abc123', name => '7th Heaven', diff --git a/templates/admin/users/read.html.ep b/templates/admin/users/read.html.ep index 9cf6740..c0463c3 100644 --- a/templates/admin/users/read.html.ep +++ b/templates/admin/users/read.html.ep @@ -49,7 +49,7 @@
      - +
      % } elsif ( my $org_rs = $user->organisation ) {

      From b5feae5f068d6b4057f16b393d83c32c8d5cddd4 Mon Sep 17 00:00:00 2001 From: Finn Date: Thu, 24 Aug 2017 13:01:11 +0100 Subject: [PATCH 18/33] Disabled some entries and attempted autocomplete removal --- templates/admin/users/read.html.ep | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/templates/admin/users/read.html.ep b/templates/admin/users/read.html.ep index c0463c3..202efed 100644 --- a/templates/admin/users/read.html.ep +++ b/templates/admin/users/read.html.ep @@ -11,25 +11,26 @@ Success! <%= $success %>

% } -
+

User Details

- +
- +
- +
- + +

Leave blank unless you want to change their password

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

From e886eb2ce6903b3aa54615b2721cd2911df38919 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Fri, 25 Aug 2017 14:44:36 +0100 Subject: [PATCH 19/33] Remove explicit undef returns --- lib/Pear/LocalLoop.pm | 2 +- lib/Pear/LocalLoop/Controller/Api/Auth.pm | 2 +- lib/Pear/LocalLoop/Controller/Api/User.pm | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Pear/LocalLoop.pm b/lib/Pear/LocalLoop.pm index c41371d..3576c48 100644 --- a/lib/Pear/LocalLoop.pm +++ b/lib/Pear/LocalLoop.pm @@ -50,7 +50,7 @@ sub startup { return $user->id; } } - return undef; + return; }, }); diff --git a/lib/Pear/LocalLoop/Controller/Api/Auth.pm b/lib/Pear/LocalLoop/Controller/Api/Auth.pm index d64bf09..41e3b1e 100644 --- a/lib/Pear/LocalLoop/Controller/Api/Auth.pm +++ b/lib/Pear/LocalLoop/Controller/Api/Auth.pm @@ -86,7 +86,7 @@ sub post_login { } elsif ( defined $user_result->organisation_id ) { $display_name = $user_result->organisation->name; } else { - return undef; + return; } return $c->render( json => { diff --git a/lib/Pear/LocalLoop/Controller/Api/User.pm b/lib/Pear/LocalLoop/Controller/Api/User.pm index 12ef04b..65f380c 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 => { From 0a9ca576892e12cd9f672ad16c3b64dd5115b36d Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Fri, 25 Aug 2017 14:59:01 +0100 Subject: [PATCH 20/33] Added user type attribute --- lib/Pear/LocalLoop/Schema/Result/User.pm | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) 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; From 4a4c568fb78ba5fe1295f33536258e6509f8860b Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Fri, 25 Aug 2017 14:59:15 +0100 Subject: [PATCH 21/33] Return user type on login --- lib/Pear/LocalLoop/Controller/Api/Auth.pm | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/lib/Pear/LocalLoop/Controller/Api/Auth.pm b/lib/Pear/LocalLoop/Controller/Api/Auth.pm index 41e3b1e..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; - } 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, }); } } From 768cc38410dad92a4fdfd1835daaaa13d6b5783e Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Fri, 25 Aug 2017 15:14:43 +0100 Subject: [PATCH 22/33] Refactor api login test --- t/api/login.t | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/t/api/login.t b/t/api/login.t index 1521378..e2fd731 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,8 +42,8 @@ $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) From deba59dc00ed42bd918c263cc0958318e33e87f5 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Fri, 25 Aug 2017 15:17:12 +0100 Subject: [PATCH 23/33] Add test for showing display name and user type correctly --- t/api/login.t | 2 ++ 1 file changed, 2 insertions(+) diff --git a/t/api/login.t b/t/api/login.t index e2fd731..08cf959 100644 --- a/t/api/login.t +++ b/t/api/login.t @@ -47,6 +47,8 @@ $t->post_ok('/api/login' => json => { }) ->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}; From ff263374ec1b9611294997f811c2dff91e12b817 Mon Sep 17 00:00:00 2001 From: Finn Date: Fri, 25 Aug 2017 15:25:52 +0100 Subject: [PATCH 24/33] Added endpoints for organisation sector --- lib/Pear/LocalLoop/Controller/Admin/Organisations.pm | 4 ++++ lib/Pear/LocalLoop/Controller/Admin/Users.pm | 2 ++ lib/Pear/LocalLoop/Controller/Api/Register.pm | 2 ++ lib/Pear/LocalLoop/Controller/Api/User.pm | 2 ++ lib/Pear/LocalLoop/Schema/Result/Organisation.pm | 4 ++++ templates/admin/organisations/add_org.html.ep | 1 + templates/admin/organisations/valid_read.html.ep | 1 + templates/admin/users/read.html.ep | 4 ++++ 8 files changed, 20 insertions(+) diff --git a/lib/Pear/LocalLoop/Controller/Admin/Organisations.pm b/lib/Pear/LocalLoop/Controller/Admin/Organisations.pm index 178d9f7..4492b36 100644 --- a/lib/Pear/LocalLoop/Controller/Admin/Organisations.pm +++ b/lib/Pear/LocalLoop/Controller/Admin/Organisations.pm @@ -28,6 +28,7 @@ sub add_org_submit { $validation->required('name'); $validation->optional('street_name'); $validation->required('town'); + $validation->optional('sector'); $validation->optional('postcode')->postcode; if ( $validation->has_error ) { @@ -43,6 +44,7 @@ sub add_org_submit { name => $validation->param('name'), street_name => $validation->param('street_name'), town => $validation->param('town'), + sector => $validation->param('sector'), postcode => $validation->param('postcode'), }); } finally { @@ -78,6 +80,7 @@ sub valid_edit { $validation->required('name'); $validation->required('street_name'); $validation->required('town'); + $validation->optional('sector'); $validation->required('postcode')->postcode; if ( $validation->has_error ) { @@ -94,6 +97,7 @@ sub valid_edit { name => $validation->param('name'), street_name => $validation->param('street_name'), town => $validation->param('town'), + sector => $validation->param('sector'), postcode => $validation->param('postcode'), }); } ); diff --git a/lib/Pear/LocalLoop/Controller/Admin/Users.pm b/lib/Pear/LocalLoop/Controller/Admin/Users.pm index 2fbc110..064ffba 100644 --- a/lib/Pear/LocalLoop/Controller/Admin/Users.pm +++ b/lib/Pear/LocalLoop/Controller/Admin/Users.pm @@ -68,6 +68,7 @@ sub edit { $validation->required('name'); $validation->required('street_name'); $validation->required('town'); + $validation->optional('sector'); } if ( $validation->has_error ) { @@ -107,6 +108,7 @@ sub edit { 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/Register.pm b/lib/Pear/LocalLoop/Controller/Api/Register.pm index da6805a..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; @@ -112,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/User.pm b/lib/Pear/LocalLoop/Controller/Api/User.pm index 12ef04b..fce779a 100644 --- a/lib/Pear/LocalLoop/Controller/Api/User.pm +++ b/lib/Pear/LocalLoop/Controller/Api/User.pm @@ -128,6 +128,7 @@ sub post_account_update { $validation->required('name'); $validation->required('street_name'); $validation->required('town'); + $validation->required('sector'); } return $c->api_validation_error if $validation->has_error; @@ -154,6 +155,7 @@ sub post_account_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/Schema/Result/Organisation.pm b/lib/Pear/LocalLoop/Schema/Result/Organisation.pm index e5a30a3..f9f6348 100644 --- a/lib/Pear/LocalLoop/Schema/Result/Organisation.pm +++ b/lib/Pear/LocalLoop/Schema/Result/Organisation.pm @@ -34,6 +34,10 @@ __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/templates/admin/organisations/add_org.html.ep b/templates/admin/organisations/add_org.html.ep index 07cf699..3e45d51 100644 --- a/templates/admin/organisations/add_org.html.ep +++ b/templates/admin/organisations/add_org.html.ep @@ -21,6 +21,7 @@
  • +
  • diff --git a/templates/admin/organisations/valid_read.html.ep b/templates/admin/organisations/valid_read.html.ep index 9fd3e97..12d67a6 100644 --- a/templates/admin/organisations/valid_read.html.ep +++ b/templates/admin/organisations/valid_read.html.ep @@ -20,6 +20,7 @@
  • +
  • diff --git a/templates/admin/users/read.html.ep b/templates/admin/users/read.html.ep index 202efed..8e6cb47 100644 --- a/templates/admin/users/read.html.ep +++ b/templates/admin/users/read.html.ep @@ -72,6 +72,10 @@ +
    + + +
    % } else {

    User is not a customer or an organisation From ef2e0627e19ad6b48ee15f32d2313234e8c1abfb Mon Sep 17 00:00:00 2001 From: Finn Date: Fri, 25 Aug 2017 15:32:39 +0100 Subject: [PATCH 25/33] Fixes and schema update stuff --- lib/Pear/LocalLoop/Schema.pm | 2 +- .../LocalLoop/Schema/Result/Organisation.pm | 1 + .../deploy/5/001-auto-__VERSION.sql | 18 + share/ddl/PostgreSQL/deploy/5/001-auto.sql | 250 ++++ share/ddl/PostgreSQL/upgrade/4-5/001-auto.sql | 12 + .../SQLite/deploy/5/001-auto-__VERSION.sql | 18 + share/ddl/SQLite/deploy/5/001-auto.sql | 170 +++ share/ddl/SQLite/upgrade/4-5/001-auto.sql | 12 + .../_source/deploy/5/001-auto-__VERSION.yml | 91 ++ share/ddl/_source/deploy/5/001-auto.yml | 1257 +++++++++++++++++ t/admin/user.t | 7 + 11 files changed, 1837 insertions(+), 1 deletion(-) create mode 100644 share/ddl/PostgreSQL/deploy/5/001-auto-__VERSION.sql create mode 100644 share/ddl/PostgreSQL/deploy/5/001-auto.sql create mode 100644 share/ddl/PostgreSQL/upgrade/4-5/001-auto.sql create mode 100644 share/ddl/SQLite/deploy/5/001-auto-__VERSION.sql create mode 100644 share/ddl/SQLite/deploy/5/001-auto.sql create mode 100644 share/ddl/SQLite/upgrade/4-5/001-auto.sql create mode 100644 share/ddl/_source/deploy/5/001-auto-__VERSION.yml create mode 100644 share/ddl/_source/deploy/5/001-auto.yml 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/Organisation.pm b/lib/Pear/LocalLoop/Schema/Result/Organisation.pm index f9f6348..5967644 100644 --- a/lib/Pear/LocalLoop/Schema/Result/Organisation.pm +++ b/lib/Pear/LocalLoop/Schema/Result/Organisation.pm @@ -38,6 +38,7 @@ __PACKAGE__->add_columns( data_type => "varchar", size => 1, is_nullable => 1, + }, ); __PACKAGE__->set_primary_key('id'); 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/user.t b/t/admin/user.t index a531600..129dce6 100644 --- a/t/admin/user.t +++ b/t/admin/user.t @@ -32,6 +32,7 @@ my $org = { name => '7th Heaven', street_name => 'Slums, Sector 7', town => 'Midgar', + sector => 'A', postcode => 'WC1E 6AD', password => 'abc123', }; @@ -65,6 +66,7 @@ $t->post_ok('/admin/users/2/edit', form => { full_name => 'Test User1', display_name => 'Test User1', town => 'Midgar', + sector => 'A', postcode => 'WC1E 6AD', })->status_is(200)->content_like(qr/Updated User/); @@ -75,6 +77,7 @@ $t->post_ok('/admin/users/2/edit', form => { 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 @@ -84,6 +87,7 @@ $t->post_ok('/admin/users/2/edit', form => { full_name => 'Test User1', town => 'Midgar', postcode => 'WC1E 6AD', + sector => 'A', })->content_like(qr/The validation has failed/); #Valid organisation user update @@ -93,6 +97,7 @@ $t->post_ok('/admin/users/3/edit', form => { name => '7th Heaven', street_name => 'Slums, Sector 7', town => 'Midgar', + sector => 'A', postcode => 'WC1E 6AD', })->status_is(200)->content_like(qr/Updated User/); @@ -103,6 +108,7 @@ $t->post_ok('/admin/users/3/edit', form => { 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 @@ -111,6 +117,7 @@ $t->post_ok('/admin/users/3/edit', form => { new_password => 'abc123', name => '7th Heaven', town => 'Midgar', + sector => 'A', postcode => 'WC1E 6AD', })->content_like(qr/The validation has failed/); From 6cdc8891c30c88471b9d3a7be7a4537a0c637146 Mon Sep 17 00:00:00 2001 From: Finn Date: Fri, 25 Aug 2017 15:47:54 +0100 Subject: [PATCH 26/33] Tests fixed --- t/admin/organisation.t | 4 ++++ t/api/register.t | 2 ++ t/api/search.t | 1 + t/api/stats.t | 1 + t/api/stats_leaderboards.t | 1 + t/api/upload.t | 1 + t/schema/leaderboard.t | 1 + t/schema/leaderboard_trend.t | 1 + t/schema/resultset_leaderboard.t | 1 + 9 files changed, 13 insertions(+) diff --git a/t/admin/organisation.t b/t/admin/organisation.t index 720415a..8ac7af6 100644 --- a/t/admin/organisation.t +++ b/t/admin/organisation.t @@ -21,6 +21,7 @@ $schema->resultset('Organisation')->create({ name => 'Shinra Electric Power Company', street_name => 'Sector 0, Midgar, Eastern Continent', town => 'Gaia', + sector => 'A', postcode => 'WC1E 6AD', }); @@ -53,6 +54,7 @@ $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/); @@ -60,6 +62,7 @@ $t->post_ok('/admin/organisations/valid/1/edit', form => { $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/); @@ -83,6 +86,7 @@ $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/); diff --git a/t/api/register.t b/t/api/register.t index a1a20ec..203cde4 100644 --- a/t/api/register.t +++ b/t/api/register.t @@ -342,6 +342,7 @@ $testJson = { 'email' => 'org@org.com', 'postcode' => 'LA1 1AA', 'password' => 'Meh', + 'sector' => 'A', }; $t->post_ok('/api/register' => json => $testJson) ->status_is(400) @@ -360,6 +361,7 @@ $testJson = { 'password' => 'Meh', 'street_name' => 'mary lane testing....', 'town' => 'Lancaster', + 'sector' => 'A', }; $t->post_ok('/api/register' => json => $testJson) ->status_is(200) 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..72020d3 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) diff --git a/t/schema/leaderboard.t b/t/schema/leaderboard.t index 5d09595..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} }) 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 c1ab6b1..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} }) From cc046f517bc633fa5fd099544d99591dbc1635c0 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Fri, 25 Aug 2017 16:36:43 +0100 Subject: [PATCH 27/33] Added users fixture set --- t/etc/fixtures/config/users.pl | 97 +++++++++++++++++++ t/etc/fixtures/data/users/_config_set | 65 +++++++++++++ t/etc/fixtures/data/users/_dumper_version | 1 + t/etc/fixtures/data/users/customers/1.fix | 7 ++ t/etc/fixtures/data/users/customers/2.fix | 7 ++ t/etc/fixtures/data/users/customers/3.fix | 7 ++ t/etc/fixtures/data/users/customers/4.fix | 7 ++ t/etc/fixtures/data/users/leaderboards/1.fix | 5 + t/etc/fixtures/data/users/leaderboards/2.fix | 5 + t/etc/fixtures/data/users/leaderboards/3.fix | 5 + t/etc/fixtures/data/users/leaderboards/4.fix | 5 + t/etc/fixtures/data/users/leaderboards/5.fix | 5 + t/etc/fixtures/data/users/leaderboards/6.fix | 5 + t/etc/fixtures/data/users/leaderboards/7.fix | 5 + t/etc/fixtures/data/users/leaderboards/8.fix | 5 + t/etc/fixtures/data/users/organisations/1.fix | 10 ++ t/etc/fixtures/data/users/users/1.fix | 11 +++ t/etc/fixtures/data/users/users/2.fix | 11 +++ t/etc/fixtures/data/users/users/3.fix | 11 +++ t/etc/fixtures/data/users/users/4.fix | 11 +++ t/etc/fixtures/data/users/users/5.fix | 11 +++ 21 files changed, 296 insertions(+) create mode 100644 t/etc/fixtures/config/users.pl create mode 100644 t/etc/fixtures/data/users/_config_set create mode 100644 t/etc/fixtures/data/users/_dumper_version create mode 100644 t/etc/fixtures/data/users/customers/1.fix create mode 100644 t/etc/fixtures/data/users/customers/2.fix create mode 100644 t/etc/fixtures/data/users/customers/3.fix create mode 100644 t/etc/fixtures/data/users/customers/4.fix create mode 100644 t/etc/fixtures/data/users/leaderboards/1.fix create mode 100644 t/etc/fixtures/data/users/leaderboards/2.fix create mode 100644 t/etc/fixtures/data/users/leaderboards/3.fix create mode 100644 t/etc/fixtures/data/users/leaderboards/4.fix create mode 100644 t/etc/fixtures/data/users/leaderboards/5.fix create mode 100644 t/etc/fixtures/data/users/leaderboards/6.fix create mode 100644 t/etc/fixtures/data/users/leaderboards/7.fix create mode 100644 t/etc/fixtures/data/users/leaderboards/8.fix create mode 100644 t/etc/fixtures/data/users/organisations/1.fix create mode 100644 t/etc/fixtures/data/users/users/1.fix create mode 100644 t/etc/fixtures/data/users/users/2.fix create mode 100644 t/etc/fixtures/data/users/users/3.fix create mode 100644 t/etc/fixtures/data/users/users/4.fix create mode 100644 t/etc/fixtures/data/users/users/5.fix 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' + }; From 7ed2636978a18fd670a63b733ac741a86436efd0 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Fri, 25 Aug 2017 17:12:12 +0100 Subject: [PATCH 28/33] Added fixture installer to main fixtures set --- lib/Test/Pear/LocalLoop.pm | 63 +++++++++++++++++++++++++++++--------- 1 file changed, 48 insertions(+), 15 deletions(-) diff --git a/lib/Test/Pear/LocalLoop.pm b/lib/Test/Pear/LocalLoop.pm index 12758fe..2780c1d 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; @@ -106,4 +124,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; From 3ae8a43df46d81a111cf1fa2e69eda7a59bc4ce3 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Tue, 29 Aug 2017 12:42:27 +0100 Subject: [PATCH 29/33] Added organisation graph endpoint and tests --- lib/Pear/LocalLoop.pm | 6 ++ .../Controller/Api/V1/Organisation.pm | 21 +++++ .../Controller/Api/V1/Organisation/Graphs.pm | 87 ++++++++++++++++++ lib/Test/Pear/LocalLoop.pm | 10 +++ t/api/v1/organisation/graphs.t | 90 +++++++++++++++++++ 5 files changed, 214 insertions(+) create mode 100644 lib/Pear/LocalLoop/Controller/Api/V1/Organisation.pm create mode 100644 lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Graphs.pm create mode 100644 t/api/v1/organisation/graphs.t diff --git a/lib/Pear/LocalLoop.pm b/lib/Pear/LocalLoop.pm index 3576c48..f06b1c0 100644 --- a/lib/Pear/LocalLoop.pm +++ b/lib/Pear/LocalLoop.pm @@ -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'); 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/Test/Pear/LocalLoop.pm b/lib/Test/Pear/LocalLoop.pm index 2780c1d..5fb6fae 100644 --- a/lib/Test/Pear/LocalLoop.pm +++ b/lib/Test/Pear/LocalLoop.pm @@ -109,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 ) = @_; diff --git a/t/api/v1/organisation/graphs.t b/t/api/v1/organisation/graphs.t new file mode 100644 index 0000000..b9d026a --- /dev/null +++ b/t/api/v1/organisation/graphs.t @@ -0,0 +1,90 @@ +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', +}); +use Devel::Dwarn; + +$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; From 3bb18103c9b6371300d100cc31eabfacd4ae2da2 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Tue, 29 Aug 2017 12:43:13 +0100 Subject: [PATCH 30/33] Remove debug items from graphs test --- t/api/v1/organisation/graphs.t | 1 - 1 file changed, 1 deletion(-) diff --git a/t/api/v1/organisation/graphs.t b/t/api/v1/organisation/graphs.t index b9d026a..d6885f3 100644 --- a/t/api/v1/organisation/graphs.t +++ b/t/api/v1/organisation/graphs.t @@ -37,7 +37,6 @@ my $session_key = $framework->login({ email => 'org@example.com', password => 'abc123', }); -use Devel::Dwarn; $t->post_ok('/api/v1/organisation/graphs' => json => { session_key => $session_key, From dac0061a0cc8efffdaf911904b8bf24fe6eb24a8 Mon Sep 17 00:00:00 2001 From: Finn Date: Wed, 30 Aug 2017 17:09:51 +0100 Subject: [PATCH 31/33] Added Selection for Sector Code --- templates/admin/organisations/add_org.html.ep | 6 +++++- .../admin/organisations/valid_read.html.ep | 6 +++++- templates/partials/sector_options.html.ep | 21 +++++++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 templates/partials/sector_options.html.ep diff --git a/templates/admin/organisations/add_org.html.ep b/templates/admin/organisations/add_org.html.ep index 3e45d51..6e7702e 100644 --- a/templates/admin/organisations/add_org.html.ep +++ b/templates/admin/organisations/add_org.html.ep @@ -21,7 +21,11 @@
  • -
  • +
  • + +
  • diff --git a/templates/admin/organisations/valid_read.html.ep b/templates/admin/organisations/valid_read.html.ep index 12d67a6..6a9f38e 100644 --- a/templates/admin/organisations/valid_read.html.ep +++ b/templates/admin/organisations/valid_read.html.ep @@ -20,7 +20,11 @@
  • -
  • +
  • + +
  • 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 @@ + + + + + + + + + + + + + + + + + + + + + From 72813ff2a3b334912243f243e215ca1bab1c6803 Mon Sep 17 00:00:00 2001 From: Finn Date: Thu, 31 Aug 2017 11:41:38 +0100 Subject: [PATCH 32/33] Made image upload optional --- lib/Pear/LocalLoop/Controller/Api/Upload.pm | 6 +++--- t/api/upload.t | 15 ++++++++------- 2 files changed, 11 insertions(+), 10 deletions(-) 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/t/api/upload.t b/t/api/upload.t index 72020d3..ca925cf 100644 --- a/t/api/upload.t +++ b/t/api/upload.t @@ -202,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 = { @@ -235,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, @@ -248,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. @@ -445,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, @@ -458,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(); From 4189025c6f5cccec535429be9b48ba7b49b811d0 Mon Sep 17 00:00:00 2001 From: Finn Date: Thu, 31 Aug 2017 11:51:22 +0100 Subject: [PATCH 33/33] Added timings and descending submit at time for receipts --- lib/Pear/LocalLoop/Controller/Admin/Organisations.pm | 1 + templates/admin/organisations/valid_read.html.ep | 2 ++ 2 files changed, 3 insertions(+) diff --git a/lib/Pear/LocalLoop/Controller/Admin/Organisations.pm b/lib/Pear/LocalLoop/Controller/Admin/Organisations.pm index 4492b36..f382a5e 100644 --- a/lib/Pear/LocalLoop/Controller/Admin/Organisations.pm +++ b/lib/Pear/LocalLoop/Controller/Admin/Organisations.pm @@ -65,6 +65,7 @@ sub valid_read { undef, { page => $c->param('page') || 1, rows => 10, + order_by => { -desc => 'submitted_at' }, }, ); $c->stash( diff --git a/templates/admin/organisations/valid_read.html.ep b/templates/admin/organisations/valid_read.html.ep index 6a9f38e..ffb7bb2 100644 --- a/templates/admin/organisations/valid_read.html.ep +++ b/templates/admin/organisations/valid_read.html.ep @@ -42,6 +42,8 @@
    From: <%= $transaction->buyer->name %>
    To: <%= $transaction->seller->name %>
    Value: <%= $transaction->value %>
    +
    Submitted At: <%= $transaction->submitted_at %>
    +
    Purchase Time: <%= $transaction->purchase_time %>