diff --git a/.env.example b/.env.example index 67c5a07e..945b10b7 100644 --- a/.env.example +++ b/.env.example @@ -51,4 +51,12 @@ MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" HELLOSIGN_API_KEY= +HELLOSIGN_API_KEY_HOOK= HELLOSIGN_CLIENT_ID= + +SHUFTI_CLIENT_ID= +SHUFTI_CLIENT_SECRET= +SITE_URL= +COINMARKETCAP_KEY= + +SEENA_API_KEY= diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..83d2aa52 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,16 @@ +## Contributing to Casper Association Member Portal + +### Please follow these guidelines if you want to contribute to this project + +1. Fork the project. +2. Create a new branch from master (e.g. features/my-new-feature or issue/123-my-bugfix) +3. Try to follow the same coding style with regards to spacing and line width, etc. Following PSR-4 will more than suffice. +4. Create a pull request. Please create PR to development branch so we can do proper testing and staging before merging with master branch. + +### Report bugs using Github issues + +You can report a bug by clicking on the issues tab and 'New Issue'. Describe the issue the best you can. This makes it easy to fork and create a branch named after the issue purposed for fixing it. + +Thank you for your interest in the project! + +For security related issues, please email thomas@ledgerleap.com \ No newline at end of file diff --git a/README.md b/README.md index ceb6ac0a..650b376a 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,81 @@ -
- +# Casper Association Member Portal + +The Casper Association's member portal. + +This is the backend repo of the portal. To see the frontend repo, visit https://github.com/ledgerleapllc/CasperAssociationPortal + +## Prerequisites + + - Apache/2.4.29+ (Ubuntu) + - PHP 7.4+ + - MySql Ver 14.14 Distrib 5.7.37 + - Laravel Framework 8.47.0 + +## Setup + +We generally would use the latest version of Ubuntu for testing installs. Example hosting server: AWS ec2 t2 medium with at least 10Gb SSD. + +```bash +sudo apt -y install apache2 +sudo a2enmod rewrite +sudo a2enmod headers +sudo a2enmod ssl +sudo apt -y install software-properties-common +sudo add-apt-repository ppa:ondrej/php +sudo apt-get update +sudo apt-get install -y php7.4 +sudo apt-get install -y php7.4-{bcmath,bz2,intl,gd,mbstring,mysql,zip,common,curl,xml} +php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" +sudo php composer-setup.php --install-dir=/usr/local/bin --filename=composer +php -r "unlink('composer-setup.php');" +``` + +Setup the repo according to our VHOST path. Note, the actual VHOST path in this case should be set to **/var/www/CasperAssociationPortalBackend/public** + +```bash +cd /var/www/ +git clone https://github.com/ledgerleapllc/CasperAssociationPortalBackend +cd CasperAssociationPortalBackend +``` + +Install packages and setup environment + +```bash +composer install +composer update +cp .env.example .env +``` + +After adjusting .env with your variables, run Artisan to finish setup + +```bash +php artisan key:generate +php artisan migrate +php artisan passport:install +php artisan config:clear +php artisan route:clear +php artisan cache:clear +(crontab -l 2>>/dev/null; echo "* * * * * cd /var/www/CasperAssociationPortalBackend && php artisan schedule:run >> /dev/null 2>&1") | crontab - +``` + +You may also have to authorize Laravel to write to the storage directory + +```bash +sudo chown -R www-data:www-data storage/ +``` + +Last, you need to setup roles and admins to start using the portal and see it work. Visit the URL of the backend with the path **/install**. This will install these things for you. You will find your admin credentials generated in the Laravel log file. You may want to disable this endpoint after the initial install to prevent this install endpoint from being used again if you are planning on deploying to a production environment in the future. This is easily done by switching ENV variable **INSTALL_PATH_ENABLED** to 0, or false. You may need to run the following command if Laravel caching is on. + +```bash +php artisan config:clear +``` + + + ## About Laravel Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as: diff --git a/app/Console/Commands/CheckBallot.php b/app/Console/Commands/CheckBallot.php new file mode 100644 index 00000000..639b1008 --- /dev/null +++ b/app/Console/Commands/CheckBallot.php @@ -0,0 +1,62 @@ +where('status', 'active')->where('time_end', '<=', $now)->get(); + foreach ($ballots as $ballot) { + $vote = $ballot->vote; + if ($vote->result_count == 0) { + $ballot->status = 'fail'; + $ballot->save(); + } else { + $quorum = $vote->for_value / $vote->result_count * 100; + if($quorum >= $quorumRate) { + $ballot->status = 'pass'; + } else { + $ballot->status = 'fail'; + } + $ballot->save(); + } + } + } +} diff --git a/app/Console/Commands/CheckNodeStatus.php b/app/Console/Commands/CheckNodeStatus.php new file mode 100644 index 00000000..5e5564b5 --- /dev/null +++ b/app/Console/Commands/CheckNodeStatus.php @@ -0,0 +1,198 @@ +first(); + $uptimeProbationStart = $uptime->probation_start; + if ($uptime->given_to_correct_unit == 'Weeks') { + $uptimeTime = (float) $uptime->given_to_correct_value * 7 * 24; + } else if ($uptime->given_to_correct_unit == 'Days') { + $uptimeTime = (float) $uptime->given_to_correct_value * 24; + } else { + $uptimeTime = (float) $uptime->given_to_correct_value; + } + + $blockHeight = MonitoringCriteria::where('type', 'block-height')->first(); + $blockHeightProbationStart = (float) $blockHeight->probation_start; + if ($blockHeight->given_to_correct_unit == 'Weeks') { + $blockHeightTime = (float) $blockHeight->given_to_correct_value * 7 * 24; + } else if ($blockHeight->given_to_correct_unit == 'Days') { + $blockHeightTime = (float) $blockHeight->given_to_correct_value * 24; + } else { + $blockHeightTime = (float) $blockHeight->given_to_correct_value; + } + + $updateResponsiveness = MonitoringCriteria::where('type', 'update-responsiveness')->first(); + $updateResponsivenessProbationStart = (float) $updateResponsiveness->probation_start; + if ($updateResponsiveness->given_to_correct_unit == 'Weeks') { + $updateResponsivenessTime = (float)$updateResponsiveness->given_to_correct_value * 7 * 24; + } else if ($updateResponsiveness->given_to_correct_unit == 'Days') { + $updateResponsivenessTime = (float) $updateResponsiveness->given_to_correct_value * 24; + } else { + $updateResponsivenessTime = (float) $updateResponsiveness->given_to_correct_value; + } + + $now = Carbon::now('UTC'); + $users = User::where('role', 'member') + ->where('banned', 0) + ->with(['metric', 'nodeInfo', 'profile']) + ->get(); + + foreach ($users as $user) { + $user->node_status = 'Online'; + $user->save(); + + $nodeInfo = $user->nodeInfo ? $user->nodeInfo : $user->metric; + + if (!$nodeInfo || !$user->node_verified_at || !$user->letter_verified_at || !$user->signature_request_id) { + $user->node_status = null; + $user->save(); + } else if ($user->is_fail_node == 1) { + $user->node_status = 'Offline'; + $user->save(); + } else if ($nodeInfo) { + $nodeInfo->uptime = $nodeInfo->uptime ? $nodeInfo->uptime : 0; + $nodeInfo->block_height_average = $nodeInfo->block_height_average ? $nodeInfo->block_height_average : 0; + $nodeInfo->update_responsiveness = $nodeInfo->update_responsiveness ? $nodeInfo->update_responsiveness : 100; + if ( + $nodeInfo->uptime >= $uptimeProbationStart && + $nodeInfo->block_height_average >= $blockHeightProbationStart && + $nodeInfo->update_responsiveness >= $updateResponsivenessProbationStart + ) { + $user->node_status = 'Online'; + $user->save(); + + $nodeInfo->uptime_time_start = null; + $nodeInfo->uptime_time_end = null; + + $nodeInfo->block_height_average_time_start = null; + $nodeInfo->block_height_average_time_end = null; + + $nodeInfo->update_responsiveness_time_start = null; + $nodeInfo->update_responsiveness_time_end = null; + + $nodeInfo->save(); + } + } + + if ($user->profile && $user->profile->status == 'approved' && $nodeInfo) { + $user->profile->extra_status = null; + $user->profile->save(); + + if ($nodeInfo->uptime < $uptimeProbationStart) { + $user->profile->extra_status = 'On Probation'; + $nodeInfo->uptime_time_start = now(); + $nodeInfo->uptime_time_end = Carbon::now('UTC')->addHours($uptimeTime); + } + + if ($nodeInfo->block_height_average < $blockHeightProbationStart) { + $user->profile->extra_status = 'On Probation'; + $nodeInfo->block_height_average_time_start = now(); + $nodeInfo->block_height_average_time_end = Carbon::now('UTC')->addHours($blockHeightTime); + } + + if ($nodeInfo->update_responsiveness < $updateResponsivenessProbationStart) { + $user->profile->extra_status = 'On Probation'; + $nodeInfo->update_responsiveness_time_start = now(); + $nodeInfo->update_responsiveness_time_end = Carbon::now('UTC')->addHours($updateResponsivenessTime); + } + + $user->profile->save(); + $nodeInfo->save(); + + if ($user->profile->extra_status == 'On Probation') { + if ($nodeInfo->uptime_time_end <= $now && $nodeInfo->uptime < $uptimeProbationStart) { + $user->profile->extra_status = 'Suspended'; + } + if ($nodeInfo->block_height_average_time_end <= $now && $nodeInfo->block_height_average < $blockHeightProbationStart) { + $user->profile->extra_status = 'Suspended'; + } + if ($nodeInfo->update_responsiveness_time_end <= $now && $nodeInfo->update_responsiveness < $updateResponsivenessProbationStart) { + $user->profile->extra_status = 'Suspended'; + } + $user->profile->save(); + } + } + + /* + if ($user->node_status != 'Probation' && $user->node_status != 'Pulled') { + if ($nodeInfo->uptime < $uptimeProbationStart) { + $user->node_status = 'Probation'; + $nodeInfo->uptime_time_start = now(); + $nodeInfo->uptime_time_end = Carbon::now('UTC')->addHours($uptimeTime); + } + + if ($nodeInfo->block_height_average < $blockHeightProbationStart) { + $user->node_status = 'Probation'; + $nodeInfo->block_height_average_time_start = now(); + $nodeInfo->block_height_average_time_end = Carbon::now('UTC')->addHours($blockHeightTime); + } + + if ($nodeInfo->update_responsiveness < $updateResponsivenessProbationStart) { + $user->node_status = 'Probation'; + $nodeInfo->update_responsiveness_time_start = now(); + $nodeInfo->update_responsiveness_time_end = Carbon::now('UTC')->addHours($updateResponsivenessTime); + } + + $user->save(); + $nodeInfo->save(); + continue; + } + + if ($user->node_status == 'Probation') { + if ($nodeInfo->uptime_time_end <= $now && $nodeInfo->uptime < $uptimeProbationStart) { + $user->node_status = 'Pulled'; + } + if ($nodeInfo->block_height_average_time_end <= $now && $nodeInfo->block_height_average < $blockHeightProbationStart) { + $user->node_status = 'Pulled'; + } + if ($nodeInfo->update_responsiveness_time_end <= $now && $nodeInfo->update_responsiveness < $updateResponsivenessProbationStart) { + $user->node_status = 'Pulled'; + } + $user->save(); + } + */ + } + } +} diff --git a/app/Console/Commands/KycReport.php b/app/Console/Commands/KycReport.php new file mode 100644 index 00000000..fca59009 --- /dev/null +++ b/app/Console/Commands/KycReport.php @@ -0,0 +1,136 @@ +subHours(12); + $records_temp = ShuftiproTemp::where('status', 'pending') + ->where('created_at', '<=', $yesterday) + ->limit(10) + ->get(); + + $persons_stuck_in_pending = array(); + $known_reference_ids = array(); + + if($records_temp) { + foreach($records_temp as $record) { + $user_id = $record->user_id ?? 0; + $person = User::where('id', $user_id)->first(); + $persons_stuck_in_pending[] = array( + "name" => $person->first_name.' '.$person->last_name, + "email" => $person->email, + "shufti_reference_id" => $record->reference_id, + "shufti_timestamp" => $record->created_at + ); + + $known_reference_ids[] = $record->reference_id; + } + } + + // now do shuftipro denied + $records_pro = Shuftipro::where('status', 'denied') + ->where('reviewed', 0) + ->limit(10) + ->get(); + + $persons_stuck_denied = array(); + + if($records_pro) { + foreach($records_pro as $record) { + if(!in_array($record->reference_id, $known_reference_ids)) { + $user_id = $record->user_id ?? 0; + $person = User::where('id', $user_id)->first(); + $persons_stuck_denied[] = array( + "name" => $person->first_name.' '.$person->last_name, + "email" => $person->email, + "shufti_reference_id" => $record->reference_id, + "shufti_timestamp" => $record->created_at + ); + } + } + } + + // now make a text list from the matching records + $body = ""; + $index = 1; + + if($persons_stuck_in_pending) { + $body .= "Members pending for over 24 hours:Name : {{ $contact->name }}
+Email : {{ $contact->email }}
+Message : {!! $contact->message !!}
+We could not complete processing of your KYC data. You will have to submit again. Please carefully follow the instructions below to avoid this happening again.
+We could not complete processing of your data. You will have to submit again. Please carefully follow the instructions below to avoid this happening again.