Merge branch 'development'

This commit is contained in:
Tom Bloor 2017-08-31 15:32:57 +01:00
commit 5d425df4f8
74 changed files with 3559 additions and 224 deletions

View file

@ -20,3 +20,4 @@ requires 'Try::Tiny';
requires 'MooX::Options::Actions';
requires 'Module::Runtime';
requires 'DBIx::Class::DeploymentHandler';
requires 'DBIx::Class::Fixtures';

View file

@ -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

View file

@ -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.

16
doc/Leaderboards.md Normal file
View file

@ -0,0 +1,16 @@
# Leaderboards
## Calculation
The leaderboards are calculated for the previous range - so Daily leaderboards
are calculated for the whole of the day before, Weeks from the week before,
etc. - The only exception is all time, which is calculated from 00:00 on the
current day.
## Recalculation
Leaderboard recalculation only affects the latest two leaderboards for any set,
so just need to recalculate the last one and the current one, in that order.
This can be done during the regular leaderboard calculation cronjob, so
verified transactions will show up in the leaderboards the next day.

View file

@ -50,7 +50,7 @@ sub startup {
return $user->id;
}
}
return undef;
return;
},
});
@ -155,6 +155,12 @@ sub startup {
$api->post('/stats')->to('api-stats#post_index');
$api->post('/stats/leaderboard')->to('api-stats#post_leaderboards');
my $api_v1 = $api->under('/v1');
my $api_v1_org = $api_v1->under('/organisation')->to('api-v1-organisation#auth');
$api_v1_org->post('/graphs')->to('api-v1-organisation-graphs#index');
my $admin_routes = $r->under('/admin')->to('admin#under');
$admin_routes->get('/home')->to('admin#home');
@ -169,10 +175,15 @@ sub startup {
$admin_routes->get('/users/:id')->to('admin-users#read');
$admin_routes->post('/users/:id')->to('admin-users#update');
$admin_routes->post('/users/:id/delete')->to('admin-users#delete');
$admin_routes->post('/users/:id/edit')->to('admin-users#edit');
$admin_routes->get('/organisations')->to('admin-organisations#list');
$admin_routes->get('/organisations/add')->to('admin-organisations#add_org');
$admin_routes->post('/organisations/add/submit')->to('admin-organisations#add_org_submit');
$admin_routes->get('/organisations/valid/:id')->to('admin-organisations#valid_read');
$admin_routes->post('/organisations/valid/:id/edit')->to('admin-organisations#valid_edit');
$admin_routes->get('/organisations/pending/:id')->to('admin-organisations#pending_read');
$admin_routes->post('/organisations/pending/:id/edit')->to('admin-organisations#pending_edit');
$admin_routes->get('/organisations/pending/:id/approve')->to('admin-organisations#pending_approve');
$admin_routes->get('/feedback')->to('admin-feedback#index');

View file

@ -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;

View file

@ -2,6 +2,7 @@ package Pear::LocalLoop::Controller::Admin::Organisations;
use Mojo::Base 'Mojolicious::Controller';
use Try::Tiny;
use Data::Dumper;
sub list {
my $c = shift;
@ -15,6 +16,48 @@ sub list {
);
}
sub add_org {
my $c = shift;
}
sub add_org_submit {
my $c = shift;
my $validation = $c->validation;
$validation->required('name');
$validation->optional('street_name');
$validation->required('town');
$validation->optional('sector');
$validation->optional('postcode')->postcode;
if ( $validation->has_error ) {
$c->flash( error => 'The validation has failed' );
$c->app->log->warn(Dumper $validation);
return $c->redirect_to( '/admin/organisations/add/' );
}
my $organisation;
try {
$organisation = $c->schema->resultset('Organisation')->create({
name => $validation->param('name'),
street_name => $validation->param('street_name'),
town => $validation->param('town'),
sector => $validation->param('sector'),
postcode => $validation->param('postcode'),
});
} finally {
if ( @_ ) {
$c->flash( error => 'Something went wrong Adding the Organisation' );
$c->app->log->warn(Dumper @_);
} else {
$c->flash( success => 'Added Organisation' );
}
};
$c->redirect_to( '/admin/organisations/add/' );
}
sub valid_read {
my $c = shift;
my $valid_org = $c->schema->resultset('Organisation')->find( $c->param('id') );
@ -22,6 +65,7 @@ sub valid_read {
undef, {
page => $c->param('page') || 1,
rows => 10,
order_by => { -desc => 'submitted_at' },
},
);
$c->stash(
@ -30,6 +74,45 @@ sub valid_read {
);
}
sub valid_edit {
my $c = shift;
my $validation = $c->validation;
$validation->required('name');
$validation->required('street_name');
$validation->required('town');
$validation->optional('sector');
$validation->required('postcode')->postcode;
if ( $validation->has_error ) {
$c->flash( error => 'The validation has failed' );
$c->app->log->warn(Dumper $validation);
return $c->redirect_to( '/admin/organisations/valid/' . $c->param('id') );
}
my $valid_org = $c->schema->resultset('Organisation')->find( $c->param('id') );
try {
$c->schema->storage->txn_do( sub {
$valid_org->update({
name => $validation->param('name'),
street_name => $validation->param('street_name'),
town => $validation->param('town'),
sector => $validation->param('sector'),
postcode => $validation->param('postcode'),
});
} );
} finally {
if ( @_ ) {
$c->flash( error => 'Something went wrong Updating the Organisation' );
$c->app->log->warn(Dumper @_);
} else {
$c->flash( success => 'Updated Organisation' );
}
};
$c->redirect_to( '/admin/organisations/valid/' . $valid_org->id );
}
sub pending_read {
my $c = shift;
my $pending_org = $c->schema->resultset('PendingOrganisation')->find( $c->param('id') );
@ -45,6 +128,43 @@ sub pending_read {
);
}
sub pending_edit {
my $c = shift;
my $validation = $c->validation;
$validation->required('name');
$validation->required('street_name');
$validation->required('town');
$validation->required('postcode')->postcode;
if ( $validation->has_error ) {
$c->flash( error => 'The validation has failed' );
$c->app->log->warn(Dumper $validation);
return $c->redirect_to( '/admin/organisations/pending/' . $c->param('id') );
}
my $pending_org = $c->schema->resultset('PendingOrganisation')->find( $c->param('id') );
try {
$c->schema->storage->txn_do( sub {
$pending_org->update({
name => $validation->param('name'),
street_name => $validation->param('street_name'),
town => $validation->param('town'),
postcode => $validation->param('postcode'),
});
} );
} finally {
if ( @_ ) {
$c->flash( error => 'Something went wrong Updating the Organisation' );
$c->app->log->warn(Dumper @_);
} else {
$c->flash( success => 'Updated Organisation' );
}
};
$c->redirect_to( '/admin/organisations/pending/' . $pending_org->id );
}
sub pending_approve {
my $c = shift;
my $pending_org = $c->schema->resultset('PendingOrganisation')->find( $c->param('id') );

View file

@ -1,15 +1,28 @@
package Pear::LocalLoop::Controller::Admin::Users;
use Mojo::Base 'Mojolicious::Controller';
has result_set => sub {
use Try::Tiny;
use Data::Dumper;
has user_result_set => sub {
my $c = shift;
return $c->schema->resultset('User');
};
has customer_result_set => sub {
my $c = shift;
return $c->schema->resultset('Customer');
};
has organisation_result_set => sub {
my $c = shift;
return $c->schema->resultset('Organisation');
};
sub index {
my $c = shift;
my $user_rs = $c->result_set;
my $user_rs = $c->user_result_set;
$user_rs->result_class('DBIx::Class::ResultClass::HashRefInflator');
$c->stash( users => [ $user_rs->all ] );
}
@ -19,7 +32,7 @@ sub read {
my $id = $c->param('id');
if ( my $user = $c->result_set->find($id) ) {
if ( my $user = $c->user_result_set->find($id) ) {
$c->stash( user => $user );
} else {
$c->flash( error => 'No User found' );
@ -27,6 +40,95 @@ sub read {
}
}
sub edit {
my $c = shift;
my $id = $c->param('id');
my $user;
unless ( $user = $c->user_result_set->find($id) ) {
$c->flash( error => 'No User found' );
return $c->redirect_to( '/admin/users/' . $id );
}
my $validation = $c->validation;
my $not_myself_user_rs = $c->user_result_set->search({
id => { "!=" => $user->id },
});
$validation->required('email')->email->not_in_resultset( 'email', $not_myself_user_rs );
$validation->required('postcode')->postcode;
$validation->optional('new_password');
if ( defined $user->customer_id ) {
$validation->required('display_name');
$validation->required('full_name');
} elsif ( defined $user->organisation_id ) {
$validation->required('name');
$validation->required('street_name');
$validation->required('town');
$validation->optional('sector');
}
if ( $validation->has_error ) {
$c->flash( error => 'The validation has failed' );
$c->app->log->warn(Dumper $validation);
return $c->redirect_to( '/admin/users/' . $id );
}
if ( defined $user->customer_id ){
try {
$c->schema->txn_do( sub {
$user->customer->update({
full_name => $validation->param('full_name'),
display_name => $validation->param('display_name'),
postcode => $validation->param('postcode'),
});
$user->update({
email => $validation->param('email'),
( defined $validation->param('new_password') ? ( password => $validation->param('new_password') ) : () ),
});
});
} finally {
if ( @_ ) {
$c->flash( error => 'Something went wrong Updating the User' );
$c->app->log->warn(Dumper @_);
} else {
$c->flash( success => 'Updated User' );
};
}
}
elsif ( defined $user->organisation_id ) {
try {
$c->schema->txn_do( sub {
$user->organisation->update({
name => $validation->param('name'),
street_name => $validation->param('street_name'),
town => $validation->param('town'),
sector => $validation->param('sector'),
postcode => $validation->param('postcode'),
});
$user->update({
email => $validation->param('email'),
( defined $validation->param('new_password') ? ( password => $validation->param('new_password') ) : () ),
});
});
} finally {
if ( @_ ) {
$c->flash( error => 'Something went wrong Updating the User' );
$c->app->log->warn(Dumper @_);
} else {
$c->flash( success => 'Updated User' );
}
}
};
$c->redirect_to( '/admin/users/' . $id );
}
sub update {
my $c = shift;
$c->redirect_to( '/admin/users' );

View file

@ -79,20 +79,12 @@ sub post_login {
if ( defined $user_result ) {
if ( $user_result->check_password($password) ) {
my $session_key = $user_result->generate_session;
my $display_name;
if ( defined $user_result->customer_id ) {
$display_name = $user_result->customer->display_name;
} elsif ( defined $user_result->organisation_id ) {
$display_name = $user_result->organisation->name;
} else {
return undef;
}
return $c->render( json => {
success => Mojo::JSON->true,
session_key => $session_key,
display_name => $display_name,
display_name => $user_result->name,
user_type => $user_result->type,
});
}
}

View file

@ -75,6 +75,7 @@ sub post_register{
$validation->required('name');
$validation->required('street_name');
$validation->required('town');
$validation->required('sector');
}
return $c->api_validation_error if $validation->has_error;
@ -102,7 +103,6 @@ sub post_register{
}
elsif ($usertype eq 'organisation') {
my $fullAddress = $validation->param('fulladdress');
$c->schema->txn_do( sub {
$c->schema->resultset('AccountToken')->find({
@ -113,6 +113,7 @@ sub post_register{
name => $validation->param('name'),
street_name => $validation->param('street_name'),
town => $validation->param('town'),
sector => $validation->param('sector'),
postcode => $validation->param('postcode'),
});
$c->schema->resultset('User')->create({

View file

@ -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),
}
);

View file

@ -72,7 +72,7 @@ sub post_account {
} elsif ( defined $user_result->organisation_id ) {
$display_name = $user_result->organisation->name;
} else {
return undef;
return;
}
return $c->render( json => {
@ -124,10 +124,11 @@ sub post_account_update {
if ( defined $user->customer_id ) {
$validation->required('display_name');
$validation->required('full_name');
} elsif ( defined $user->customer_id ) {
} elsif ( defined $user->organisation_id ) {
$validation->required('name');
$validation->required('street_name');
$validation->required('town');
$validation->required('sector');
}
return $c->api_validation_error if $validation->has_error;
@ -148,13 +149,13 @@ sub post_account_update {
}
elsif ( defined $user->organisation_id ) {
my $fullAddress = $validation->param('fulladdress');
$c->schema->txn_do( sub {
$user->organisation->update({
name => $validation->param('name'),
street_name => $validation->param('street_name'),
town => $validation->param('town'),
sector => $validation->param('sector'),
postcode => $validation->param('postcode'),
});
$user->update({

View file

@ -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;

View file

@ -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;

View file

@ -6,7 +6,7 @@ use warnings;
use base 'DBIx::Class::Schema';
our $VERSION = 4;
our $VERSION = 5;
__PACKAGE__->load_namespaces;

View file

@ -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;

View file

@ -34,6 +34,11 @@ __PACKAGE__->add_columns(
size => 16,
is_nullable => 1,
},
sector => {
data_type => "varchar",
size => 1,
is_nullable => 1,
},
);
__PACKAGE__->set_primary_key('id');

View file

@ -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;

View file

@ -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;

View file

@ -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 ) = @_;

View file

@ -5,6 +5,7 @@ use Test::More;
use File::Temp;
use Test::Mojo;
use DateTime::Format::Strptime;
use DBIx::Class::Fixtures;
has config => sub {
my $file = File::Temp->new;
@ -21,30 +22,47 @@ END
return $file;
};
has framework => sub {
has mojo => sub {
my $self = shift;
$ENV{MOJO_CONFIG} = $self->config->filename;
my $t = Test::Mojo->new('Pear::LocalLoop');
my $schema = $t->app->schema;
$schema->deploy;
$schema->resultset('Leaderboard')->populate([
[ qw/ name type / ],
[ 'Daily Total', 'daily_total' ],
[ 'Daily Count', 'daily_count' ],
[ 'Weekly Total', 'weekly_total' ],
[ 'Weekly Count', 'weekly_count' ],
[ 'Monthly Total', 'monthly_total' ],
[ 'Monthly Count', 'monthly_count' ],
[ 'All Time Total', 'all_time_total' ],
[ 'All Time Count', 'all_time_count' ],
]);
$t->app->schema->deploy;
return $t;
};
has _deployed => sub { 0 };
sub framework {
my $self = shift;
my $no_populate = shift;
my $t = $self->mojo;
my $schema = $t->app->schema;
unless ( $no_populate || $self->_deployed ) {
$schema->resultset('Leaderboard')->populate([
[ qw/ name type / ],
[ 'Daily Total', 'daily_total' ],
[ 'Daily Count', 'daily_count' ],
[ 'Weekly Total', 'weekly_total' ],
[ 'Weekly Count', 'weekly_count' ],
[ 'Monthly Total', 'monthly_total' ],
[ 'Monthly Count', 'monthly_count' ],
[ 'All Time Total', 'all_time_total' ],
[ 'All Time Count', 'all_time_count' ],
]);
}
$self->_deployed(1);
return $t;
};
has etc_dir => sub { die "etc dir not set" };
sub dump_error {
return sub {
my $self = shift;
@ -91,6 +109,16 @@ sub login {
return $self->framework->tx->res->json->{session_key};
}
sub logout {
my $self = shift;
my $session_key = shift;
$self->framework->post_ok('/api/logout' => json => { session_key => $session_key })
->status_is(200)
->json_is('/success', Mojo::JSON->true)
->json_like('/message', qr/Logged Out/);
}
sub gen_upload {
my ( $self, $args ) = @_;
@ -106,4 +134,19 @@ sub gen_upload {
};
}
sub install_fixtures {
my ( $self, $fixture_name ) = @_;
my $fixtures = DBIx::Class::Fixtures->new({
config_dir => File::Spec->catdir( $self->etc_dir, 'fixtures', 'config'),
});
my $t = $self->framework(1);
$fixtures->populate({
directory => File::Spec->catdir( $self->etc_dir, 'fixtures', 'data', $fixture_name ),
no_deploy => 1,
schema => $t->app->schema,
});
}
1;

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,5 @@
#! /bin/bash
eval $(perl -I ~/perl5/lib/perl5/ -Mlocal::lib)
MOJO_MODE=production ./script/pear-local_loop recalc_leaderboards

View file

@ -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

View file

@ -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")
);
;

View file

@ -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;
;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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

File diff suppressed because it is too large Load diff

100
t/admin/organisation.t Normal file
View file

@ -0,0 +1,100 @@
use Mojo::Base -strict;
use Test::More;
use Mojo::JSON;
use Test::Pear::LocalLoop;
my $framework = Test::Pear::LocalLoop->new;
my $t = $framework->framework;
my $schema = $t->app->schema;
my $user = $schema->resultset('User')->create({
email => 'admin@example.com',
password => 'abc123',
administrator => {},
});
is $schema->resultset('Administrator')->count, 1, 'Admin Created';
$schema->resultset('Organisation')->create({
id => 1,
name => 'Shinra Electric Power Company',
street_name => 'Sector 0, Midgar, Eastern Continent',
town => 'Gaia',
sector => 'A',
postcode => 'WC1E 6AD',
});
$schema->resultset('PendingOrganisation')->create({
id => 2,
name => '7th Heaven',
street_name => 'Slums, Sector 7',
town => 'Midgar',
postcode => 'WC1E 6AD',
submitted_by_id => $user->id,
});
#login to admin
$t->ua->max_redirects(10);
$t->post_ok('/admin', form => {
email => 'admin@example.com',
password => 'abc123',
})->status_is(200);
#Read approved organisation
$t->get_ok('/admin/organisations/valid/1/')
->status_is(200);
#Read pending organisation
$t->get_ok('/admin/organisations/pending/2/')
->status_is(200);
#Valid approved organisation update
$t->post_ok('/admin/organisations/valid/1/edit', form => {
name => 'Shinra Electric Power Company',
street_name => 'Sector 0, Midgar, Eastern Continent',
town => 'Gaia',
sector => 'A',
postcode => 'WC1E 6AD',
})->status_is(200)->content_like(qr/Updated Organisation/);
#Failed validation on approved organisation
$t->post_ok('/admin/organisations/valid/1/edit', form => {
name => 'Shinra Electric Power Company',
street_name => 'Sector 0, Midgar, Eastern Continent',
sector => 'A',
postcode => 'WC1E 6AD',
})->content_like(qr/The validation has failed/);
#Valid pending organisation update
$t->post_ok('/admin/organisations/pending/2/edit', form => {
name => '7th Heaven',
street_name => 'Slums, Sector 7',
town => 'Midgar',
postcode => 'WC1E 6AD',
})->status_is(200)->content_like(qr/Updated Organisation/);
#Failed validation on pending organisation
$t->post_ok('/admin/organisations/pending/2/edit', form => {
name => '7th Heaven',
street_name => 'Slums, Sector 7',
postcode => 'WC1E 6AD',
})->content_like(qr/The validation has failed/);
#Valid adding organisation
$t->post_ok('/admin/organisations/add/submit', form => {
name => 'Wall Market',
street_name => 'Slums, Sector 6',
town => 'Midgar',
sector => 'A',
postcode => 'TN35 5AQ',
})->status_is(200)->content_like(qr/Added Organisation/);
#Failed validation on adding organisation
$t->post_ok('/admin/organisations/add/submit', form => {
name => 'Wall Market',
street_name => 'Slums, Sector 6',
postcode => 'TN35 5AQ',
})->content_like(qr/The validation has failed/);
done_testing();

124
t/admin/user.t Normal file
View file

@ -0,0 +1,124 @@
use Mojo::Base -strict;
use Test::More;
use Mojo::JSON;
use Test::Pear::LocalLoop;
my $framework = Test::Pear::LocalLoop->new;
my $t = $framework->framework;
my $schema = $t->app->schema;
my $user = $schema->resultset('User')->create({
email => 'admin@example.com',
password => 'abc123',
administrator => {},
});
is $schema->resultset('Administrator')->count, 1, 'Admin Created';
my $user1 = {
token => 'a',
full_name => 'Test User1',
display_name => 'Test User1',
email => 'test1@example.com',
postcode => 'LA1 1AA',
password => 'abc123',
year_of_birth => 2006,
};
my $org = {
token => 'e',
email => 'test50@example.com',
name => '7th Heaven',
street_name => 'Slums, Sector 7',
town => 'Midgar',
sector => 'A',
postcode => 'WC1E 6AD',
password => 'abc123',
};
$schema->resultset('AccountToken')->create({ name => $_->{token} })
for ( $user1, $org );
$framework->register_customer($user1);
$framework->register_organisation($org);
#login to admin
$t->ua->max_redirects(10);
$t->post_ok('/admin', form => {
email => 'admin@example.com',
password => 'abc123',
})->status_is(200);
#Read customer user
$t->get_ok('/admin/users/2/')
->status_is(200);
#Read organisation user
$t->get_ok('/admin/users/3/')
->status_is(200);
#Valid customer user update
$t->post_ok('/admin/users/2/edit', form => {
email => 'test12@example.com',
new_password => 'abc123',
full_name => 'Test User1',
display_name => 'Test User1',
town => 'Midgar',
sector => 'A',
postcode => 'WC1E 6AD',
})->status_is(200)->content_like(qr/Updated User/);
#Failed validation on customer user from no postcode
$t->post_ok('/admin/users/2/edit', form => {
email => 'test12@example.com',
new_password => 'abc123',
full_name => 'Test User1',
display_name => 'Test User1',
town => 'Midgar',
sector => 'A',
})->content_like(qr/The validation has failed/);
#Failed validation on customer user from no display name
$t->post_ok('/admin/users/2/edit', form => {
email => 'test12@example.com',
new_password => 'abc123',
full_name => 'Test User1',
town => 'Midgar',
postcode => 'WC1E 6AD',
sector => 'A',
})->content_like(qr/The validation has failed/);
#Valid organisation user update
$t->post_ok('/admin/users/3/edit', form => {
email => 'test51@example.com',
new_password => 'abc123',
name => '7th Heaven',
street_name => 'Slums, Sector 7',
town => 'Midgar',
sector => 'A',
postcode => 'WC1E 6AD',
})->status_is(200)->content_like(qr/Updated User/);
#Failed validation on organisation user from no postcode
$t->post_ok('/admin/users/3/edit', form => {
email => 'test50@example.com',
new_password => 'abc123',
name => '7th Heaven',
street_name => 'Slums, Sector 7',
town => 'Midgar',
sector => 'A',
})->content_like(qr/The validation has failed/);
#Failed validation on organisation user from no street name
$t->post_ok('/admin/users/3/edit', form => {
email => 'test50@example.com',
new_password => 'abc123',
name => '7th Heaven',
town => 'Midgar',
sector => 'A',
postcode => 'WC1E 6AD',
})->content_like(qr/The validation has failed/);
done_testing();

View file

@ -8,28 +8,22 @@ my $framework = Test::Pear::LocalLoop->new;
my $t = $framework->framework;
my $schema = $t->app->schema;
my $account_token = 'a';
my $email = 'rufus@shinra.energy';
my $password = 'MakoGold';
$schema->resultset('AccountToken')->create({
name => $account_token
});
my $test_json = {
'usertype' => 'customer',
'token' => $account_token,
'display_name' => 'RufusShinra',
'full_name' => 'RufusShinra',
'email' => $email,
'postcode' => 'LA1 1AA',
'password' => $password,
year_of_birth => 2006
my $user = {
token => 'a',
usertype => 'customer',
display_name => 'Display Guy',
full_name => 'Real Name',
email => 'test@example.com',
postcode => 'LA1 1AA',
password => 'testerising',
year_of_birth => 2006,
};
$t->post_ok('/api/register' => json => $test_json)
$schema->resultset('AccountToken')->create({ name => $user->{token} });
$t->post_ok('/api/register' => json => $user)
->status_is(200)
->json_is('/success', Mojo::JSON->true);
use Data::Dumper;
is $schema->resultset('User')->count, 1, 'found a user';
@ -48,11 +42,13 @@ $t->post_ok('/api/login' => json => {
->json_is('/success', Mojo::JSON->false);
$t->post_ok('/api/login' => json => {
email => $email,
password => $password,
email => $user->{email},
password => $user->{password},
})
->status_is(200)
->json_is('/success', Mojo::JSON->true)
->json_is('/display_name', 'Display Guy')
->json_is('/user_type', 'customer')
->json_has('/session_key');
my $session_key = $t->tx->res->json->{session_key};

View file

@ -92,12 +92,12 @@ $t->post_ok('/api/register' => json => $testJson)
#Blank name
$testJson = {
'usertype' => 'customer',
'token' => 'a',
'display_name' => 'test name',
'full_name' => '',
'email' => 'a@b.com',
'postcode' => 'LA1 1AA',
'password' => 'Meh',
'token' => 'a',
'display_name' => 'test name',
'full_name' => '',
'email' => 'a@b.com',
'postcode' => 'LA1 1AA',
'password' => 'Meh',
'year_of_birth' => 2005
};
$t->post_ok('/api/register' => json => $testJson)
@ -108,12 +108,12 @@ $t->post_ok('/api/register' => json => $testJson)
#Blank name
$testJson = {
'usertype' => 'customer',
'token' => 'a',
'display_name' => '',
'full_name' => 'test name',
'email' => 'a@b.com',
'postcode' => 'LA1 1AA',
'password' => 'Meh',
'token' => 'a',
'display_name' => '',
'full_name' => 'test name',
'email' => 'a@b.com',
'postcode' => 'LA1 1AA',
'password' => 'Meh',
'year_of_birth' => 2005
};
$t->post_ok('/api/register' => json => $testJson)
@ -125,13 +125,13 @@ $t->post_ok('/api/register' => json => $testJson)
#Valid customer
$testJson = {
'usertype' => 'customer',
'token' => 'a',
'full_name' => 'test name',
'display_name' => 'test name',
'email' => 'a@b.com',
'postcode' => 'LA1 1AA',
'password' => 'Meh',
'usertype' => 'customer',
'token' => 'a',
'full_name' => 'test name',
'display_name' => 'test name',
'email' => 'a@b.com',
'postcode' => 'LA1 1AA',
'password' => 'Meh',
'year_of_birth' => 2005
};
$t->post_ok('/api/register' => json => $testJson)
@ -140,35 +140,35 @@ $t->post_ok('/api/register' => json => $testJson)
#Valid customer2
$testJson = {
'usertype' => 'customer',
'token' => 'b',
'full_name' => 'test name',
'display_name' => 'test name',
'email' => 'b@c.com',
'postcode' => 'LA1 1AA',
'password' => 'Meh',
'usertype' => 'customer',
'token' => 'b',
'full_name' => 'test name',
'display_name' => 'test name',
'email' => 'b@c.com',
'postcode' => 'LA1 1AA',
'password' => 'Meh',
'year_of_birth' => 2005
};
$t->post_ok('/api/register' => json => $testJson)
->or($dump_error)
->status_is(200)
->status_is(200)
->or($dump_error)
->json_is('/success', Mojo::JSON->true)
->or($dump_error);
#Valid customer3
$testJson = {
'usertype' => 'customer',
'token' => 'c',
'full_name' => 'test name',
'display_name' => 'test name',
'email' => 'c@d.com',
'postcode' => 'LA1 1AA',
'password' => 'Meh',
'usertype' => 'customer',
'token' => 'c',
'full_name' => 'test name',
'display_name' => 'test name',
'email' => 'c@d.com',
'postcode' => 'LA1 1AA',
'password' => 'Meh',
'year_of_birth' => 2005
};
$t->post_ok('/api/register' => json => $testJson)
->status_is(200)
->status_is(200)
->json_is('/success', Mojo::JSON->true);
#email missing JSON
@ -186,53 +186,53 @@ $t->post_ok('/api/register' => json => $testJson)
->json_is('/success', Mojo::JSON->false)
->content_like(qr/no email sent/i);
#invalid email 1
#invalid email 1
$testJson = {
'usertype' => 'customer',
'token' => 'd',
'full_name' => 'test name',
'display_name' => 'test name',
'email' => 'dfsd@.com',
'postcode' => 'LA1 1AA',
'password' => 'Meh',
'token' => 'd',
'full_name' => 'test name',
'display_name' => 'test name',
'email' => 'dfsd@.com',
'postcode' => 'LA1 1AA',
'password' => 'Meh',
'year_of_birth' => 2006
};
$t->post_ok('/api/register' => json => $testJson)
->status_is(400)
->status_is(400)
->json_is('/success', Mojo::JSON->false)
->content_like(qr/email/i)
->content_like(qr/invalid/i);
#invalid email 2
$testJson = {
'usertype' => 'customer',
'token' => 'd',
'full_name' => 'test name',
'display_name' => 'test name',
'email' => 'dfsd@com',
'postcode' => 'LA1 1AA',
'password' => 'Meh',
'usertype' => 'customer',
'token' => 'd',
'full_name' => 'test name',
'display_name' => 'test name',
'email' => 'dfsd@com',
'postcode' => 'LA1 1AA',
'password' => 'Meh',
'year_of_birth' => 2006
};
$t->post_ok('/api/register' => json => $testJson)
->status_is(400)
->status_is(400)
->json_is('/success', Mojo::JSON->false)
->content_like(qr/email/i)
->content_like(qr/invalid/i);
#Email exists
$testJson = {
'usertype' => 'customer',
'token' => 'd',
'full_name' => 'test name',
'display_name' => 'test name',
'email' => 'a@b.com',
'postcode' => 'LA1 1AA',
'password' => 'Meh',
'usertype' => 'customer',
'token' => 'd',
'full_name' => 'test name',
'display_name' => 'test name',
'email' => 'a@b.com',
'postcode' => 'LA1 1AA',
'password' => 'Meh',
'year_of_birth' => 2006
};
$t->post_ok('/api/register' => json => $testJson)
->status_is(403)
->status_is(403)
->json_is('/success', Mojo::JSON->false)
->content_like(qr/email/i)
->content_like(qr/exists/i);
@ -288,16 +288,15 @@ $t->post_ok('/api/register' => json => $testJson)
#Invalid user type
$testJson = {
'usertype' => 'organisation1',
'token' => 'f',
'name' => 'test name',
'email' => 'org@org.com',
'postcode' => 'LA1 1AA',
'password' => 'Meh',
'fulladdress' => 'mary lane testing....'
'usertype' => 'organisation1',
'token' => 'f',
'name' => 'test name',
'email' => 'org@org.com',
'postcode' => 'LA1 1AA',
'password' => 'Meh',
};
$t->post_ok('/api/register' => json => $testJson)
->status_is(400)
->status_is(400)
->json_is('/success', Mojo::JSON->false)
->content_like(qr/usertype/i)
->content_like(qr/invalid/i);
@ -320,29 +319,30 @@ $t->post_ok('/api/register' => json => $testJson)
#Age is invalid
$testJson = {
'usertype' => 'customer',
'token' => 'f',
'full_name' => 'test name',
'display_name' => 'test name',
'email' => 'test@example.com',
'postcode' => 'LA1 1AA',
'password' => 'Meh',
'usertype' => 'customer',
'token' => 'f',
'full_name' => 'test name',
'display_name' => 'test name',
'email' => 'test@example.com',
'postcode' => 'LA1 1AA',
'password' => 'Meh',
'year_of_birth' => 'invalid'
};
$t->post_ok('/api/register' => json => $testJson)
->status_is(400)
->status_is(400)
->json_is('/success', Mojo::JSON->false)
->content_like(qr/year_of_birth/i)
->content_like(qr/invalid/i);
#full address missing JSON
$testJson = {
'usertype' => 'organisation',
'token' => 'f',
'name' => 'test org',
'email' => 'org@org.com',
'postcode' => 'LA1 1AA',
'password' => 'Meh',
'usertype' => 'organisation',
'token' => 'f',
'name' => 'test org',
'email' => 'org@org.com',
'postcode' => 'LA1 1AA',
'password' => 'Meh',
'sector' => 'A',
};
$t->post_ok('/api/register' => json => $testJson)
->status_is(400)
@ -353,17 +353,18 @@ $t->post_ok('/api/register' => json => $testJson)
#Organisation valid
$testJson = {
'usertype' => 'organisation',
'token' => 'f',
'name' => 'org name',
'email' => 'org@org.com',
'postcode' => 'LA1 1AA',
'password' => 'Meh',
'usertype' => 'organisation',
'token' => 'f',
'name' => 'org name',
'email' => 'org@org.com',
'postcode' => 'LA1 1AA',
'password' => 'Meh',
'street_name' => 'mary lane testing....',
'town' => 'Lancaster',
'sector' => 'A',
};
$t->post_ok('/api/register' => json => $testJson)
->status_is(200)
->status_is(200)
->json_is('/success', Mojo::JSON->true);
is $t->app->schema->resultset('User')->count, 4, 'Correct user count';

View file

@ -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)

View file

@ -28,6 +28,7 @@ my $org = {
town => 'Lancaster',
postcode => 'LA1 1AA',
password => 'abc123',
sector => 'A',
};
$schema->resultset('AccountToken')->create({ name => $user->{token} });

View file

@ -56,6 +56,7 @@ my $org = {
name => 'Test Org',
street_name => 'Test Street',
town => 'Lancaster',
sector => 'A',
postcode => 'LA1 1AA',
password => 'abc123',
};

View file

@ -81,6 +81,7 @@ $testJson = {
'password' => $passwordBilly,
'street_name' => 'Chocobo Farm, Eastern Continent',
'town' => 'Gaia',
'sector' => 'A',
};
$t->post_ok('/api/register' => json => $testJson)
->status_is(200)
@ -201,9 +202,10 @@ $json = {
};
$upload = {json => Mojo::JSON::encode_json($json)};
$t->post_ok('/api/upload' => form => $upload )
->status_is(400)
->json_is('/success', Mojo::JSON->false)
->content_like(qr/no file uploaded/i);
->status_is(200)
->json_is('/success', Mojo::JSON->true)
->json_like('/message', qr/Upload Successful/);
is $schema->resultset('Transaction')->count, 1, "1 transaction";
print "test 13 - organisation_id missing (type 1: already validated)\n";
$json = {
@ -234,7 +236,7 @@ $t->post_ok('/api/upload' => form => $upload )
->content_like(qr/organisation_id does not exist in the database/i);
print "test 15 - valid addition. (type 1: already validated)\n";
is $schema->resultset('Transaction')->count, 0, "no transactions";
is $schema->resultset('Transaction')->count, 1, "1 transaction";
$json = {
transaction_value => 10,
transaction_type => 1,
@ -247,7 +249,7 @@ $t->post_ok('/api/upload' => form => $upload )
->status_is(200)
->json_is('/success', Mojo::JSON->true)
->json_like('/message', qr/Upload Successful/);
is $schema->resultset('Transaction')->count, 1, "1 transaction";
is $schema->resultset('Transaction')->count, 2, "2 transaction";
# Add type 3 (new organisation) checking.
@ -444,7 +446,7 @@ $t->post_ok('/api/login' => json => $testJson)
$session_key = $t->tx->res->json('/session_key');
print "test 30 - organisation buy from another organisation\n";
is $schema->resultset('Transaction')->count, 1, "1 transaction";
is $schema->resultset('Transaction')->count, 2, "2 transaction";
$json = {
transaction_value => 100000,
transaction_type => 1,
@ -457,6 +459,6 @@ $t->post_ok('/api/upload' => form => $upload )
->status_is(200)
->json_is('/success', Mojo::JSON->true)
->json_like('/message', qr/Upload Successful/);
is $schema->resultset('Transaction')->count, 2, "2 transaction";
is $schema->resultset('Transaction')->count, 3, "3 transaction";
done_testing();

View file

@ -0,0 +1,89 @@
use Mojo::Base -strict;
use FindBin qw/ $Bin /;
use Test::More;
use Mojo::JSON;
use Test::Pear::LocalLoop;
use DateTime;
my $framework = Test::Pear::LocalLoop->new(
etc_dir => "$Bin/../../../etc",
);
$framework->install_fixtures('users');
my $t = $framework->framework;
my $schema = $t->app->schema;
my $start = DateTime->today->subtract( hours => 12 );
# create 30 days worth of data
for my $count ( 0 .. 29 ) {
my $trans_day = $start->clone->subtract( days => $count );
create_random_transaction( 'test1@example.com', $trans_day );
if ( $count % 2 ) {
create_random_transaction( 'test2@example.com', $trans_day );
}
if ( $count % 3 ) {
create_random_transaction( 'test3@example.com', $trans_day );
}
if ( $count % 4 ) {
create_random_transaction( 'test4@example.com', $trans_day );
}
}
my $session_key = $framework->login({
email => 'org@example.com',
password => 'abc123',
});
$t->post_ok('/api/v1/organisation/graphs' => json => {
session_key => $session_key,
graph => 'customers_last_7_days',
})
->status_is(200)->or($framework->dump_error)
->json_is('/graph', {
day => [ map { $start->clone->subtract( days => $_ )->day_name } reverse ( 0 .. 6 ) ],
count => [ 2, 4, 2, 3, 3, 4, 1 ],
});
$t->post_ok('/api/v1/organisation/graphs' => json => {
session_key => $session_key,
graph => 'customers_last_30_days',
})
->status_is(200)->or($framework->dump_error)
->json_is('/graph', {
day => [ map { $start->clone->subtract( days => $_ )->day_name } reverse ( 0 .. 29 ) ],
count => [ 4, 2, 3, 3, 4, 1, 4, 3, 3, 2, 4, 2, 4, 2, 3, 3, 4, 1, 4, 3, 3, 2, 4, 2, 4, 2, 3, 3, 4, 1 ],
});
$framework->logout( $session_key );
$session_key = $framework->login({
email => 'test1@example.com',
password => 'abc123',
});
$t->post_ok('/api/v1/organisation/graphs' => json => {
session_key => $session_key,
graph => 'customers_last_7_days',
})
->status_is(403)
->json_is('/success', Mojo::JSON->false)
->json_is('/error', 'user_not_org');
sub create_random_transaction {
my $buyer = shift;
my $time = shift;
$schema->resultset('Transaction')->create({
buyer => { email => $buyer },
seller => { name => 'Test Org' },
value => ( int( rand( 10000 ) ) / 100 ),
proof_image => 'a',
purchase_time => $time,
});
}
done_testing;

View file

@ -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,
});

View file

@ -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,
});

View file

@ -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,
});

View file

@ -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'
}
]
};

View file

@ -0,0 +1 @@
1.001036

View file

@ -0,0 +1,7 @@
$HASH1 = {
display_name => 'Test User1',
full_name => 'Test User1',
id => 1,
postcode => 'LA1 1AA',
year_of_birth => 2006
};

View file

@ -0,0 +1,7 @@
$HASH1 = {
display_name => 'Test User2',
full_name => 'Test User2',
id => 2,
postcode => 'LA1 1AA',
year_of_birth => 2006
};

View file

@ -0,0 +1,7 @@
$HASH1 = {
display_name => 'Test User3',
full_name => 'Test User3',
id => 3,
postcode => 'LA1 1AA',
year_of_birth => 2006
};

View file

@ -0,0 +1,7 @@
$HASH1 = {
display_name => 'Test User4',
full_name => 'Test User4',
id => 4,
postcode => 'LA1 1AA',
year_of_birth => 2006
};

View file

@ -0,0 +1,5 @@
$HASH1 = {
id => 1,
name => 'Daily Total',
type => 'daily_total'
};

View file

@ -0,0 +1,5 @@
$HASH1 = {
id => 2,
name => 'Daily Count',
type => 'daily_count'
};

View file

@ -0,0 +1,5 @@
$HASH1 = {
id => 3,
name => 'Weekly Total',
type => 'weekly_total'
};

View file

@ -0,0 +1,5 @@
$HASH1 = {
id => 4,
name => 'Weekly Count',
type => 'weekly_count'
};

View file

@ -0,0 +1,5 @@
$HASH1 = {
id => 5,
name => 'Monthly Total',
type => 'monthly_total'
};

View file

@ -0,0 +1,5 @@
$HASH1 = {
id => 6,
name => 'Monthly Count',
type => 'monthly_count'
};

View file

@ -0,0 +1,5 @@
$HASH1 = {
id => 7,
name => 'All Time Total',
type => 'all_time_total'
};

View file

@ -0,0 +1,5 @@
$HASH1 = {
id => 8,
name => 'All Time Count',
type => 'all_time_count'
};

View file

@ -0,0 +1,10 @@
$HASH1 = {
id => 1,
name => 'Test Org',
postcode
=> 'LA1 1AA',
sector => undef,
street_name
=> 'Test Street',
town => 'Lancaster'
};

View file

@ -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'
};

View file

@ -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'
};

View file

@ -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'
};

View file

@ -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'
};

View file

@ -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'
};

View file

@ -58,6 +58,7 @@ my $org = {
town => 'Lancaster',
postcode => 'LA1 1AA',
password => 'abc123',
sector => 'A',
};
$schema->resultset('AccountToken')->create({ name => $_->{token} })
@ -215,10 +216,10 @@ test_leaderboard(
'all_time_total',
$now,
[
{ user_id => 4, value => 980 },
{ user_id => 3, value => 940 },
{ user_id => 2, value => 900 },
{ user_id => 1, value => 860 },
{ user_id => 4, value => 885 },
{ user_id => 3, value => 855 },
{ user_id => 2, value => 825 },
{ user_id => 1, value => 795 },
]
);
@ -227,10 +228,10 @@ test_leaderboard(
'all_time_count',
$now,
[
{ user_id => 1, value => 40 },
{ user_id => 2, value => 40 },
{ user_id => 3, value => 40 },
{ user_id => 4, value => 40 },
{ user_id => 1, value => 30 },
{ user_id => 2, value => 30 },
{ user_id => 3, value => 30 },
{ user_id => 4, value => 30 },
]
);

View file

@ -58,6 +58,7 @@ my $org = {
town => 'Lancaster',
postcode => 'LA1 1AA',
password => 'abc123',
sector => 'A',
};
$schema->resultset('AccountToken')->create({ name => $_->{token} })

View file

@ -58,6 +58,7 @@ my $org = {
town => 'Lancaster',
postcode => 'LA1 1AA',
password => 'abc123',
sector => 'A',
};
$schema->resultset('AccountToken')->create({ name => $_->{token} })
@ -217,10 +218,10 @@ test_leaderboard(
'all_time_total',
$now,
[
{ user_id => 4, value => 980, position => 1 },
{ user_id => 3, value => 940, position => 2 },
{ user_id => 2, value => 900, position => 3 },
{ user_id => 1, value => 860, position => 4 },
{ user_id => 4, value => 885, position => 1 },
{ user_id => 3, value => 855, position => 2 },
{ user_id => 2, value => 825, position => 3 },
{ user_id => 1, value => 795, position => 4 },
]
);
@ -229,10 +230,10 @@ test_leaderboard(
'all_time_count',
$now,
[
{ user_id => 1, value => 40, position => 1 },
{ user_id => 2, value => 40, position => 2 },
{ user_id => 3, value => 40, position => 3 },
{ user_id => 4, value => 40, position => 4 },
{ user_id => 1, value => 30, position => 1 },
{ user_id => 2, value => 30, position => 2 },
{ user_id => 3, value => 30, position => 3 },
{ user_id => 4, value => 30, position => 4 },
]
);

View file

@ -0,0 +1,33 @@
% layout 'admin';
% title 'Organisations';
% content_for javascript => begin
% end
% if ( my $error = flash 'error' ) {
<div class="alert alert-danger" role="alert">
<strong>Error!</strong> <%= $error %>
</div>
% } elsif ( my $success = flash 'success' ) {
<div class="alert alert-success" role="alert">
<strong>Success!</strong> <%= $success %>
</div>
% }
<div class="card mb-3">
<h3 class="card-header">
Add an Organisation
</h3>
<ul class="list-group list-group-flush">
<form action="<%= url_for . '/submit' %>" method="post">
<li class="list-group-item">Only 'Organisation Name' and 'Town' are required entries.</li>
<li class="list-group-item"><input id="name" type="text" class="form-control" placeholder="Organisation Name" name="name"></li>
<li class="list-group-item"><input id="street_name" type="text" class="form-control" placeholder="Street Name" name="street_name"></li>
<li class="list-group-item"><input id="town" type="text" class="form-control" placeholder="Town" name="town"></li>
<li class="list-group-item">
<select class="form-control" name="sector">
%= include 'partials/sector_options', selected_sector => '';
</select>
</li>
<li class="list-group-item"><input id="postcode" type="text" class="form-control" placeholder="Postcode" name="postcode"></li>
<li class="list-group-item"><button class="btn btn-success" type="submit">Add Organisation</button></li>
</form>
</ul>
</div>

View file

@ -16,6 +16,7 @@
<div class="card">
<h3 class="card-header">
Valid Organisations
<a href="<%= url_for . '/add' %>" class="btn btn-success" style="float: right">Add Organisation</a>
</h3>
<div class="list-group list-group-flush">
% for my $valid_org ($valid_orgs_rs->all) {

View file

@ -16,10 +16,12 @@
%= $pending_org->name
</h3>
<ul class="list-group list-group-flush">
<li class="list-group-item">Street Name: <%= $pending_org->street_name %></li>
<li class="list-group-item">Town: <%= $pending_org->town %></li>
<li class="list-group-item">Postcode: <%= $pending_org->postcode %></li>
<li class="list-group-item"><a href="<%= url_for . '/approve' %>" class="btn btn-success">Approve Organisation</a></li>
<form action="<%= url_for . '/edit' %>" method="post">
<li class="list-group-item"><input id="name" type="text" class="form-control" placeholder="Organisation Name" name="name" value="<%= $pending_org->name %>"></li>
<li class="list-group-item"><input id="street_name" type="text" class="form-control" placeholder="Street Name" name="street_name" value="<%= $pending_org->street_name %>"></li>
<li class="list-group-item"><input id="town" type="text" class="form-control" placeholder="Town" name="town" value="<%= $pending_org->town %>"></li>
<li class="list-group-item"><input id="postcode" type="text" class="form-control" placeholder="Postcode" name="postcode" value="<%= $pending_org->postcode %>"></li>
<li class="list-group-item"><button class="btn btn-success" type="submit">Update</button><a href="<%= url_for . '/approve' %>" class="btn btn-success">Approve Organisation</a></li>
</ul>
</div>
<div class="card mb-3">

View file

@ -16,9 +16,18 @@
%= $valid_org->name
</h3>
<ul class="list-group list-group-flush">
<li class="list-group-item">Street Name: <%= $valid_org->street_name %></li>
<li class="list-group-item">Town: <%= $valid_org->town %></li>
<li class="list-group-item">Postcode: <%= $valid_org->postcode %></li>
<form action="<%= url_for . '/edit' %>" method="post">
<li class="list-group-item"><input id="name" type="text" class="form-control" placeholder="Organisation Name" name="name" value="<%= $valid_org->name %>"></li>
<li class="list-group-item"><input id="street_name" type="text" class="form-control" placeholder="Street Name" name="street_name" value="<%= $valid_org->street_name %>"></li>
<li class="list-group-item"><input id="town" type="text" class="form-control" placeholder="Town" name="town" value="<%= $valid_org->town %>"></li>
<li class="list-group-item">
<select class="form-control" name="sector">
%= include 'partials/sector_options', selected_sector => $valid_org->sector;
</select>
</li>
<li class="list-group-item"><input id="postcode" type="text" class="form-control" placeholder="Postcode" name="postcode" value="<%= $valid_org->postcode %>"></li>
<li class="list-group-item"><button class="btn btn-success" type="submit">Edit Organisation</button></li>
</form>
</ul>
</div>
<div class="card mb-3">
@ -33,6 +42,8 @@
<div class="col">From: <%= $transaction->buyer->name %></div>
<div class="col">To: <%= $transaction->seller->name %></div>
<div class="col">Value: <%= $transaction->value %></div>
<div class="col">Submitted At: <%= $transaction->submitted_at %></div>
<div class="col">Purchase Time: <%= $transaction->purchase_time %></div>
</div>
</div>
</li>

View file

@ -11,20 +11,78 @@
<strong>Success!</strong> <%= $success %>
</div>
% }
<form action="<%= url_for %>" method="post">
<div class="form-group">
<label for="email">Email Address</label>
<input id="email" type="text" class="form-control" placeholder="Email" name="email" value="<%= $user->email %>" disabled>
</div>
<div class="form-group">
<label for="joindate">Join Date</label>
<input id="joindate" type="datetime" class="form-control" placeholder="Date" name="joindate" value="<%= $user->join_date %>" disabled>
</div>
<div class="form-group">
<label for="type">Account Type</label>
<input id="type" type="text" class="form-control" value="<%= defined $user->customer_id ? 'Customer' : 'Organisation' %>" disabled>
</div>
<div class="form-group">
<button class="btn btn-primary form-control" type="submit">Update</button>
</div>
<form action="<%= url_for . '/edit' %>" method="post" autocomplete="off">
<h3 class="card-header">
User Details
</h3>
<div class="form-group">
<label for="email">Email Address</label>
<input id="email" type="text" autocomplete="off" class="form-control" placeholder="Email" name="email" value="<%= $user->email %>">
</div>
<div class="form-group">
<label for="joindate">Join Date</label>
<input id="joindate" type="datetime" class="form-control" placeholder="Date" name="joindate" value="<%= $user->join_date %>" disabled>
</div>
<div class="form-group">
<label for="type">Account Type</label>
<input id="type" type="text" class="form-control" value="<%= defined $user->customer_id ? 'Customer' : 'Organisation' %>" disabled>
</div>
<div class="form-group">
<label for="new_password">New Password</label>
<input id="new_password" type="password" autocomplete="off" class="form-control" placeholder="New Password" name="new_password">
<p class="help-block">Leave blank unless you want to change their password</p>
</div>
% if ( my $customer_rs = $user->customer ) {
<h3 class="card-header">
Customer Details
</h3>
<div class="form-group">
<label for="postcode">Customer Postcode</label>
<input id="postcode" type="text" class="form-control" placeholder="Postcode" name="postcode" value="<%= $customer_rs->postcode %>">
</div>
<div class="form-group">
<label for="full_name">Full Name</label>
<input id="full_name" type="text" class="form-control" placeholder="Full Name" name="full_name" value="<%= $customer_rs->full_name %>">
</div>
<div class="form-group">
<label for="display_name">Display Name</label>
<input id="display_name" type="text" class="form-control" placeholder="Display Name" name="display_name" value="<%= $customer_rs->display_name %>">
</div>
<div class="form-group">
<label for="year_of_birth">Year of Birth</label>
<input id="year_of_birth" type="number" class="form-control" placeholder="Year of Birth" name="year_of_birth" value="<%= $customer_rs->year_of_birth %>" disabled>
</div>
% } elsif ( my $org_rs = $user->organisation ) {
<h3 class="card-header">
Organisation Details
</h3>
<div class="form-group">
<label for="postcode">Organisation Postcode</label>
<input id="postcode" type="text" class="form-control" placeholder="Postcode" name="postcode" value="<%= $org_rs->postcode %>">
</div>
<div class="form-group">
<label for="name">Organisation Name</label>
<input id="name" type="text" class="form-control" placeholder="Organisation Name" name="name" value="<%= $org_rs->name %>">
</div>
<div class="form-group">
<label for="street_name">Street Name</label>
<input id="street_name" type="text" class="form-control" placeholder="Street Name" name="street_name" value="<%= $org_rs->street_name %>">
</div>
<div class="form-group">
<label for="town">Town</label>
<input id="town" type="text" class="form-control" placeholder="Town" name="town" value="<%= $org_rs->town %>">
</div>
<div class="form-group">
<label for="town">Town</label>
<input id="town" type="sector" class="form-control" placeholder="Sector Area Code" name="sector" value="<%= $org_rs->sector %>">
</div>
% } else {
<h3 class="card-header">
User is not a customer or an organisation
</h3>
% }
<div class="form-group">
<button class="btn btn-primary form-control" type="submit">Edit Account</button>
</div>
</form>

View file

@ -0,0 +1,21 @@
<option value=''>Select Organisation Sector</option>
<option value='A'<%= $selected_sector eq 'A' ? ' selected' : '' %>>Agriculture, Forestry & Fishing</option>
<option value='B'<%= $selected_sector eq 'B' ? ' selected' : '' %>>Mining & Quarrying</option>
<option value='C'<%= $selected_sector eq 'C' ? ' selected' : '' %>>Manufacturing</option>
<option value='D'<%= $selected_sector eq 'D' ? ' selected' : '' %>>Electricity, Gas, Steam & Air Conditioning</option>
<option value='E'<%= $selected_sector eq 'E' ? ' selected' : '' %>>Water & Waste Management</option>
<option value='F'<%= $selected_sector eq 'F' ? ' selected' : '' %>>Construction</option>
<option value='G'<%= $selected_sector eq 'G' ? ' selected' : '' %>>Wholesale & Retail Trade</option>
<option value='H'<%= $selected_sector eq 'H' ? ' selected' : '' %>>Transportation & Storage</option>
<option value='I'<%= $selected_sector eq 'I' ? ' selected' : '' %>>Accomodation & Food Services</option>
<option value='J'<%= $selected_sector eq 'J' ? ' selected' : '' %>>Information & Communication</option>
<option value='K'<%= $selected_sector eq 'K' ? ' selected' : '' %>>Financial & Insurance Activities</option>
<option value='L'<%= $selected_sector eq 'L' ? ' selected' : '' %>>Real Estate</option>
<option value='M'<%= $selected_sector eq 'M' ? ' selected' : '' %>>Professional, Scientfic & Technical</option>
<option value='N'<%= $selected_sector eq 'N' ? ' selected' : '' %>>Administrative & Support Services</option>
<option value='O'<%= $selected_sector eq 'O' ? ' selected' : '' %>>Public Administration, Defence & Social Security</option>
<option value='P'<%= $selected_sector eq 'P' ? ' selected' : '' %>>Education</option>
<option value='Q'<%= $selected_sector eq 'Q' ? ' selected' : '' %>>Human Health & Social Work</option>
<option value='R'<%= $selected_sector eq 'R' ? ' selected' : '' %>>Arts, Entertainment & Recreation</option>
<option value='S'<%= $selected_sector eq 'S' ? ' selected' : '' %>>Other Service Activities</option>
<option value='T'<%= $selected_sector eq 'T' ? ' selected' : '' %>>Household Domestic Business</option>