ProcessProposals.php 5.34 KB
Newer Older
1 2 3 4
<?php

namespace App\Console\Commands;

5
use App\Project;
6 7
use App\Repository\State;
use App\Repository\Connection;
8
use GuzzleHttp\Client;
9 10
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;
11
use Yaml;
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38

class ProcessProposals extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'proposal:process';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Check for changes to proposals';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

39 40 41 42 43
    private function getMergedMrFilenameToUrlMap()
    {
        $result = [];

        $connection = new Connection(new Client());
44
        $merged = $connection->mergeRequests(State::Merged);
45
        foreach ($merged as $request) {
46 47
            $newFiles = $connection->getNewFiles($request);
            if ($newFiles->count() != 1) {
48 49
                continue;
            }
50
            $filename = $newFiles->first();
51 52 53 54 55
            if (!preg_match('/.+\.md$/', $filename)) {
                continue;
            }
            if (basename($filename) != $filename) {
                continue;
56
            }
57
            $result[$filename] = $request->url();
58 59 60 61 62
        }

        return $result;
    }

xiphon's avatar
xiphon committed
63 64 65
    private const layoutToState = [ 'fr'    => 'FUNDING-REQUIRED',
                                    'wip'   => 'WORK-IN-PROGRESS',
                                    'cp'    => 'COMPLETED'];
66 67 68 69 70 71 72 73

    private const mandatoryFields = [   'amount',
                                        'author',
                                        'date',
                                        'layout',
                                        'milestones',
                                        'title'];

74 75 76 77 78
    /**
     * Execute the console command.
     */
    public function handle()
    {
79 80
        $mergedMrFilenameToUrlMap = null;

81
        $files = Storage::files('proposals');
82
        foreach ($files as $file) {
83 84 85 86 87 88 89 90
            if (!strpos($file,'.md')) {
                continue;
            }

            $filename = basename($file);

            try {
                $detail['name'] = $filename;
91
                $detail['values'] = $this->getAmountFromText($file);
92

93 94 95 96 97 98
                foreach ($this::mandatoryFields as $field) {
                    if (empty($detail['values'][$field])) {
                        throw new \Exception("Mandatory field $field is missing");
                    }
                }

99 100 101 102
                $amount = floatval(str_replace(",", ".", $detail['values']['amount']));
                $author = htmlspecialchars($detail['values']['author'], ENT_QUOTES);
                $date = strtotime($detail['values']['date']);
                $state = $this::layoutToState[$detail['values']['layout']];
xiphon's avatar
xiphon committed
103
                $milestones = $detail['values']['milestones'];
104 105 106 107 108 109 110 111 112
                $title = htmlspecialchars($detail['values']['title'], ENT_QUOTES);

                $project = Project::where('filename', $filename)->first();
                if (!$project) {
                    if ($mergedMrFilenameToUrlMap === null) {
                        $mergedMrFilenameToUrlMap = $this->getMergedMrFilenameToUrlMap();
                    }
                    if (!isset($mergedMrFilenameToUrlMap[$filename])) {
                        $this->error("Project $filename: failed to find matching merged MR");
113
                        $gitlab_url = null;
114 115
                    } else {
                        $gitlab_url = htmlspecialchars($mergedMrFilenameToUrlMap[$filename], ENT_QUOTES);
116
                    }
117 118 119 120 121 122 123 124

                    $this->info("New project $filename Gitlab MR '$gitlab_url'");
                    $project = new Project();
                    $project->gitlab_url = $gitlab_url;
                    $project->created_at = $date;
                    $project->filename = $filename;
                } else {
                    $this->info("Updating project $filename");
125
                }
126

127 128 129 130
                if (isset($detail['values']['gitlab_url'])) {
                    $project->gitlab_url = htmlspecialchars($detail['values']['gitlab_url'], ENT_QUOTES);
                }

131 132 133 134
                $project->author = $author;
                $project->state = $state;
                $project->target_amount = $amount;
                $project->title = $title;
xiphon's avatar
xiphon committed
135 136
                $project->milestones = sizeof($milestones);
                $project->milestones_completed = array_reduce($milestones, function($k, $milestone) { return $milestone['done'] ? $k + 1 : $k; }, 0);
137 138 139
                $project->save();
            } catch (\Exception $e) {
                $this->error("Error processing project $filename: {$e->getMessage()}");
140 141 142 143 144
            }
        }
    }

    /**
xiphon's avatar
xiphon committed
145
     * Gets the proposal variables out the top of the file
146 147
     *
     * @param string $filename
148
     * @return array
149 150 151 152
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
     */
    public function getAmountFromText($filename = 'additional-gui-dev.md')
    {
153 154
        $contents = preg_split('/\r?\n?---\r?\n/m', Storage::get($filename));
        if (sizeof($contents) < 3) {
155
            throw new \Exception("Failed to parse proposal, can't find YAML description surrounded by '---' lines");
156
        }
157
        return Yaml::parse($contents[1]);
158 159
    }
}