Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • monero-project/ccs-back
  • xiphon/ccs-back
  • Fudin/ccs-back
  • john_r365/ccs-back
  • plowsofff/ccs-back
5 results
Show changes
Commits on Source (42)
Showing
with 461 additions and 118 deletions
...@@ -38,11 +38,10 @@ PUSHER_APP_CLUSTER=mt1 ...@@ -38,11 +38,10 @@ PUSHER_APP_CLUSTER=mt1
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
MONERO_USERNAME= COIN=monero
MONERO_PASSWORD=
RPC_URL=http://127.0.0.1:28080/json_rpc RPC_URL=http://127.0.0.1:28080/json_rpc
RPC_USER=
RPC_PASSWORD=
WALLET_ADDRESS= REPOSITORY_URL=https://repo.getmonero.org/api/v4/projects/54
GITHUB_ACCESS_TOKEN=
GITLAB_URL=https://repo.getmonero.org/api/v4/projects/54
GITLAB_ACCESS_TOKEN=
\ No newline at end of file
[submodule "storage/app/ffs-proposals"]
path = storage/app/ffs-proposals
url = https://repo.getmonero.org/monero-project/ffs-proposals.git
@task('pull') @task('pull')
git -C "storage/app/ffs-proposals" pull origin master git -C "storage/app/proposals" pull origin master
@endtask @endtask
\ No newline at end of file
BSD 3-Clause License
Copyright (c) 2021, monero-project
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
<?php
namespace App\Coin;
use Illuminate\Console\Command;
use Monero\WalletCommon;
interface Coin
{
public function newWallet() : WalletCommon;
public function onNotifyGetTransactions(Command $command, WalletCommon $wallet);
public function subaddrIndex($addressDetails, $project);
}
class CoinAuto
{
public static function newCoin() : Coin
{
$coin = env('COIN', 'monero');
switch ($coin) {
case 'monero':
return new CoinMonero();
case 'zcoin':
return new CoinZcoin();
default:
throw new \Exception('Unsupported COIN ' . $coin);
}
}
}
<?php
namespace App\Coin;
use App\Deposit;
use Illuminate\Console\Command;
use Monero\WalletCommon;
use Monero\WalletOld;
class CoinMonero implements Coin
{
public function newWallet() : WalletCommon
{
return new WalletOld();
}
public function onNotifyGetTransactions(Command $command, WalletCommon $wallet)
{
$min_height = $command->arguments()['height'] ?? Deposit::max('block_received');
return $wallet->scanIncomingTransfers(max($min_height, 50) - 50);
}
public function subaddrIndex($addressDetails, $project)
{
return $addressDetails['subaddr_index'];
}
}
<?php
namespace App\Coin;
use App\Deposit;
use App\Project;
use Illuminate\Console\Command;
use Monero\WalletCommon;
use Monero\WalletZcoin;
class CoinZcoin implements Coin
{
public function newWallet() : WalletCommon
{
return new WalletZcoin();
}
public function onNotifyGetTransactions(Command $command, WalletCommon $wallet)
{
return $wallet->scanIncomingTransfers()->each(function ($tx) {
$project = Project::where('address', $tx->address)->first();
if ($project) {
$tx->subaddr_index = $project->subaddr_index;
}
});
}
public function subaddrIndex($addressDetails, $project)
{
return $project->id;
}
}
<?php
namespace App\Console\Commands;
use App\Deposit;
use Illuminate\Console\Command;
class depositList extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'deposit:list';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Print all deposits in JSON format';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$this->info(Deposit::all()->toJson(JSON_PRETTY_PRINT));
}
}
...@@ -2,10 +2,10 @@ ...@@ -2,10 +2,10 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use App\Coin\CoinAuto;
use App\Project; use App\Project;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Monero\WalletOld;
class GenerateAddresses extends Command class GenerateAddresses extends Command
{ {
...@@ -40,18 +40,30 @@ class GenerateAddresses extends Command ...@@ -40,18 +40,30 @@ class GenerateAddresses extends Command
*/ */
public function handle() public function handle()
{ {
$projects = Project::whereNotNull('filename')->whereNull('payment_id')->where('state', 'FUNDING-REQUIRED')->get(); $coin = CoinAuto::newCoin();
$wallet = new WalletOld(); $wallet = $coin->newWallet();
foreach ($projects as $project) {
$projects = Project::whereNotNull('filename')->whereNull('address')->where('state', 'FUNDING-REQUIRED')->get();
foreach ($projects as $project) {
$addressDetails = $wallet->getPaymentAddress(); $addressDetails = $wallet->getPaymentAddress();
$address = $addressDetails['address'];
$subaddr_index = $coin->subaddrIndex($addressDetails, $project);
if (Project::where('address', $address)->orWhere('subaddr_index', $subaddr_index)->first())
{
$this->error('Skipping already used address ' . $address . ' or subaddr_index ' . $subaddr_index);
continue;
}
$project->address_uri = $wallet->createQrCodeString($addressDetails['address']); $project->address_uri = $wallet->createQrCodeString($addressDetails['address']);
$project->address = $addressDetails['address']; $project->address = $address;
$project->payment_id = $addressDetails['paymentId']; $project->subaddr_index = $subaddr_index;
Storage::disk('public')->put("/img/qrcodes/{$project->payment_id}.png", $project->generateQrcode()); Storage::disk('public')->put("/img/qrcodes/{$project->subaddr_index}.png", $project->generateQrcode());
$project->qr_code = "img/qrcodes/{$project->payment_id}.png"; $project->qr_code = "img/qrcodes/{$project->subaddr_index}.png";
$project->raised_amount = 0; $project->raised_amount = 0;
$project->save(); $project->save();
$this->info('Project: ' . $project->filename . ', address: ' . $project->address);
} }
} }
......
<?php
namespace App\Console\Commands;
class moneroNotify extends walletNotify
{
private $coin;
private $wallet;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'monero:notify
{height? : Scan wallet transactions starting from the specified height}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Checks the monero blockchain for transactions';
}
...@@ -3,7 +3,8 @@ ...@@ -3,7 +3,8 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use App\Project; use App\Project;
use GitLab\Connection; use App\Repository\State;
use App\Repository\Connection;
use GuzzleHttp\Client; use GuzzleHttp\Client;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
...@@ -40,28 +41,36 @@ class ProcessProposals extends Command ...@@ -40,28 +41,36 @@ class ProcessProposals extends Command
$result = []; $result = [];
$connection = new Connection(new Client()); $connection = new Connection(new Client());
$merged = $connection->mergeRequests('merged'); $merged = $connection->mergeRequests(State::Merged);
foreach ($merged as $request) { foreach ($merged as $request) {
$newFiles = $connection->getNewFiles($request->iid); $newFiles = $connection->getNewFiles($request);
if (sizeof($newFiles) != 1) { if ($newFiles->count() != 1) {
continue; continue;
} }
$filename = $newFiles[0]; $filename = $newFiles->first();
if (!preg_match('/.+\.md$/', $filename)) { if (!preg_match('/.+\.md$/', $filename)) {
continue; continue;
} }
if (basename($filename) != $filename) { if (basename($filename) != $filename) {
continue; continue;
} }
$result[$filename] = $request->web_url; $result[$filename] = $request->url();
} }
return $result; return $result;
} }
private const layoutToState = [ 'ffs-fr' => 'FUNDING-REQUIRED', private const layoutToState = [ 'fr' => 'FUNDING-REQUIRED',
'ffs-wip' => 'WORK-IN-PROGRESS', 'wip' => 'WORK-IN-PROGRESS',
'ffs-cp' => 'COMPLETED']; 'cp' => 'COMPLETED'];
private const mandatoryFields = [ 'amount',
'author',
'date',
'layout',
'milestones',
'title'];
/** /**
* Execute the console command. * Execute the console command.
*/ */
...@@ -69,7 +78,7 @@ class ProcessProposals extends Command ...@@ -69,7 +78,7 @@ class ProcessProposals extends Command
{ {
$mergedMrFilenameToUrlMap = null; $mergedMrFilenameToUrlMap = null;
$files = Storage::files('ffs-proposals'); $files = Storage::files('proposals');
foreach ($files as $file) { foreach ($files as $file) {
if (!strpos($file,'.md')) { if (!strpos($file,'.md')) {
continue; continue;
...@@ -81,6 +90,12 @@ class ProcessProposals extends Command ...@@ -81,6 +90,12 @@ class ProcessProposals extends Command
$detail['name'] = $filename; $detail['name'] = $filename;
$detail['values'] = $this->getAmountFromText($file); $detail['values'] = $this->getAmountFromText($file);
foreach ($this::mandatoryFields as $field) {
if (empty($detail['values'][$field])) {
throw new \Exception("Mandatory field $field is missing");
}
}
$amount = floatval(str_replace(",", ".", $detail['values']['amount'])); $amount = floatval(str_replace(",", ".", $detail['values']['amount']));
$author = htmlspecialchars($detail['values']['author'], ENT_QUOTES); $author = htmlspecialchars($detail['values']['author'], ENT_QUOTES);
$date = strtotime($detail['values']['date']); $date = strtotime($detail['values']['date']);
...@@ -95,7 +110,7 @@ class ProcessProposals extends Command ...@@ -95,7 +110,7 @@ class ProcessProposals extends Command
} }
if (!isset($mergedMrFilenameToUrlMap[$filename])) { if (!isset($mergedMrFilenameToUrlMap[$filename])) {
$this->error("Project $filename: failed to find matching merged MR"); $this->error("Project $filename: failed to find matching merged MR");
$gitlab_url = null; $gitlab_url = null;
} else { } else {
$gitlab_url = htmlspecialchars($mergedMrFilenameToUrlMap[$filename], ENT_QUOTES); $gitlab_url = htmlspecialchars($mergedMrFilenameToUrlMap[$filename], ENT_QUOTES);
} }
...@@ -109,6 +124,10 @@ class ProcessProposals extends Command ...@@ -109,6 +124,10 @@ class ProcessProposals extends Command
$this->info("Updating project $filename"); $this->info("Updating project $filename");
} }
if (isset($detail['values']['gitlab_url'])) {
$project->gitlab_url = htmlspecialchars($detail['values']['gitlab_url'], ENT_QUOTES);
}
$project->author = $author; $project->author = $author;
$project->state = $state; $project->state = $state;
$project->target_amount = $amount; $project->target_amount = $amount;
...@@ -123,7 +142,7 @@ class ProcessProposals extends Command ...@@ -123,7 +142,7 @@ class ProcessProposals extends Command
} }
/** /**
* Gets the ffs variables out the top of the file * Gets the proposal variables out the top of the file
* *
* @param string $filename * @param string $filename
* @return array * @return array
...@@ -133,7 +152,7 @@ class ProcessProposals extends Command ...@@ -133,7 +152,7 @@ class ProcessProposals extends Command
{ {
$contents = preg_split('/\r?\n?---\r?\n/m', Storage::get($filename)); $contents = preg_split('/\r?\n?---\r?\n/m', Storage::get($filename));
if (sizeof($contents) < 3) { if (sizeof($contents) < 3) {
throw new \Exception("Failed to parse proposal, can't find YAML description surrounded by '---' lines"); throw new \Exception("Failed to parse proposal, can't find YAML description surrounded by '---' lines");
} }
return Yaml::parse($contents[1]); return Yaml::parse($contents[1]);
} }
......
...@@ -3,8 +3,9 @@ ...@@ -3,8 +3,9 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use App\Project; use App\Project;
use App\Repository\State;
use App\Repository\Connection;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use GitLab\Connection;
use GuzzleHttp\Client; use GuzzleHttp\Client;
use stdClass; use stdClass;
use Symfony\Component\Yaml\Yaml; use Symfony\Component\Yaml\Yaml;
...@@ -16,7 +17,7 @@ class UpdateSiteProposals extends Command ...@@ -16,7 +17,7 @@ class UpdateSiteProposals extends Command
* *
* @var string * @var string
*/ */
protected $signature = 'ffs:update'; protected $signature = 'proposal:update';
/** /**
* The console command description. * The console command description.
...@@ -46,7 +47,7 @@ class UpdateSiteProposals extends Command ...@@ -46,7 +47,7 @@ class UpdateSiteProposals extends Command
$this->getProposals('Work in Progress', 'WORK-IN-PROGRESS'), $this->getProposals('Work in Progress', 'WORK-IN-PROGRESS'),
]; ];
$json = json_encode($response, JSON_UNESCAPED_SLASHES|JSON_PRETTY_PRINT); $json = json_encode($response, JSON_UNESCAPED_SLASHES|JSON_PRETTY_PRINT);
\Storage::put('ffs.json', $json); \Storage::put('proposals.json', $json);
$response = [ $response = [
$this->getProposals('Completed Proposals', 'COMPLETED'), $this->getProposals('Completed Proposals', 'COMPLETED'),
...@@ -55,6 +56,19 @@ class UpdateSiteProposals extends Command ...@@ -55,6 +56,19 @@ class UpdateSiteProposals extends Command
\Storage::put('complete.json', $json); \Storage::put('complete.json', $json);
} }
private function sortProposalsByDateDesc($responseProposals)
{
usort($responseProposals, function($a, $b){
return strtotime($a->date) < strtotime($b->date) ? 1 : -1;
});
return $responseProposals;
}
private function proposalFileExists($filename)
{
return \Storage::exists('proposals/'. basename($filename));
}
private function ideaProposals() private function ideaProposals()
{ {
$group = new stdClass(); $group = new stdClass();
...@@ -63,42 +77,42 @@ class UpdateSiteProposals extends Command ...@@ -63,42 +77,42 @@ class UpdateSiteProposals extends Command
$ideas = []; $ideas = [];
$connection = new Connection(new Client()); $connection = new Connection(new Client());
$mergeRequests = $connection->mergeRequests('opened'); $mergeRequests = $connection->mergeRequests(State::Opened);
foreach ($mergeRequests as $mergeRequest) { foreach ($mergeRequests as $mergeRequest) {
$newFiles = $connection->getNewFiles($mergeRequest->iid); $newFiles = $connection->getNewFiles($mergeRequest);
if (sizeof($newFiles) != 1) { if ($newFiles->count() != 1) {
$this->error ("Skipping MR #$mergeRequest->id '$mergeRequest->title': contains multiple files"); $this->error ("Skipping MR #{$mergeRequest->id()} '{$mergeRequest->title()}': contains multiple files");
continue; continue;
} }
$filename = $newFiles[0]; $filename = $newFiles->first();
if (!preg_match('/.+\.md$/', $filename)) { if (!preg_match('/.+\.md$/', $filename)) {
$this->error("Skipping MR #$mergeRequest->id '$mergeRequest->title': doesn't contain any .md file"); $this->error("Skipping MR #{$mergeRequest->id()} '{$mergeRequest->title()}': doesn't contain any .md file");
continue; continue;
} }
if (basename($filename) != $filename) { if (basename($filename) != $filename) {
$this->error("Skipping MR #$mergeRequest->id '$mergeRequest->title': $filename must be in the root folder"); $this->error("Skipping MR #{$mergeRequest->id()} '{$mergeRequest->title()}': $filename must be in the root folder");
continue; continue;
} }
if (in_array($filename, $ideas)) { if (in_array($filename, $ideas)) {
$this->error("Skipping MR #$mergeRequest->id '$mergeRequest->title': duplicated $filename, another MR #$ideas[$filename]->id"); $this->error("Skipping MR #{$mergeRequest->id()} '{$mergeRequest->title()}': duplicated $filename, another MR #$ideas[$filename]->id");
continue; continue;
} }
$project = Project::where('filename', $filename)->first(); $project = Project::where('filename', $filename)->first();
if ($project) { if ($project && $this->proposalFileExists($filename)) {
$this->error("Skipping MR #$mergeRequest->id '$mergeRequest->title': already have a project $filename"); $this->error("Skipping MR #{$mergeRequest->id()} '{$mergeRequest->title()}': already have a project $filename");
continue; continue;
} }
$this->info("Idea MR #$mergeRequest->id '$mergeRequest->title': $filename"); $this->info("Idea MR #{$mergeRequest->id()} '{$mergeRequest->title()}': $filename");
$prop = new stdClass(); $prop = new stdClass();
$prop->name = htmlspecialchars(trim($mergeRequest->title), ENT_QUOTES); $prop->name = htmlspecialchars(trim($mergeRequest->title()), ENT_QUOTES);
$prop->{'gitlab-url'} = htmlspecialchars($mergeRequest->web_url, ENT_QUOTES); $prop->{'gitlab-url'} = htmlspecialchars($mergeRequest->url(), ENT_QUOTES);
$prop->author = htmlspecialchars($mergeRequest->author->username, ENT_QUOTES); $prop->author = htmlspecialchars($mergeRequest->author(), ENT_QUOTES);
$prop->date = date('F j, Y', strtotime($mergeRequest->created_at)); $prop->date = date('F j, Y', $mergeRequest->created_at());
$responseProposals[] = $prop; $responseProposals[] = $prop;
} }
$group->proposals = $responseProposals; $group->proposals = $this->sortProposalsByDateDesc($responseProposals);
return $group; return $group;
} }
...@@ -106,16 +120,18 @@ class UpdateSiteProposals extends Command ...@@ -106,16 +120,18 @@ class UpdateSiteProposals extends Command
{ {
$prop = new stdClass(); $prop = new stdClass();
$prop->name = $proposal->title; $prop->name = $proposal->title;
$prop->{'donate-url'} = url("projects/{$proposal->payment_id}/donate"); $prop->{'donate-address'} = $proposal->address;
$prop->{'donate-qr-code'} = $proposal->address_uri ? $proposal->getQrCodeSrcAttribute() : null;
$prop->{'gitlab-url'} = $proposal->gitlab_url; $prop->{'gitlab-url'} = $proposal->gitlab_url;
$prop->{'local-url'} = '/forum-funding-system/proposals/'. pathinfo($proposal->filename, PATHINFO_FILENAME) . '.html'; $prop->{'local-url'} = '/proposals/'. pathinfo($proposal->filename, PATHINFO_FILENAME) . '.html';
$prop->contributions = $proposal->contributions;
$prop->milestones = $proposal->milestones; $prop->milestones = $proposal->milestones;
$prop->{'milestones-completed'} = $proposal->milestones_completed; $prop->{'milestones-completed'} = $proposal->milestones_completed;
$milestones_percentage = min(100, (int)(($proposal->milestones_completed * 100) / $proposal->milestones)); $milestones_percentage = min(100, (int)(($proposal->milestones_completed * 100) / $proposal->milestones));
$prop->{'milestones-percentage'} = $milestones_percentage; $prop->{'milestones-percentage'} = $milestones_percentage;
$prop->percentage = $proposal->percentage_funded; $prop->percentage = $proposal->percentage_funded;
$prop->amount = $proposal->target_amount; $prop->amount = $proposal->target_amount;
$prop->{'amount-funded'} = $proposal->amount_received; $prop->{'amount-funded'} = $proposal->raised_amount;
$prop->author = $proposal->author; $prop->author = $proposal->author;
$prop->date = $proposal->created_at->format('F j, Y'); $prop->date = $proposal->created_at->format('F j, Y');
return $prop; return $prop;
...@@ -128,9 +144,11 @@ class UpdateSiteProposals extends Command ...@@ -128,9 +144,11 @@ class UpdateSiteProposals extends Command
$responseProposals = []; $responseProposals = [];
$proposals = Project::where('state', $state)->get(); $proposals = Project::where('state', $state)->get();
foreach ($proposals as $proposal) { foreach ($proposals as $proposal) {
$responseProposals[] = $this->formatProposal($proposal); if ($this->proposalFileExists($proposal->filename)) {
$responseProposals[] = $this->formatProposal($proposal);
}
} }
$group->proposals = $responseProposals; $group->proposals = $this->sortProposalsByDateDesc($responseProposals);
return $group; return $group;
} }
} }
...@@ -2,27 +2,30 @@ ...@@ -2,27 +2,30 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use App\Coin\CoinAuto;
use App\Deposit; use App\Deposit;
use App\Project; use App\Project;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Monero\Transaction; use Monero\Transaction;
use Monero\WalletOld;
class walletNotify extends Command class walletNotify extends Command
{ {
private $coin;
private $wallet;
/** /**
* The name and signature of the console command. * The name and signature of the console command.
* *
* @var string * @var string
*/ */
protected $signature = 'monero:notify'; protected $signature = 'wallet:notify';
/** /**
* The console command description. * The console command description.
* *
* @var string * @var string
*/ */
protected $description = 'Checks the monero blockchain for transactions'; protected $description = 'Checks the blockchain for transactions';
/** /**
* Create a new command instance. * Create a new command instance.
...@@ -32,6 +35,9 @@ class walletNotify extends Command ...@@ -32,6 +35,9 @@ class walletNotify extends Command
public function __construct() public function __construct()
{ {
parent::__construct(); parent::__construct();
$this->coin = CoinAuto::newCoin();
$this->wallet = $this->coin->newWallet();
} }
/** /**
...@@ -41,30 +47,18 @@ class walletNotify extends Command ...@@ -41,30 +47,18 @@ class walletNotify extends Command
*/ */
public function handle() public function handle()
{ {
$wallet = new WalletOld(); $blockheight = $this->wallet->blockHeight();
$blockheight = $wallet->blockHeight();
if ($blockheight < 1) { if ($blockheight < 1) {
$this->error('monero daemon down or wrong port in db ?'); $this->error('failed to fetch blockchain height');
return; return;
} }
// check mempool $transactions = $this->coin->onNotifyGetTransactions($this, $this->wallet);
$transactionsMempool = $wallet->scanMempool($blockheight); $transactions->each(function ($transaction) {
$transactionsMempool->each(function ($transaction) use ($wallet) {
$this->processPayment($transaction); $this->processPayment($transaction);
}); });
$paymentIDs = $wallet->getPaymentIds();
if (count($paymentIDs)) {
// check blockchain
$transactions = $wallet->scanBlocks($blockheight, $paymentIDs);
$transactions->each(function ($transaction) use ($wallet) {
$this->processPayment($transaction);
});
}
$this->updateAllConfirmations($blockheight); $this->updateAllConfirmations($blockheight);
} }
...@@ -75,20 +69,27 @@ class walletNotify extends Command ...@@ -75,20 +69,27 @@ class walletNotify extends Command
*/ */
public function processPayment(Transaction $transaction) public function processPayment(Transaction $transaction)
{ {
// if the deposit exist, no need to try add it again $amountCoins = $transaction->amount / 10 ** $this->wallet->digitsAfterTheRadixPoint();
if (Deposit::where('tx_id', $transaction->id)->exists()) { $details = 'address: ' . $transaction->address . ' amount: '. $amountCoins . ' txid: '.$transaction->id;
$deposit = Deposit::where('tx_id', $transaction->id)->where('subaddr_index', $transaction->subaddr_index)->first();
if ($deposit) {
if ($deposit->block_received == 0) {
$deposit->block_received = $transaction->block_height;
$deposit->save();
}
return null; return null;
} }
$this->info('amount: '.$transaction->amount / 1000000000000 .' confirmations:'.$transaction->confirmations.' tx_hash:'.$transaction->id);
$this->info('paymentid: '.$transaction->paymentId);
$this->createDeposit($transaction); $this->createDeposit($transaction);
$project = Project::where('payment_id', $transaction->paymentId)->first(); if ($project = Project::where('subaddr_index', $transaction->subaddr_index)->first()) {
if ($project) {
// update the project total // update the project total
$project->raised_amount = $project->raised_amount + $transaction->amount; $project->raised_amount = $project->raised_amount + $amountCoins;
$project->save(); $project->save();
$this->info('Donation to "' . $project->filename . '" '. $details);
} else {
$this->error('Unrecognized donation, ' . $details);
} }
return; return;
...@@ -105,7 +106,8 @@ class walletNotify extends Command ...@@ -105,7 +106,8 @@ class walletNotify extends Command
{ {
$count = 0; $count = 0;
//update all xmr deposit confirmations //update all xmr deposit confirmations
Deposit::where('confirmations', '<', 10) Deposit::where('confirmations', '<', 50)
->where('block_received', '>', 0)
->each(function ($deposit) use ($blockheight, &$count) { ->each(function ($deposit) use ($blockheight, &$count) {
$this->updateConfirmation($blockheight, $deposit); $this->updateConfirmation($blockheight, $deposit);
$count++; $count++;
...@@ -124,7 +126,7 @@ class walletNotify extends Command ...@@ -124,7 +126,7 @@ class walletNotify extends Command
*/ */
public function updateConfirmation($blockheight, Deposit $deposit) public function updateConfirmation($blockheight, Deposit $deposit)
{ {
$diff = $blockheight - $deposit->block_received; $diff = $blockheight - $deposit->block_received + 1;
$deposit->confirmations = $diff; $deposit->confirmations = $diff;
$deposit->save(); $deposit->save();
...@@ -144,7 +146,7 @@ class walletNotify extends Command ...@@ -144,7 +146,7 @@ class walletNotify extends Command
$deposit->tx_id = $transaction->id; $deposit->tx_id = $transaction->id;
$deposit->amount = $transaction->amount; $deposit->amount = $transaction->amount;
$deposit->confirmations = $transaction->confirmations; $deposit->confirmations = $transaction->confirmations;
$deposit->payment_id = $transaction->paymentId; $deposit->subaddr_index = $transaction->subaddr_index;
$deposit->time_received = $transaction->time_received; $deposit->time_received = $transaction->time_received;
$deposit->block_received = $transaction->block_height; $deposit->block_received = $transaction->block_height;
$deposit->save(); $deposit->save();
......
...@@ -28,14 +28,14 @@ class Kernel extends ConsoleKernel ...@@ -28,14 +28,14 @@ class Kernel extends ConsoleKernel
*/ */
protected function schedule(Schedule $schedule) protected function schedule(Schedule $schedule)
{ {
$schedule->command(GenerateAddresses::class)
->everyMinute();
$schedule->command(ProcessProposals::class) $schedule->command(ProcessProposals::class)
->everyMinute(); ->everyMinute();
$schedule->command(UpdateSiteProposals::class) $schedule->command(GenerateAddresses::class)
->everyMinute(); ->everyMinute();
$schedule->command(walletNotify::class) $schedule->command(walletNotify::class)
->everyMinute(); ->everyMinute();
$schedule->command(UpdateSiteProposals::class)
->everyMinute();
} }
/** /**
......
...@@ -8,7 +8,7 @@ use Illuminate\Database\Eloquent\Model; ...@@ -8,7 +8,7 @@ use Illuminate\Database\Eloquent\Model;
* App\Deposit * App\Deposit
* *
* @property int $id * @property int $id
* @property string $payment_id * @property int $subaddr_index
* @property string $amount * @property string $amount
* @property string $time_received * @property string $time_received
* @property string $tx_id * @property string $tx_id
......
...@@ -17,26 +17,19 @@ class FundingController extends Controller ...@@ -17,26 +17,19 @@ class FundingController extends Controller
*/ */
public function index(Request $request) public function index(Request $request)
{ {
$projects = Project::paginate(15); return ProjectResource::collection(Project::all());
// If the request has header `Accept: */json`, return JSON
if ($request->wantsJson())
{
return ProjectResource::collection($projects);
}
return view('projects.index')
->with('projects', $projects);
} }
/** /**
* Shows the project based on the payment id * Shows the project based on the payment id
* *
* @param Request $request * @param Request $request
* @param $paymentId * @param $subaddr_index
* @return ProjectResource|\Illuminate\Contracts\View\Factory|\Illuminate\View\View * @return ProjectResource|\Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/ */
public function show(Request $request, $paymentId) public function show(Request $request, $subaddr_index)
{ {
$project = Project::where('payment_id', $paymentId)->firstOrFail(); $project = Project::where('subaddr_index', $subaddr_index)->firstOrFail();
if ($request->wantsJson()) if ($request->wantsJson())
{ {
...@@ -47,9 +40,9 @@ class FundingController extends Controller ...@@ -47,9 +40,9 @@ class FundingController extends Controller
->with('project', $project); ->with('project', $project);
} }
public function donate(Request $request, $paymentId) public function donate(Request $request, $subaddr_index)
{ {
$project = Project::where('payment_id', $paymentId)->firstOrFail(); $project = Project::where('subaddr_index', $subaddr_index)->firstOrFail();
if ($request->wantsJson()) if ($request->wantsJson())
{ {
......
...@@ -15,13 +15,15 @@ class ProjectResource extends JsonResource ...@@ -15,13 +15,15 @@ class ProjectResource extends JsonResource
public function toArray($request) public function toArray($request)
{ {
return [ return [
'payment_id' => $this->payment_id, 'address' => $this->address,
'status' => $this->status, 'author' => $this->author,
'amount_received' => $this->amount_received,
'target_amount' => $this->target_amount,
'percentage_funded' => $this->percentage_funded,
'qrcode' => ['base64' => base64_encode($this->qrcode)],
'contributions' => $this->contributions, 'contributions' => $this->contributions,
'date' => $this->created_at->format('F j, Y'),
'percentage_funded' => $this->percentage_funded,
'raised_amount' => $this->raised_amount,
'state' => $this->state,
'target_amount' => $this->target_amount,
'title' => $this->title,
]; ];
} }
} }
...@@ -9,14 +9,14 @@ use SimpleSoftwareIO\QrCode\Facades\QrCode; ...@@ -9,14 +9,14 @@ use SimpleSoftwareIO\QrCode\Facades\QrCode;
* App\ProjectResource * App\ProjectResource
* *
* @property int $id * @property int $id
* @property string $payment_id * @property int $subaddr_index
* @property string $target_amount * @property string $target_amount
* @property string $status * @property string $status
* @property \Illuminate\Support\Carbon|null $created_at * @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at * @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Deposit[] $deposits * @property-read \Illuminate\Database\Eloquent\Collection|\App\Deposit[] $deposits
* @property-read mixed $amount_received * @property-read mixed $raised_amount
* @property-read string $uri * @property-read string $address_uri
* @property-read int $percentage_funded * @property-read int $percentage_funded
* @property-read int $contributions * @property-read int $contributions
* @property-read string $qrcode * @property-read string $qrcode
...@@ -43,15 +43,11 @@ class Project extends Model ...@@ -43,15 +43,11 @@ class Project extends Model
*/ */
public function deposits() public function deposits()
{ {
return $this->hasMany(Deposit::class, 'payment_id', 'payment_id'); return $this->hasMany(Deposit::class, 'subaddr_index', 'subaddr_index');
}
public function getAmountReceivedAttribute() {
return $this->deposits->sum('amount') * 1e-12;
} }
public function getPercentageFundedAttribute() { public function getPercentageFundedAttribute() {
return min(100, round($this->amount_received / $this->target_amount * 100)); return min(100, round($this->raised_amount / $this->target_amount * 100));
} }
public function getContributionsAttribute() { public function getContributionsAttribute() {
......
<?php
namespace App\Repository;
use GuzzleHttp\Client;
class Connection
{
private $repo;
public function __construct(Client $client)
{
$url = env('REPOSITORY_URL') ?? env('GITLAB_URL');
if (parse_url($url, PHP_URL_HOST) == 'github.com') {
$this->repo = new Github($client, $url);
} else {
$this->repo = new Gitlab($client, $url);
}
}
public function mergeRequests($state) {
return $this->repo->mergeRequests($state);
}
public function getNewFiles($merge_request_iid) {
return $this->repo->getNewFiles($merge_request_iid);
}
}
<?php
namespace App\Repository;
use GuzzleHttp\Client;
class PullRequest implements Proposal
{
private $pull_request;
public function __construct($pull_request)
{
$this->pull_request = $pull_request;
}
public function id() : int
{
return $this->pull_request->number;
}
public function url() : string
{
return $this->pull_request->html_url;
}
public function title() : string
{
return $this->pull_request->title;
}
public function author() : string
{
return $this->pull_request->user->login;
}
public function created_at() : int
{
return strtotime($this->pull_request->created_at);
}
}
class Github implements Repository
{
private $client;
private $options = [];
private $owner_repo;
private const stateToString = [ State::Opened => 'open' ,
State::Merged => 'closed'];
public function __construct(Client $client, string $repository_url)
{
$this->client = $client;
$this->owner_repo = parse_url($repository_url, PHP_URL_PATH);
if ($token = env('GITHUB_ACCESS_TOKEN')) {
$this->options = ['headers' => ['Authorization' => 'token ' . $token]];
}
}
private function getUrl($url)
{
return $this->client->request('GET', $url, $this->options);
}
public function mergeRequests($state)
{
$url = 'https://api.github.com/repos' . $this->owner_repo . '/pulls?state=' . Self::stateToString[$state];
$response = $this->getUrl($url);
$result = collect(json_decode($response->getBody()));
if ($state == State::Merged) {
$result = $result->filter(function ($pull_request) {
return $pull_request->merged_at !== null;
});
}
return $result->map(function ($pull_request) {
return new PullRequest($pull_request);
});
}
public function getNewFiles($pull_request)
{
$url = 'https://api.github.com/repos' . $this->owner_repo . '/pulls/' . $pull_request->id() . '/files';
$response = $this->getUrl($url);
return collect(json_decode($response->getBody()))->filter(function ($change) {
return $change->status == 'added';
})->map(function ($change) {
return $change->filename;
});
}
}