diff --git a/bin/doctum b/bin/doctum new file mode 100755 --- /dev/null +++ b/bin/doctum @@ -0,0 +1,15 @@ +#!/bin/bash + +cwd=$(dirname $0) + +pushd ${cwd}/../src/ + +rm -rf cache/store/ + +php -dmemory_limit=-1 \ + vendor/bin/doctum.php \ + update \ + doctum.config.php \ + -v + +popd diff --git a/bin/phpunit b/bin/phpunit --- a/bin/phpunit +++ b/bin/phpunit @@ -4,7 +4,9 @@ pushd ${cwd}/../src/ -php -dzend_extension=xdebug.so \ +php \ + -dmemory_limit=-1 \ + -dzend_extension=xdebug.so \ vendor/bin/phpunit \ --stop-on-defect \ --stop-on-error \ diff --git a/docker/redis/Dockerfile b/docker/redis/Dockerfile --- a/docker/redis/Dockerfile +++ b/docker/redis/Dockerfile @@ -24,4 +24,4 @@ WORKDIR /root/ -CMD ["/lib/systemd/systemd"] +CMD ["/usr/bin/redis-server"] diff --git a/src/app/Auth/LDAPUserProvider.php b/src/app/Auth/LDAPUserProvider.php --- a/src/app/Auth/LDAPUserProvider.php +++ b/src/app/Auth/LDAPUserProvider.php @@ -22,7 +22,7 @@ */ public function retrieveByCredentials(array $credentials) { - $entries = User::where('email', '=', $credentials['email'])->get(); + $entries = User::where('email', \strtolower($credentials['email']))->get(); $count = $entries->count(); @@ -51,7 +51,7 @@ { $authenticated = false; - if ($user->email == $credentials['email']) { + if ($user->email === \strtolower($credentials['email'])) { if (!empty($user->password)) { if (Hash::check($credentials['password'], $user->password)) { $authenticated = true; diff --git a/src/app/Backends/LDAP.php b/src/app/Backends/LDAP.php --- a/src/app/Backends/LDAP.php +++ b/src/app/Backends/LDAP.php @@ -98,7 +98,10 @@ $result = $ldap->add_entry($dn, $entry); if (!$result) { - self::throwException($ldap, "Failed to create domain {$domain->namespace} in LDAP"); + self::throwException( + $ldap, + "Failed to create domain {$domain->namespace} in LDAP (" . __LINE__ . ")" + ); } } @@ -144,12 +147,19 @@ ); if (!$ldap->get_entry($domainBaseDN)) { - $ldap->add_entry($domainBaseDN, $entry); + $result = $ldap->add_entry($domainBaseDN, $entry); + + if (!$result) { + self::throwException( + $ldap, + "Failed to create domain {$domain->namespace} in LDAP (" . __LINE__ . ")" + ); + } } foreach (['Groups', 'People', 'Resources', 'Shared Folders'] as $item) { if (!$ldap->get_entry("ou={$item},{$domainBaseDN}")) { - $ldap->add_entry( + $result = $ldap->add_entry( "ou={$item},{$domainBaseDN}", [ 'ou' => $item, @@ -160,12 +170,19 @@ ] ] ); + + if (!$result) { + self::throwException( + $ldap, + "Failed to create domain {$domain->namespace} in LDAP (" . __LINE__ . ")" + ); + } } } foreach (['kolab-admin'] as $item) { if (!$ldap->get_entry("cn={$item},{$domainBaseDN}")) { - $ldap->add_entry( + $result = $ldap->add_entry( "cn={$item},{$domainBaseDN}", [ 'cn' => $item, @@ -179,6 +196,13 @@ ] ] ); + + if (!$result) { + self::throwException( + $ldap, + "Failed to create domain {$domain->namespace} in LDAP (" . __LINE__ . ")" + ); + } } } @@ -230,13 +254,20 @@ 'nsroledn' => [] ]; - if (!self::getUserEntry($ldap, $user->email, $dn) && $dn) { + if (!self::getUserEntry($ldap, $user->email, $dn)) { + if (empty($dn)) { + self::throwException($ldap, "Failed to create user {$user->email} in LDAP (" . __LINE__ . ")"); + } + self::setUserAttributes($user, $entry); $result = $ldap->add_entry($dn, $entry); if (!$result) { - self::throwException($ldap, "Failed to create user {$user->email} in LDAP"); + self::throwException( + $ldap, + "Failed to create user {$user->email} in LDAP (" . __LINE__ . ")" + ); } } @@ -266,7 +297,10 @@ $result = $ldap->delete_entry_recursive($domainBaseDN); if (!$result) { - self::throwException($ldap, "Failed to delete domain {$domain->namespace} from LDAP"); + self::throwException( + $ldap, + "Failed to delete domain {$domain->namespace} from LDAP (" . __LINE__ . ")" + ); } } @@ -275,7 +309,10 @@ $result = $ldap->delete_entry($ldap_domain['dn']); if (!$result) { - self::throwException($ldap, "Failed to delete domain {$domain->namespace} from LDAP"); + self::throwException( + $ldap, + "Failed to delete domain {$domain->namespace} from LDAP (" . __LINE__ . ")" + ); } } } @@ -301,7 +338,10 @@ $result = $ldap->delete_entry($dn); if (!$result) { - self::throwException($ldap, "Failed to delete user {$user->email} from LDAP"); + self::throwException( + $ldap, + "Failed to delete user {$user->email} from LDAP (" . __LINE__ . ")" + ); } } @@ -373,7 +413,10 @@ $ldapDomain = $ldap->find_domain($domain->namespace); if (!$ldapDomain) { - self::throwException($ldap, "Failed to update domain {$domain->namespace} in LDAP (domain not found)"); + self::throwException( + $ldap, + "Failed to update domain {$domain->namespace} in LDAP (domain not found)" + ); } $oldEntry = $ldap->get_entry($ldapDomain['dn']); @@ -388,7 +431,10 @@ $result = $ldap->modify_entry($ldapDomain['dn'], $oldEntry, $newEntry); if (!is_array($result)) { - self::throwException($ldap, "Failed to update domain {$domain->namespace} in LDAP"); + self::throwException( + $ldap, + "Failed to update domain {$domain->namespace} in LDAP (" . __LINE__ . ")" + ); } if (empty(self::$ldap)) { @@ -411,7 +457,10 @@ $newEntry = $oldEntry = self::getUserEntry($ldap, $user->email, $dn, true); if (!$oldEntry) { - self::throwException($ldap, "Failed to update user {$user->email} in LDAP (user not found)"); + self::throwException( + $ldap, + "Failed to update user {$user->email} in LDAP (user not found)" + ); } self::setUserAttributes($user, $newEntry); @@ -433,7 +482,10 @@ $result = $ldap->modify_entry($dn, $oldEntry, $newEntry); if (!is_array($result)) { - self::throwException($ldap, "Failed to update user {$user->email} in LDAP"); + self::throwException( + $ldap, + "Failed to update user {$user->email} in LDAP (" . __LINE__ . ")" + ); } if (empty(self::$ldap)) { @@ -458,7 +510,10 @@ throw new \Exception("Failed to connect to LDAP"); } - $bound = $ldap->bind(\config("ldap.{$privilege}.bind_dn"), \config("ldap.{$privilege}.bind_pw")); + $bound = $ldap->bind( + \config("ldap.{$privilege}.bind_dn"), + \config("ldap.{$privilege}.bind_pw") + ); if (!$bound) { throw new \Exception("Failed to bind to LDAP"); diff --git a/src/app/Console/Command.php b/src/app/Console/Command.php new file mode 100644 --- /dev/null +++ b/src/app/Console/Command.php @@ -0,0 +1,104 @@ +getObject(\App\Domain::class, $domain, 'namespace'); + } + + /** + * Find an object. + * + * @param string $objectClass The name of the class + * @param string $objectIdOrTitle The name of a database field to match. + * @param string|null $objectTitle An additional database field to match. + * + * @return mixed + */ + public function getObject($objectClass, $objectIdOrTitle, $objectTitle) + { + $object = $objectClass::find($objectIdOrTitle); + + if (!$object && !empty($objectTitle)) { + $object = $objectClass::where($objectTitle, $objectIdOrTitle)->first(); + } + + return $object; + } + + /** + * Find the user. + * + * @param string $user User ID or email + * + * @return \App\User|null + */ + public function getUser($user) + { + return $this->getObject(\App\User::class, $user, 'email'); + } + + /** + * Find the wallet. + * + * @param string $wallet Wallet ID + * + * @return \App\Wallet|null + */ + public function getWallet($wallet) + { + return $this->getObject(\App\Wallet::class, $wallet, null); + } + + /** + * Return a string for output, with any additional attributes specified as well. + * + * @param mixed $entry An object + * + * @return string + */ + protected function toString($entry) + { + /** + * Haven't figured out yet, how to test if this command implements an option for additional + * attributes. + if (!in_array('attr', $this->options())) { + return $entry->{$entry->getKeyName()}; + } + */ + + $str = [ + $entry->{$entry->getKeyName()} + ]; + + foreach ($this->option('attr') as $attr) { + if ($attr == $entry->getKeyName()) { + $this->warn("Specifying {$attr} is not useful."); + continue; + } + + if (!array_key_exists($attr, $entry->toArray())) { + $this->error("Attribute {$attr} isn't available"); + continue; + } + + if (is_numeric($entry->{$attr})) { + $str[] = $entry->{$attr}; + } else { + $str[] = !empty($entry->{$attr}) ? $entry->{$attr} : "null"; + } + } + + return implode(" ", $str); + } +} diff --git a/src/app/Console/Commands/Data/Import/CountriesCommand.php b/src/app/Console/Commands/Data/Import/CountriesCommand.php new file mode 100644 --- /dev/null +++ b/src/app/Console/Commands/Data/Import/CountriesCommand.php @@ -0,0 +1,114 @@ + currency + 'LT' => 'EUR', + ]; + + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'data:import:countries'; + + /** + * The console command description. + * + * @var string + */ + protected $description = 'Fetches countries map from country.io'; + + /** + * Execute the console command. + * + * @return mixed + */ + public function handle() + { + $today = Carbon::now()->toDateString(); + + $countries = []; + $currencies = []; + $currencySource = 'http://country.io/currency.json'; + $countrySource = 'http://country.io/names.json'; + + // + // countries + // + $file = storage_path("countries-{$today}.json"); + + \App\Utils::downloadFile($countrySource, $file); + + $countryJson = file_get_contents($file); + + if (!$countryJson) { + $this->error("Failed to fetch countries"); + return 1; + } + + $countries = json_decode($countryJson, true); + + if (!is_array($countries) || empty($countries)) { + $this->error("Invalid countries data"); + return 1; + } + + // + // currencies + // + $file = storage_path("currencies-{$today}.json"); + + \App\Utils::downloadFile($currencySource, $file); + + // fetch currency table and create an index by country page url + $currencyJson = file_get_contents($file); + + if (!$currencyJson) { + $this->error("Failed to fetch currencies"); + return; + } + + $currencies = json_decode($currencyJson, true); + + if (!is_array($currencies) || empty($currencies)) { + $this->error("Invalid currencies data"); + return 1; + } + + // + // export + // + $file = resource_path('countries.php'); + + asort($countries); + + $out = " $name) { + $currency = $currencies[$code] ?? null; + + if (!empty($this->currency_fixes[$code])) { + $currency = $this->currency_fixes[$code]; + } + + if (!$currency) { + $this->warn("Unknown currency for {$name} ({$code}). Skipped."); + continue; + } + + $out .= sprintf(" '%s' => ['%s','%s'],\n", $code, $currency, addslashes($name)); + } + + $out .= "];\n"; + + file_put_contents($file, $out); + } +} diff --git a/src/app/Console/Commands/Data/Import/IP4NetsCommand.php b/src/app/Console/Commands/Data/Import/IP4NetsCommand.php new file mode 100644 --- /dev/null +++ b/src/app/Console/Commands/Data/Import/IP4NetsCommand.php @@ -0,0 +1,225 @@ + 'http://ftp.afrinic.net/stats/afrinic/delegated-afrinic-latest', + 'apnic' => 'http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest', + 'arin' => 'http://ftp.arin.net/pub/stats/arin/delegated-arin-extended-latest', + 'lacnic' => 'http://ftp.lacnic.net/pub/stats/lacnic/delegated-lacnic-latest', + 'ripencc' => 'https://ftp.ripe.net/ripe/stats/delegated-ripencc-latest' + ]; + + $today = Carbon::now()->toDateString(); + + foreach ($rirs as $rir => $url) { + $file = storage_path("{$rir}-{$today}"); + + \App\Utils::downloadFile($url, $file); + + $serial = $this->serialFromStatsFile($file); + + if (!$serial) { + \Log::error("Can not derive serial from {$file}"); + continue; + } + + $numLines = $this->countLines($file); + + if (!$numLines) { + \Log::error("No relevant lines could be found in {$file}"); + continue; + } + + $bar = \App\Utils::createProgressBar( + $this->output, + $numLines, + "Importing IPv4 Networks from {$file}" + ); + + $fp = fopen($file, 'r'); + + $nets = []; + + while (!feof($fp)) { + $line = trim(fgets($fp)); + + if ($line == "") { + continue; + } + + if ((int)$line) { + continue; + } + + if ($line[0] == "#") { + continue; + } + + $items = explode('|', $line); + + if (sizeof($items) < 7) { + continue; + } + + if ($items[1] == "*") { + continue; + } + + if ($items[2] != "ipv4") { + continue; + } + + if ($items[5] == "00000000") { + $items[5] = "19700102"; + } + + if ($items[1] == "" || $items[1] == "ZZ") { + continue; + } + + $bar->advance(); + + $mask = 32 - log($items[4], 2); + + $net = \App\IP4Net::where( + [ + 'net_number' => $items[3], + 'net_mask' => $mask, + 'net_broadcast' => long2ip((ip2long($items[3]) + 2 ** (32 - $mask)) - 1) + ] + )->first(); + + if ($net) { + if ($net->updated_at > Carbon::now()->subDays(1)) { + continue; + } + + // don't use ->update() method because it doesn't update updated_at which we need for expiry + $net->rir_name = $rir; + $net->country = $items[1]; + $net->serial = $serial; + $net->updated_at = Carbon::now(); + + $net->save(); + + continue; + } + + $nets[] = [ + 'rir_name' => $rir, + 'net_number' => $items[3], + 'net_mask' => $mask, + 'net_broadcast' => long2ip((ip2long($items[3]) + 2 ** (32 - $mask)) - 1), + 'country' => $items[1], + 'serial' => $serial, + 'created_at' => Carbon::parse($items[5], 'UTC'), + 'updated_at' => Carbon::now() + ]; + + if (sizeof($nets) >= 100) { + \App\IP4Net::insert($nets); + $nets = []; + } + } + + if (sizeof($nets) > 0) { + \App\IP4Net::insert($nets); + $nets = []; + } + + $bar->finish(); + + $this->info("DONE"); + } + + return 0; + } + + private function countLines($file) + { + $numLines = 0; + + $fh = fopen($file, 'r'); + + while (!feof($fh)) { + $line = trim(fgets($fh)); + + $items = explode('|', $line); + + if (sizeof($items) < 3) { + continue; + } + + if ($items[2] == "ipv4") { + $numLines++; + } + } + + fclose($fh); + + return $numLines; + } + + private function serialFromStatsFile($file) + { + $serial = null; + + $fh = fopen($file, 'r'); + + while (!feof($fh)) { + $line = trim(fgets($fh)); + + $items = explode('|', $line); + + if (sizeof($items) < 2) { + continue; + } + + if ((int)$items[2]) { + $serial = (int)$items[2]; + break; + } + } + + fclose($fh); + + return $serial; + } +} diff --git a/src/app/Console/Commands/Data/Import/IP6NetsCommand.php b/src/app/Console/Commands/Data/Import/IP6NetsCommand.php new file mode 100644 --- /dev/null +++ b/src/app/Console/Commands/Data/Import/IP6NetsCommand.php @@ -0,0 +1,223 @@ + 'http://ftp.afrinic.net/stats/afrinic/delegated-afrinic-latest', + 'apnic' => 'http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest', + 'arin' => 'http://ftp.arin.net/pub/stats/arin/delegated-arin-extended-latest', + 'lacnic' => 'http://ftp.lacnic.net/pub/stats/lacnic/delegated-lacnic-latest', + 'ripencc' => 'https://ftp.ripe.net/ripe/stats/delegated-ripencc-latest' + ]; + + $today = Carbon::now()->toDateString(); + + foreach ($rirs as $rir => $url) { + $file = storage_path("{$rir}-{$today}"); + + \App\Utils::downloadFile($url, $file); + + $serial = $this->serialFromStatsFile($file); + + if (!$serial) { + \Log::error("Can not derive serial from {$file}"); + continue; + } + + $numLines = $this->countLines($file); + + if (!$numLines) { + \Log::error("No relevant lines could be found in {$file}"); + continue; + } + + $bar = \App\Utils::createProgressBar( + $this->output, + $numLines, + "Importing IPv6 Networks from {$file}" + ); + + $fp = fopen($file, 'r'); + + $nets = []; + + while (!feof($fp)) { + $line = trim(fgets($fp)); + + if ($line == "") { + continue; + } + + if ((int)$line) { + continue; + } + + if ($line[0] == "#") { + continue; + } + + $items = explode('|', $line); + + if (sizeof($items) < 7) { + continue; + } + + if ($items[1] == "*") { + continue; + } + + if ($items[2] != "ipv6") { + continue; + } + + if ($items[5] == "00000000") { + $items[5] = "19700102"; + } + + if ($items[1] == "" || $items[1] == "ZZ") { + continue; + } + + $bar->advance(); + + $broadcast = \App\Utils::ip6Broadcast($items[3], (int)$items[4]); + + $net = \App\IP6Net::where( + [ + 'net_number' => $items[3], + 'net_mask' => (int)$items[4], + 'net_broadcast' => $broadcast + ] + )->first(); + + if ($net) { + if ($net->updated_at > Carbon::now()->subDays(1)) { + continue; + } + + // don't use ->update() method because it doesn't update updated_at which we need for expiry + $net->rir_name = $rir; + $net->country = $items[1]; + $net->serial = $serial; + $net->updated_at = Carbon::now(); + + $net->save(); + + continue; + } + + $nets[] = [ + 'rir_name' => $rir, + 'net_number' => $items[3], + 'net_mask' => (int)$items[4], + 'net_broadcast' => $broadcast, + 'country' => $items[1], + 'serial' => $serial, + 'created_at' => Carbon::parse($items[5], 'UTC'), + 'updated_at' => Carbon::now() + ]; + + if (sizeof($nets) >= 100) { + \App\IP6Net::insert($nets); + $nets = []; + } + } + + if (sizeof($nets) > 0) { + \App\IP6Net::insert($nets); + $nets = []; + } + + $bar->finish(); + + $this->info("DONE"); + } + } + + private function countLines($file) + { + $numLines = 0; + + $fh = fopen($file, 'r'); + + while (!feof($fh)) { + $line = trim(fgets($fh)); + + $items = explode('|', $line); + + if (sizeof($items) < 3) { + continue; + } + + if ($items[2] == "ipv6") { + $numLines++; + } + } + + fclose($fh); + + return $numLines; + } + + private function serialFromStatsFile($file) + { + $serial = null; + + $fh = fopen($file, 'r'); + + while (!feof($fh)) { + $line = trim(fgets($fh)); + + $items = explode('|', $line); + + if (sizeof($items) < 2) { + continue; + } + + if ((int)$items[2]) { + $serial = (int)$items[2]; + break; + } + } + + fclose($fh); + + return $serial; + } +} diff --git a/src/app/Console/Commands/Data/ImportCommand.php b/src/app/Console/Commands/Data/ImportCommand.php new file mode 100644 --- /dev/null +++ b/src/app/Console/Commands/Data/ImportCommand.php @@ -0,0 +1,54 @@ +output = $this->output; + $execution->handle(); + } + + return 0; + } +} diff --git a/src/app/Console/Commands/DataCountries.php b/src/app/Console/Commands/DataCountries.php deleted file mode 100644 --- a/src/app/Console/Commands/DataCountries.php +++ /dev/null @@ -1,142 +0,0 @@ -info("Fetching currencies from $currencies_url..."); - - // fetch currency table and create an index by country page url - $page = file_get_contents($currencies_url); - - if (!$page) { - $this->error("Failed to fetch currencies"); - return; - } - - $table_regexp = '!!ims'; - if (preg_match_all($table_regexp, $page, $matches, PREG_PATTERN_ORDER)) { - foreach ($matches[0] as $currency_table) { - preg_match_all('!\s*\s*!Ums', $currency_table, $rows); - - foreach ($rows[1] as $row) { - $cells = preg_split('!\s*]*>!', $row); - - if (count($cells) == 5) { - // actual currency table - $currency = preg_match('/([A-Z]{3})/', $cells[0], $m) ? $m[1] : ''; - - if (preg_match('/(\d+)/', $cells[1], $m)) { - $isocode = $m[1]; - $currencies[$m[1]] = $currency; - } - - preg_match_all('!]+href="(/wiki/[^"]+)"[^>]*>!', $cells[4], $links, PREG_PATTERN_ORDER); - - foreach ($links[1] as $link) { - $currencies[strtolower($link)] = $currency; - } - } elseif (count($cells) == 7) { - // replacements table - $currency = preg_match('/([A-Z]{3})/', $cells[6], $m) ? $m[1] : ''; - - if (preg_match('/(\d+)/', $cells[1], $m)) { - $currencies[$m[1]] = $currency; - } - } - } - } - } - - $namecol = 0; - $codecol = 1; - $numcol = 3; - $lang = 'en'; - - $this->info("Fetching countries from $countries_url..."); - - $page = file_get_contents($countries_url); - - if (!$page) { - $this->error("Failed to fetch countries"); - return; - } - - if (preg_match($table_regexp, $page, $matches)) { - preg_match_all('!\s*\s*!Ums', $matches[0], $rows); - - foreach ($rows[1] as $row) { - $cells = preg_split('!\s*]*>!', $row); - - if (count($cells) < 5) { - continue; - } - - $regexp = '!]+href="(/wiki/[^"]+)"[^>]*>([^>]+)!i'; - $content = preg_match($regexp, $cells[$namecol], $m) ? $m : null; - - if (preg_match('/>([A-Z]{2})info("Generating resource file $file..."); - - $out = " $names) { - if (!empty($names['en']) && !empty($names['currency'])) { - $out .= sprintf(" '%s' => ['%s','%s'],\n", $code, $names['currency'], addslashes($names['en'])); - } - } - $out .= "];\n"; - - file_put_contents($file, $out); - } -} diff --git a/src/app/Console/Commands/DomainStatus.php b/src/app/Console/Commands/DomainStatus.php --- a/src/app/Console/Commands/DomainStatus.php +++ b/src/app/Console/Commands/DomainStatus.php @@ -44,6 +44,21 @@ return 1; } - $this->info($domain->status); + $statuses = [ + 'active' => Domain::STATUS_ACTIVE, + 'suspended' => Domain::STATUS_SUSPENDED, + 'deleted' => Domain::STATUS_DELETED, + 'ldapReady' => Domain::STATUS_LDAP_READY, + 'verified' => Domain::STATUS_VERIFIED, + 'confirmed' => Domain::STATUS_CONFIRMED, + ]; + + foreach ($statuses as $text => $bit) { + $func = 'is' . \ucfirst($text); + + $this->info(sprintf("%d %s: %s", $bit, $text, $domain->$func())); + } + + $this->info("In total: {$domain->status}"); } } diff --git a/src/app/Console/Commands/Job/DomainCreate.php b/src/app/Console/Commands/Job/DomainCreate.php --- a/src/app/Console/Commands/Job/DomainCreate.php +++ b/src/app/Console/Commands/Job/DomainCreate.php @@ -34,7 +34,7 @@ return 1; } - $job = new \App\Jobs\DomainCreate($domain); + $job = new \App\Jobs\Domain\CreateJob($domain->id); $job->handle(); } } diff --git a/src/app/Console/Commands/Job/DomainUpdate.php b/src/app/Console/Commands/Job/DomainUpdate.php --- a/src/app/Console/Commands/Job/DomainUpdate.php +++ b/src/app/Console/Commands/Job/DomainUpdate.php @@ -34,7 +34,7 @@ return 1; } - $job = new \App\Jobs\DomainUpdate($domain->id); + $job = new \App\Jobs\Domain\UpdateJob($domain->id); $job->handle(); } } diff --git a/src/app/Console/Commands/Job/UserCreate.php b/src/app/Console/Commands/Job/UserCreate.php --- a/src/app/Console/Commands/Job/UserCreate.php +++ b/src/app/Console/Commands/Job/UserCreate.php @@ -34,7 +34,7 @@ return 1; } - $job = new \App\Jobs\UserCreate($user); + $job = new \App\Jobs\User\CreateJob($user->id); $job->handle(); } } diff --git a/src/app/Console/Commands/Job/UserUpdate.php b/src/app/Console/Commands/Job/UserUpdate.php --- a/src/app/Console/Commands/Job/UserUpdate.php +++ b/src/app/Console/Commands/Job/UserUpdate.php @@ -34,7 +34,7 @@ return 1; } - $job = new \App\Jobs\UserUpdate($user); + $job = new \App\Jobs\User\UpdateJob($user->id); $job->handle(); } } diff --git a/src/app/Console/Commands/MollieInfo.php b/src/app/Console/Commands/MollieInfo.php --- a/src/app/Console/Commands/MollieInfo.php +++ b/src/app/Console/Commands/MollieInfo.php @@ -43,10 +43,21 @@ if ($mandate = $provider->getMandate($wallet)) { $amount = $wallet->getSetting('mandate_amount'); $balance = $wallet->getSetting('mandate_balance') ?: 0; + $status = 'invalid'; + + if ($mandate['isPending']) { + $status = 'pending'; + } elseif ($mandate['isValid']) { + $status = 'valid'; + } + + if ($wallet->getSetting('mandate_disabled')) { + $status .= ' (disabled)'; + } $this->info("Auto-payment: {$mandate['method']}"); $this->info(" id: {$mandate['id']}"); - $this->info(" status: " . ($mandate['isPending'] ? 'pending' : 'valid')); + $this->info(" status: {$status}"); $this->info(" amount: {$amount} {$wallet->currency}"); $this->info(" min-balance: {$balance} {$wallet->currency}"); } else { diff --git a/src/app/Console/Commands/StripeInfo.php b/src/app/Console/Commands/StripeInfo.php --- a/src/app/Console/Commands/StripeInfo.php +++ b/src/app/Console/Commands/StripeInfo.php @@ -45,10 +45,21 @@ if ($mandate = $provider->getMandate($wallet)) { $amount = $wallet->getSetting('mandate_amount'); $balance = $wallet->getSetting('mandate_balance') ?: 0; + $status = 'invalid'; + + if ($mandate['isPending']) { + $status = 'pending'; + } elseif ($mandate['isValid']) { + $status = 'valid'; + } + + if ($wallet->getSetting('mandate_disabled')) { + $status .= ' (disabled)'; + } $this->info("Auto-payment: {$mandate['method']}"); $this->info(" id: {$mandate['id']}"); - $this->info(" status: " . ($mandate['isPending'] ? 'pending' : 'valid')); + $this->info(" status: {$status}"); $this->info(" amount: {$amount} {$wallet->currency}"); $this->info(" min-balance: {$balance} {$wallet->currency}"); } else { diff --git a/src/app/Console/Commands/UserForceDelete.php b/src/app/Console/Commands/UserForceDelete.php new file mode 100644 --- /dev/null +++ b/src/app/Console/Commands/UserForceDelete.php @@ -0,0 +1,46 @@ +where('email', $this->argument('user'))->first(); + + if (!$user) { + return 1; + } + + if (!$user->trashed()) { + $this->error('The user is not yet deleted'); + return 1; + } + + DB::beginTransaction(); + $user->forceDelete(); + DB::commit(); + } +} diff --git a/src/app/Console/Commands/UserStatus.php b/src/app/Console/Commands/UserStatus.php --- a/src/app/Console/Commands/UserStatus.php +++ b/src/app/Console/Commands/UserStatus.php @@ -44,8 +44,20 @@ return 1; } - $this->info("Found user: {$user->id}"); + $statuses = [ + 'active' => User::STATUS_ACTIVE, + 'suspended' => User::STATUS_SUSPENDED, + 'deleted' => User::STATUS_DELETED, + 'ldapReady' => User::STATUS_LDAP_READY, + 'imapReady' => User::STATUS_IMAP_READY, + ]; + + foreach ($statuses as $text => $bit) { + $func = 'is' . \ucfirst($text); + + $this->info(sprintf("%d %s: %s", $bit, $text, $user->$func())); + } - $this->info($user->status); + $this->info("In total: {$user->status}"); } } diff --git a/src/app/Console/Commands/UserVerify.php b/src/app/Console/Commands/UserVerify.php --- a/src/app/Console/Commands/UserVerify.php +++ b/src/app/Console/Commands/UserVerify.php @@ -45,7 +45,7 @@ $this->info("Found user: {$user->id}"); - $job = new \App\Jobs\UserVerify($user); + $job = new \App\Jobs\User\VerifyJob($user->id); $job->handle(); } } diff --git a/src/app/Console/Kernel.php b/src/app/Console/Kernel.php --- a/src/app/Console/Kernel.php +++ b/src/app/Console/Kernel.php @@ -25,8 +25,18 @@ */ protected function schedule(Schedule $schedule) { - // $schedule->command('inspire') - // ->hourly(); + // This command imports countries and the current set of IPv4 and IPv6 networks allocated to countries. + $schedule->command('data:import')->dailyAt('05:00'); + + $schedule->command('wallet:charge')->dailyAt('00:00'); + $schedule->command('wallet:charge')->dailyAt('04:00'); + $schedule->command('wallet:charge')->dailyAt('08:00'); + $schedule->command('wallet:charge')->dailyAt('12:00'); + $schedule->command('wallet:charge')->dailyAt('16:00'); + $schedule->command('wallet:charge')->dailyAt('20:00'); + + // this is a laravel 8-ism + //$schedule->command('wallet:charge')->everyFourHours(); } /** diff --git a/src/app/Documents/Receipt.php b/src/app/Documents/Receipt.php --- a/src/app/Documents/Receipt.php +++ b/src/app/Documents/Receipt.php @@ -160,7 +160,7 @@ ->where('status', PaymentProvider::STATUS_PAID) ->where('updated_at', '>=', $start) ->where('updated_at', '<', $end) - ->where('amount', '>', 0) + ->where('amount', '<>', 0) ->orderBy('updated_at') ->get(); } @@ -185,9 +185,17 @@ $total += $amount; + if ($item->type == PaymentProvider::TYPE_REFUND) { + $description = \trans('documents.receipt-refund'); + } elseif ($item->type == PaymentProvider::TYPE_CHARGEBACK) { + $description = \trans('documents.receipt-chargeback'); + } else { + $description = \trans('documents.receipt-item-desc', ['site' => $appName]); + } + return [ 'amount' => $this->wallet->money($amount), - 'description' => \trans('documents.receipt-item-desc', ['site' => $appName]), + 'description' => $description, 'date' => $item->updated_at->toDateString(), ]; }); diff --git a/src/app/Domain.php b/src/app/Domain.php --- a/src/app/Domain.php +++ b/src/app/Domain.php @@ -232,11 +232,11 @@ $allowed_values = [ self::STATUS_NEW, self::STATUS_ACTIVE, - self::STATUS_CONFIRMED, self::STATUS_SUSPENDED, self::STATUS_DELETED, - self::STATUS_LDAP_READY, + self::STATUS_CONFIRMED, self::STATUS_VERIFIED, + self::STATUS_LDAP_READY, ]; foreach ($allowed_values as $value) { @@ -250,6 +250,31 @@ throw new \Exception("Invalid domain status: {$status}"); } + if ($this->isPublic()) { + $this->attributes['status'] = $new_status; + return; + } + + if ($new_status & self::STATUS_CONFIRMED) { + // if we have confirmed ownership of or management access to the domain, then we have + // also confirmed the domain exists in DNS. + $new_status |= self::STATUS_VERIFIED; + $new_status |= self::STATUS_ACTIVE; + } + + if ($new_status & self::STATUS_DELETED && $new_status & self::STATUS_ACTIVE) { + $new_status ^= self::STATUS_ACTIVE; + } + + if ($new_status & self::STATUS_SUSPENDED && $new_status & self::STATUS_ACTIVE) { + $new_status ^= self::STATUS_ACTIVE; + } + + // if the domain is now active, it is not new anymore. + if ($new_status & self::STATUS_ACTIVE && $new_status & self::STATUS_NEW) { + $new_status ^= self::STATUS_NEW; + } + $this->attributes['status'] = $new_status; } @@ -349,6 +374,15 @@ /** * Unsuspend this domain. * + * The domain is unsuspended through either of the following courses of actions; + * + * * The account balance has been topped up, or + * * a suspected spammer has resolved their issues, or + * * the command-line is triggered. + * + * Therefore, we can also confidently set the domain status to 'active' should the ownership of or management + * access to have been confirmed before. + * * @return void */ public function unsuspend(): void @@ -358,6 +392,11 @@ } $this->status ^= Domain::STATUS_SUSPENDED; + + if ($this->isConfirmed() && $this->isVerified()) { + $this->status |= Domain::STATUS_ACTIVE; + } + $this->save(); } @@ -399,7 +438,7 @@ public function wallet(): ?Wallet { // Note: Not all domains have a entitlement/wallet - $entitlement = $this->entitlement()->first(); + $entitlement = $this->entitlement()->withTrashed()->first(); return $entitlement ? $entitlement->wallet : null; } diff --git a/src/app/Http/Controllers/API/AuthController.php b/src/app/Http/Controllers/API/AuthController.php --- a/src/app/Http/Controllers/API/AuthController.php +++ b/src/app/Http/Controllers/API/AuthController.php @@ -38,8 +38,10 @@ { // @phpstan-ignore-next-line $token = Auth::guard()->login($user); + $response = V4\UsersController::userResponse($user); + $response['status'] = 'success'; - return self::respondWithToken($token, ['status' => 'success']); + return self::respondWithToken($token, $response); } /** @@ -67,13 +69,16 @@ $credentials = $request->only('email', 'password'); if ($token = Auth::guard()->attempt($credentials)) { - $sf = new \App\Auth\SecondFactor(Auth::guard()->user()); + $user = Auth::guard()->user(); + $sf = new \App\Auth\SecondFactor($user); if ($response = $sf->requestHandler($request)) { return $response; } - return $this->respondWithToken($token); + $response = V4\UsersController::userResponse($user); + + return $this->respondWithToken($token, $response); } return response()->json(['status' => 'error', 'message' => __('auth.failed')], 401); diff --git a/src/app/Http/Controllers/API/SignupController.php b/src/app/Http/Controllers/API/SignupController.php --- a/src/app/Http/Controllers/API/SignupController.php +++ b/src/app/Http/Controllers/API/SignupController.php @@ -211,11 +211,11 @@ $plan = $this->getPlan(); $is_domain = $plan->hasDomain(); - $login = $request->login; - $domain = $request->domain; + $login = $request->login; + $domain_name = $request->domain; // Validate login - if ($errors = self::validateLogin($login, $domain, $is_domain)) { + if ($errors = self::validateLogin($login, $domain_name, $is_domain)) { return response()->json(['status' => 'error', 'errors' => $errors], 422); } @@ -225,26 +225,26 @@ // We allow only ASCII, so we can safely lower-case the email address $login = Str::lower($login); - $domain = Str::lower($domain); + $domain_name = Str::lower($domain_name); + $domain = null; DB::beginTransaction(); - // Create user record - $user = User::create([ - 'email' => $login . '@' . $domain, - 'password' => $request->password, - ]); - // Create domain record - // FIXME: Should we do this in UserObserver::created()? if ($is_domain) { $domain = Domain::create([ - 'namespace' => $domain, + 'namespace' => $domain_name, 'status' => Domain::STATUS_NEW, 'type' => Domain::TYPE_EXTERNAL, ]); } + // Create user record + $user = User::create([ + 'email' => $login . '@' . $domain_name, + 'password' => $request->password, + ]); + if (!empty($discount)) { $wallet = $user->wallets()->first(); $wallet->discount()->associate($discount); diff --git a/src/app/Http/Controllers/API/V4/Admin/DomainsController.php b/src/app/Http/Controllers/API/V4/Admin/DomainsController.php --- a/src/app/Http/Controllers/API/V4/Admin/DomainsController.php +++ b/src/app/Http/Controllers/API/V4/Admin/DomainsController.php @@ -58,7 +58,7 @@ * Suspend the domain * * @param \Illuminate\Http\Request $request The API request. - * @params string $id Domain identifier + * @param string $id Domain identifier * * @return \Illuminate\Http\JsonResponse The response */ @@ -82,7 +82,7 @@ * Un-Suspend the domain * * @param \Illuminate\Http\Request $request The API request. - * @params string $id Domain identifier + * @param string $id Domain identifier * * @return \Illuminate\Http\JsonResponse The response */ diff --git a/src/app/Http/Controllers/API/V4/Admin/UsersController.php b/src/app/Http/Controllers/API/V4/Admin/UsersController.php --- a/src/app/Http/Controllers/API/V4/Admin/UsersController.php +++ b/src/app/Http/Controllers/API/V4/Admin/UsersController.php @@ -29,32 +29,34 @@ } } elseif (strpos($search, '@')) { // Search by email - $user = User::where('email', $search)->first(); - if ($user) { - $result->push($user); - } else { + $result = User::withTrashed()->where('email', $search) + ->orderBy('email')->get(); + + if ($result->isEmpty()) { // Search by an alias $user_ids = UserAlias::where('alias', $search)->get()->pluck('user_id'); - if ($user_ids->isEmpty()) { - // Search by an external email - $user_ids = UserSetting::where('key', 'external_email') - ->where('value', $search)->get()->pluck('user_id'); - } + + // Search by an external email + $ext_user_ids = UserSetting::where('key', 'external_email') + ->where('value', $search)->get()->pluck('user_id'); + + $user_ids = $user_ids->merge($ext_user_ids)->unique(); if (!$user_ids->isEmpty()) { - $result = User::whereIn('id', $user_ids)->orderBy('email')->get(); + $result = User::withTrashed()->whereIn('id', $user_ids) + ->orderBy('email')->get(); } } } elseif (is_numeric($search)) { // Search by user ID - if ($user = User::find($search)) { + if ($user = User::withTrashed()->find($search)) { $result->push($user); } } elseif (!empty($search)) { // Search by domain - if ($domain = Domain::where('namespace', $search)->first()) { + if ($domain = Domain::withTrashed()->where('namespace', $search)->first()) { if ($wallet = $domain->wallet()) { - $result->push($wallet->owner); + $result->push($wallet->owner()->withTrashed()->first()); } } } @@ -79,7 +81,7 @@ * Reset 2-Factor Authentication for the user * * @param \Illuminate\Http\Request $request The API request. - * @params string $id User identifier + * @param string $id User identifier * * @return \Illuminate\Http\JsonResponse The response */ @@ -109,7 +111,7 @@ * Suspend the user * * @param \Illuminate\Http\Request $request The API request. - * @params string $id User identifier + * @param string $id User identifier * * @return \Illuminate\Http\JsonResponse The response */ @@ -133,7 +135,7 @@ * Un-Suspend the user * * @param \Illuminate\Http\Request $request The API request. - * @params string $id User identifier + * @param string $id User identifier * * @return \Illuminate\Http\JsonResponse The response */ @@ -157,7 +159,7 @@ * Update user data. * * @param \Illuminate\Http\Request $request The API request. - * @params string $id User identifier + * @param string $id User identifier * * @return \Illuminate\Http\JsonResponse The response */ diff --git a/src/app/Http/Controllers/API/V4/Admin/WalletsController.php b/src/app/Http/Controllers/API/V4/Admin/WalletsController.php --- a/src/app/Http/Controllers/API/V4/Admin/WalletsController.php +++ b/src/app/Http/Controllers/API/V4/Admin/WalletsController.php @@ -52,7 +52,7 @@ * Award/penalize a wallet. * * @param \Illuminate\Http\Request $request The API request. - * @params string $id Wallet identifier + * @param string $id Wallet identifier * * @return \Illuminate\Http\JsonResponse The response */ @@ -111,7 +111,7 @@ * Update wallet data. * * @param \Illuminate\Http\Request $request The API request. - * @params string $id Wallet identifier + * @param string $id Wallet identifier * * @return \Illuminate\Http\JsonResponse The response */ diff --git a/src/app/Http/Controllers/API/V4/UsersController.php b/src/app/Http/Controllers/API/V4/UsersController.php --- a/src/app/Http/Controllers/API/V4/UsersController.php +++ b/src/app/Http/Controllers/API/V4/UsersController.php @@ -291,7 +291,7 @@ * Update user data. * * @param \Illuminate\Http\Request $request The API request. - * @params string $id User identifier + * @param string $id User identifier * * @return \Illuminate\Http\JsonResponse The response */ @@ -592,14 +592,20 @@ switch ($step) { case 'user-ldap-ready': // User not in LDAP, create it - $job = new \App\Jobs\UserCreate($user); + $job = new \App\Jobs\User\CreateJob($user->id); $job->handle(); + + $user->refresh(); + return $user->isLdapReady(); case 'user-imap-ready': // User not in IMAP? Verify again - $job = new \App\Jobs\UserVerify($user); + $job = new \App\Jobs\User\VerifyJob($user->id); $job->handle(); + + $user->refresh(); + return $user->isImapReady(); } } catch (\Exception $e) { @@ -629,14 +635,14 @@ return \trans('validation.entryinvalid', ['attribute' => $attribute]); } - list($login, $domain) = explode('@', $email); + list($login, $domain) = explode('@', Str::lower($email)); if (strlen($login) === 0 || strlen($domain) === 0) { return \trans('validation.entryinvalid', ['attribute' => $attribute]); } // Check if domain exists - $domain = Domain::where('namespace', Str::lower($domain))->first(); + $domain = Domain::where('namespace', $domain)->first(); if (empty($domain)) { return \trans('validation.domaininvalid'); diff --git a/src/app/Http/Controllers/API/V4/WalletsController.php b/src/app/Http/Controllers/API/V4/WalletsController.php --- a/src/app/Http/Controllers/API/V4/WalletsController.php +++ b/src/app/Http/Controllers/API/V4/WalletsController.php @@ -93,7 +93,7 @@ * Update the specified resource in storage. * * @param \Illuminate\Http\Request $request - * @param int $id + * @param string $id * * @return \Illuminate\Http\JsonResponse */ @@ -179,7 +179,7 @@ $result = $wallet->payments() ->selectRaw('distinct date_format(updated_at, "%Y-%m") as ident') ->where('status', PaymentProvider::STATUS_PAID) - ->where('amount', '>', 0) + ->where('amount', '<>', 0) ->orderBy('ident', 'desc') ->get() ->whereNotIn('ident', [date('Y-m')]) // exclude current month @@ -284,24 +284,28 @@ */ protected function getWalletNotice(Wallet $wallet): ?string { + // there is no credit if ($wallet->balance < 0) { return \trans('app.wallet-notice-nocredit'); } + // the discount is 100%, no credit is needed if ($wallet->discount && $wallet->discount->discount == 100) { return null; } - if ($wallet->owner->created_at > Carbon::now()->subDays(14)) { + // the owner was created less than a month ago + if ($wallet->owner->created_at > Carbon::now()->subMonthsWithoutOverflow(1)) { + // but more than two weeks ago, notice of trial ending + if ($wallet->owner->created_at <= Carbon::now()->subWeeks(2)) { + return \trans('app.wallet-notice-trial-end'); + } + return \trans('app.wallet-notice-trial'); } if ($until = $wallet->balanceLastsUntil()) { if ($until->isToday()) { - if ($wallet->owner->created_at > Carbon::now()->subDays(30)) { - return \trans('app.wallet-notice-trial-end'); - } - return \trans('app.wallet-notice-today'); } diff --git a/src/app/IP4Net.php b/src/app/IP4Net.php new file mode 100644 --- /dev/null +++ b/src/app/IP4Net.php @@ -0,0 +1,19 @@ += INET6_ATON(?) + ORDER BY INET6_ATON(net_number), net_mask DESC LIMIT 1 + "; + + $results = DB::select($query, [$ip, $ip]); + + if (sizeof($results) == 0) { + return null; + } + + return \App\IP6Net::find($results[0]->id); + } +} diff --git a/src/app/Jobs/CommonJob.php b/src/app/Jobs/CommonJob.php new file mode 100644 --- /dev/null +++ b/src/app/Jobs/CommonJob.php @@ -0,0 +1,73 @@ +handle(); + * ``` + */ +abstract class CommonJob implements ShouldQueue +{ + use Dispatchable; + use InteractsWithQueue; + use Queueable; + + /** + * The failure message. + * + * @var string + */ + public $failureMessage; + + /** + * The number of tries for this Job. + * + * @var int + */ + public $tries = 5; + + /** + * Execute the job. + * + * @return void + */ + abstract public function handle(); + + /** + * Delete the job, call the "failed" method, and raise the failed job event. + * + * @param \Throwable|null $e An Exception + * + * @return void + */ + public function fail($e = null) + { + // Save the message, for testing purposes + $this->failureMessage = $e->getMessage(); + + // @phpstan-ignore-next-line + if ($this->job) { + $this->job->fail($e); + } + } + + /** + * Check if the job has failed + * + * @return bool + */ + public function hasFailed(): bool + { + return $this->failureMessage !== null; + } +} diff --git a/src/app/Jobs/Domain/CreateJob.php b/src/app/Jobs/Domain/CreateJob.php new file mode 100644 --- /dev/null +++ b/src/app/Jobs/Domain/CreateJob.php @@ -0,0 +1,27 @@ +getDomain(); + + if (!$domain->isLdapReady()) { + \App\Backends\LDAP::createDomain($domain); + + $domain->status |= \App\Domain::STATUS_LDAP_READY; + $domain->save(); + + \App\Jobs\Domain\VerifyJob::dispatch($domain->id); + } + } +} diff --git a/src/app/Jobs/Domain/DeleteJob.php b/src/app/Jobs/Domain/DeleteJob.php new file mode 100644 --- /dev/null +++ b/src/app/Jobs/Domain/DeleteJob.php @@ -0,0 +1,29 @@ +getDomain(); + + // sanity checks + if ($domain->isDeleted()) { + $this->fail(new \Exception("Domain {$this->domainId} is already marked as deleted.")); + return; + } + + \App\Backends\LDAP::deleteDomain($domain); + + $domain->status |= \App\Domain::STATUS_DELETED; + $domain->save(); + } +} diff --git a/src/app/Jobs/Domain/UpdateJob.php b/src/app/Jobs/Domain/UpdateJob.php new file mode 100644 --- /dev/null +++ b/src/app/Jobs/Domain/UpdateJob.php @@ -0,0 +1,25 @@ +getDomain(); + + if (!$domain->isLdapReady()) { + $this->delete(); + return; + } + + \App\Backends\LDAP::updateDomain($domain); + } +} diff --git a/src/app/Jobs/Domain/VerifyJob.php b/src/app/Jobs/Domain/VerifyJob.php new file mode 100644 --- /dev/null +++ b/src/app/Jobs/Domain/VerifyJob.php @@ -0,0 +1,24 @@ +getDomain(); + + $domain->verify(); + + // TODO: What should happen if the domain is not registered yet? + // Should we start a new job with some specified delay? + // Or we just give the user a button to start verification again? + } +} diff --git a/src/app/Jobs/DomainCreate.php b/src/app/Jobs/DomainCreate.php deleted file mode 100644 --- a/src/app/Jobs/DomainCreate.php +++ /dev/null @@ -1,55 +0,0 @@ -domain = $domain; - } - - /** - * Execute the job. - * - * @return void - */ - public function handle() - { - if (!$this->domain->isLdapReady()) { - LDAP::createDomain($this->domain); - - $this->domain->status |= Domain::STATUS_LDAP_READY; - $this->domain->save(); - - DomainVerify::dispatch($this->domain); - } - } -} diff --git a/src/app/Jobs/DomainDelete.php b/src/app/Jobs/DomainDelete.php deleted file mode 100644 --- a/src/app/Jobs/DomainDelete.php +++ /dev/null @@ -1,53 +0,0 @@ -domain = Domain::withTrashed()->find($domain_id); - } - - /** - * Execute the job. - * - * @return void - */ - public function handle() - { - if (!$this->domain->isDeleted()) { - LDAP::deleteDomain($this->domain); - - $this->domain->status |= Domain::STATUS_DELETED; - $this->domain->save(); - } - } -} diff --git a/src/app/Jobs/DomainJob.php b/src/app/Jobs/DomainJob.php new file mode 100644 --- /dev/null +++ b/src/app/Jobs/DomainJob.php @@ -0,0 +1,66 @@ +handle(); + * ``` + */ +abstract class DomainJob extends CommonJob +{ + /** + * The ID for the \App\Domain. This is the shortest globally unique identifier and saves Redis space + * compared to a serialized version of the complete \App\Domain object. + * + * @var int + */ + protected $domainId; + + /** + * The \App\Domain namespace property, for legibility in the queue management. + * + * @var string + */ + protected $domainNamespace; + + /** + * Create a new job instance. + * + * @param int $domainId The ID for the user to create. + * + * @return void + */ + public function __construct(int $domainId) + { + $this->domainId = $domainId; + + $domain = $this->getDomain(); + + if ($domain) { + $this->domainNamespace = $domain->namespace; + } + } + + /** + * Get the \App\Domain entry associated with this job. + * + * @return \App\Domain|null + * + * @throws \Exception + */ + protected function getDomain() + { + $domain = \App\Domain::withTrashed()->find($this->domainId); + + if (!$domain) { + $this->fail(new \Exception("Domain {$this->domainId} could not be found in the database.")); + } + + return $domain; + } +} diff --git a/src/app/Jobs/DomainUpdate.php b/src/app/Jobs/DomainUpdate.php deleted file mode 100644 --- a/src/app/Jobs/DomainUpdate.php +++ /dev/null @@ -1,44 +0,0 @@ -domain_id = $domain_id; - } - - /** - * Execute the job. - * - * @return void - */ - public function handle() - { - $domain = \App\Domain::find($this->domain_id); - - LDAP::updateDomain($domain); - } -} diff --git a/src/app/Jobs/DomainVerify.php b/src/app/Jobs/DomainVerify.php deleted file mode 100644 --- a/src/app/Jobs/DomainVerify.php +++ /dev/null @@ -1,51 +0,0 @@ -domain = $domain; - } - - /** - * Execute the job. - * - * @return void - */ - public function handle() - { - $this->domain->verify(); - - // TODO: What should happen if the domain is not registered yet? - // Should we start a new job with some specified delay? - // Or we just give the user a button to start verification again? - } -} diff --git a/src/app/Jobs/PaymentEmail.php b/src/app/Jobs/PaymentEmail.php --- a/src/app/Jobs/PaymentEmail.php +++ b/src/app/Jobs/PaymentEmail.php @@ -62,25 +62,49 @@ $this->controller = $wallet->owner; } - $ext_email = $this->controller->getSetting('external_email'); - $cc = []; - - if ($ext_email && $ext_email != $this->controller->email) { - $cc[] = $ext_email; + if (empty($this->controller)) { + return; } if ($this->payment->status == PaymentProvider::STATUS_PAID) { $mail = new \App\Mail\PaymentSuccess($this->payment, $this->controller); + $label = "Success"; } elseif ( $this->payment->status == PaymentProvider::STATUS_EXPIRED || $this->payment->status == PaymentProvider::STATUS_FAILED ) { $mail = new \App\Mail\PaymentFailure($this->payment, $this->controller); + $label = "Failure"; } else { return; } - Mail::to($this->controller->email)->cc($cc)->send($mail); + list($to, $cc) = \App\Mail\Helper::userEmails($this->controller); + + if (!empty($to)) { + try { + Mail::to($to)->cc($cc)->send($mail); + + $msg = sprintf( + "[Payment] %s mail sent for %s (%s)", + $label, + $wallet->id, + empty($cc) ? $to : implode(', ', array_merge([$to], $cc)) + ); + + \Log::info($msg); + } catch (\Exception $e) { + $msg = sprintf( + "[Payment] Failed to send mail for wallet %s (%s): %s", + $wallet->id, + empty($cc) ? $to : implode(', ', array_merge([$to], $cc)), + $e->getMessage() + ); + + \Log::error($msg); + throw $e; + } + } /* // Send the email to all wallet controllers too diff --git a/src/app/Jobs/PaymentMandateDisabledEmail.php b/src/app/Jobs/PaymentMandateDisabledEmail.php --- a/src/app/Jobs/PaymentMandateDisabledEmail.php +++ b/src/app/Jobs/PaymentMandateDisabledEmail.php @@ -60,16 +60,37 @@ $this->controller = $this->wallet->owner; } - $ext_email = $this->controller->getSetting('external_email'); - $cc = []; - - if ($ext_email && $ext_email != $this->controller->email) { - $cc[] = $ext_email; + if (empty($this->controller)) { + return; } $mail = new PaymentMandateDisabled($this->wallet, $this->controller); - Mail::to($this->controller->email)->cc($cc)->send($mail); + list($to, $cc) = \App\Mail\Helper::userEmails($this->controller); + + if (!empty($to)) { + try { + Mail::to($to)->cc($cc)->send($mail); + + $msg = sprintf( + "[PaymentMandateDisabled] Sent mail for %s (%s)", + $this->wallet->id, + empty($cc) ? $to : implode(', ', array_merge([$to], $cc)) + ); + + \Log::info($msg); + } catch (\Exception $e) { + $msg = sprintf( + "[PaymentMandateDisabled] Failed to send mail for wallet %s (%s): %s", + $this->wallet->id, + empty($cc) ? $to : implode(', ', array_merge([$to], $cc)), + $e->getMessage() + ); + + \Log::error($msg); + throw $e; + } + } /* // Send the email to all controllers too diff --git a/src/app/Jobs/User/CreateJob.php b/src/app/Jobs/User/CreateJob.php new file mode 100644 --- /dev/null +++ b/src/app/Jobs/User/CreateJob.php @@ -0,0 +1,73 @@ +isDeleted()`), or + * * the user is actually deleted (`$user->deleted_at`), or + * * the user is already marked as ready in LDAP (`$user->isLdapReady()`). + * + */ +class CreateJob extends UserJob +{ + /** + * Execute the job. + * + * @return void + * + * @throws \Exception + */ + public function handle() + { + $user = $this->getUser(); + + if (!$user) { + return; + } + + // sanity checks + if ($user->isDeleted()) { + $this->fail(new \Exception("User {$this->userId} is marked as deleted.")); + return; + } + + if ($user->deleted_at) { + $this->fail(new \Exception("User {$this->userId} is actually deleted.")); + return; + } + + if ($user->isLdapReady()) { + $this->fail(new \Exception("User {$this->userId} is already marked as ldap-ready.")); + return; + } + + // see if the domain is ready + $domain = $user->domain(); + + if (!$domain) { + $this->fail(new \Exception("The domain for {$this->userId} does not exist.")); + return; + } + + if ($domain->isDeleted()) { + $this->fail(new \Exception("The domain for {$this->userId} is marked as deleted.")); + return; + } + + if (!$domain->isLdapReady()) { + $this->release(60); + return; + } + + \App\Backends\LDAP::createUser($user); + + $user->status |= \App\User::STATUS_LDAP_READY; + $user->save(); + } +} diff --git a/src/app/Jobs/User/DeleteJob.php b/src/app/Jobs/User/DeleteJob.php new file mode 100644 --- /dev/null +++ b/src/app/Jobs/User/DeleteJob.php @@ -0,0 +1,33 @@ +getUser(); + + if (!$user) { + return; + } + + // sanity checks + if ($user->isDeleted()) { + $this->fail(new \Exception("User {$this->userId} is already marked as deleted.")); + return; + } + + \App\Backends\LDAP::deleteUser($user); + + $user->status |= \App\User::STATUS_DELETED; + $user->save(); + } +} diff --git a/src/app/Jobs/User/UpdateJob.php b/src/app/Jobs/User/UpdateJob.php new file mode 100644 --- /dev/null +++ b/src/app/Jobs/User/UpdateJob.php @@ -0,0 +1,25 @@ +getUser(); + + if (!$user->isLdapReady()) { + $this->delete(); + return; + } + + \App\Backends\LDAP::updateUser($user); + } +} diff --git a/src/app/Jobs/User/VerifyJob.php b/src/app/Jobs/User/VerifyJob.php new file mode 100644 --- /dev/null +++ b/src/app/Jobs/User/VerifyJob.php @@ -0,0 +1,40 @@ +getUser(); + + if (!$user) { + return; + } + + // sanity checks + if (!$user->hasSku('mailbox')) { + $this->fail(new \Exception("User {$this->userId} has no mailbox SKU.")); + return; + } + + // the user has a mailbox (or is marked as such) + if ($user->isImapReady()) { + $this->fail(new \Exception("User {$this->userId} is already verified.")); + return; + } + + if (\App\Backends\IMAP::verifyAccount($user->email)) { + $user->status |= \App\User::STATUS_IMAP_READY; + $user->status |= \App\User::STATUS_ACTIVE; + $user->save(); + } + } +} diff --git a/src/app/Jobs/UserCreate.php b/src/app/Jobs/UserCreate.php deleted file mode 100644 --- a/src/app/Jobs/UserCreate.php +++ /dev/null @@ -1,54 +0,0 @@ -user = $user; - } - - /** - * Execute the job. - * - * @return void - */ - public function handle() - { - if (!$this->user->isLdapReady()) { - LDAP::createUser($this->user); - - $this->user->status |= User::STATUS_LDAP_READY; - $this->user->save(); - } - } -} diff --git a/src/app/Jobs/UserDelete.php b/src/app/Jobs/UserDelete.php deleted file mode 100644 --- a/src/app/Jobs/UserDelete.php +++ /dev/null @@ -1,53 +0,0 @@ -user = User::withTrashed()->find($user_id); - } - - /** - * Execute the job. - * - * @return void - */ - public function handle() - { - if (!$this->user->isDeleted()) { - LDAP::deleteUser($this->user); - - $this->user->status |= User::STATUS_DELETED; - $this->user->save(); - } - } -} diff --git a/src/app/Jobs/UserJob.php b/src/app/Jobs/UserJob.php new file mode 100644 --- /dev/null +++ b/src/app/Jobs/UserJob.php @@ -0,0 +1,66 @@ +handle(); + * ``` + */ +abstract class UserJob extends CommonJob +{ + /** + * The ID for the \App\User. This is the shortest globally unique identifier and saves Redis space + * compared to a serialized version of the complete \App\User object. + * + * @var int + */ + protected $userId; + + /** + * The \App\User email property, for legibility in the queue management. + * + * @var string + */ + protected $userEmail; + + /** + * Create a new job instance. + * + * @param int $userId The ID for the user to create. + * + * @return void + */ + public function __construct(int $userId) + { + $this->userId = $userId; + + $user = $this->getUser(); + + if ($user) { + $this->userEmail = $user->email; + } + } + + /** + * Get the \App\User entry associated with this job. + * + * @return \App\User|null + * + * @throws \Exception + */ + protected function getUser() + { + $user = \App\User::withTrashed()->find($this->userId); + + if (!$user) { + $this->fail(new \Exception("User {$this->userId} could not be found in the database.")); + } + + return $user; + } +} diff --git a/src/app/Jobs/UserUpdate.php b/src/app/Jobs/UserUpdate.php deleted file mode 100644 --- a/src/app/Jobs/UserUpdate.php +++ /dev/null @@ -1,47 +0,0 @@ -user = $user; - } - - /** - * Execute the job. - * - * @return void - */ - public function handle() - { - LDAP::updateUser($this->user); - } -} diff --git a/src/app/Jobs/UserVerify.php b/src/app/Jobs/UserVerify.php deleted file mode 100644 --- a/src/app/Jobs/UserVerify.php +++ /dev/null @@ -1,60 +0,0 @@ -user = $user; - } - - /** - * Execute the job. - * - * @return void - */ - public function handle() - { - if (!$this->user->hasSku('mailbox')) { - return; - } - - // The user has a mailbox - if (!$this->user->isImapReady()) { - if (IMAP::verifyAccount($this->user->email)) { - $this->user->status |= User::STATUS_IMAP_READY; - $this->user->status |= User::STATUS_ACTIVE; - $this->user->save(); - } - } - } -} diff --git a/src/app/Jobs/WalletCheck.php b/src/app/Jobs/WalletCheck.php --- a/src/app/Jobs/WalletCheck.php +++ b/src/app/Jobs/WalletCheck.php @@ -2,6 +2,7 @@ namespace App\Jobs; +use App\Http\Controllers\API\V4\PaymentsController; use App\Wallet; use Carbon\Carbon; use Illuminate\Bus\Queueable; @@ -21,7 +22,9 @@ public const THRESHOLD_DELETE = 'delete'; public const THRESHOLD_BEFORE_DELETE = 'before_delete'; public const THRESHOLD_SUSPEND = 'suspend'; + public const THRESHOLD_BEFORE_SUSPEND = 'before_suspend'; public const THRESHOLD_REMINDER = 'reminder'; + public const THRESHOLD_BEFORE_REMINDER = 'before_reminder'; public const THRESHOLD_INITIAL = 'initial'; /** @var int The number of seconds to wait before retrying the job. */ @@ -52,12 +55,12 @@ /** * Execute the job. * - * @return void + * @return ?string Executed action (THRESHOLD_*) */ public function handle() { if ($this->wallet->balance >= 0) { - return; + return null; } $now = Carbon::now(); @@ -65,32 +68,46 @@ // Delete the account if (self::threshold($this->wallet, self::THRESHOLD_DELETE) < $now) { $this->deleteAccount(); - return; + return self::THRESHOLD_DELETE; } // Warn about the upcomming account deletion if (self::threshold($this->wallet, self::THRESHOLD_BEFORE_DELETE) < $now) { $this->warnBeforeDelete(); - return; + return self::THRESHOLD_BEFORE_DELETE; } // Suspend the account if (self::threshold($this->wallet, self::THRESHOLD_SUSPEND) < $now) { $this->suspendAccount(); - return; + return self::THRESHOLD_SUSPEND; + } + + // Try to top-up the wallet before suspending the account + if (self::threshold($this->wallet, self::THRESHOLD_BEFORE_SUSPEND) < $now) { + PaymentsController::topUpWallet($this->wallet); + return self::THRESHOLD_BEFORE_SUSPEND; } // Send the second reminder if (self::threshold($this->wallet, self::THRESHOLD_REMINDER) < $now) { $this->secondReminder(); - return; + return self::THRESHOLD_REMINDER; + } + + // Try to top-up the wallet before the second reminder + if (self::threshold($this->wallet, self::THRESHOLD_BEFORE_REMINDER) < $now) { + PaymentsController::topUpWallet($this->wallet); + return self::THRESHOLD_BEFORE_REMINDER; } // Send the initial reminder if (self::threshold($this->wallet, self::THRESHOLD_INITIAL) < $now) { $this->initialReminder(); - return; + return self::THRESHOLD_INITIAL; } + + return null; } /** @@ -104,12 +121,12 @@ // TODO: Should we check if the account is already suspended? - $this->sendMail(\App\Mail\NegativeBalance::class); + $label = "Notification sent for"; + + $this->sendMail(\App\Mail\NegativeBalance::class, false, $label); $now = \Carbon\Carbon::now()->toDateTimeString(); $this->wallet->setSetting('balance_warning_initial', $now); - - \Log::info("[WalletCheck] Notification sent for {$this->wallet->owner->email}"); } /** @@ -123,12 +140,12 @@ // TODO: Should we check if the account is already suspended? - $this->sendMail(\App\Mail\NegativeBalanceReminder::class); + $label = "Reminder sent for"; + + $this->sendMail(\App\Mail\NegativeBalanceReminder::class, false, $label); $now = \Carbon\Carbon::now()->toDateTimeString(); $this->wallet->setSetting('balance_warning_reminder', $now); - - \Log::info("[WalletCheck] Reminder sent for {$this->wallet->owner->email}"); } /** @@ -145,8 +162,6 @@ return; } - \Log::info("[WalletCheck] Suspend account {$this->wallet->owner->email}"); - // Suspend the account $this->wallet->owner->suspend(); foreach ($this->wallet->entitlements as $entitlement) { @@ -158,7 +173,9 @@ } } - $this->sendMail(\App\Mail\NegativeBalanceSuspended::class); + $label = "Account suspended"; + + $this->sendMail(\App\Mail\NegativeBalanceSuspended::class, false, $label); $now = \Carbon\Carbon::now()->toDateTimeString(); $this->wallet->setSetting('balance_warning_suspended', $now); @@ -178,12 +195,12 @@ return; } - $this->sendMail(\App\Mail\NegativeBalanceBeforeDelete::class, true); + $label = "Last warning sent for"; + + $this->sendMail(\App\Mail\NegativeBalanceBeforeDelete::class, true, $label); $now = \Carbon\Carbon::now()->toDateTimeString(); $this->wallet->setSetting('balance_warning_before_delete', $now); - - \Log::info("[WalletCheck] Last warning sent for {$this->wallet->owner->email}"); } /** @@ -196,18 +213,28 @@ // and calculate summarized balance from all wallets. // The dirty work will be done by UserObserver if ($this->wallet->owner) { - \Log::info("[WalletCheck] Delete account {$this->wallet->owner->email}"); + $email = $this->wallet->owner->email; + $this->wallet->owner->delete(); + + \Log::info( + sprintf( + "[WalletCheck] Account deleted %s (%s)", + $this->wallet->id, + $email + ) + ); } } /** * Send the email * - * @param string $class Mailable class name - * @param bool $with_external Use users's external email + * @param string $class Mailable class name + * @param bool $with_external Use users's external email + * @param ?string $log_label Log label */ - protected function sendMail($class, $with_external = false): void + protected function sendMail($class, $with_external = false, $log_label = null): void { // TODO: Send the email to all wallet controllers? @@ -218,11 +245,22 @@ if (!empty($to) || !empty($cc)) { try { Mail::to($to)->cc($cc)->send($mail); + + if ($log_label) { + $msg = sprintf( + "[WalletCheck] %s %s (%s)", + $log_label, + $this->wallet->id, + empty($cc) ? $to : implode(', ', array_merge([$to], $cc)), + ); + + \Log::info($msg); + } } catch (\Exception $e) { $msg = sprintf( - "[WalletCheck] Failed to send mail for wallet %s (%s): %s", + "[WalletCheck] Failed to send mail for %s (%s): %s", $this->wallet->id, - json_encode(array_merge([$to], $cc)), + empty($cc) ? $to : implode(', ', array_merge([$to], $cc)), $e->getMessage() ); @@ -274,11 +312,21 @@ return $negative_since->addDays($suspend + $remind); } + // A day before account suspension + if ($type == self::THRESHOLD_BEFORE_SUSPEND) { + return $negative_since->addDays($suspend + $remind - 1); + } + // Second notification if ($type == self::THRESHOLD_REMINDER) { return $negative_since->addDays($remind); } + // A day before the second reminder + if ($type == self::THRESHOLD_BEFORE_REMINDER) { + return $negative_since->addDays($remind - 1); + } + // Initial notification // Give it an hour so the async recurring payment has a chance to be finished if ($type == self::THRESHOLD_INITIAL) { diff --git a/src/app/Observers/DomainObserver.php b/src/app/Observers/DomainObserver.php --- a/src/app/Observers/DomainObserver.php +++ b/src/app/Observers/DomainObserver.php @@ -24,7 +24,9 @@ } } - $domain->status |= Domain::STATUS_NEW | Domain::STATUS_ACTIVE; + $domain->namespace = \strtolower($domain->namespace); + + $domain->status |= Domain::STATUS_NEW; } /** @@ -38,7 +40,7 @@ { // Create domain record in LDAP // Note: DomainCreate job will dispatch DomainVerify job - \App\Jobs\DomainCreate::dispatch($domain); + \App\Jobs\Domain\CreateJob::dispatch($domain->id); } /** @@ -66,7 +68,7 @@ */ public function deleted(Domain $domain) { - \App\Jobs\DomainDelete::dispatch($domain->id); + \App\Jobs\Domain\DeleteJob::dispatch($domain->id); } /** @@ -78,7 +80,7 @@ */ public function updated(Domain $domain) { - \App\Jobs\DomainUpdate::dispatch($domain->id); + \App\Jobs\Domain\UpdateJob::dispatch($domain->id); } /** diff --git a/src/app/Observers/EntitlementObserver.php b/src/app/Observers/EntitlementObserver.php --- a/src/app/Observers/EntitlementObserver.php +++ b/src/app/Observers/EntitlementObserver.php @@ -93,22 +93,44 @@ $entitlement->createTransaction(\App\Transaction::ENTITLEMENT_DELETED); } + /** + * Handle the entitlement "deleting" event. + * + * @param \App\Entitlement $entitlement The entitlement. + * + * @return void + */ public function deleting(Entitlement $entitlement) { + if ($entitlement->trashed()) { + return; + } + // Start calculating the costs for the consumption of this entitlement if the // existing consumption spans >= 14 days. - // anything's free for 14 days + // + // Effect is that anything's free for the first 14 days if ($entitlement->created_at >= Carbon::now()->subDays(14)) { return; } + $owner = $entitlement->wallet->owner; + + // Determine if we're still within the free first month + $freeMonthEnds = $owner->created_at->copy()->addMonthsWithoutOverflow(1); + + if ($freeMonthEnds >= Carbon::now()) { + return; + } + $cost = 0; + $now = Carbon::now(); // get the discount rate applied to the wallet. $discount = $entitlement->wallet->getDiscountRate(); // just in case this had not been billed yet, ever - $diffInMonths = $entitlement->updated_at->diffInMonths(Carbon::now()); + $diffInMonths = $entitlement->updated_at->diffInMonths($now); $cost += (int) ($entitlement->cost * $discount * $diffInMonths); // this moves the hypothetical updated at forward to however many months past the original @@ -116,24 +138,21 @@ // now we have the diff in days since the last "billed" period end. // This may be an entitlement paid up until February 28th, 2020, with today being March - // 12th 2020. Calculating the costs for the entitlement is based on the daily price for the - // past month -- i.e. $price/29 in the case at hand -- times the number of (full) days in - // between the period end and now. - // - // a) The number of days left in the past month, 1 - // b) The cost divided by the number of days in the past month, for example, 555/29, - // c) a) + Todays day-of-month, 12, so 13. - // + // 12th 2020. Calculating the costs for the entitlement is based on the daily price - $diffInDays = $updatedAt->diffInDays(Carbon::now()); + // the price per day is based on the number of days in the last month + // or the current month if the period does not overlap with the previous month + // FIXME: This really should be simplified to $daysInMonth=30 - $dayOfThisMonth = Carbon::now()->day; + $diffInDays = $updatedAt->diffInDays($now); - // days in the month for the month prior to this one. - // the price per day is based on the number of days left in the last month - $daysInLastMonth = \App\Utils::daysInLastMonth(); + if ($now->day >= $diffInDays) { + $daysInMonth = $now->daysInMonth; + } else { + $daysInMonth = \App\Utils::daysInLastMonth(); + } - $pricePerDay = (float)$entitlement->cost / $daysInLastMonth; + $pricePerDay = $entitlement->cost / $daysInMonth; $cost += (int) (round($pricePerDay * $discount * $diffInDays, 0)); diff --git a/src/app/Observers/UserAliasObserver.php b/src/app/Observers/UserAliasObserver.php --- a/src/app/Observers/UserAliasObserver.php +++ b/src/app/Observers/UserAliasObserver.php @@ -60,7 +60,7 @@ public function created(UserAlias $alias) { if ($alias->user) { - \App\Jobs\UserUpdate::dispatch($alias->user); + \App\Jobs\User\UpdateJob::dispatch($alias->user_id); } } @@ -74,7 +74,7 @@ public function updated(UserAlias $alias) { if ($alias->user) { - \App\Jobs\UserUpdate::dispatch($alias->user); + \App\Jobs\User\UpdateJob::dispatch($alias->user_id); } } @@ -88,7 +88,7 @@ public function deleted(UserAlias $alias) { if ($alias->user) { - \App\Jobs\UserUpdate::dispatch($alias->user); + \App\Jobs\User\UpdateJob::dispatch($alias->user_id); } } } diff --git a/src/app/Observers/UserObserver.php b/src/app/Observers/UserObserver.php --- a/src/app/Observers/UserObserver.php +++ b/src/app/Observers/UserObserver.php @@ -4,7 +4,9 @@ use App\Entitlement; use App\Domain; +use App\Transaction; use App\User; +use App\Wallet; use Illuminate\Support\Facades\DB; class UserObserver @@ -30,6 +32,8 @@ } } + $user->email = \strtolower($user->email); + // only users that are not imported get the benefit of the doubt. $user->status |= User::STATUS_NEW | User::STATUS_ACTIVE; @@ -50,7 +54,7 @@ public function created(User $user) { $settings = [ - 'country' => 'CH', + 'country' => \App\Utils::countryForRequest(), 'currency' => 'CHF', /* 'first_name' => '', @@ -78,10 +82,10 @@ // Create user record in LDAP, then check if the account is created in IMAP $chain = [ - new \App\Jobs\UserVerify($user), + new \App\Jobs\User\VerifyJob($user->id), ]; - \App\Jobs\UserCreate::withChain($chain)->dispatch($user); + \App\Jobs\User\CreateJob::withChain($chain)->dispatch($user->id); } /** @@ -105,6 +109,11 @@ */ public function deleting(User $user) { + if ($user->isForceDeleting()) { + $this->forceDeleting($user); + return; + } + // TODO: Especially in tests we're doing delete() on a already deleted user. // Should we escape here - for performance reasons? // TODO: I think all of this should use database transactions @@ -138,9 +147,8 @@ $users = array_unique($users); $domains = array_unique($domains); - // Note: Domains/users need to be deleted one by one to make sure - // events are fired and observers can do the proper cleanup. - // Entitlements have no delete event handlers as for now. + // Domains/users/entitlements need to be deleted one by one to make sure + // events are fired and observers can do the proper cleanup. if (!empty($users)) { foreach (User::whereIn('id', $users)->get() as $_user) { $_user->delete(); @@ -159,7 +167,70 @@ // FIXME: What do we do with user wallets? - \App\Jobs\UserDelete::dispatch($user->id); + \App\Jobs\User\DeleteJob::dispatch($user->id); + } + + /** + * Handle the "deleting" event on forceDelete() call. + * + * @param User $user The user that is being deleted. + * + * @return void + */ + public function forceDeleting(User $user) + { + // TODO: We assume that at this moment all belongings are already soft-deleted. + + // Remove owned users/domains + $wallets = $user->wallets()->pluck('id')->all(); + $assignments = Entitlement::withTrashed()->whereIn('wallet_id', $wallets)->get(); + $entitlements = []; + $domains = []; + $users = []; + + foreach ($assignments as $entitlement) { + $entitlements[] = $entitlement->id; + + if ($entitlement->entitleable_type == Domain::class) { + $domains[] = $entitlement->entitleable_id; + } elseif ( + $entitlement->entitleable_type == User::class + && $entitlement->entitleable_id != $user->id + ) { + $users[] = $entitlement->entitleable_id; + } + } + + $users = array_unique($users); + $domains = array_unique($domains); + + // Remove the user "direct" entitlements explicitely, if they belong to another + // user's wallet they will not be removed by the wallets foreign key cascade + Entitlement::withTrashed() + ->where('entitleable_id', $user->id) + ->where('entitleable_type', User::class) + ->forceDelete(); + + // Users need to be deleted one by one to make sure observers can do the proper cleanup. + if (!empty($users)) { + foreach (User::withTrashed()->whereIn('id', $users)->get() as $_user) { + $_user->forceDelete(); + } + } + + // Domains can be just removed + if (!empty($domains)) { + Domain::withTrashed()->whereIn('id', $domains)->forceDelete(); + } + + // Remove transactions, they also have no foreign key constraint + Transaction::where('object_type', Entitlement::class) + ->whereIn('object_id', $entitlements) + ->delete(); + + Transaction::where('object_type', Wallet::class) + ->whereIn('object_id', $wallets) + ->delete(); } /** @@ -173,7 +244,7 @@ */ public function retrieving(User $user) { - // TODO \App\Jobs\UserRead::dispatch($user); + // TODO \App\Jobs\User\ReadJob::dispatch($user->id); } /** @@ -185,6 +256,6 @@ */ public function updating(User $user) { - \App\Jobs\UserUpdate::dispatch($user); + \App\Jobs\User\UpdateJob::dispatch($user->id); } } diff --git a/src/app/Observers/UserSettingObserver.php b/src/app/Observers/UserSettingObserver.php --- a/src/app/Observers/UserSettingObserver.php +++ b/src/app/Observers/UserSettingObserver.php @@ -17,7 +17,7 @@ public function created(UserSetting $userSetting) { if (in_array($userSetting->key, LDAP::USER_SETTINGS)) { - \App\Jobs\UserUpdate::dispatch($userSetting->user); + \App\Jobs\User\UpdateJob::dispatch($userSetting->user_id); } } @@ -31,7 +31,7 @@ public function updated(UserSetting $userSetting) { if (in_array($userSetting->key, LDAP::USER_SETTINGS)) { - \App\Jobs\UserUpdate::dispatch($userSetting->user); + \App\Jobs\User\UpdateJob::dispatch($userSetting->user_id); } } @@ -45,7 +45,7 @@ public function deleted(UserSetting $userSetting) { if (in_array($userSetting->key, LDAP::USER_SETTINGS)) { - \App\Jobs\UserUpdate::dispatch($userSetting->user); + \App\Jobs\User\UpdateJob::dispatch($userSetting->user_id); } } } diff --git a/src/app/Providers/Payment/Mollie.php b/src/app/Providers/Payment/Mollie.php --- a/src/app/Providers/Payment/Mollie.php +++ b/src/app/Providers/Payment/Mollie.php @@ -293,6 +293,7 @@ } // Get the payment details from Mollie + // TODO: Consider https://github.com/mollie/mollie-api-php/issues/502 when it's fixed $mollie_payment = mollie()->payments()->get($payment_id); if (empty($mollie_payment)) { @@ -300,22 +301,44 @@ return 200; } + $refunds = []; + if ($mollie_payment->isPaid()) { - if (!$mollie_payment->hasRefunds() && !$mollie_payment->hasChargebacks()) { - // The payment is paid and isn't refunded or charged back. - // Update the balance, if it wasn't already - if ($payment->status != self::STATUS_PAID && $payment->amount > 0) { - $credit = true; - $notify = $payment->type == self::TYPE_RECURRING; + // The payment is paid. Update the balance, and notify the user + if ($payment->status != self::STATUS_PAID && $payment->amount > 0) { + $credit = true; + $notify = $payment->type == self::TYPE_RECURRING; + } + + // The payment has been (partially) refunded. + // Let's process refunds with status "refunded". + if ($mollie_payment->hasRefunds()) { + foreach ($mollie_payment->refunds() as $refund) { + if ($refund->isTransferred() && $refund->amount->value) { + $refunds[] = [ + 'id' => $refund->id, + 'description' => $refund->description, + 'amount' => round(floatval($refund->amount->value) * 100), + 'type' => self::TYPE_REFUND, + // Note: we assume this is the original payment/wallet currency + ]; + } + } + } + + // The payment has been (partially) charged back. + // Let's process chargebacks (they have no states as refunds) + if ($mollie_payment->hasChargebacks()) { + foreach ($mollie_payment->chargebacks() as $chargeback) { + if ($chargeback->amount->value) { + $refunds[] = [ + 'id' => $chargeback->id, + 'amount' => round(floatval($chargeback->amount->value) * 100), + 'type' => self::TYPE_CHARGEBACK, + // Note: we assume this is the original payment/wallet currency + ]; + } } - } elseif ($mollie_payment->hasRefunds()) { - // The payment has been (partially) refunded. - // The status of the payment is still "paid" - // TODO: Update balance - } elseif ($mollie_payment->hasChargebacks()) { - // The payment has been (partially) charged back. - // The status of the payment is still "paid" - // TODO: Update balance } } elseif ($mollie_payment->isFailed()) { // Note: I didn't find a way to get any description of the problem with a payment @@ -343,6 +366,10 @@ self::creditPayment($payment, $mollie_payment); } + foreach ($refunds as $refund) { + $this->storeRefund($payment->wallet, $refund); + } + DB::commit(); if (!empty($notify)) { diff --git a/src/app/Providers/Payment/Stripe.php b/src/app/Providers/Payment/Stripe.php --- a/src/app/Providers/Payment/Stripe.php +++ b/src/app/Providers/Payment/Stripe.php @@ -70,8 +70,8 @@ $request = [ 'customer' => $customer_id, - 'cancel_url' => \url('/wallet'), // required - 'success_url' => \url('/wallet'), // required + 'cancel_url' => Utils::serviceUrl('/wallet'), // required + 'success_url' => Utils::serviceUrl('/wallet'), // required 'payment_method_types' => ['card'], // required 'locale' => 'en', 'mode' => 'setup', @@ -181,8 +181,8 @@ $request = [ 'customer' => $customer_id, - 'cancel_url' => \url('/wallet'), // required - 'success_url' => \url('/wallet'), // required + 'cancel_url' => Utils::serviceUrl('/wallet'), // required + 'success_url' => Utils::serviceUrl('/wallet'), // required 'payment_method_types' => ['card'], // required 'locale' => 'en', 'line_items' => [ diff --git a/src/app/Providers/PaymentProvider.php b/src/app/Providers/PaymentProvider.php --- a/src/app/Providers/PaymentProvider.php +++ b/src/app/Providers/PaymentProvider.php @@ -2,6 +2,7 @@ namespace App\Providers; +use App\Transaction; use App\Payment; use App\Wallet; @@ -18,6 +19,8 @@ public const TYPE_ONEOFF = 'oneoff'; public const TYPE_RECURRING = 'recurring'; public const TYPE_MANDATE = 'mandate'; + public const TYPE_REFUND = 'refund'; + public const TYPE_CHARGEBACK = 'chargeback'; /** const int Minimum amount of money in a single payment (in cents) */ public const MIN_AMOUNT = 1000; @@ -153,4 +156,42 @@ return $db_payment; } + + /** + * Deduct an amount of pecunia from the wallet. + * Creates a payment and transaction records for the refund/chargeback operation. + * + * @param \App\Wallet $wallet A wallet object + * @param array $refund A refund or chargeback data (id, type, amount, description) + * + * @return void + */ + protected function storeRefund(Wallet $wallet, array $refund): void + { + if (empty($refund) || empty($refund['amount'])) { + return; + } + + $wallet->balance -= $refund['amount']; + $wallet->save(); + + if ($refund['type'] == self::TYPE_CHARGEBACK) { + $transaction_type = Transaction::WALLET_CHARGEBACK; + } else { + $transaction_type = Transaction::WALLET_REFUND; + } + + Transaction::create([ + 'object_id' => $wallet->id, + 'object_type' => Wallet::class, + 'type' => $transaction_type, + 'amount' => $refund['amount'], + 'description' => $refund['description'] ?? '', + ]); + + $refund['status'] = self::STATUS_PAID; + $refund['amount'] *= -1; + + $this->storePayment($refund, $wallet->id); + } } diff --git a/src/app/Transaction.php b/src/app/Transaction.php --- a/src/app/Transaction.php +++ b/src/app/Transaction.php @@ -28,6 +28,8 @@ public const WALLET_CREDIT = 'credit'; public const WALLET_DEBIT = 'debit'; public const WALLET_PENALTY = 'penalty'; + public const WALLET_REFUND = 'refund'; + public const WALLET_CHARGEBACK = 'chback'; protected $fillable = [ // actor, if any @@ -94,6 +96,8 @@ case self::WALLET_CREDIT: case self::WALLET_DEBIT: case self::WALLET_PENALTY: + case self::WALLET_REFUND: + case self::WALLET_CHARGEBACK: // TODO: This must be a wallet. $this->attributes['type'] = $value; break; @@ -112,7 +116,9 @@ { $label = $this->objectTypeToLabelString() . '-' . $this->{'type'} . '-short'; - return \trans("transactions.{$label}", $this->descriptionParams()); + $result = \trans("transactions.{$label}", $this->descriptionParams()); + + return trim($result, ': '); } /** diff --git a/src/app/User.php b/src/app/User.php --- a/src/app/User.php +++ b/src/app/User.php @@ -268,6 +268,20 @@ return $this->canDelete($object); } + /** + * Return the \App\Domain for this user. + * + * @return \App\Domain|null + */ + public function domain() + { + list($local, $domainName) = explode('@', $this->email); + + $domain = \App\Domain::withTrashed()->where('namespace', $domainName)->first(); + + return $domain; + } + /** * List the domains to which this user is entitled. * diff --git a/src/app/Utils.php b/src/app/Utils.php --- a/src/app/Utils.php +++ b/src/app/Utils.php @@ -11,6 +11,98 @@ */ class Utils { + /** + * Count the number of lines in a file. + * + * Useful for progress bars. + * + * @param string $file The filepath to count the lines of. + * + * @return int + */ + public static function countLines($file) + { + $fh = fopen($file, 'rb'); + $numLines = 0; + + while (!feof($fh)) { + $numLines += substr_count(fread($fh, 8192), "\n"); + } + + fclose($fh); + + return $numLines; + } + + /** + * Return the country ISO code for an IP address. + * + * @return string + */ + public static function countryForIP($ip) + { + if (strpos(':', $ip) === false) { + $query = " + SELECT country FROM ip4nets + WHERE INET_ATON(net_number) <= INET_ATON(?) + AND INET_ATON(net_broadcast) >= INET_ATON(?) + ORDER BY INET_ATON(net_number), net_mask DESC LIMIT 1 + "; + } else { + $query = " + SELECT id FROM ip6nets + WHERE INET6_ATON(net_number) <= INET6_ATON(?) + AND INET6_ATON(net_broadcast) >= INET6_ATON(?) + ORDER BY INET6_ATON(net_number), net_mask DESC LIMIT 1 + "; + } + + $nets = \Illuminate\Support\Facades\DB::select($query, [$ip, $ip]); + + if (sizeof($nets) > 0) { + return $nets[0]->country; + } + + return 'CH'; + } + + /** + * Return the country ISO code for the current request. + */ + public static function countryForRequest() + { + $request = \request(); + $ip = $request->ip(); + + return self::countryForIP($ip); + } + + /** + * Shortcut to creating a progress bar of a particular format with a particular message. + * + * @param \Illuminate\Console\OutputStyle $output Console output object + * @param int $count Number of progress steps + * @param string $message The description + * + * @return \Symfony\Component\Console\Helper\ProgressBar + */ + public static function createProgressBar($output, $count, $message = null) + { + $bar = $output->createProgressBar($count); + + $bar->setFormat( + '%current:7s%/%max:7s% [%bar%] %percent:3s%% %elapsed:7s%/%estimated:-7s% %message% ' + ); + + if ($message) { + $bar->setMessage($message . " ..."); + } + + $bar->start(); + + return $bar; + } + /** * Return the number of days in the month prior to this one. * @@ -24,6 +116,100 @@ return $start->diffInDays($end) + 1; } + /** + * Download a file from the interwebz and store it locally. + * + * @param string $source The source location + * @param string $target The target location + * @param bool $force Force the download (and overwrite target) + * + * @return void + */ + public static function downloadFile($source, $target, $force = false) + { + if (is_file($target) && !$force) { + return; + } + + \Log::info("Retrieving {$source}"); + + $fp = fopen($target, 'w'); + + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, $source); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_FILE, $fp); + curl_exec($curl); + + if (curl_errno($curl)) { + \Log::error("Request error on {$source}: " . curl_error($curl)); + + curl_close($curl); + fclose($fp); + + unlink($target); + return; + } + + curl_close($curl); + fclose($fp); + } + + /** + * Calculate the broadcast address provided a net number and a prefix. + * + * @param string $net A valid IPv6 network number. + * @param int $prefix The network prefix. + * + * @return string + */ + public static function ip6Broadcast($net, $prefix) + { + $netHex = bin2hex(inet_pton($net)); + + // Overwriting first address string to make sure notation is optimal + $net = inet_ntop(hex2bin($netHex)); + + // Calculate the number of 'flexible' bits + $flexbits = 128 - $prefix; + + // Build the hexadecimal string of the last address + $lastAddrHex = $netHex; + + // We start at the end of the string (which is always 32 characters long) + $pos = 31; + while ($flexbits > 0) { + // Get the character at this position + $orig = substr($lastAddrHex, $pos, 1); + + // Convert it to an integer + $origval = hexdec($orig); + + // OR it with (2^flexbits)-1, with flexbits limited to 4 at a time + $newval = $origval | (pow(2, min(4, $flexbits)) - 1); + + // Convert it back to a hexadecimal character + $new = dechex($newval); + + // And put that character back in the string + $lastAddrHex = substr_replace($lastAddrHex, $new, $pos, 1); + + // We processed one nibble, move to previous position + $flexbits -= 4; + $pos -= 1; + } + + // Convert the hexadecimal string to a binary string + # Using pack() here + # Newer PHP version can use hex2bin() + $lastaddrbin = pack('H*', $lastAddrHex); + + // And create an IPv6 address from the binary string + $lastaddrstr = inet_ntop($lastaddrbin); + + return $lastaddrstr; + } + /** * Provide all unique combinations of elements in $input, with order and duplicates irrelevant. * @@ -116,20 +302,13 @@ */ public static function serviceUrl(string $route): string { - return trim(\config('app.public_url'), '/') . '/' . ltrim($route, '/'); - - // TODO: Investigate why it does not work - - $url = \secure_url($route); - - $app_url = trim(\config('app.url'), '/'); - $pub_url = trim(\config('app.public_url'), '/'); + $url = \config('app.public_url'); - if ($pub_url != $app_url) { - $url = str_replace($app_url, $pub_url, $url); + if (!$url) { + $url = \config('app.url'); } - return $url; + return rtrim(trim($url, '/') . '/' . ltrim($route, '/'), '/'); } /** diff --git a/src/app/Wallet.php b/src/app/Wallet.php --- a/src/app/Wallet.php +++ b/src/app/Wallet.php @@ -60,6 +60,22 @@ public function chargeEntitlements($apply = true) { + // This wallet has been created less than a month ago, this is the trial period + if ($this->owner->created_at >= Carbon::now()->subMonthsWithoutOverflow(1)) { + // Move all the current entitlement's updated_at timestamps forward to one month after + // this wallet was created. + $freeMonthEnds = $this->owner->created_at->copy()->addMonthsWithoutOverflow(1); + + foreach ($this->entitlements()->get()->fresh() as $entitlement) { + if ($entitlement->updated_at < $freeMonthEnds) { + $entitlement->updated_at = $freeMonthEnds; + $entitlement->save(); + } + } + + return 0; + } + $charges = 0; $discount = $this->getDiscountRate(); @@ -80,7 +96,7 @@ continue; } - // created more than a month ago -- was it billed? + // updated last more than a month ago -- was it billed? if ($entitlement->updated_at <= Carbon::now()->subMonthsWithoutOverflow(1)) { $diff = $entitlement->updated_at->diffInMonths(Carbon::now()); @@ -93,7 +109,9 @@ continue; } - $entitlement->updated_at = $entitlement->updated_at->copy()->addMonthsWithoutOverflow($diff); + $entitlement->updated_at = $entitlement->updated_at->copy() + ->addMonthsWithoutOverflow($diff); + $entitlement->save(); if ($cost == 0) { diff --git a/src/composer.json b/src/composer.json --- a/src/composer.json +++ b/src/composer.json @@ -16,27 +16,24 @@ "require": { "php": "^7.1.3", "barryvdh/laravel-dompdf": "^0.8.6", - "doctrine/dbal": "^2.9", + "dyrynda/laravel-nullable-fields": "*", "fideloper/proxy": "^4.0", - "geoip2/geoip2": "^2.9", - "iatstuti/laravel-nullable-fields": "*", "kolab/net_ldap3": "dev-master", "laravel/framework": "6.*", "laravel/tinker": "^2.4", "mollie/laravel-mollie": "^2.9", "morrislaptop/laravel-queue-clear": "^1.2", - "silviolleite/laravelpwa": "^1.0", + "silviolleite/laravelpwa": "^2.0", "spatie/laravel-translatable": "^4.2", "spomky-labs/otphp": "~4.0.0", "stripe/stripe-php": "^7.29", "swooletw/laravel-swoole": "^2.6", - "torann/currency": "^1.0", - "torann/geoip": "^1.0", "tymon/jwt-auth": "^1.0" }, "require-dev": { "beyondcode/laravel-dump-server": "^1.0", "beyondcode/laravel-er-diagram-generator": "^1.3", + "code-lts/doctum": "^5.1", "filp/whoops": "^2.0", "fzaninotto/faker": "^1.4", "kirschbaum-development/mail-intercept": "^0.2.4", diff --git a/src/config/geoip.php b/src/config/geoip.php deleted file mode 100644 --- a/src/config/geoip.php +++ /dev/null @@ -1,165 +0,0 @@ - true, - - /* - |-------------------------------------------------------------------------- - | Include Currency in Results - |-------------------------------------------------------------------------- - | - | When enabled the system will do it's best in deciding the user's currency - | by matching their ISO code to a preset list of currencies. - | - */ - - 'include_currency' => true, - - /* - |-------------------------------------------------------------------------- - | Default Service - |-------------------------------------------------------------------------- - | - | Here you may specify the default storage driver that should be used - | by the framework. - | - | Supported: "maxmind_database", "maxmind_api", "ipapi" - | - */ - - 'service' => 'maxmind_database', - - /* - |-------------------------------------------------------------------------- - | Storage Specific Configuration - |-------------------------------------------------------------------------- - | - | Here you may configure as many storage drivers as you wish. - | - */ - - 'services' => [ - - 'maxmind_database' => [ - 'class' => \Torann\GeoIP\Services\MaxMindDatabase::class, - 'database_path' => storage_path('app/geoip.mmdb'), - 'update_url' => 'https://geolite.maxmind.com/download/geoip/database/GeoLite2-City.mmdb.gz', - 'locales' => ['en'], - ], - - 'maxmind_api' => [ - 'class' => \Torann\GeoIP\Services\MaxMindWebService::class, - 'user_id' => env('MAXMIND_USER_ID'), - 'license_key' => env('MAXMIND_LICENSE_KEY'), - 'locales' => ['en'], - ], - - 'ipapi' => [ - 'class' => \Torann\GeoIP\Services\IPApi::class, - 'secure' => true, - 'key' => env('IPAPI_KEY'), - 'continent_path' => storage_path('app/continents.json'), - 'lang' => 'en', - ], - - 'ipgeolocation' => [ - 'class' => \Torann\GeoIP\Services\IPGeoLocation::class, - 'secure' => true, - 'key' => env('IPGEOLOCATION_KEY'), - 'continent_path' => storage_path('app/continents.json'), - 'lang' => 'en', - ], - - 'ipdata' => [ - 'class' => \Torann\GeoIP\Services\IPData::class, - 'key' => env('IPDATA_API_KEY'), - 'secure' => true, - ], - - 'ipfinder' => [ - 'class' => \Torann\GeoIP\Services\IPFinder::class, - 'key' => env('IPFINDER_API_KEY'), - 'secure' => true, - 'locales' => ['en'], - ], - - ], - - /* - |-------------------------------------------------------------------------- - | Default Cache Driver - |-------------------------------------------------------------------------- - | - | Here you may specify the type of caching that should be used - | by the package. - | - | Options: - | - | all - All location are cached - | some - Cache only the requesting user - | none - Disable cached - | - */ - - 'cache' => 'all', - - /* - |-------------------------------------------------------------------------- - | Cache Tags - |-------------------------------------------------------------------------- - | - | Cache tags are not supported when using the file or database cache - | drivers in Laravel. This is done so that only locations can be cleared. - | - */ - - 'cache_tags' => false, - - /* - |-------------------------------------------------------------------------- - | Cache Expiration - |-------------------------------------------------------------------------- - | - | Define how long cached location are valid. - | - */ - - 'cache_expires' => 30, - - /* - |-------------------------------------------------------------------------- - | Default Location - |-------------------------------------------------------------------------- - | - | Return when a location is not found. - | - */ - - 'default_location' => [ - 'ip' => '127.0.0.0', - 'iso_code' => 'CH', - 'country' => 'Switzerland', - 'city' => 'Zurich', - 'state' => 'ZH', - 'state_name' => 'Zurich', - 'postal_code' => '8703', - 'lat' => 47.30, - 'lon' => 8.59, - 'timezone' => 'Europe/Zurich', - 'continent' => 'EU', - 'default' => true, - 'currency' => 'CHF', - ], - -]; diff --git a/src/database/migrations/2020_06_04_140800_create_ip4nets_table.php b/src/database/migrations/2020_06_04_140800_create_ip4nets_table.php new file mode 100644 --- /dev/null +++ b/src/database/migrations/2020_06_04_140800_create_ip4nets_table.php @@ -0,0 +1,43 @@ +bigIncrements('id'); + $table->string('rir_name', 8); + $table->string('net_number', 15)->index(); + $table->tinyInteger('net_mask')->unsigned(); + $table->string('net_broadcast', 15)->index(); + $table->string('country', 2)->nullable(); + $table->bigInteger('serial')->unsigned(); + $table->timestamps(); + + $table->index(['net_number', 'net_mask', 'net_broadcast']); + } + ); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('ip4nets'); + } +} diff --git a/src/database/migrations/2020_06_04_140800_create_ip6nets_table.php b/src/database/migrations/2020_06_04_140800_create_ip6nets_table.php new file mode 100644 --- /dev/null +++ b/src/database/migrations/2020_06_04_140800_create_ip6nets_table.php @@ -0,0 +1,43 @@ +bigIncrements('id'); + $table->string('rir_name', 8); + $table->string('net_number', 39)->index(); + $table->tinyInteger('net_mask')->unsigned(); + $table->string('net_broadcast', 39)->index(); + $table->string('country', 2)->nullable(); + $table->bigInteger('serial')->unsigned(); + $table->timestamps(); + + $table->index(['net_number', 'net_mask', 'net_broadcast']); + } + ); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('ip6nets'); + } +} diff --git a/src/database/seeds/local/UserSeeder.php b/src/database/seeds/local/UserSeeder.php --- a/src/database/seeds/local/UserSeeder.php +++ b/src/database/seeds/local/UserSeeder.php @@ -127,7 +127,7 @@ $joe->setAliases(['joe.monster@kolab.org']); - factory(User::class, 10)->create(); + // factory(User::class, 10)->create(); $jeroen = User::create( [ @@ -136,8 +136,7 @@ ] ); - $jeroen->role = "admin"; - + $jeroen->role = 'admin'; $jeroen->save(); } } diff --git a/src/database/seeds/production/PackageSeeder.php b/src/database/seeds/production/PackageSeeder.php --- a/src/database/seeds/production/PackageSeeder.php +++ b/src/database/seeds/production/PackageSeeder.php @@ -15,6 +15,7 @@ */ public function run() { + $skuActiveSync = Sku::firstOrCreate(['title' => 'activesync']); $skuGroupware = Sku::firstOrCreate(['title' => 'groupware']); $skuMailbox = Sku::firstOrCreate(['title' => 'mailbox']); $skuStorage = Sku::firstOrCreate(['title' => 'storage']); @@ -31,7 +32,8 @@ $skus = [ $skuMailbox, $skuGroupware, - $skuStorage + $skuStorage, + $skuActiveSync ]; $package->skus()->saveMany($skus); diff --git a/src/doctum b/src/doctum new file mode 120000 --- /dev/null +++ b/src/doctum @@ -0,0 +1 @@ +../bin/doctum \ No newline at end of file diff --git a/src/doctum.config.php b/src/doctum.config.php new file mode 100644 --- /dev/null +++ b/src/doctum.config.php @@ -0,0 +1,26 @@ +files() + ->name('*.php') + ->exclude('bootstrap') + ->exclude('cache') + ->exclude('database') + ->exclude('include') + ->exclude('node_modules') + ->exclude('tests') + ->exclude('vendor') + ->in(__DIR__); + +return new Doctum( + $iterator, + [ + 'build_dir' => __DIR__ . '/../docs/build/%version%/', + 'cache_dir' => __DIR__ . '/cache/', + 'default_opened_level' => 1, + //'include_parent_data' => false, + ] +); diff --git a/src/package-lock.json b/src/package-lock.json --- a/src/package-lock.json +++ b/src/package-lock.json @@ -12,9 +12,9 @@ } }, "@babel/compat-data": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.10.3.tgz", - "integrity": "sha512-BDIfJ9uNZuI0LajPfoYV28lX8kyCPMHY6uY4WH1lJdcicmAfxCK5ASzaeV0D/wsUaRH/cLk+amuxtC37sZ8TUg==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.11.0.tgz", + "integrity": "sha512-TPSvJfv73ng0pfnEOh17bYMPQbI95+nGWc71Ss4vZdRBHTDqmM9Z8ZV4rYz8Ks7sfzc95n30k6ODIq5UGnXcYQ==", "dev": true, "requires": { "browserslist": "^4.12.0", @@ -99,31 +99,69 @@ } }, "@babel/helper-annotate-as-pure": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.1.tgz", - "integrity": "sha512-ewp3rvJEwLaHgyWGe4wQssC2vjks3E80WiUe2BpMb0KhreTjMROCbxXcEovTrbeGVdQct5VjQfrv9EgC+xMzCw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz", + "integrity": "sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA==", "dev": true, "requires": { - "@babel/types": "^7.10.1" + "@babel/types": "^7.10.4" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/types": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.3.tgz", - "integrity": "sha512-lo4XXRnBlU6eRM92FkiZxpo1xFLmv3VsPFk61zJKMm7XYJfwqXHsYJTY6agoc4a3L8QPw1HqWehO18coZgbT6A==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz", + "integrity": "sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg==", "dev": true, "requires": { - "@babel/helper-explode-assignable-expression": "^7.10.3", - "@babel/types": "^7.10.3" + "@babel/helper-explode-assignable-expression": "^7.10.4", + "@babel/types": "^7.10.4" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/types": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/helper-compilation-targets": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.2.tgz", - "integrity": "sha512-hYgOhF4To2UTB4LTaZepN/4Pl9LD4gfbJx8A34mqoluT8TLbof1mhUlYuNWTEebONa8+UlCC4X0TEXu7AOUyGA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.4.tgz", + "integrity": "sha512-a3rYhlsGV0UHNDvrtOXBg8/OpfV0OKTkxKPzIplS1zpx7CygDcWWxckxZeDd3gzPzC4kUT0A4nVFDK0wGMh4MQ==", "dev": true, "requires": { - "@babel/compat-data": "^7.10.1", + "@babel/compat-data": "^7.10.4", "browserslist": "^4.12.0", "invariant": "^2.2.4", "levenary": "^1.1.1", @@ -139,49 +177,329 @@ } }, "@babel/helper-create-class-features-plugin": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.3.tgz", - "integrity": "sha512-iRT9VwqtdFmv7UheJWthGc/h2s7MqoweBF9RUj77NFZsg9VfISvBTum3k6coAhJ8RWv2tj3yUjA03HxPd0vfpQ==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.5.tgz", + "integrity": "sha512-0nkdeijB7VlZoLT3r/mY3bUkw3T8WG/hNw+FATs/6+pG2039IJWjTYL0VTISqsNHMUTEnwbVnc89WIJX9Qed0A==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.10.3", - "@babel/helper-member-expression-to-functions": "^7.10.3", - "@babel/helper-optimise-call-expression": "^7.10.3", - "@babel/helper-plugin-utils": "^7.10.3", - "@babel/helper-replace-supers": "^7.10.1", - "@babel/helper-split-export-declaration": "^7.10.1" + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-member-expression-to-functions": "^7.10.5", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.10.4" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/generator": { + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", + "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", + "dev": true, + "requires": { + "@babel/types": "^7.11.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz", + "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", + "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-replace-supers": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", + "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", + "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", + "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", + "dev": true + }, + "@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/traverse": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", + "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.11.5", + "@babel/types": "^7.11.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } } }, "@babel/helper-create-regexp-features-plugin": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.1.tgz", - "integrity": "sha512-Rx4rHS0pVuJn5pJOqaqcZR4XSgeF9G/pO/79t+4r7380tXFJdzImFnxMU19f83wjSrmKHq6myrM10pFHTGzkUA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.4.tgz", + "integrity": "sha512-2/hu58IEPKeoLF45DBwx3XFqsbCXmkdAay4spVr2x0jYgRxrSNp+ePwvSsy9g6YSaNDcKIQVPXk1Ov8S2edk2g==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.10.1", - "@babel/helper-regex": "^7.10.1", + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-regex": "^7.10.4", "regexpu-core": "^4.7.0" } }, "@babel/helper-define-map": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.3.tgz", - "integrity": "sha512-bxRzDi4Sin/k0drWCczppOhov1sBSdBvXJObM1NLHQzjhXhwRtn7aRWGvLJWCYbuu2qUk3EKs6Ci9C9ps8XokQ==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz", + "integrity": "sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.10.3", - "@babel/types": "^7.10.3", - "lodash": "^4.17.13" + "@babel/helper-function-name": "^7.10.4", + "@babel/types": "^7.10.5", + "lodash": "^4.17.19" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/helper-function-name": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", + "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", + "dev": true + }, + "@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/types": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } } }, "@babel/helper-explode-assignable-expression": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.10.3.tgz", - "integrity": "sha512-0nKcR64XrOC3lsl+uhD15cwxPvaB6QKUDlD84OT9C3myRbhJqTMYir69/RWItUvHpharv0eJ/wk7fl34ONSwZw==", + "version": "7.11.4", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.11.4.tgz", + "integrity": "sha512-ux9hm3zR4WV1Y3xXxXkdG/0gxF9nvI0YVmKVhvK9AfMoaQkemL3sJpXw+Xbz65azo8qJiEz2XVDUpK3KYhH3ZQ==", "dev": true, "requires": { - "@babel/traverse": "^7.10.3", - "@babel/types": "^7.10.3" + "@babel/types": "^7.10.4" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/types": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/helper-function-name": { @@ -205,12 +523,31 @@ } }, "@babel/helper-hoist-variables": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.3.tgz", - "integrity": "sha512-9JyafKoBt5h20Yv1+BXQMdcXXavozI1vt401KBiRc2qzUepbVnd7ogVNymY1xkQN9fekGwfxtotH2Yf5xsGzgg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz", + "integrity": "sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA==", "dev": true, "requires": { - "@babel/types": "^7.10.3" + "@babel/types": "^7.10.4" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/types": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/helper-member-expression-to-functions": { @@ -256,31 +593,97 @@ } }, "@babel/helper-plugin-utils": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.3.tgz", - "integrity": "sha512-j/+j8NAWUTxOtx4LKHybpSClxHoq6I91DQ/mKgAXn5oNUPIUiGppjPIX3TDtJWPrdfP9Kfl7e4fgVMiQR9VE/g==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", "dev": true }, "@babel/helper-regex": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.10.1.tgz", - "integrity": "sha512-7isHr19RsIJWWLLFn21ubFt223PjQyg1HY7CZEMRr820HttHPpVvrsIN3bUOo44DEfFV4kBXO7Abbn9KTUZV7g==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.10.5.tgz", + "integrity": "sha512-68kdUAzDrljqBrio7DYAEgCoJHxppJOERHOgOrDN7WjOzP0ZQ1LsSDRXcemzVZaLvjaJsJEESb6qt+znNuENDg==", "dev": true, "requires": { - "lodash": "^4.17.13" + "lodash": "^4.17.19" } }, "@babel/helper-remap-async-to-generator": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.10.3.tgz", - "integrity": "sha512-sLB7666ARbJUGDO60ZormmhQOyqMX/shKBXZ7fy937s+3ID8gSrneMvKSSb+8xIM5V7Vn6uNVtOY1vIm26XLtA==", + "version": "7.11.4", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.11.4.tgz", + "integrity": "sha512-tR5vJ/vBa9wFy3m5LLv2faapJLnDFxNWff2SAYkSE4rLUdbp7CdObYFgI7wK4T/Mj4UzpjPwzR8Pzmr5m7MHGA==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.10.1", - "@babel/helper-wrap-function": "^7.10.1", - "@babel/template": "^7.10.3", - "@babel/traverse": "^7.10.3", - "@babel/types": "^7.10.3" + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-wrap-function": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", + "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", + "dev": true + }, + "@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/types": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } } }, "@babel/helper-replace-supers": { @@ -305,6 +708,34 @@ "@babel/types": "^7.10.1" } }, + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.11.0.tgz", + "integrity": "sha512-0XIdiQln4Elglgjbwo9wuJpL/K7AGCY26kmEt0+pRP0TAj4jjyNq1MjoRvikrTVqKcx4Gysxt4cXvVFXP/JO2Q==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/types": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } + } + }, "@babel/helper-split-export-declaration": { "version": "7.10.1", "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.1.tgz", @@ -321,43 +752,188 @@ "dev": true }, "@babel/helper-wrap-function": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.10.1.tgz", - "integrity": "sha512-C0MzRGteVDn+H32/ZgbAv5r56f2o1fZSA/rj/TYo8JEJNHg+9BdSmKBUND0shxWRztWhjlT2cvHYuynpPsVJwQ==", - "dev": true, - "requires": { - "@babel/helper-function-name": "^7.10.1", - "@babel/template": "^7.10.1", - "@babel/traverse": "^7.10.1", - "@babel/types": "^7.10.1" - } - }, - "@babel/helpers": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.10.1.tgz", - "integrity": "sha512-muQNHF+IdU6wGgkaJyhhEmI54MOZBKsFfsXFhboz1ybwJ1Kl7IHlbm2a++4jwrmY5UYsgitt5lfqo1wMFcHmyw==", - "dev": true, - "requires": { - "@babel/template": "^7.10.1", - "@babel/traverse": "^7.10.1", - "@babel/types": "^7.10.1" - } - }, - "@babel/highlight": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.3.tgz", - "integrity": "sha512-Ih9B/u7AtgEnySE2L2F0Xm0GaM729XqqLfHkalTsbjXGyqmf/6M0Cu0WpvqueUlW+xk88BHw9Nkpj49naU+vWw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.10.4.tgz", + "integrity": "sha512-6py45WvEF0MhiLrdxtRjKjufwLL1/ob2qDJgg5JgNdojBAZSAKnAjkyOCNug6n+OBl4VW76XjvgSFTdaMcW0Ug==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.10.3", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" + "@babel/helper-function-name": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" }, "dependencies": { - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/generator": { + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", + "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", + "dev": true, + "requires": { + "@babel/types": "^7.11.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", + "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", + "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", + "dev": true + }, + "@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/traverse": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", + "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.11.5", + "@babel/types": "^7.11.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@babel/helpers": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.10.1.tgz", + "integrity": "sha512-muQNHF+IdU6wGgkaJyhhEmI54MOZBKsFfsXFhboz1ybwJ1Kl7IHlbm2a++4jwrmY5UYsgitt5lfqo1wMFcHmyw==", + "dev": true, + "requires": { + "@babel/template": "^7.10.1", + "@babel/traverse": "^7.10.1", + "@babel/types": "^7.10.1" + } + }, + "@babel/highlight": { + "version": "7.10.3", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.3.tgz", + "integrity": "sha512-Ih9B/u7AtgEnySE2L2F0Xm0GaM729XqqLfHkalTsbjXGyqmf/6M0Cu0WpvqueUlW+xk88BHw9Nkpj49naU+vWw==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.3", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { "ansi-styles": "^3.2.1", @@ -374,115 +950,136 @@ "dev": true }, "@babel/plugin-proposal-async-generator-functions": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.3.tgz", - "integrity": "sha512-WUUWM7YTOudF4jZBAJIW9D7aViYC/Fn0Pln4RIHlQALyno3sXSjqmTA4Zy1TKC2D49RCR8Y/Pn4OIUtEypK3CA==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.5.tgz", + "integrity": "sha512-cNMCVezQbrRGvXJwm9fu/1sJj9bHdGAgKodZdLqOQIpfoH3raqmRPBM17+lh7CzhiKRRBrGtZL9WcjxSoGYUSg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.3", - "@babel/helper-remap-async-to-generator": "^7.10.3", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-remap-async-to-generator": "^7.10.4", "@babel/plugin-syntax-async-generators": "^7.8.0" } }, "@babel/plugin-proposal-class-properties": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.1.tgz", - "integrity": "sha512-sqdGWgoXlnOdgMXU+9MbhzwFRgxVLeiGBqTrnuS7LC2IBU31wSsESbTUreT2O418obpfPdGUR2GbEufZF1bpqw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.4.tgz", + "integrity": "sha512-vhwkEROxzcHGNu2mzUC0OFFNXdZ4M23ib8aRRcJSsW8BZK9pQMD7QB7csl97NBbgGZO7ZyHUyKDnxzOaP4IrCg==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-create-class-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-proposal-dynamic-import": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.1.tgz", - "integrity": "sha512-Cpc2yUVHTEGPlmiQzXj026kqwjEQAD9I4ZC16uzdbgWgitg/UHKHLffKNCQZ5+y8jpIZPJcKcwsr2HwPh+w3XA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.4.tgz", + "integrity": "sha512-up6oID1LeidOOASNXgv/CFbgBqTuKJ0cJjz6An5tWD+NVBNlp3VNSBxv2ZdU7SYl3NxJC7agAQDApZusV6uFwQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-dynamic-import": "^7.8.0" } }, + "@babel/plugin-proposal-export-namespace-from": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.10.4.tgz", + "integrity": "sha512-aNdf0LY6/3WXkhh0Fdb6Zk9j1NMD8ovj3F6r0+3j837Pn1S1PdNtcwJ5EG9WkVPNHPxyJDaxMaAOVq4eki0qbg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + } + }, "@babel/plugin-proposal-json-strings": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.1.tgz", - "integrity": "sha512-m8r5BmV+ZLpWPtMY2mOKN7wre6HIO4gfIiV+eOmsnZABNenrt/kzYBwrh+KOfgumSWpnlGs5F70J8afYMSJMBg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.4.tgz", + "integrity": "sha512-fCL7QF0Jo83uy1K0P2YXrfX11tj3lkpN7l4dMv9Y9VkowkhkQDwFHFd8IiwyK5MZjE8UpbgokkgtcReH88Abaw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.0" } }, + "@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.11.0.tgz", + "integrity": "sha512-/f8p4z+Auz0Uaf+i8Ekf1iM7wUNLcViFUGiPxKeXvxTSl63B875YPiVdUDdem7hREcI0E0kSpEhS8tF5RphK7Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + } + }, "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.1.tgz", - "integrity": "sha512-56cI/uHYgL2C8HVuHOuvVowihhX0sxb3nnfVRzUeVHTWmRHTZrKuAh/OBIMggGU/S1g/1D2CRCXqP+3u7vX7iA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.4.tgz", + "integrity": "sha512-wq5n1M3ZUlHl9sqT2ok1T2/MTt6AXE0e1Lz4WzWBr95LsAZ5qDXe4KnFuauYyEyLiohvXFMdbsOTMyLZs91Zlw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" } }, "@babel/plugin-proposal-numeric-separator": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.1.tgz", - "integrity": "sha512-jjfym4N9HtCiNfyyLAVD8WqPYeHUrw4ihxuAynWj6zzp2gf9Ey2f7ImhFm6ikB3CLf5Z/zmcJDri6B4+9j9RsA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.4.tgz", + "integrity": "sha512-73/G7QoRoeNkLZFxsoCCvlg4ezE4eM+57PnOqgaPOozd5myfj7p0muD1mRVJvbUWbOzD+q3No2bWbaKy+DJ8DA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/plugin-syntax-numeric-separator": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.10.3.tgz", - "integrity": "sha512-ZZh5leCIlH9lni5bU/wB/UcjtcVLgR8gc+FAgW2OOY+m9h1II3ItTO1/cewNUcsIDZSYcSaz/rYVls+Fb0ExVQ==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.11.0.tgz", + "integrity": "sha512-wzch41N4yztwoRw0ak+37wxwJM2oiIiy6huGCoqkvSTA9acYWcPfn9Y4aJqmFFJ70KTJUu29f3DQ43uJ9HXzEA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.3", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-transform-parameters": "^7.10.1" + "@babel/plugin-transform-parameters": "^7.10.4" } }, "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.1.tgz", - "integrity": "sha512-VqExgeE62YBqI3ogkGoOJp1R6u12DFZjqwJhqtKc2o5m1YTUuUWnos7bZQFBhwkxIFpWYJ7uB75U7VAPPiKETA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.4.tgz", + "integrity": "sha512-LflT6nPh+GK2MnFiKDyLiqSqVHkQnVf7hdoAvyTnnKj9xB3docGRsdPuxp6qqqW19ifK3xgc9U5/FwrSaCNX5g==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" } }, "@babel/plugin-proposal-optional-chaining": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.10.3.tgz", - "integrity": "sha512-yyG3n9dJ1vZ6v5sfmIlMMZ8azQoqx/5/nZTSWX1td6L1H1bsjzA8TInDChpafCZiJkeOFzp/PtrfigAQXxI1Ng==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.11.0.tgz", + "integrity": "sha512-v9fZIu3Y8562RRwhm1BbMRxtqZNFmFA2EG+pT2diuU8PT3H6T/KXoZ54KgYisfOFZHV6PfvAiBIZ9Rcz+/JCxA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.3", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-skip-transparent-expression-wrappers": "^7.11.0", "@babel/plugin-syntax-optional-chaining": "^7.8.0" } }, "@babel/plugin-proposal-private-methods": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.10.1.tgz", - "integrity": "sha512-RZecFFJjDiQ2z6maFprLgrdnm0OzoC23Mx89xf1CcEsxmHuzuXOdniEuI+S3v7vjQG4F5sa6YtUp+19sZuSxHg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.10.4.tgz", + "integrity": "sha512-wh5GJleuI8k3emgTg5KkJK6kHNsGEr0uBTDBuQUBJwckk9xs1ez79ioheEVVxMLyPscB0LfkbVHslQqIzWV6Bw==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-create-class-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.1.tgz", - "integrity": "sha512-JjfngYRvwmPwmnbRZyNiPFI8zxCZb8euzbCG/LxyKdeTb59tVciKo9GK9bi6JYKInk1H11Dq9j/zRqIH4KigfQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.4.tgz", + "integrity": "sha512-H+3fOgPnEXFL9zGYtKQe4IDOPKYlZdF1kqFDQRRb8PK4B8af1vAGK04tF5iQAAsui+mHNBQSAtd2/ndEDe9wuA==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-create-regexp-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-syntax-async-generators": { @@ -495,12 +1092,12 @@ } }, "@babel/plugin-syntax-class-properties": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.1.tgz", - "integrity": "sha512-Gf2Yx/iRs1JREDtVZ56OrjjgFHCaldpTnuy9BHla10qyVT3YkIIGEtoDWhyop0ksu1GvNjHIoYRBqm3zoR1jyQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.4.tgz", + "integrity": "sha512-GCSBF7iUle6rNugfURwNmCGG3Z/2+opxAMLs1nND4bhEG5PuxTIggDBoeYYSujAlLtsupzOHYJQgPS3pivwXIA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-syntax-dynamic-import": { @@ -512,6 +1109,15 @@ "@babel/helper-plugin-utils": "^7.8.0" } }, + "@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, "@babel/plugin-syntax-json-strings": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", @@ -521,6 +1127,15 @@ "@babel/helper-plugin-utils": "^7.8.0" } }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, "@babel/plugin-syntax-nullish-coalescing-operator": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", @@ -531,12 +1146,12 @@ } }, "@babel/plugin-syntax-numeric-separator": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.1.tgz", - "integrity": "sha512-uTd0OsHrpe3tH5gRPTxG8Voh99/WCU78vIm5NMRYPAqC8lR4vajt6KkCAknCHrx24vkPdd/05yfdGSB4EIY2mg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-syntax-object-rest-spread": { @@ -567,283 +1182,1629 @@ } }, "@babel/plugin-syntax-top-level-await": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.1.tgz", - "integrity": "sha512-hgA5RYkmZm8FTFT3yu2N9Bx7yVVOKYT6yEdXXo6j2JTm0wNxgqaGeQVaSHRjhfnQbX91DtjFB6McRFSlcJH3xQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.4.tgz", + "integrity": "sha512-ni1brg4lXEmWyafKr0ccFWkJG0CeMt4WV1oyeBW6EFObF4oOHclbkj5cARxAPQyAQ2UTuplJyK4nfkXIMMFvsQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-arrow-functions": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.1.tgz", - "integrity": "sha512-6AZHgFJKP3DJX0eCNJj01RpytUa3SOGawIxweHkNX2L6PYikOZmoh5B0d7hIHaIgveMjX990IAa/xK7jRTN8OA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.4.tgz", + "integrity": "sha512-9J/oD1jV0ZCBcgnoFWFq1vJd4msoKb/TCpGNFyyLt0zABdcvgK3aYikZ8HjzB14c26bc7E3Q1yugpwGy2aTPNA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-async-to-generator": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.1.tgz", - "integrity": "sha512-XCgYjJ8TY2slj6SReBUyamJn3k2JLUIiiR5b6t1mNCMSvv7yx+jJpaewakikp0uWFQSF7ChPPoe3dHmXLpISkg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.4.tgz", + "integrity": "sha512-F6nREOan7J5UXTLsDsZG3DXmZSVofr2tGNwfdrVwkDWHfQckbQXnXSPfD7iO+c/2HGqycwyLST3DnZ16n+cBJQ==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/helper-remap-async-to-generator": "^7.10.1" + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-remap-async-to-generator": "^7.10.4" + }, + "dependencies": { + "@babel/helper-module-imports": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz", + "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/types": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/plugin-transform-block-scoped-functions": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.1.tgz", - "integrity": "sha512-B7K15Xp8lv0sOJrdVAoukKlxP9N59HS48V1J3U/JGj+Ad+MHq+am6xJVs85AgXrQn4LV8vaYFOB+pr/yIuzW8Q==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.4.tgz", + "integrity": "sha512-WzXDarQXYYfjaV1szJvN3AD7rZgZzC1JtjJZ8dMHUyiK8mxPRahynp14zzNjU3VkPqPsO38CzxiWO1c9ARZ8JA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-block-scoping": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.10.1.tgz", - "integrity": "sha512-8bpWG6TtF5akdhIm/uWTyjHqENpy13Fx8chg7pFH875aNLwX8JxIxqm08gmAT+Whe6AOmaTeLPe7dpLbXt+xUw==", + "version": "7.11.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.11.1.tgz", + "integrity": "sha512-00dYeDE0EVEHuuM+26+0w/SCL0BH2Qy7LwHuI4Hi4MH5gkC8/AqMN5uWFJIsoXZrAphiMm1iXzBw6L2T+eA0ew==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1", - "lodash": "^4.17.13" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-classes": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.3.tgz", - "integrity": "sha512-irEX0ChJLaZVC7FvvRoSIxJlmk0IczFLcwaRXUArBKYHCHbOhe57aG8q3uw/fJsoSXvZhjRX960hyeAGlVBXZw==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.10.1", - "@babel/helper-define-map": "^7.10.3", - "@babel/helper-function-name": "^7.10.3", - "@babel/helper-optimise-call-expression": "^7.10.3", - "@babel/helper-plugin-utils": "^7.10.3", - "@babel/helper-replace-supers": "^7.10.1", - "@babel/helper-split-export-declaration": "^7.10.1", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.4.tgz", + "integrity": "sha512-2oZ9qLjt161dn1ZE0Ms66xBncQH4In8Sqw1YWgBUZuGVJJS5c0OFZXL6dP2MRHrkU/eKhWg8CzFJhRQl50rQxA==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-define-map": "^7.10.4", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.10.4", "globals": "^11.1.0" }, "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/generator": { + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", + "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", + "dev": true, + "requires": { + "@babel/types": "^7.11.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz", + "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", + "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-replace-supers": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", + "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", + "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", + "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", + "dev": true + }, + "@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/traverse": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", + "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.11.5", + "@babel/types": "^7.11.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true } } }, "@babel/plugin-transform-computed-properties": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.3.tgz", - "integrity": "sha512-GWzhaBOsdbjVFav96drOz7FzrcEW6AP5nax0gLIpstiFaI3LOb2tAg06TimaWU6YKOfUACK3FVrxPJ4GSc5TgA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.4.tgz", + "integrity": "sha512-JFwVDXcP/hM/TbyzGq3l/XWGut7p46Z3QvqFMXTfk6/09m7xZHJUN9xHfsv7vqqD4YnfI5ueYdSJtXqqBLyjBw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-destructuring": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.1.tgz", - "integrity": "sha512-V/nUc4yGWG71OhaTH705pU8ZSdM6c1KmmLP8ys59oOYbT7RpMYAR3MsVOt6OHL0WzG7BlTU076va9fjJyYzJMA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.4.tgz", + "integrity": "sha512-+WmfvyfsyF603iPa6825mq6Qrb7uLjTOsa3XOFzlYcYDHSS4QmpOWOL0NNBY5qMbvrcf3tq0Cw+v4lxswOBpgA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-dotall-regex": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.1.tgz", - "integrity": "sha512-19VIMsD1dp02RvduFUmfzj8uknaO3uiHHF0s3E1OHnVsNj8oge8EQ5RzHRbJjGSetRnkEuBYO7TG1M5kKjGLOA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.4.tgz", + "integrity": "sha512-ZEAVvUTCMlMFAbASYSVQoxIbHm2OkG2MseW6bV2JjIygOjdVv8tuxrCTzj1+Rynh7ODb8GivUy7dzEXzEhuPaA==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-create-regexp-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-duplicate-keys": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.1.tgz", - "integrity": "sha512-wIEpkX4QvX8Mo9W6XF3EdGttrIPZWozHfEaDTU0WJD/TDnXMvdDh30mzUl/9qWhnf7naicYartcEfUghTCSNpA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.4.tgz", + "integrity": "sha512-GL0/fJnmgMclHiBTTWXNlYjYsA7rDrtsazHG6mglaGSTh0KsrW04qml+Bbz9FL0LcJIRwBWL5ZqlNHKTkU3xAA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-exponentiation-operator": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.1.tgz", - "integrity": "sha512-lr/przdAbpEA2BUzRvjXdEDLrArGRRPwbaF9rvayuHRvdQ7lUTTkZnhZrJ4LE2jvgMRFF4f0YuPQ20vhiPYxtA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.4.tgz", + "integrity": "sha512-S5HgLVgkBcRdyQAHbKj+7KyuWx8C6t5oETmUuwz1pt3WTWJhsUV0WIIXuVvfXMxl/QQyHKlSCNNtaIamG8fysw==", "dev": true, "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-for-of": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.1.tgz", - "integrity": "sha512-US8KCuxfQcn0LwSCMWMma8M2R5mAjJGsmoCBVwlMygvmDUMkTCykc84IqN1M7t+agSfOmLYTInLCHJM+RUoz+w==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.4.tgz", + "integrity": "sha512-ItdQfAzu9AlEqmusA/65TqJ79eRcgGmpPPFvBnGILXZH975G0LNjP1yjHvGgfuCxqrPPueXOPe+FsvxmxKiHHQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-function-name": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.1.tgz", - "integrity": "sha512-//bsKsKFBJfGd65qSNNh1exBy5Y9gD9ZN+DvrJ8f7HXr4avE5POW6zB7Rj6VnqHV33+0vXWUwJT0wSHubiAQkw==", - "dev": true, - "requires": { - "@babel/helper-function-name": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "@babel/plugin-transform-literals": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.1.tgz", - "integrity": "sha512-qi0+5qgevz1NHLZroObRm5A+8JJtibb7vdcPQF1KQE12+Y/xxl8coJ+TpPW9iRq+Mhw/NKLjm+5SHtAHCC7lAw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.4.tgz", + "integrity": "sha512-OcDCq2y5+E0dVD5MagT5X+yTRbcvFjDI2ZVAottGH6tzqjx/LKpgkUepu3hp/u4tZBzxxpNGwLsAvGBvQ2mJzg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "@babel/plugin-transform-member-expression-literals": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.1.tgz", - "integrity": "sha512-UmaWhDokOFT2GcgU6MkHC11i0NQcL63iqeufXWfRy6pUOGYeCGEKhvfFO6Vz70UfYJYHwveg62GS83Rvpxn+NA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "@babel/plugin-transform-modules-amd": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.1.tgz", - "integrity": "sha512-31+hnWSFRI4/ACFr1qkboBbrTxoBIzj7qA69qlq8HY8p7+YCzkCT6/TvQ1a4B0z27VeWtAeJd6pr5G04dc1iHw==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/helper-function-name": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", + "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", + "dev": true + }, + "@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/types": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } + } + }, + "@babel/plugin-transform-literals": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.4.tgz", + "integrity": "sha512-Xd/dFSTEVuUWnyZiMu76/InZxLTYilOSr1UlHV+p115Z/Le2Fi1KXkJUYz0b42DfndostYlPub3m8ZTQlMaiqQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.4.tgz", + "integrity": "sha512-0bFOvPyAoTBhtcJLr9VcwZqKmSjFml1iVxvPL0ReomGU53CX53HsM4h2SzckNdkQcHox1bpAqzxBI1Y09LlBSw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.5.tgz", + "integrity": "sha512-elm5uruNio7CTLFItVC/rIzKLfQ17+fX7EVz5W0TMgIHFo1zY0Ozzx+lgwhL4plzl8OzVn6Qasx5DeEFyoNiRw==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.10.5", + "@babel/helper-plugin-utils": "^7.10.4", + "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/generator": { + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", + "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", + "dev": true, + "requires": { + "@babel/types": "^7.11.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz", + "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-module-imports": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz", + "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-module-transforms": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz", + "integrity": "sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-simple-access": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/template": "^7.10.4", + "@babel/types": "^7.11.0", + "lodash": "^4.17.19" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", + "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-replace-supers": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", + "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-simple-access": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz", + "integrity": "sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==", + "dev": true, + "requires": { + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", + "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", + "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", + "dev": true + }, + "@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/traverse": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", + "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.11.5", + "@babel/types": "^7.11.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.1.tgz", - "integrity": "sha512-AQG4fc3KOah0vdITwt7Gi6hD9BtQP/8bhem7OjbaMoRNCH5Djx42O2vYMfau7QnAzQCa+RJnhJBmFFMGpQEzrg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.4.tgz", + "integrity": "sha512-Xj7Uq5o80HDLlW64rVfDBhao6OX89HKUmb+9vWYaLXBZOma4gA6tw4Ni1O5qVDoZWUV0fxMYA0aYzOawz0l+1w==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/helper-simple-access": "^7.10.1", + "@babel/helper-module-transforms": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-simple-access": "^7.10.4", "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/generator": { + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", + "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", + "dev": true, + "requires": { + "@babel/types": "^7.11.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz", + "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-module-imports": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz", + "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-module-transforms": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz", + "integrity": "sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-simple-access": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/template": "^7.10.4", + "@babel/types": "^7.11.0", + "lodash": "^4.17.19" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", + "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-replace-supers": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", + "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-simple-access": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz", + "integrity": "sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==", + "dev": true, + "requires": { + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", + "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", + "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", + "dev": true + }, + "@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/traverse": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", + "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.11.5", + "@babel/types": "^7.11.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } } }, "@babel/plugin-transform-modules-systemjs": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.3.tgz", - "integrity": "sha512-GWXWQMmE1GH4ALc7YXW56BTh/AlzvDWhUNn9ArFF0+Cz5G8esYlVbXfdyHa1xaD1j+GnBoCeoQNlwtZTVdiG/A==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.5.tgz", + "integrity": "sha512-f4RLO/OL14/FP1AEbcsWMzpbUz6tssRaeQg11RH1BP/XnPpRoVwgeYViMFacnkaw4k4wjRSjn3ip1Uw9TaXuMw==", "dev": true, "requires": { - "@babel/helper-hoist-variables": "^7.10.3", - "@babel/helper-module-transforms": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.3", + "@babel/helper-hoist-variables": "^7.10.4", + "@babel/helper-module-transforms": "^7.10.5", + "@babel/helper-plugin-utils": "^7.10.4", "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/generator": { + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", + "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", + "dev": true, + "requires": { + "@babel/types": "^7.11.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz", + "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-module-imports": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz", + "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-module-transforms": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz", + "integrity": "sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-simple-access": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/template": "^7.10.4", + "@babel/types": "^7.11.0", + "lodash": "^4.17.19" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", + "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-replace-supers": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", + "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-simple-access": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz", + "integrity": "sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==", + "dev": true, + "requires": { + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", + "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", + "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", + "dev": true + }, + "@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/traverse": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", + "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.11.5", + "@babel/types": "^7.11.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } } }, "@babel/plugin-transform-modules-umd": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.1.tgz", - "integrity": "sha512-EIuiRNMd6GB6ulcYlETnYYfgv4AxqrswghmBRQbWLHZxN4s7mupxzglnHqk9ZiUpDI4eRWewedJJNj67PWOXKA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.4.tgz", + "integrity": "sha512-mohW5q3uAEt8T45YT7Qc5ws6mWgJAaL/8BfWD9Dodo1A3RKWli8wTS+WiQ/knF+tXlPirW/1/MqzzGfCExKECA==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-module-transforms": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/generator": { + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", + "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", + "dev": true, + "requires": { + "@babel/types": "^7.11.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz", + "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-module-imports": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz", + "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-module-transforms": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz", + "integrity": "sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-simple-access": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/template": "^7.10.4", + "@babel/types": "^7.11.0", + "lodash": "^4.17.19" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", + "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-replace-supers": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", + "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-simple-access": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz", + "integrity": "sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==", + "dev": true, + "requires": { + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", + "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", + "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", + "dev": true + }, + "@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/traverse": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", + "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.11.5", + "@babel/types": "^7.11.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } } }, "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.10.3.tgz", - "integrity": "sha512-I3EH+RMFyVi8Iy/LekQm948Z4Lz4yKT7rK+vuCAeRm0kTa6Z5W7xuhRxDNJv0FPya/her6AUgrDITb70YHtTvA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.10.4.tgz", + "integrity": "sha512-V6LuOnD31kTkxQPhKiVYzYC/Jgdq53irJC/xBSmqcNcqFGV+PER4l6rU5SH2Vl7bH9mLDHcc0+l9HUOe4RNGKA==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.8.3" + "@babel/helper-create-regexp-features-plugin": "^7.10.4" } }, "@babel/plugin-transform-new-target": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.1.tgz", - "integrity": "sha512-MBlzPc1nJvbmO9rPr1fQwXOM2iGut+JC92ku6PbiJMMK7SnQc1rytgpopveE3Evn47gzvGYeCdgfCDbZo0ecUw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.4.tgz", + "integrity": "sha512-YXwWUDAH/J6dlfwqlWsztI2Puz1NtUAubXhOPLQ5gjR/qmQ5U96DY4FQO8At33JN4XPBhrjB8I4eMmLROjjLjw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-object-super": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.1.tgz", - "integrity": "sha512-WnnStUDN5GL+wGQrJylrnnVlFhFmeArINIR9gjhSeYyvroGhBrSAXYg/RHsnfzmsa+onJrTJrEClPzgNmmQ4Gw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.4.tgz", + "integrity": "sha512-5iTw0JkdRdJvr7sY0vHqTpnruUpTea32JHmq/atIWqsnNussbRzjEDyWep8UNztt1B5IusBYg8Irb0bLbiEBCQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/helper-replace-supers": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/generator": { + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", + "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", + "dev": true, + "requires": { + "@babel/types": "^7.11.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz", + "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", + "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-replace-supers": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", + "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", + "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", + "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", + "dev": true + }, + "@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/traverse": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", + "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.11.5", + "@babel/types": "^7.11.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } } }, "@babel/plugin-transform-parameters": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.1.tgz", - "integrity": "sha512-tJ1T0n6g4dXMsL45YsSzzSDZCxiHXAQp/qHrucOq5gEHncTA3xDxnd5+sZcoQp+N1ZbieAaB8r/VUCG0gqseOg==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.5.tgz", + "integrity": "sha512-xPHwUj5RdFV8l1wuYiu5S9fqWGM2DrYc24TMvUiRrPVm+SM3XeqU9BcokQX/kEUe+p2RBwy+yoiR1w/Blq6ubw==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + }, + "dependencies": { + "@babel/helper-get-function-arity": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/types": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/plugin-transform-property-literals": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.1.tgz", - "integrity": "sha512-Kr6+mgag8auNrgEpbfIWzdXYOvqDHZOF0+Bx2xh4H2EDNwcbRb9lY6nkZg8oSjsX+DH9Ebxm9hOqtKW+gRDeNA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.4.tgz", + "integrity": "sha512-ofsAcKiUxQ8TY4sScgsGeR2vJIsfrzqvFb9GvJ5UdXDzl+MyYCaBj/FGzXuv7qE0aJcjWMILny1epqelnFlz8g==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-regenerator": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.3.tgz", - "integrity": "sha512-H5kNeW0u8mbk0qa1jVIVTeJJL6/TJ81ltD4oyPx0P499DhMJrTmmIFCmJ3QloGpQG8K9symccB7S7SJpCKLwtw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.4.tgz", + "integrity": "sha512-3thAHwtor39A7C04XucbMg17RcZ3Qppfxr22wYzZNcVIkPHfpM9J0SO8zuCV6SZa265kxBJSrfKTvDCYqBFXGw==", "dev": true, "requires": { "regenerator-transform": "^0.14.2" } }, "@babel/plugin-transform-reserved-words": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.1.tgz", - "integrity": "sha512-qN1OMoE2nuqSPmpTqEM7OvJ1FkMEV+BjVeZZm9V9mq/x1JLKQ4pcv8riZJMNN3u2AUGl0ouOMjRr2siecvHqUQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.4.tgz", + "integrity": "sha512-hGsw1O6Rew1fkFbDImZIEqA8GoidwTAilwCyWqLBM9f+e/u/sQMQu7uX6dyokfOayRuuVfKOW4O7HvaBWM+JlQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-runtime": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.10.3.tgz", - "integrity": "sha512-b5OzMD1Hi8BBzgQdRHyVVaYrk9zG0wset1it2o3BgonkPadXfOv0aXRqd7864DeOIu3FGKP/h6lr15FE5mahVw==", + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.11.5.tgz", + "integrity": "sha512-9aIoee+EhjySZ6vY5hnLjigHzunBlscx9ANKutkeWTJTx6m5Rbq6Ic01tLvO54lSusR+BxV7u4UDdCmXv5aagg==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.10.3", - "@babel/helper-plugin-utils": "^7.10.3", + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", "resolve": "^1.8.1", "semver": "^5.5.1" }, "dependencies": { + "@babel/helper-module-imports": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz", + "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/types": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", @@ -853,136 +2814,141 @@ } }, "@babel/plugin-transform-shorthand-properties": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.1.tgz", - "integrity": "sha512-AR0E/lZMfLstScFwztApGeyTHJ5u3JUKMjneqRItWeEqDdHWZwAOKycvQNCasCK/3r5YXsuNG25funcJDu7Y2g==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.4.tgz", + "integrity": "sha512-AC2K/t7o07KeTIxMoHneyX90v3zkm5cjHJEokrPEAGEy3UCp8sLKfnfOIGdZ194fyN4wfX/zZUWT9trJZ0qc+Q==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-spread": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.10.1.tgz", - "integrity": "sha512-8wTPym6edIrClW8FI2IoaePB91ETOtg36dOkj3bYcNe7aDMN2FXEoUa+WrmPc4xa1u2PQK46fUX2aCb+zo9rfw==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.11.0.tgz", + "integrity": "sha512-UwQYGOqIdQJe4aWNyS7noqAnN2VbaczPLiEtln+zPowRNlD+79w3oi2TWfYe0eZgd+gjZCbsydN7lzWysDt+gw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-skip-transparent-expression-wrappers": "^7.11.0" } }, "@babel/plugin-transform-sticky-regex": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.1.tgz", - "integrity": "sha512-j17ojftKjrL7ufX8ajKvwRilwqTok4q+BjkknmQw9VNHnItTyMP5anPFzxFJdCQs7clLcWpCV3ma+6qZWLnGMA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.4.tgz", + "integrity": "sha512-Ddy3QZfIbEV0VYcVtFDCjeE4xwVTJWTmUtorAJkn6u/92Z/nWJNV+mILyqHKrUxXYKA2EoCilgoPePymKL4DvQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/helper-regex": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-regex": "^7.10.4" } }, "@babel/plugin-transform-template-literals": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.3.tgz", - "integrity": "sha512-yaBn9OpxQra/bk0/CaA4wr41O0/Whkg6nqjqApcinxM7pro51ojhX6fv1pimAnVjVfDy14K0ULoRL70CA9jWWA==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.5.tgz", + "integrity": "sha512-V/lnPGIb+KT12OQikDvgSuesRX14ck5FfJXt6+tXhdkJ+Vsd0lDCVtF6jcB4rNClYFzaB2jusZ+lNISDk2mMMw==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.3" + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-typeof-symbol": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.1.tgz", - "integrity": "sha512-qX8KZcmbvA23zDi+lk9s6hC1FM7jgLHYIjuLgULgc8QtYnmB3tAVIYkNoKRQ75qWBeyzcoMoK8ZQmogGtC/w0g==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.4.tgz", + "integrity": "sha512-QqNgYwuuW0y0H+kUE/GWSR45t/ccRhe14Fs/4ZRouNNQsyd4o3PG4OtHiIrepbM2WKUBDAXKCAK/Lk4VhzTaGA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-unicode-escapes": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.10.1.tgz", - "integrity": "sha512-zZ0Poh/yy1d4jeDWpx/mNwbKJVwUYJX73q+gyh4bwtG0/iUlzdEu0sLMda8yuDFS6LBQlT/ST1SJAR6zYwXWgw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.10.4.tgz", + "integrity": "sha512-y5XJ9waMti2J+e7ij20e+aH+fho7Wb7W8rNuu72aKRwCHFqQdhkdU2lo3uZ9tQuboEJcUFayXdARhcxLQ3+6Fg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-unicode-regex": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.1.tgz", - "integrity": "sha512-Y/2a2W299k0VIUdbqYm9X2qS6fE0CUBhhiPpimK6byy7OJ/kORLlIX+J6UrjgNu5awvs62k+6RSslxhcvVw2Tw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.4.tgz", + "integrity": "sha512-wNfsc4s8N2qnIwpO/WP2ZiSyjfpTamT2C9V9FDH/Ljub9zw6P3SjkXcFmc0RQUt96k2fmIvtla2MMjgTwIAC+A==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-create-regexp-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/preset-env": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.10.3.tgz", - "integrity": "sha512-jHaSUgiewTmly88bJtMHbOd1bJf2ocYxb5BWKSDQIP5tmgFuS/n0gl+nhSrYDhT33m0vPxp+rP8oYYgPgMNQlg==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.10.3", - "@babel/helper-compilation-targets": "^7.10.2", - "@babel/helper-module-imports": "^7.10.3", - "@babel/helper-plugin-utils": "^7.10.3", - "@babel/plugin-proposal-async-generator-functions": "^7.10.3", - "@babel/plugin-proposal-class-properties": "^7.10.1", - "@babel/plugin-proposal-dynamic-import": "^7.10.1", - "@babel/plugin-proposal-json-strings": "^7.10.1", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.1", - "@babel/plugin-proposal-numeric-separator": "^7.10.1", - "@babel/plugin-proposal-object-rest-spread": "^7.10.3", - "@babel/plugin-proposal-optional-catch-binding": "^7.10.1", - "@babel/plugin-proposal-optional-chaining": "^7.10.3", - "@babel/plugin-proposal-private-methods": "^7.10.1", - "@babel/plugin-proposal-unicode-property-regex": "^7.10.1", + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.11.5.tgz", + "integrity": "sha512-kXqmW1jVcnB2cdueV+fyBM8estd5mlNfaQi6lwLgRwCby4edpavgbFhiBNjmWA3JpB/yZGSISa7Srf+TwxDQoA==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.11.0", + "@babel/helper-compilation-targets": "^7.10.4", + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-proposal-async-generator-functions": "^7.10.4", + "@babel/plugin-proposal-class-properties": "^7.10.4", + "@babel/plugin-proposal-dynamic-import": "^7.10.4", + "@babel/plugin-proposal-export-namespace-from": "^7.10.4", + "@babel/plugin-proposal-json-strings": "^7.10.4", + "@babel/plugin-proposal-logical-assignment-operators": "^7.11.0", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.4", + "@babel/plugin-proposal-numeric-separator": "^7.10.4", + "@babel/plugin-proposal-object-rest-spread": "^7.11.0", + "@babel/plugin-proposal-optional-catch-binding": "^7.10.4", + "@babel/plugin-proposal-optional-chaining": "^7.11.0", + "@babel/plugin-proposal-private-methods": "^7.10.4", + "@babel/plugin-proposal-unicode-property-regex": "^7.10.4", "@babel/plugin-syntax-async-generators": "^7.8.0", - "@babel/plugin-syntax-class-properties": "^7.10.1", + "@babel/plugin-syntax-class-properties": "^7.10.4", "@babel/plugin-syntax-dynamic-import": "^7.8.0", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", "@babel/plugin-syntax-json-strings": "^7.8.0", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0", - "@babel/plugin-syntax-numeric-separator": "^7.10.1", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.0", "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", "@babel/plugin-syntax-optional-chaining": "^7.8.0", - "@babel/plugin-syntax-top-level-await": "^7.10.1", - "@babel/plugin-transform-arrow-functions": "^7.10.1", - "@babel/plugin-transform-async-to-generator": "^7.10.1", - "@babel/plugin-transform-block-scoped-functions": "^7.10.1", - "@babel/plugin-transform-block-scoping": "^7.10.1", - "@babel/plugin-transform-classes": "^7.10.3", - "@babel/plugin-transform-computed-properties": "^7.10.3", - "@babel/plugin-transform-destructuring": "^7.10.1", - "@babel/plugin-transform-dotall-regex": "^7.10.1", - "@babel/plugin-transform-duplicate-keys": "^7.10.1", - "@babel/plugin-transform-exponentiation-operator": "^7.10.1", - "@babel/plugin-transform-for-of": "^7.10.1", - "@babel/plugin-transform-function-name": "^7.10.1", - "@babel/plugin-transform-literals": "^7.10.1", - "@babel/plugin-transform-member-expression-literals": "^7.10.1", - "@babel/plugin-transform-modules-amd": "^7.10.1", - "@babel/plugin-transform-modules-commonjs": "^7.10.1", - "@babel/plugin-transform-modules-systemjs": "^7.10.3", - "@babel/plugin-transform-modules-umd": "^7.10.1", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.10.3", - "@babel/plugin-transform-new-target": "^7.10.1", - "@babel/plugin-transform-object-super": "^7.10.1", - "@babel/plugin-transform-parameters": "^7.10.1", - "@babel/plugin-transform-property-literals": "^7.10.1", - "@babel/plugin-transform-regenerator": "^7.10.3", - "@babel/plugin-transform-reserved-words": "^7.10.1", - "@babel/plugin-transform-shorthand-properties": "^7.10.1", - "@babel/plugin-transform-spread": "^7.10.1", - "@babel/plugin-transform-sticky-regex": "^7.10.1", - "@babel/plugin-transform-template-literals": "^7.10.3", - "@babel/plugin-transform-typeof-symbol": "^7.10.1", - "@babel/plugin-transform-unicode-escapes": "^7.10.1", - "@babel/plugin-transform-unicode-regex": "^7.10.1", + "@babel/plugin-syntax-top-level-await": "^7.10.4", + "@babel/plugin-transform-arrow-functions": "^7.10.4", + "@babel/plugin-transform-async-to-generator": "^7.10.4", + "@babel/plugin-transform-block-scoped-functions": "^7.10.4", + "@babel/plugin-transform-block-scoping": "^7.10.4", + "@babel/plugin-transform-classes": "^7.10.4", + "@babel/plugin-transform-computed-properties": "^7.10.4", + "@babel/plugin-transform-destructuring": "^7.10.4", + "@babel/plugin-transform-dotall-regex": "^7.10.4", + "@babel/plugin-transform-duplicate-keys": "^7.10.4", + "@babel/plugin-transform-exponentiation-operator": "^7.10.4", + "@babel/plugin-transform-for-of": "^7.10.4", + "@babel/plugin-transform-function-name": "^7.10.4", + "@babel/plugin-transform-literals": "^7.10.4", + "@babel/plugin-transform-member-expression-literals": "^7.10.4", + "@babel/plugin-transform-modules-amd": "^7.10.4", + "@babel/plugin-transform-modules-commonjs": "^7.10.4", + "@babel/plugin-transform-modules-systemjs": "^7.10.4", + "@babel/plugin-transform-modules-umd": "^7.10.4", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.10.4", + "@babel/plugin-transform-new-target": "^7.10.4", + "@babel/plugin-transform-object-super": "^7.10.4", + "@babel/plugin-transform-parameters": "^7.10.4", + "@babel/plugin-transform-property-literals": "^7.10.4", + "@babel/plugin-transform-regenerator": "^7.10.4", + "@babel/plugin-transform-reserved-words": "^7.10.4", + "@babel/plugin-transform-shorthand-properties": "^7.10.4", + "@babel/plugin-transform-spread": "^7.11.0", + "@babel/plugin-transform-sticky-regex": "^7.10.4", + "@babel/plugin-transform-template-literals": "^7.10.4", + "@babel/plugin-transform-typeof-symbol": "^7.10.4", + "@babel/plugin-transform-unicode-escapes": "^7.10.4", + "@babel/plugin-transform-unicode-regex": "^7.10.4", "@babel/preset-modules": "^0.1.3", - "@babel/types": "^7.10.3", + "@babel/types": "^7.11.5", "browserslist": "^4.12.0", "core-js-compat": "^3.6.2", "invariant": "^2.2.2", @@ -990,6 +2956,32 @@ "semver": "^5.5.0" }, "dependencies": { + "@babel/helper-module-imports": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz", + "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/types": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", @@ -999,9 +2991,9 @@ } }, "@babel/preset-modules": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.3.tgz", - "integrity": "sha512-Ra3JXOHBq2xd56xSF7lMKXdjBn3T772Y1Wet3yWnkDly9zHvJki029tAFzvAAK5cf4YV3yoxuP61crYRol6SVg==", + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.4.tgz", + "integrity": "sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", @@ -1012,9 +3004,9 @@ } }, "@babel/runtime": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.3.tgz", - "integrity": "sha512-RzGO0RLSdokm9Ipe/YD+7ww8X2Ro79qiXZF3HU9ljrM+qnJmH1Vqth+hbiQZy761LnMJTMitHDuKVYTk3k4dLw==", + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", + "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", "dev": true, "requires": { "regenerator-runtime": "^0.13.4" @@ -1083,45 +3075,45 @@ } }, "@fortawesome/fontawesome-common-types": { - "version": "0.2.30", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.30.tgz", - "integrity": "sha512-TsRwpTuKwFNiPhk1UfKgw7zNPeV5RhNp2Uw3pws+9gDAkPGKrtjR1y2lI3SYn7+YzyfuNknflpBA1LRKjt7hMg==", + "version": "0.2.32", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.32.tgz", + "integrity": "sha512-ux2EDjKMpcdHBVLi/eWZynnPxs0BtFVXJkgHIxXRl+9ZFaHPvYamAfCzeeQFqHRjuJtX90wVnMRaMQAAlctz3w==", "dev": true }, "@fortawesome/fontawesome-svg-core": { - "version": "1.2.30", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.30.tgz", - "integrity": "sha512-E3sAXATKCSVnT17HYmZjjbcmwihrNOCkoU7dVMlasrcwiJAHxSKeZ+4WN5O+ElgO/FaYgJmASl8p9N7/B/RttA==", + "version": "1.2.32", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.32.tgz", + "integrity": "sha512-XjqyeLCsR/c/usUpdWcOdVtWFVjPbDFBTQkn2fQRrWhhUoxriQohO2RWDxLyUM8XpD+Zzg5xwJ8gqTYGDLeGaQ==", "dev": true, "requires": { - "@fortawesome/fontawesome-common-types": "^0.2.30" + "@fortawesome/fontawesome-common-types": "^0.2.32" } }, "@fortawesome/free-brands-svg-icons": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-5.14.0.tgz", - "integrity": "sha512-WsqPFTvJFI7MYkcy0jeFE2zY+blC4OrnB9MJOcn1NxRXT/sSfEEhrI7CwzIkiYajLiVDBKWeErYOvpsMeodmCQ==", + "version": "5.15.1", + "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-5.15.1.tgz", + "integrity": "sha512-pkTZIWn7iuliCCgV+huDfZmZb2UjslalXGDA2PcqOVUYJmYL11y6ooFiMJkJvUZu+xgAc1gZgQe+Px12mZF0CA==", "dev": true, "requires": { - "@fortawesome/fontawesome-common-types": "^0.2.30" + "@fortawesome/fontawesome-common-types": "^0.2.32" } }, "@fortawesome/free-regular-svg-icons": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.14.0.tgz", - "integrity": "sha512-6LCFvjGSMPoUQbn3NVlgiG4CY5iIY8fOm+to/D6QS/GvdqhDt+xZklQeERdCvVRbnFa1ITc1rJHPRXqkX5wztQ==", + "version": "5.15.1", + "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.15.1.tgz", + "integrity": "sha512-eD9NWFy89e7SVVtrLedJUxIpCBGhd4x7s7dhesokjyo1Tw62daqN5UcuAGu1NrepLLq1IeAYUVfWwnOjZ/j3HA==", "dev": true, "requires": { - "@fortawesome/fontawesome-common-types": "^0.2.30" + "@fortawesome/fontawesome-common-types": "^0.2.32" } }, "@fortawesome/free-solid-svg-icons": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.14.0.tgz", - "integrity": "sha512-M933RDM8cecaKMWDSk3FRYdnzWGW7kBBlGNGfvqLVwcwhUPNj9gcw+xZMrqBdRqxnSXdl3zWzTCNNGEtFUq67Q==", + "version": "5.15.1", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.1.tgz", + "integrity": "sha512-EFMuKtzRMNbvjab/SvJBaOOpaqJfdSap/Nl6hst7CgrJxwfORR1drdTV6q1Ib/JVzq4xObdTDcT6sqTaXMqfdg==", "dev": true, "requires": { - "@fortawesome/fontawesome-common-types": "^0.2.30" + "@fortawesome/fontawesome-common-types": "^0.2.32" } }, "@fortawesome/vue-fontawesome": { @@ -1175,9 +3167,9 @@ } }, "@stylelint/postcss-css-in-js": { - "version": "0.37.1", - "resolved": "https://registry.npmjs.org/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.1.tgz", - "integrity": "sha512-UMf2Rni3JGKi3ZwYRGMYJ5ipOA5ENJSKMtYA/pE1ZLURwdh7B5+z2r73RmWvub+N0UuH1Lo+TGfCgYwPvqpXNw==", + "version": "0.37.2", + "resolved": "https://registry.npmjs.org/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.2.tgz", + "integrity": "sha512-nEhsFoJurt8oUmieT8qy4nk81WRHmJynmVwn/Vts08PL9fhgIsMhk1GId5yAN643OzqEEb5S/6At2TZW7pqPDA==", "dev": true, "requires": { "@babel/core": ">=7.9.0" @@ -1200,9 +3192,9 @@ "dev": true }, "@types/glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-VgNIkxK+j7Nz5P7jvUZlRvhuPSmsEfS03b0alKcq5V/STUKAa3Plemsn5mrQUO7am6OErJ4rhGEGJbACclrtRA==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", "dev": true, "requires": { "@types/minimatch": "*", @@ -1228,9 +3220,9 @@ "dev": true }, "@types/node": { - "version": "14.0.14", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.14.tgz", - "integrity": "sha512-syUgf67ZQpaJj01/tRTknkMNoBBLWJOBODF0Zm4NrXmiSuxjymFrxnTu1QVYRubhVkRcZLYZG8STTwJRdVm/WQ==", + "version": "14.11.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.8.tgz", + "integrity": "sha512-KPcKqKm5UKDkaYPTuXSx8wEP7vE9GnuaXIZKijwRYcePpZFDVuy2a57LarFKiORbHOuTOOwYzxVxcUzsh2P2Pw==", "dev": true }, "@types/normalize-package-data": { @@ -1258,9 +3250,9 @@ "dev": true }, "@vue/component-compiler-utils": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-3.1.2.tgz", - "integrity": "sha512-QLq9z8m79mCinpaEeSURhnNCN6djxpHw0lpP/bodMlt5kALfONpryMthvnrQOlTcIKoF+VoPi+lPHUYeDFPXug==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-3.2.0.tgz", + "integrity": "sha512-lejBLa7xAMsfiZfNp7Kv51zOzifnb29FwdnMLa96z26kXErPFioSf9BMcePVIQ6/Gc6/mC0UrPpxAWIHyae0vw==", "dev": true, "requires": { "consolidate": "^0.15.1", @@ -1554,9 +3546,9 @@ } }, "aggregate-error": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz", - "integrity": "sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", "dev": true, "requires": { "clean-stack": "^2.0.0", @@ -1725,14 +3717,15 @@ "dev": true }, "asn1.js": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", - "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", "dev": true, "requires": { "bn.js": "^4.0.0", "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" }, "dependencies": { "bn.js": { @@ -2068,9 +4061,9 @@ "dev": true }, "bn.js": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.2.tgz", - "integrity": "sha512-40rZaf3bUNKTVYu9sIeeEGOg7g14Yvnj9kH7b50EiwX0Q7A6umbvfI5tvHaOERH0XigqKkfLkFQxzb4e6CIXnA==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz", + "integrity": "sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ==", "dev": true }, "body-parser": { @@ -2129,9 +4122,9 @@ "dev": true }, "bootstrap": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.5.0.tgz", - "integrity": "sha1-l9nby1qJcvhyLJliSDVDuQfZuew=", + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.5.3.tgz", + "integrity": "sha512-o9ppKQioXGqhw8Z7mah6KdTYpNQY//tipnkxppWhPbiSWdD+1raYsnhwEZjkTHYbGee4cVQ0Rx65EhOY/HNLcQ==", "dev": true }, "brace-expansion": { @@ -2241,16 +4234,16 @@ } }, "browserify-sign": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.0.tgz", - "integrity": "sha512-hEZC1KEeYuoHRqhGhTy6gWrpJA3ZDjFWv0DE61643ZnOXAKJb3u7yWcrU0mMc9SwAqK1n7myPGndkp0dFG7NFA==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", + "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", "dev": true, "requires": { "bn.js": "^5.1.1", "browserify-rsa": "^4.0.1", "create-hash": "^1.2.0", "create-hmac": "^1.1.7", - "elliptic": "^6.5.2", + "elliptic": "^6.5.3", "inherits": "^2.0.4", "parse-asn1": "^5.1.5", "readable-stream": "^3.6.0", @@ -2492,12 +4485,11 @@ }, "dependencies": { "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" } }, @@ -2523,9 +4515,9 @@ "dev": true }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -2570,9 +4562,9 @@ "dev": true }, "chokidar": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.0.tgz", - "integrity": "sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", + "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", "dev": true, "requires": { "anymatch": "~3.1.1", @@ -2582,7 +4574,7 @@ "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", - "readdirp": "~3.4.0" + "readdirp": "~3.5.0" }, "dependencies": { "anymatch": { @@ -2626,15 +4618,6 @@ "dev": true, "optional": true }, - "glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, "is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -2651,9 +4634,9 @@ "dev": true }, "readdirp": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", - "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", "dev": true, "requires": { "picomatch": "^2.2.1" @@ -2828,12 +4811,6 @@ } } }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true - }, "collapse-white-space": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.6.tgz", @@ -2841,9 +4818,9 @@ "dev": true }, "collect.js": { - "version": "4.27.3", - "resolved": "https://registry.npmjs.org/collect.js/-/collect.js-4.27.3.tgz", - "integrity": "sha512-2nmoyhUJbhjVVE0W9W0cSBeg8/PL3ObGe1ijj9WDlLG3RrpvePsBZd6p3uTm1dTAUKJVd3qT8mnH6iXCdENEHQ==", + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/collect.js/-/collect.js-4.28.2.tgz", + "integrity": "sha512-Ok2z0kuyywWJ4AtkeUI61pbSxCmaN5XYr/fkUYJP4bYk6Dz3NKH2FA8RhF7i3Do9Iq80MLRFWasSOpyE9X7hDA==", "dev": true }, "collection-visit": { @@ -2857,13 +4834,13 @@ } }, "color": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/color/-/color-3.1.2.tgz", - "integrity": "sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/color/-/color-3.1.3.tgz", + "integrity": "sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ==", "dev": true, "requires": { "color-convert": "^1.9.1", - "color-string": "^1.5.2" + "color-string": "^1.5.4" } }, "color-convert": { @@ -2882,9 +4859,9 @@ "dev": true }, "color-string": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", - "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.4.tgz", + "integrity": "sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw==", "dev": true, "requires": { "color-name": "^1.0.0", @@ -2898,9 +4875,9 @@ "dev": true }, "commander": { - "version": "2.17.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", - "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, "commondir": { @@ -2950,6 +4927,15 @@ } } }, + "concat": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/concat/-/concat-1.0.3.tgz", + "integrity": "sha1-QPM1MInWVGdpXLGIa0Xt1jfYzKg=", + "dev": true, + "requires": { + "commander": "^2.9.0" + } + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2968,15 +4954,6 @@ "typedarray": "^0.0.6" } }, - "concatenate": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/concatenate/-/concatenate-0.0.2.tgz", - "integrity": "sha1-C0nW6MQQR9dyjNyNYqCGYjOXtJ8=", - "dev": true, - "requires": { - "globs": "^0.1.2" - } - }, "connect-history-api-fallback": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", @@ -3115,13 +5092,13 @@ } }, "create-ecdh": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", - "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", "dev": true, "requires": { "bn.js": "^4.1.0", - "elliptic": "^6.0.0" + "elliptic": "^6.5.3" }, "dependencies": { "bn.js": { @@ -3315,14 +5292,13 @@ "dev": true }, "css-selector-tokenizer": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.2.tgz", - "integrity": "sha512-yj856NGuAymN6r8bn8/Jl46pR+OC3eEvAhfGYDUe7YPtTPAYrSSw4oAniZ9Y8T5B92hjhwTBLUen0/vKPxf6pw==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.3.tgz", + "integrity": "sha512-jWQv3oCEL5kMErj4wRnK/OPoBi0D+P1FR2cDCKYPaMeD2eW3/mttav8HT4hT1CKopiJI/psEULjkClhvJo4Lvg==", "dev": true, "requires": { "cssesc": "^3.0.0", - "fastparse": "^1.1.2", - "regexpu-core": "^4.6.0" + "fastparse": "^1.1.2" } }, "css-tree": { @@ -3344,9 +5320,9 @@ } }, "css-what": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.3.0.tgz", - "integrity": "sha512-pv9JPyatiPaQ6pf4OvD/dbfm0o5LviWmwxNWzblYf/1u9QZd0ihV+PMwy5jdQWQ3349kZmKEx9WXuSka2dM4cg==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.1.tgz", + "integrity": "sha512-wHOppVDKl4vTAOWzJt5Ek37Sgd9qq1Bmj/T1OjvicWbU5W7ru7Pqbn0Jdqii3Drx/h+dixHKXNhZYx7blthL7g==", "dev": true }, "cssesc": { @@ -3793,9 +5769,9 @@ } }, "dot-prop": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", - "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", "dev": true, "requires": { "is-obj": "^2.0.0" @@ -3888,9 +5864,9 @@ } }, "enhanced-resolve": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.2.0.tgz", - "integrity": "sha512-S7eiFb/erugyd1rLb6mQ3Vuq+EXHv5cpCkNqqIkYkBgN2QdFnyCZzFBleqwGEx4lgNGYij81BWnCrFNK7vxvjQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz", + "integrity": "sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ==", "dev": true, "requires": { "graceful-fs": "^4.1.2", @@ -3944,20 +5920,21 @@ } }, "es-abstract": { - "version": "1.17.6", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", - "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "version": "1.18.0-next.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", + "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", "dev": true, "requires": { "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", "has": "^1.0.3", "has-symbols": "^1.0.1", - "is-callable": "^1.2.0", - "is-regex": "^1.1.0", - "object-inspect": "^1.7.0", + "is-callable": "^1.2.2", + "is-negative-zero": "^2.0.0", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", "object-keys": "^1.1.1", - "object.assign": "^4.1.0", + "object.assign": "^4.1.1", "string.prototype.trimend": "^1.0.1", "string.prototype.trimstart": "^1.0.1" } @@ -4246,15 +6223,15 @@ "dev": true }, "eventemitter3": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz", - "integrity": "sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", "dev": true }, "events": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.1.0.tgz", - "integrity": "sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz", + "integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==", "dev": true }, "eventsource": { @@ -4642,6 +6619,12 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "fastest-levenshtein": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", + "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==", + "dev": true + }, "fastparse": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", @@ -5188,15 +7171,6 @@ "integrity": "sha1-L0SUrIkZ43Z8XLtpHp9GMyQoXUM=", "dev": true }, - "globs": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/globs/-/globs-0.1.4.tgz", - "integrity": "sha512-D23dWbOq48vlOraoSigbcQV4tWrnhwk+E/Um2cMuDS3/5dwGmdFeA7L/vAvDhLFlQOTDqHcXh35m/71g2A2WzQ==", - "dev": true, - "requires": { - "glob": "^7.1.1" - } - }, "gonzales-pe": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/gonzales-pe/-/gonzales-pe-4.3.0.tgz", @@ -5452,6 +7426,14 @@ "param-case": "2.1.x", "relateurl": "0.2.x", "uglify-js": "3.4.x" + }, + "dependencies": { + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "dev": true + } } }, "html-tags": { @@ -5655,9 +7637,9 @@ } }, "img-loader": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/img-loader/-/img-loader-3.0.1.tgz", - "integrity": "sha512-0jDJqexgzOuq3zlXwFTBKJlMcaP1uXyl5t4Qu6b1IgXb3IwBDjPfVylBC8vHFIIESDw/S+5QkBbtBrt4T8wESA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/img-loader/-/img-loader-3.0.2.tgz", + "integrity": "sha512-rSriLKgvi85Km7ppSF+AEAM3nU4fxpvCkaXtC/IoCEU7jfks55bEANFs0bB9YXYkxY9JurZQIZFtXh5Gue3upw==", "dev": true, "requires": { "loader-utils": "^1.1.0" @@ -5882,12 +7864,6 @@ "loose-envify": "^1.0.0" } }, - "invert-kv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", - "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", - "dev": true - }, "ip": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", @@ -5988,9 +7964,9 @@ "dev": true }, "is-callable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", - "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", + "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==", "dev": true }, "is-color-stop": { @@ -6106,6 +8082,12 @@ "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", "dev": true }, + "is-negative-zero": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz", + "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=", + "dev": true + }, "is-number": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", @@ -6178,9 +8160,9 @@ } }, "is-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz", - "integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", "dev": true, "requires": { "has-symbols": "^1.0.1" @@ -6287,9 +8269,9 @@ "dev": true }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -6331,6 +8313,12 @@ "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", "dev": true }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -6386,9 +8374,9 @@ "dev": true }, "laravel-mix": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/laravel-mix/-/laravel-mix-5.0.4.tgz", - "integrity": "sha512-/fkcMdlxhGDBcH+kFDqKONlAfhJinMAWd+fjQ+VLii4UzIeXUF5Q8FbS4+ZrZs9JO3Y1E4KoNq3hMw0t/soahA==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/laravel-mix/-/laravel-mix-5.0.6.tgz", + "integrity": "sha512-yeX23rnpUUs7LXZlDMQMh2yg5nOkizlvUjwir8d54yhIqDec7Btnkg935Pxeo81kq/JONssHEaVPrwp1/TsPsQ==", "dev": true, "requires": { "@babel/core": "^7.2.0", @@ -6403,7 +8391,7 @@ "chokidar": "^2.0.3", "clean-css": "^4.1.3", "collect.js": "^4.12.8", - "concatenate": "0.0.2", + "concat": "^1.0.3", "css-loader": "^1.0.1", "dotenv": "^6.2.0", "dotenv-expand": "^4.2.0", @@ -6428,7 +8416,7 @@ "webpack-dev-server": "^3.1.14", "webpack-merge": "^4.1.0", "webpack-notifier": "^1.5.1", - "yargs": "^12.0.5" + "yargs": "^15.4.1" }, "dependencies": { "chokidar": { @@ -6484,15 +8472,6 @@ "webpack-sources": "^1.1.0" } }, - "lcid": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", - "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", - "dev": true, - "requires": { - "invert-kv": "^2.0.0" - } - }, "leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -6563,9 +8542,9 @@ } }, "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", "dev": true }, "lodash._baseassign": { @@ -6676,9 +8655,9 @@ } }, "loglevel": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.8.tgz", - "integrity": "sha512-bsU7+gc9AJ2SqpzxwU3+1fedl8zAntbtC5XYlt3s2j1hJcn2PsXSmgN8TaLG/J1/2mod4+cE/3vNL70/c1RNCA==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.7.0.tgz", + "integrity": "sha512-i2sY04nal5jDcagM3FMfG++T69GEEM8CYuOfeOIvmXzOIcwE9a/CJPR0MFM97pYMj/u10lzz7/zd7+qwhrBTqQ==", "dev": true }, "longest-streak": { @@ -6737,15 +8716,6 @@ } } }, - "map-age-cleaner": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", - "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", - "dev": true, - "requires": { - "p-defer": "^1.0.0" - } - }, "map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", @@ -6789,14 +8759,14 @@ "dev": true }, "md5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz", - "integrity": "sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", + "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", "dev": true, "requires": { - "charenc": "~0.0.1", - "crypt": "~0.0.1", - "is-buffer": "~1.1.1" + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "~1.1.6" }, "dependencies": { "is-buffer": { @@ -6839,17 +8809,6 @@ "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", "dev": true }, - "mem": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", - "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", - "dev": true, - "requires": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^2.0.0", - "p-is-promise": "^2.0.0" - } - }, "memory-fs": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", @@ -6861,18 +8820,16 @@ } }, "meow": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-7.0.1.tgz", - "integrity": "sha512-tBKIQqVrAHqwit0vfuFPY3LlzJYkEOFyKa3bPgxzNl6q/RtN8KQ+ALYEASYuFayzSAsjlhXj/JZ10rH85Q6TUw==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-7.1.1.tgz", + "integrity": "sha512-GWHvA5QOcS412WCo8vwKDlTelGLsCGBVevQB5Kva961rmNfun0PCbv5+xta2kUMFJyR8/oWnn7ddeKdosbAPbA==", "dev": true, "requires": { "@types/minimist": "^1.2.0", - "arrify": "^2.0.1", - "camelcase": "^6.0.0", "camelcase-keys": "^6.2.2", "decamelize-keys": "^1.1.0", "hard-rejection": "^2.1.0", - "minimist-options": "^4.0.2", + "minimist-options": "4.1.0", "normalize-package-data": "^2.5.0", "read-pkg-up": "^7.0.1", "redent": "^3.0.0", @@ -6881,18 +8838,6 @@ "yargs-parser": "^18.1.3" }, "dependencies": { - "arrify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", - "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", - "dev": true - }, - "camelcase": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.0.0.tgz", - "integrity": "sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==", - "dev": true - }, "type-fest": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", @@ -6907,14 +8852,6 @@ "requires": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" - }, - "dependencies": { - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - } } } } @@ -7106,9 +9043,9 @@ } }, "minipass-pipeline": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.3.tgz", - "integrity": "sha512-cFOknTvng5vqnwOpDsZTWhNll6Jf8o2x+/diplafmxpuIymAjzoOolZG0VvQf3V2HgqzJNhnuKHYp2BqDgz8IQ==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", "dev": true, "requires": { "minipass": "^3.0.0" @@ -7253,9 +9190,9 @@ } }, "node-forge": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.0.tgz", - "integrity": "sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", + "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", "dev": true }, "node-libs-browser": { @@ -7409,12 +9346,6 @@ "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", "dev": true }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true - }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -7465,13 +9396,13 @@ "dev": true }, "object-is": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.2.tgz", - "integrity": "sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.3.tgz", + "integrity": "sha512-teyqLvFWzLkq5B9ki8FVWA902UER2qkxmdA4nLf+wjOLAWgxzCWZNCxpDq9MvE8MmhWNr+I8w3BN49Vx36Y6Xg==", "dev": true, "requires": { "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" + "es-abstract": "^1.18.0-next.1" } }, "object-keys": { @@ -7496,15 +9427,15 @@ } }, "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.1.tgz", + "integrity": "sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA==", "dev": true, "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.0", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" } }, "object.getownpropertydescriptors": { @@ -7515,6 +9446,27 @@ "requires": { "define-properties": "^1.1.3", "es-abstract": "^1.17.0-next.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } } }, "object.omit": { @@ -7545,6 +9497,27 @@ "es-abstract": "^1.17.0-next.1", "function-bind": "^1.1.1", "has": "^1.0.3" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } } }, "obuf": { @@ -7596,9 +9569,9 @@ } }, "optimize-css-assets-webpack-plugin": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.3.tgz", - "integrity": "sha512-q9fbvCRS6EYtUKKSwI87qm2IxlyJK5b4dygW1rKUBT6mMDhdG5e5bZT63v6tnJR9F9FB/H5a0HTmtw+laUBxKA==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.4.tgz", + "integrity": "sha512-wqd6FdI2a5/FdoiCNNkEvLeA//lHHfG24Ln2Xm2qqdIk4aOlsR18jwpyOihqQ8849W3qu2DX8fOYxpvTMj+93A==", "dev": true, "requires": { "cssnano": "^4.1.10", @@ -7634,41 +9607,18 @@ "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", "dev": true }, - "os-locale": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", - "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", - "dev": true, - "requires": { - "execa": "^1.0.0", - "lcid": "^2.0.0", - "mem": "^4.0.0" - } - }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true }, - "p-defer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", - "dev": true - }, "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", "dev": true }, - "p-is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", - "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", - "dev": true - }, "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -7753,14 +9703,13 @@ } }, "parse-asn1": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz", - "integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", + "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", "dev": true, "requires": { - "asn1.js": "^4.0.0", + "asn1.js": "^5.2.0", "browserify-aes": "^1.0.0", - "create-hash": "^1.1.0", "evp_bytestokey": "^1.0.0", "pbkdf2": "^3.0.3", "safe-buffer": "^5.1.1" @@ -7929,14 +9878,14 @@ "dev": true }, "portfinder": { - "version": "1.0.26", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.26.tgz", - "integrity": "sha512-Xi7mKxJHHMI3rIUrnm/jjUgwhbYMkp/XKEcZX3aG4BrumLpq3nmoQMX+ClYnDZnZ/New7IatC1no5RX0zo1vXQ==", + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", + "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", "dev": true, "requires": { "async": "^2.6.2", "debug": "^3.1.1", - "mkdirp": "^0.5.1" + "mkdirp": "^0.5.5" }, "dependencies": { "debug": { @@ -8013,9 +9962,9 @@ } }, "postcss-calc": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.2.tgz", - "integrity": "sha512-rofZFHUg6ZIrvRwPeFktv06GdbDYLcGqh9EwiMutZg+a0oePCCw1zHOEiji6LCpyRcjTREtPASuUqeAvYlEVvQ==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.5.tgz", + "integrity": "sha512-1tKHutbGtLtEZF6PT4JSihCHfIVldU72mZ8SdZHIYriIZ9fh9k9aWSppaT8rHsyI3dX+KSR+W+Ix9BMY3AODrg==", "dev": true, "requires": { "postcss": "^7.0.27", @@ -8117,9 +10066,9 @@ } }, "postcss-load-config": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.0.tgz", - "integrity": "sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.2.tgz", + "integrity": "sha512-/rDeGV6vMUo3mwJZmeHfEDvwnTKKqQ0S7OHUi/kJvvtx3aWtyWG2/0ZWnzCt2keEclwN6Tf0DST2v9kITdOKYw==", "dev": true, "requires": { "cosmiconfig": "^5.0.0", @@ -8662,40 +10611,6 @@ } } }, - "postcss-reporter": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-6.0.1.tgz", - "integrity": "sha512-LpmQjfRWyabc+fRygxZjpRxfhRf9u/fdlKf4VHG4TSPbV2XNsuISzYW1KL+1aQzx53CAppa1bKG4APIB/DOXXw==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "lodash": "^4.17.11", - "log-symbols": "^2.2.0", - "postcss": "^7.0.7" - }, - "dependencies": { - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "log-symbols": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", - "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", - "dev": true, - "requires": { - "chalk": "^2.0.1" - } - } - } - }, "postcss-resolve-nested-selector": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz", @@ -8935,9 +10850,9 @@ "dev": true }, "querystringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", - "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", "dev": true }, "quick-lru": { @@ -9004,14 +10919,14 @@ }, "dependencies": { "parse-json": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", - "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", + "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1", + "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, @@ -9142,19 +11057,18 @@ } }, "regenerator-runtime": { - "version": "0.13.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", - "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==", + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", "dev": true }, "regenerator-transform": { - "version": "0.14.4", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.4.tgz", - "integrity": "sha512-EaJaKPBI9GvKpvUz2mz4fhx7WPgvwRLY9v3hlNHWmAuJHI13T4nwKnNvm5RWJzEdnI5g5UwtOww+S8IdoUC2bw==", + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", + "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", "dev": true, "requires": { - "@babel/runtime": "^7.8.4", - "private": "^0.1.8" + "@babel/runtime": "^7.8.4" } }, "regex-not": { @@ -9181,6 +11095,27 @@ "requires": { "define-properties": "^1.1.3", "es-abstract": "^1.17.0-next.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } } }, "regexpp": { @@ -9190,9 +11125,9 @@ "dev": true }, "regexpu-core": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.0.tgz", - "integrity": "sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ==", + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.1.tgz", + "integrity": "sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ==", "dev": true, "requires": { "regenerate": "^1.4.0", @@ -9233,9 +11168,9 @@ "dev": true }, "remark": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/remark/-/remark-12.0.0.tgz", - "integrity": "sha512-oX4lMIS0csgk8AEbzY0h2jdR0ngiCHOpwwpxjmRa5TqAkeknY+tkhjRJGZqnCmvyuWh55/0SW5WY3R3nn3PH9A==", + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/remark/-/remark-12.0.1.tgz", + "integrity": "sha512-gS7HDonkdIaHmmP/+shCPejCEEW+liMp/t/QwmF0Xt47Rpuhl32lLtDV1uKWvGoq+kxr5jSgg5oAIpGuyULjUw==", "dev": true, "requires": { "remark-parse": "^8.0.0", @@ -9244,9 +11179,9 @@ } }, "remark-parse": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-8.0.2.tgz", - "integrity": "sha512-eMI6kMRjsAGpMXXBAywJwiwAse+KNpmt+BK55Oofy4KvBZEqUDj6mWbGLJZrujoPIPPxDXzn3T9baRlpsm2jnQ==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-8.0.3.tgz", + "integrity": "sha512-E1K9+QLGgggHxCQtLt++uXltxEprmWzNfg+MxpfHsZlrddKzZ/hZyWHDbK3/Ap8HJQqYJRXP+jHczdL6q6i85Q==", "dev": true, "requires": { "ccount": "^1.0.0", @@ -9268,9 +11203,9 @@ } }, "remark-stringify": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-8.1.0.tgz", - "integrity": "sha512-FSPZv1ds76oAZjurhhuV5qXSUSoz6QRPuwYK38S41sLHwg4oB7ejnmZshj7qwjgYLf93kdz6BOX9j5aidNE7rA==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-8.1.1.tgz", + "integrity": "sha512-q4EyPZT3PcA3Eq7vPpT6bIdokXzFGp9i85igjmhRyXWmPs0Y6/d2FYwUNotKAWyLch7g0ASZJn/KHHcHZQ163A==", "dev": true, "requires": { "ccount": "^1.0.0", @@ -9552,9 +11487,9 @@ "dev": true }, "sass": { - "version": "1.26.9", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.26.9.tgz", - "integrity": "sha512-t8AkRVi+xvba4yZiLWkJdgJHBFCB3Dh4johniQkPy9ywkgFHNasXFEFP+RG/F6LhQ+aoE4aX+IorIWQjS0esVw==", + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.27.0.tgz", + "integrity": "sha512-0gcrER56OkzotK/GGwgg4fPrKuiFlPNitO7eUJ18Bs+/NBlofJfMxmxqpqJxjae9vu0Wq8TZzrSyxZal00WDig==", "dev": true, "requires": { "chokidar": ">=2.0.0 <4.0.0" @@ -9605,12 +11540,12 @@ "dev": true }, "selfsigned": { - "version": "1.10.7", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.7.tgz", - "integrity": "sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA==", + "version": "1.10.8", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.8.tgz", + "integrity": "sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w==", "dev": true, "requires": { - "node-forge": "0.9.0" + "node-forge": "^0.10.0" } }, "semver": { @@ -9666,9 +11601,9 @@ } }, "serialize-javascript": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.1.0.tgz", - "integrity": "sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", "dev": true, "requires": { "randombytes": "^2.1.0" @@ -10118,9 +12053,9 @@ } }, "spdx-license-ids": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", - "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.6.tgz", + "integrity": "sha512-+orQK83kyMva3WyPf59k1+Y525csj5JejicWut55zeTWANuN17qSiSLUXWtzHeNWORSvT7GLDJ/E/XiIWoXBTw==", "dev": true }, "spdy": { @@ -10137,12 +12072,12 @@ }, "dependencies": { "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "ms": { @@ -10168,12 +12103,12 @@ }, "dependencies": { "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "ms": { @@ -10346,6 +12281,27 @@ "requires": { "define-properties": "^1.1.3", "es-abstract": "^1.17.5" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } } }, "string.prototype.trimstart": { @@ -10356,6 +12312,27 @@ "requires": { "define-properties": "^1.1.3", "es-abstract": "^1.17.5" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } } }, "string_decoder": { @@ -10464,19 +12441,21 @@ } }, "stylelint": { - "version": "13.6.1", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-13.6.1.tgz", - "integrity": "sha512-XyvKyNE7eyrqkuZ85Citd/Uv3ljGiuYHC6UiztTR6sWS9rza8j3UeQv/eGcQS9NZz/imiC4GKdk1EVL3wst5vw==", + "version": "13.7.2", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-13.7.2.tgz", + "integrity": "sha512-mmieorkfmO+ZA6CNDu1ic9qpt4tFvH2QUB7vqXgrMVHe5ENU69q7YDq0YUg/UHLuCsZOWhUAvcMcLzLDIERzSg==", "dev": true, "requires": { - "@stylelint/postcss-css-in-js": "^0.37.1", + "@stylelint/postcss-css-in-js": "^0.37.2", "@stylelint/postcss-markdown": "^0.36.1", - "autoprefixer": "^9.8.0", + "autoprefixer": "^9.8.6", "balanced-match": "^1.0.0", "chalk": "^4.1.0", - "cosmiconfig": "^6.0.0", + "cosmiconfig": "^7.0.0", "debug": "^4.1.1", "execall": "^2.0.0", + "fast-glob": "^3.2.4", + "fastest-levenshtein": "^1.0.12", "file-entry-cache": "^5.0.1", "get-stdin": "^8.0.0", "global-modules": "^2.0.0", @@ -10487,18 +12466,16 @@ "import-lazy": "^4.0.0", "imurmurhash": "^0.1.4", "known-css-properties": "^0.19.0", - "leven": "^3.1.0", - "lodash": "^4.17.15", + "lodash": "^4.17.20", "log-symbols": "^4.0.0", "mathml-tag-names": "^2.1.3", - "meow": "^7.0.1", + "meow": "^7.1.1", "micromatch": "^4.0.2", "normalize-selector": "^0.2.0", "postcss": "^7.0.32", "postcss-html": "^0.36.0", "postcss-less": "^3.1.4", "postcss-media-query-parser": "^0.2.3", - "postcss-reporter": "^6.0.1", "postcss-resolve-nested-selector": "^0.1.1", "postcss-safe-parser": "^4.0.2", "postcss-sass": "^0.4.4", @@ -10514,7 +12491,7 @@ "style-search": "^0.1.0", "sugarss": "^2.0.0", "svg-tags": "^1.0.0", - "table": "^5.4.6", + "table": "^6.0.1", "v8-compile-cache": "^2.1.1", "write-file-atomic": "^3.0.3" }, @@ -10525,12 +12502,54 @@ "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==", "dev": true }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, "array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, + "autoprefixer": { + "version": "9.8.6", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.6.tgz", + "integrity": "sha512-XrvP4VVHdRBCdX1S3WXVD8+RyG9qeb1D5Sn1DeLiG2xfSpzellk5k54xbUERJ3M5DggQxes39UGOTP8CFrEGbg==", + "dev": true, + "requires": { + "browserslist": "^4.12.0", + "caniuse-lite": "^1.0.30001109", + "colorette": "^1.2.1", + "normalize-range": "^0.1.2", + "num2fraction": "^1.2.2", + "postcss": "^7.0.32", + "postcss-value-parser": "^4.1.0" + } + }, "braces": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", @@ -10540,26 +12559,53 @@ "fill-range": "^7.0.1" } }, + "caniuse-lite": { + "version": "1.0.30001148", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001148.tgz", + "integrity": "sha512-E66qcd0KMKZHNJQt9hiLZGE3J4zuTqE1OnU53miEVtylFbwOEmeA5OsRu90noZful+XGSQOni1aT2tiqu/9yYw==", + "dev": true + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "colorette": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz", + "integrity": "sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==", + "dev": true + }, "cosmiconfig": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", - "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", + "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", "dev": true, "requires": { "@types/parse-json": "^4.0.0", - "import-fresh": "^3.1.0", + "import-fresh": "^3.2.1", "parse-json": "^5.0.0", "path-type": "^4.0.0", - "yaml": "^1.7.2" + "yaml": "^1.10.0" } }, "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "dir-glob": { @@ -10649,14 +12695,14 @@ "dev": true }, "parse-json": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", - "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", + "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1", + "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, @@ -10678,6 +12724,17 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + } + }, "string-width": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", @@ -10689,6 +12746,18 @@ "strip-ansi": "^6.0.0" } }, + "table": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/table/-/table-6.0.3.tgz", + "integrity": "sha512-8321ZMcf1B9HvVX/btKv8mMZahCjn2aYrDlpqHaBFCfnox64edeH9kEid0vTLTRR8gWR2A20aDgeuTTea4sVtw==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "lodash": "^4.17.20", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.0" + } + }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -10802,12 +12871,6 @@ "source-map-support": "~0.5.10" }, "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -10817,9 +12880,9 @@ } }, "terser-webpack-plugin": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-2.3.7.tgz", - "integrity": "sha512-xzYyaHUNhzgaAdBsXxk2Yvo/x1NJdslUaussK3fdpBbvttm1iIwU+c26dj9UxJcwk2c5UWt5F55MUTIA8BE7Dg==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-2.3.8.tgz", + "integrity": "sha512-/fKw3R+hWyHfYx7Bv6oPqmk4HGQcrWLtV3X6ggvPuwPNHSnzvVV51z6OaaCOus4YLjutYGOz3pEpbhe6Up2s1w==", "dev": true, "requires": { "cacache": "^13.0.1", @@ -10827,18 +12890,12 @@ "jest-worker": "^25.4.0", "p-limit": "^2.3.0", "schema-utils": "^2.6.6", - "serialize-javascript": "^3.1.0", + "serialize-javascript": "^4.0.0", "source-map": "^0.6.1", "terser": "^4.6.12", "webpack-sources": "^1.4.3" }, "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, "find-cache-dir": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", @@ -10902,12 +12959,6 @@ "find-up": "^4.0.0" } }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -11184,9 +13235,9 @@ "dev": true }, "unified": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/unified/-/unified-9.0.0.tgz", - "integrity": "sha512-ssFo33gljU3PdlWLjNp15Inqb77d6JnJSfyplGJPT/a+fNRNyCBeveBAYJdO5khKdF6WVHa/yYCC7Xl6BDwZUQ==", + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/unified/-/unified-9.2.0.tgz", + "integrity": "sha512-vx2Z0vY+a3YoTj8+pttM3tiJHCwY5UFbYdiWrwBEbHmK8pvsPj2rtAX2BFfgXen8T39CJWblWRDT4L5WGXtDdg==", "dev": true, "requires": { "bail": "^1.0.0", @@ -11281,9 +13332,9 @@ } }, "unist-util-visit": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.2.tgz", - "integrity": "sha512-HoHNhGnKj6y+Sq+7ASo2zpVdfdRifhTgX2KTU3B/sO/TTlZchp7E3S4vjRzDJ7L60KmrCPsQkVK3lEF3cz36XQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz", + "integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==", "dev": true, "requires": { "@types/unist": "^2.0.0", @@ -11292,9 +13343,9 @@ } }, "unist-util-visit-parents": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.0.2.tgz", - "integrity": "sha512-yJEfuZtzFpQmg1OSCyS9M5NJRrln/9FbYosH3iW0MG402QbdbaB8ZESwUv9RO6nRfLAKvWcMxCwdLWOov36x/g==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.0.tgz", + "integrity": "sha512-0g4wbluTF93npyPrp/ymd3tCDTMnP0yo2akFD2FIBAYXq/Sga3lwaU1D8OYKbtpioaI6CkDcQ6fsMnmtzt7htw==", "dev": true, "requires": { "@types/unist": "^2.0.0", @@ -11453,6 +13504,27 @@ "es-abstract": "^1.17.2", "has-symbols": "^1.0.1", "object.getownpropertydescriptors": "^2.1.0" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } } }, "utils-merge": { @@ -11496,9 +13568,9 @@ "dev": true }, "vfile": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.1.1.tgz", - "integrity": "sha512-lRjkpyDGjVlBA7cDQhQ+gNcvB1BGaTHYuSOcY3S7OhDmBtnzX95FhtZZDecSTDm6aajFymyve6S5DN4ZHGezdQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.2.0.tgz", + "integrity": "sha512-a/alcwCvtuc8OX92rqqo7PflxiCgXRFjdyoGVuYV+qbgCb0GgZJRvIgCD4+U/Kl1yhaRsaTwksF88xbPyGsgpw==", "dev": true, "requires": { "@types/unist": "^2.0.0", @@ -11517,9 +13589,9 @@ } }, "vfile-location": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-3.0.1.tgz", - "integrity": "sha512-yYBO06eeN/Ki6Kh1QAkgzYpWT1d3Qln+ZCtSbJqFExPl1S3y2qqotJQXoh6qEvl/jDlgpUJolBn3PItVnnZRqQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-3.1.0.tgz", + "integrity": "sha512-FCZ4AN9xMcjFIG1oGmZKo61PjwJHRVA+0/tPUP2ul4uIwjGGndIxavEMRpWn5p4xwm/ZsdXp9YNygf1ZyE4x8g==", "dev": true }, "vfile-message": { @@ -11539,9 +13611,9 @@ "dev": true }, "vue": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.11.tgz", - "integrity": "sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ==", + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.12.tgz", + "integrity": "sha512-uhmLFETqPPNyuLLbsKz6ioJ4q7AZHzD8ZVFNATNyICSZouqP2Sz0rotWQC8UNBF6VGSCs5abnKJoStA6JbCbfg==", "dev": true }, "vue-eslint-parser": { @@ -11606,9 +13678,9 @@ } }, "vue-router": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.3.4.tgz", - "integrity": "sha512-SdKRBeoXUjaZ9R/8AyxsdTqkOfMcI5tWxPZOUX5Ie1BTL5rPSZ0O++pbiZCeYeythiZIdLEfkDiQPKIaWk5hDg==", + "version": "3.4.6", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.4.6.tgz", + "integrity": "sha512-kaXnB3pfFxhAJl/Mp+XG1HJMyFqrL/xPqV7oXlpXn4AwMmm6VNgf0nllW8ksflmZANfI4kdo0bVn/FYSsAolPQ==", "dev": true }, "vue-style-loader": { @@ -11622,9 +13694,9 @@ } }, "vue-template-compiler": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.11.tgz", - "integrity": "sha512-KIq15bvQDrcCjpGjrAhx4mUlyyHfdmTaoNfeoATHLAiWB+MU3cx4lOzMwrnUh9cCxy0Lt1T11hAFY6TQgroUAA==", + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.12.tgz", + "integrity": "sha512-OzzZ52zS41YUbkCBfdXShQTe69j1gQDZ9HIX8miuC9C3rBCk9wIRjLiZZLrmX9V+Ftq/YEyv1JaVr5Y/hNtByg==", "dev": true, "requires": { "de-indent": "^1.0.2", @@ -11644,15 +13716,116 @@ "dev": true }, "watchpack": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.2.tgz", - "integrity": "sha512-ymVbbQP40MFTp+cNMvpyBpBtygHnPzPkHqoIwRRj/0B8KhqQwV8LaKjtbaxF2lK4vl8zN9wCxS46IFCU5K4W0g==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.4.tgz", + "integrity": "sha512-aWAgTW4MoSJzZPAicljkO1hsi1oKj/RRq/OJQh2PKI2UKL04c2Bs+MBOB+BBABHTXJpf9mCwHN7ANCvYsvY2sg==", "dev": true, "requires": { - "chokidar": "^3.4.0", + "chokidar": "^3.4.1", "graceful-fs": "^4.1.2", "neo-async": "^2.5.0", "watchpack-chokidar2": "^2.0.0" + }, + "dependencies": { + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "optional": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "binary-extensions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", + "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", + "dev": true, + "optional": true + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "optional": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chokidar": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz", + "integrity": "sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==", + "dev": true, + "optional": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.4.0" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "optional": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "optional": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "optional": true + }, + "readdirp": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", + "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", + "dev": true, + "optional": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "optional": true, + "requires": { + "is-number": "^7.0.0" + } + } } }, "watchpack-chokidar2": { @@ -11721,9 +13894,9 @@ } }, "webpack": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.43.0.tgz", - "integrity": "sha512-GW1LjnPipFW2Y78OOab8NJlCflB7EFskMih2AHdvjbpKMeDJqEgSx24cXXXiPS65+WSwVyxtDsJH6jGX2czy+g==", + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.44.2.tgz", + "integrity": "sha512-6KJVGlCxYdISyurpQ0IPTklv+DULv05rs2hseIXer6D7KrUicRDLFb4IUM1S6LUAKypPM/nSiVSuv8jHu1m3/Q==", "dev": true, "requires": { "@webassemblyjs/ast": "1.9.0", @@ -11734,7 +13907,7 @@ "ajv": "^6.10.2", "ajv-keywords": "^3.4.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^4.1.0", + "enhanced-resolve": "^4.3.0", "eslint-scope": "^4.0.3", "json-parse-better-errors": "^1.0.2", "loader-runner": "^2.4.0", @@ -11747,14 +13920,14 @@ "schema-utils": "^1.0.0", "tapable": "^1.1.3", "terser-webpack-plugin": "^1.4.3", - "watchpack": "^1.6.1", + "watchpack": "^1.7.4", "webpack-sources": "^1.4.1" }, "dependencies": { "acorn": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", - "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", "dev": true }, "cacache": { @@ -11780,12 +13953,6 @@ "y18n": "^4.0.0" } }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, "eslint-scope": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", @@ -11834,16 +14001,16 @@ } }, "terser-webpack-plugin": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.4.tgz", - "integrity": "sha512-U4mACBHIegmfoEe5fdongHESNJWqsGU+W0S/9+BmYGVQDw1+c2Ow05TpMhxjPK1sRb7cuYq1BPl1e5YHJMTCqA==", + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz", + "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==", "dev": true, "requires": { "cacache": "^12.0.2", "find-cache-dir": "^2.1.0", "is-wsl": "^1.1.0", "schema-utils": "^1.0.0", - "serialize-javascript": "^3.1.0", + "serialize-javascript": "^4.0.0", "source-map": "^0.6.1", "terser": "^4.1.2", "webpack-sources": "^1.4.0", @@ -12086,12 +14253,12 @@ } }, "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "glob-parent": { @@ -12138,12 +14305,6 @@ "ajv-keywords": "^3.1.0" } }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", @@ -12381,124 +14542,131 @@ "dev": true }, "yargs": { - "version": "12.0.5", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", - "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", "dev": true, "requires": { - "cliui": "^4.0.0", + "cliui": "^6.0.0", "decamelize": "^1.2.0", - "find-up": "^3.0.0", - "get-caller-file": "^1.0.1", - "os-locale": "^3.0.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", + "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", - "string-width": "^2.0.0", + "string-width": "^4.2.0", "which-module": "^2.0.0", - "y18n": "^3.2.1 || ^4.0.0", - "yargs-parser": "^11.1.1" + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" }, "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } }, "cliui": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", - "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" + "color-name": "~1.1.4" } }, - "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" } }, - "strip-ansi": { + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "path-exists": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" } }, "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - } + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" } }, "yargs-parser": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", - "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", "dev": true, "requires": { "camelcase": "^5.0.0", diff --git a/src/package.json b/src/package.json --- a/src/package.json +++ b/src/package.json @@ -11,27 +11,27 @@ "lint": "eslint --ext .js,.vue resources && stylelint \"resources/sass/*.scss\" \"resources/vue/*.vue\"" }, "devDependencies": { - "@fortawesome/fontawesome-svg-core": "^1.2.29", - "@fortawesome/free-brands-svg-icons": "^5.13.1", - "@fortawesome/free-regular-svg-icons": "^5.13.1", - "@fortawesome/free-solid-svg-icons": "^5.13.1", + "@fortawesome/fontawesome-svg-core": "^1.2.32", + "@fortawesome/free-brands-svg-icons": "^5.15.1", + "@fortawesome/free-regular-svg-icons": "^5.15.1", + "@fortawesome/free-solid-svg-icons": "^5.15.1", "@fortawesome/vue-fontawesome": "^0.1.10", "axios": "^0.19", - "bootstrap": "^4.5.0", + "bootstrap": "^4.5.3", "cross-env": "^7.0", "eslint": "^6.8.0", "eslint-plugin-vue": "^6.2.2", "jquery": "^3.5.1", - "laravel-mix": "^5.0.4", + "laravel-mix": "^5.0.6", "popper.js": "^1.16.0", "resolve-url-loader": "^2.3.1", - "sass": "^1.26.9", + "sass": "^1.27.0", "sass-loader": "^8.0.0", - "stylelint": "^13.6.1", + "stylelint": "^13.7.2", "stylelint-config-standard": "^20.0.0", - "vue": "^2.6.11", - "vue-router": "^3.3.4", - "vue-template-compiler": "^2.6.11", + "vue": "^2.6.12", + "vue-router": "^3.4.6", + "vue-template-compiler": "^2.6.12", "vuex": "^3.4.0" } } diff --git a/src/phpunit.xml b/src/phpunit.xml --- a/src/phpunit.xml +++ b/src/phpunit.xml @@ -13,6 +13,10 @@ tests/Unit + + tests/Functional + + tests/Feature diff --git a/src/resources/countries.php b/src/resources/countries.php --- a/src/resources/countries.php +++ b/src/resources/countries.php @@ -1,6 +1,6 @@ ['AFN','Afghanistan'], - 'AX' => ['EUR','Åland Islands'], + 'AX' => ['EUR','Aland Islands'], 'AL' => ['ALL','Albania'], 'DZ' => ['DZD','Algeria'], 'AS' => ['USD','American Samoa'], @@ -14,60 +14,62 @@ 'AU' => ['AUD','Australia'], 'AT' => ['EUR','Austria'], 'AZ' => ['AZN','Azerbaijan'], + 'BS' => ['BSD','Bahamas'], 'BH' => ['BHD','Bahrain'], 'BD' => ['BDT','Bangladesh'], - 'BB' => ['USD','Barbados'], - 'BY' => ['BYN','Belarus'], + 'BB' => ['BBD','Barbados'], + 'BY' => ['BYR','Belarus'], 'BE' => ['EUR','Belgium'], 'BZ' => ['BZD','Belize'], 'BJ' => ['XOF','Benin'], - 'BM' => ['USD','Bermuda'], - 'BT' => ['INR','Bhutan'], - 'BO' => ['BOV','Bolivia (Plurinational State of)'], - 'BQ' => ['USD','Bonaire, Sint Eustatius and Saba'], + 'BM' => ['BMD','Bermuda'], + 'BT' => ['BTN','Bhutan'], + 'BO' => ['BOB','Bolivia'], + 'BQ' => ['USD','Bonaire, Saint Eustatius and Saba '], 'BA' => ['BAM','Bosnia and Herzegovina'], 'BW' => ['BWP','Botswana'], 'BV' => ['NOK','Bouvet Island'], 'BR' => ['BRL','Brazil'], 'IO' => ['USD','British Indian Ocean Territory'], + 'VG' => ['USD','British Virgin Islands'], + 'BN' => ['BND','Brunei'], 'BG' => ['BGN','Bulgaria'], 'BF' => ['XOF','Burkina Faso'], 'BI' => ['BIF','Burundi'], - 'KH' => ['USD','Cambodia'], + 'KH' => ['KHR','Cambodia'], 'CM' => ['XAF','Cameroon'], 'CA' => ['CAD','Canada'], + 'CV' => ['CVE','Cape Verde'], 'KY' => ['KYD','Cayman Islands'], 'CF' => ['XAF','Central African Republic'], 'TD' => ['XAF','Chad'], 'CL' => ['CLP','Chile'], 'CN' => ['CNY','China'], 'CX' => ['AUD','Christmas Island'], - 'CC' => ['AUD','Cocos (Keeling) Islands'], - 'CO' => ['COU','Colombia'], + 'CC' => ['AUD','Cocos Islands'], + 'CO' => ['COP','Colombia'], 'KM' => ['KMF','Comoros'], - 'CG' => ['XAF','Congo'], - 'CD' => ['CDF','Congo, Democratic Republic of the'], 'CK' => ['NZD','Cook Islands'], 'CR' => ['CRC','Costa Rica'], - 'CI' => ['XOF','Côte d\'Ivoire'], 'HR' => ['HRK','Croatia'], 'CU' => ['CUP','Cuba'], - 'CW' => ['ANG','Curaçao'], + 'CW' => ['ANG','Curacao'], 'CY' => ['EUR','Cyprus'], - 'CZ' => ['CZK','Czechia'], + 'CZ' => ['CZK','Czech Republic'], + 'CD' => ['CDF','Democratic Republic of the Congo'], 'DK' => ['DKK','Denmark'], 'DJ' => ['DJF','Djibouti'], 'DM' => ['XCD','Dominica'], 'DO' => ['DOP','Dominican Republic'], + 'TL' => ['USD','East Timor'], 'EC' => ['USD','Ecuador'], 'EG' => ['EGP','Egypt'], 'SV' => ['USD','El Salvador'], 'GQ' => ['XAF','Equatorial Guinea'], 'ER' => ['ERN','Eritrea'], 'EE' => ['EUR','Estonia'], - 'SZ' => ['SZL','Eswatini'], 'ET' => ['ETB','Ethiopia'], - 'FK' => ['FKP','Falkland Islands (Malvinas)'], + 'FK' => ['FKP','Falkland Islands'], 'FO' => ['DKK','Faroe Islands'], 'FJ' => ['FJD','Fiji'], 'FI' => ['EUR','Finland'], @@ -76,6 +78,7 @@ 'PF' => ['XPF','French Polynesia'], 'TF' => ['EUR','French Southern Territories'], 'GA' => ['XAF','Gabon'], + 'GM' => ['GMD','Gambia'], 'GE' => ['GEL','Georgia'], 'DE' => ['EUR','Germany'], 'GH' => ['GHS','Ghana'], @@ -86,10 +89,11 @@ 'GP' => ['EUR','Guadeloupe'], 'GU' => ['USD','Guam'], 'GT' => ['GTQ','Guatemala'], + 'GG' => ['GBP','Guernsey'], 'GN' => ['GNF','Guinea'], 'GW' => ['XOF','Guinea-Bissau'], 'GY' => ['GYD','Guyana'], - 'HT' => ['USD','Haiti'], + 'HT' => ['HTG','Haiti'], 'HM' => ['AUD','Heard Island and McDonald Islands'], 'HN' => ['HNL','Honduras'], 'HK' => ['HKD','Hong Kong'], @@ -97,12 +101,13 @@ 'IS' => ['ISK','Iceland'], 'IN' => ['INR','India'], 'ID' => ['IDR','Indonesia'], - 'IR' => ['IRR','Iran (Islamic Republic of)'], + 'IR' => ['IRR','Iran'], 'IQ' => ['IQD','Iraq'], 'IE' => ['EUR','Ireland'], 'IM' => ['GBP','Isle of Man'], 'IL' => ['ILS','Israel'], 'IT' => ['EUR','Italy'], + 'CI' => ['XOF','Ivory Coast'], 'JM' => ['JMD','Jamaica'], 'JP' => ['JPY','Japan'], 'JE' => ['GBP','Jersey'], @@ -110,20 +115,20 @@ 'KZ' => ['KZT','Kazakhstan'], 'KE' => ['KES','Kenya'], 'KI' => ['AUD','Kiribati'], - 'KP' => ['KPW','Korea (Democratic People\'s Republic of)'], - 'KR' => ['KRW','Korea, Republic of'], + 'XK' => ['EUR','Kosovo'], 'KW' => ['KWD','Kuwait'], 'KG' => ['KGS','Kyrgyzstan'], - 'LA' => ['LAK','Lao People\'s Democratic Republic'], + 'LA' => ['LAK','Laos'], 'LV' => ['EUR','Latvia'], 'LB' => ['LBP','Lebanon'], - 'LS' => ['ZAR','Lesotho'], + 'LS' => ['LSL','Lesotho'], 'LR' => ['LRD','Liberia'], 'LY' => ['LYD','Libya'], 'LI' => ['CHF','Liechtenstein'], 'LT' => ['EUR','Lithuania'], 'LU' => ['EUR','Luxembourg'], 'MO' => ['MOP','Macao'], + 'MK' => ['MKD','Macedonia'], 'MG' => ['MGA','Madagascar'], 'MW' => ['MWK','Malawi'], 'MY' => ['MYR','Malaysia'], @@ -132,12 +137,12 @@ 'MT' => ['EUR','Malta'], 'MH' => ['USD','Marshall Islands'], 'MQ' => ['EUR','Martinique'], - 'MR' => ['MRU','Mauritania'], + 'MR' => ['MRO','Mauritania'], 'MU' => ['MUR','Mauritius'], 'YT' => ['EUR','Mayotte'], - 'MX' => ['MXV','Mexico'], - 'FM' => ['USD','Micronesia (Federated States of)'], - 'MD' => ['MDL','Moldova, Republic of'], + 'MX' => ['MXN','Mexico'], + 'FM' => ['USD','Micronesia'], + 'MD' => ['MDL','Moldova'], 'MC' => ['EUR','Monaco'], 'MN' => ['MNT','Mongolia'], 'ME' => ['EUR','Montenegro'], @@ -145,9 +150,10 @@ 'MA' => ['MAD','Morocco'], 'MZ' => ['MZN','Mozambique'], 'MM' => ['MMK','Myanmar'], - 'NA' => ['ZAR','Namibia'], + 'NA' => ['NAD','Namibia'], 'NR' => ['AUD','Nauru'], 'NP' => ['NPR','Nepal'], + 'NL' => ['EUR','Netherlands'], 'NC' => ['XPF','New Caledonia'], 'NZ' => ['NZD','New Zealand'], 'NI' => ['NIO','Nicaragua'], @@ -155,14 +161,14 @@ 'NG' => ['NGN','Nigeria'], 'NU' => ['NZD','Niue'], 'NF' => ['AUD','Norfolk Island'], - 'MK' => ['MKD','North Macedonia'], + 'KP' => ['KPW','North Korea'], 'MP' => ['USD','Northern Mariana Islands'], - 'NL' => ['EUR','The Netherlands'], 'NO' => ['NOK','Norway'], 'OM' => ['OMR','Oman'], 'PK' => ['PKR','Pakistan'], 'PW' => ['USD','Palau'], - 'PA' => ['USD','Panama'], + 'PS' => ['ILS','Palestinian Territory'], + 'PA' => ['PAB','Panama'], 'PG' => ['PGK','Papua New Guinea'], 'PY' => ['PYG','Paraguay'], 'PE' => ['PEN','Peru'], @@ -172,42 +178,49 @@ 'PT' => ['EUR','Portugal'], 'PR' => ['USD','Puerto Rico'], 'QA' => ['QAR','Qatar'], - 'RE' => ['EUR','Réunion'], + 'CG' => ['XAF','Republic of the Congo'], + 'RE' => ['EUR','Reunion'], 'RO' => ['RON','Romania'], - 'RU' => ['RUB','Russian Federation'], + 'RU' => ['RUB','Russia'], 'RW' => ['RWF','Rwanda'], - 'BL' => ['EUR','Saint Barthélemy'], + 'BL' => ['EUR','Saint Barthelemy'], + 'SH' => ['SHP','Saint Helena'], 'KN' => ['XCD','Saint Kitts and Nevis'], 'LC' => ['XCD','Saint Lucia'], - 'MF' => ['EUR','Saint Martin (French part)'], + 'MF' => ['EUR','Saint Martin'], 'PM' => ['EUR','Saint Pierre and Miquelon'], 'VC' => ['XCD','Saint Vincent and the Grenadines'], 'WS' => ['WST','Samoa'], 'SM' => ['EUR','San Marino'], + 'ST' => ['STD','Sao Tome and Principe'], 'SA' => ['SAR','Saudi Arabia'], 'SN' => ['XOF','Senegal'], 'RS' => ['RSD','Serbia'], 'SC' => ['SCR','Seychelles'], 'SL' => ['SLL','Sierra Leone'], 'SG' => ['SGD','Singapore'], - 'SX' => ['ANG','Sint Maarten (Dutch part)'], + 'SX' => ['ANG','Sint Maarten'], 'SK' => ['EUR','Slovakia'], 'SI' => ['EUR','Slovenia'], 'SB' => ['SBD','Solomon Islands'], 'SO' => ['SOS','Somalia'], 'ZA' => ['ZAR','South Africa'], + 'GS' => ['GBP','South Georgia and the South Sandwich Islands'], + 'KR' => ['KRW','South Korea'], 'SS' => ['SSP','South Sudan'], 'ES' => ['EUR','Spain'], 'LK' => ['LKR','Sri Lanka'], 'SD' => ['SDG','Sudan'], 'SR' => ['SRD','Suriname'], + 'SJ' => ['NOK','Svalbard and Jan Mayen'], + 'SZ' => ['SZL','Swaziland'], 'SE' => ['SEK','Sweden'], - 'CH' => ['CHW','Switzerland'], - 'SY' => ['SYP','Syrian Arab Republic'], + 'CH' => ['CHF','Switzerland'], + 'SY' => ['SYP','Syria'], + 'TW' => ['TWD','Taiwan'], 'TJ' => ['TJS','Tajikistan'], - 'TZ' => ['TZS','Tanzania, United Republic of'], + 'TZ' => ['TZS','Tanzania'], 'TH' => ['THB','Thailand'], - 'TL' => ['USD','Timor-Leste'], 'TG' => ['XOF','Togo'], 'TK' => ['NZD','Tokelau'], 'TO' => ['TOP','Tonga'], @@ -217,22 +230,22 @@ 'TM' => ['TMT','Turkmenistan'], 'TC' => ['USD','Turks and Caicos Islands'], 'TV' => ['AUD','Tuvalu'], + 'VI' => ['USD','U.S. Virgin Islands'], 'UG' => ['UGX','Uganda'], 'UA' => ['UAH','Ukraine'], 'AE' => ['AED','United Arab Emirates'], - 'GB' => ['GBP','United Kingdom of Great Britain and Northern Ireland'], - 'US' => ['USN','United States of America'], + 'GB' => ['GBP','United Kingdom'], + 'US' => ['USD','United States'], 'UM' => ['USD','United States Minor Outlying Islands'], - 'UY' => ['UYW','Uruguay'], + 'UY' => ['UYU','Uruguay'], 'UZ' => ['UZS','Uzbekistan'], 'VU' => ['VUV','Vanuatu'], - 'VE' => ['VES','Venezuela (Bolivarian Republic of)'], - 'VN' => ['VND','Viet Nam'], - 'VG' => ['USD','Virgin Islands (British)'], - 'VI' => ['USD','Virgin Islands (U.S.)'], + 'VA' => ['EUR','Vatican'], + 'VE' => ['VEF','Venezuela'], + 'VN' => ['VND','Vietnam'], 'WF' => ['XPF','Wallis and Futuna'], 'EH' => ['MAD','Western Sahara'], 'YE' => ['YER','Yemen'], - 'ZM' => ['ZMW','Zambia'], + 'ZM' => ['ZMK','Zambia'], 'ZW' => ['ZWL','Zimbabwe'], ]; diff --git a/src/resources/js/app.js b/src/resources/js/app.js --- a/src/resources/js/app.js +++ b/src/resources/js/app.js @@ -59,6 +59,10 @@ localStorage.setItem('token', response.access_token) axios.defaults.headers.common.Authorization = 'Bearer ' + response.access_token + if (response.email) { + store.state.authInfo = response + } + if (dashboard !== false) { this.$router.push(store.state.afterLogin || { name: 'dashboard' }) } @@ -186,7 +190,10 @@ }, clickRecord(event) { if (!/^(a|button|svg|path)$/i.test(event.target.nodeName)) { - $(event.target).closest('tr').find('a')[0].click() + let link = $(event.target).closest('tr').find('a')[0] + if (link) { + link.click() + } } }, domainStatusClass(domain) { diff --git a/src/resources/lang/en/documents.php b/src/resources/lang/en/documents.php --- a/src/resources/lang/en/documents.php +++ b/src/resources/lang/en/documents.php @@ -32,6 +32,8 @@ 'receipt-filename' => ":site Receipt for :id", 'receipt-title' => "Receipt for :month :year", 'receipt-item-desc' => ":site Services", + 'receipt-refund' => "Refund", + 'receipt-chargeback' => "Chargeback", 'subtotal' => "Subtotal", 'vat' => "VAT (:rate%)", diff --git a/src/resources/lang/en/transactions.php b/src/resources/lang/en/transactions.php --- a/src/resources/lang/en/transactions.php +++ b/src/resources/lang/en/transactions.php @@ -5,17 +5,22 @@ 'entitlement-billed' => ':sku_title for :object is billed at :amount', 'entitlement-deleted' => ':user_email deleted :sku_title for :object', + 'entitlement-created-short' => 'Added :sku_title for :object', + 'entitlement-billed-short' => 'Billed :sku_title for :object', + 'entitlement-deleted-short' => 'Deleted :sku_title for :object', + 'wallet-award' => 'Bonus of :amount awarded to :wallet; :description', + 'wallet-chargeback' => ':amount was charged back from :wallet', 'wallet-credit' => ':amount was added to the balance of :wallet', 'wallet-debit' => ':amount was deducted from the balance of :wallet', 'wallet-penalty' => 'The balance of :wallet was reduced by :amount; :description', - - 'entitlement-created-short' => 'Added :sku_title for :object', - 'entitlement-billed-short' => 'Billed :sku_title for :object', - 'entitlement-deleted-short' => 'Deleted :sku_title for :object', + 'wallet-refund' => ':amount was refunded from the balance of :wallet', + 'wallet-refund' => ':amount was refunded from :wallet', 'wallet-award-short' => 'Bonus: :description', + 'wallet-chargeback-short' => 'Chargeback', 'wallet-credit-short' => 'Payment', 'wallet-debit-short' => 'Deduction', 'wallet-penalty-short' => 'Charge: :description', + 'wallet-refund-short' => 'Refund: :description', ]; diff --git a/src/resources/sass/app.scss b/src/resources/sass/app.scss --- a/src/resources/sass/app.scss +++ b/src/resources/sass/app.scss @@ -88,7 +88,7 @@ &.fadeOut { visibility: hidden; opacity: 0; - transition: visibility 400ms linear, opacity 400ms linear; + transition: visibility 300ms linear, opacity 300ms linear; } } diff --git a/src/resources/vue/Admin/Dashboard.vue b/src/resources/vue/Admin/Dashboard.vue --- a/src/resources/vue/Admin/Dashboard.vue +++ b/src/resources/vue/Admin/Dashboard.vue @@ -15,17 +15,23 @@ + + - - + + +
(.+)
(.+)
Primary Email IDCreatedDeleted
+
- {{ user.email }} + {{ user.email }} + {{ user.email }} - {{ user.id }} + {{ user.id }} + {{ user.id }} {{ toDate(user.created_at) }}{{ toDate(user.deleted_at) }}
@@ -65,7 +71,7 @@ axios.get('/api/v4/users', { params: { search: this.search } }) .then(response => { - if (response.data.count == 1) { + if (response.data.count == 1 && !response.data.list[0].isDeleted) { this.$router.push({ name: 'user', params: { user: response.data.list[0].id } }) return } @@ -77,6 +83,11 @@ this.users = response.data.list }) .catch(this.$root.errorHandler) + }, + toDate(datetime) { + if (datetime) { + return datetime.split(' ')[0] + } } } } diff --git a/src/resources/vue/Admin/User.vue b/src/resources/vue/Admin/User.vue --- a/src/resources/vue/Admin/User.vue +++ b/src/resources/vue/Admin/User.vue @@ -244,7 +244,8 @@ - {{ item.email }} + {{ item.email }} + {{ item.email }} @@ -479,9 +480,7 @@ // TODO: Multiple wallets axios.get('/api/v4/users?owner=' + user_id) .then(response => { - this.users = response.data.list.filter(user => { - return user.id != user_id; - }) + this.users = response.data.list; }) // Fetch domains diff --git a/src/resources/vue/App.vue b/src/resources/vue/App.vue --- a/src/resources/vue/App.vue +++ b/src/resources/vue/App.vue @@ -19,10 +19,9 @@ axios.get('/api/auth/info?refresh_token=1') .then(response => { - this.isLoading = false - this.$root.stopLoading() this.$root.loginUser(response.data, false) - this.$store.state.authInfo = response.data + this.$root.stopLoading() + this.isLoading = false }) .catch(error => { // Release lock on the router-view, otherwise links (e.g. Logout) will not work diff --git a/src/resources/vue/Dashboard.vue b/src/resources/vue/Dashboard.vue --- a/src/resources/vue/Dashboard.vue +++ b/src/resources/vue/Dashboard.vue @@ -34,22 +34,9 @@ } }, mounted() { - const authInfo = this.$store.state.isLoggedIn ? this.$store.state.authInfo : null - - if (authInfo) { - this.status = authInfo.statusInfo - this.getBalance(authInfo) - } else { - this.$root.startLoading() - axios.get('/api/auth/info') - .then(response => { - this.$store.state.authInfo = response.data - this.status = response.data.statusInfo - this.getBalance(response.data) - this.$root.stopLoading() - }) - .catch(this.$root.errorHandler) - } + const authInfo = this.$store.state.authInfo + this.status = authInfo.statusInfo + this.getBalance(authInfo) }, methods: { getBalance(authInfo) { diff --git a/src/resources/vue/Widgets/ListInput.vue b/src/resources/vue/Widgets/ListInput.vue --- a/src/resources/vue/Widgets/ListInput.vue +++ b/src/resources/vue/Widgets/ListInput.vue @@ -27,14 +27,28 @@ list: { type: Array, default: () => [] }, id: { type: String, default: '' } }, + mounted() { + this.input = $(this.$el).find('.main-input')[0] + + // On form submit add the text from main input to the list + // Users tend to forget about pressing the "plus" button + // Note: We can't use form.onsubmit (too late) + // Note: Use of input.onblur has been proven to be problematic + // TODO: What with forms that have no submit button? + $(this.$el).closest('form').find('button[type=submit]').on('click', () => { + this.addItem(false) + }) + }, methods: { - addItem() { - let input = $(this.$el).find('.main-input') - let value = input.val() + addItem(focus) { + let value = this.input.value if (value) { this.list.push(value) - input.val('').focus() + this.input.value = '' + if (focus !== false) { + this.input.focus() + } } }, deleteItem(index) { diff --git a/src/tests/Browser.php b/src/tests/Browser.php --- a/src/tests/Browser.php +++ b/src/tests/Browser.php @@ -119,7 +119,24 @@ { $element = $this->resolver->findOrFail($selector); - Assert::assertTrue(strpos($element->getText(), $text) !== false, "No expected text in [$selector]"); + if ($text === '') { + Assert::assertTrue((string) $element->getText() === $text, "Element's text is not empty [$selector]"); + } else { + Assert::assertTrue(strpos($element->getText(), $text) !== false, "No expected text in [$selector]"); + } + + return $this; + } + + /** + * Assert that the given element contains specified text, + * no matter it's displayed or not - using a regular expression. + */ + public function assertTextRegExp($selector, $regexp) + { + $element = $this->resolver->findOrFail($selector); + + Assert::assertRegExp($regexp, $element->getText(), "No expected text in [$selector]"); return $this; } diff --git a/src/tests/Browser/Admin/DashboardTest.php b/src/tests/Browser/Admin/DashboardTest.php --- a/src/tests/Browser/Admin/DashboardTest.php +++ b/src/tests/Browser/Admin/DashboardTest.php @@ -2,12 +2,12 @@ namespace Tests\Browser\Admin; +use Illuminate\Support\Facades\Queue; use Tests\Browser; use Tests\Browser\Components\Toast; use Tests\Browser\Pages\Dashboard; use Tests\Browser\Pages\Home; use Tests\TestCaseDusk; -use Illuminate\Foundation\Testing\DatabaseMigrations; class DashboardTest extends TestCaseDusk { @@ -21,6 +21,9 @@ $jack = $this->getTestUser('jack@kolab.org'); $jack->setSetting('external_email', null); + + $this->deleteTestUser('test@testsearch.com'); + $this->deleteTestDomain('testsearch.com'); } /** @@ -31,6 +34,9 @@ $jack = $this->getTestUser('jack@kolab.org'); $jack->setSetting('external_email', null); + $this->deleteTestUser('test@testsearch.com'); + $this->deleteTestDomain('testsearch.com'); + parent::tearDown(); } @@ -60,9 +66,24 @@ $browser->type('@search input', 'john.doe.external@gmail.com') ->click('@search form button') ->assertToast(Toast::TYPE_INFO, '2 user accounts have been found.') - ->whenAvailable('@search table', function (Browser $browser) { - $browser->assertElementsCount('tbody tr', 2); - // TODO: Assert table content + ->whenAvailable('@search table', function (Browser $browser) use ($john, $jack) { + $browser->assertElementsCount('tbody tr', 2) + ->with('tbody tr:first-child', function (Browser $browser) use ($jack) { + $browser->assertSeeIn('td:nth-child(1) a', $jack->email) + ->assertSeeIn('td:nth-child(2) a', $jack->id) + ->assertVisible('td:nth-child(3)') + ->assertTextRegExp('td:nth-child(3)', '/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/') + ->assertVisible('td:nth-child(4)') + ->assertText('td:nth-child(4)', ''); + }) + ->with('tbody tr:last-child', function (Browser $browser) use ($john) { + $browser->assertSeeIn('td:nth-child(1) a', $john->email) + ->assertSeeIn('td:nth-child(2) a', $john->id) + ->assertVisible('td:nth-child(3)') + ->assertTextRegExp('td:nth-child(3)', '/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/') + ->assertVisible('td:nth-child(4)') + ->assertText('td:nth-child(4)', ''); + }); }); // Test search with single record result -> redirect to user page @@ -76,4 +97,44 @@ }); }); } + + /** + * Test user search deleted user/domain + */ + public function testSearchDeleted(): void + { + $this->browse(function (Browser $browser) { + $browser->visit(new Home()) + ->submitLogon('jeroen@jeroen.jeroen', 'jeroen', true) + ->on(new Dashboard()) + ->assertFocused('@search input') + ->assertMissing('@search table'); + + // Deleted users/domains + $domain = $this->getTestDomain('testsearch.com', ['type' => \App\Domain::TYPE_EXTERNAL]); + $user = $this->getTestUser('test@testsearch.com'); + $plan = \App\Plan::where('title', 'group')->first(); + $user->assignPlan($plan, $domain); + $user->setAliases(['alias@testsearch.com']); + Queue::fake(); + $user->delete(); + + // Test search with multiple results + $browser->type('@search input', 'testsearch.com') + ->click('@search form button') + ->assertToast(Toast::TYPE_INFO, '1 user accounts have been found.') + ->whenAvailable('@search table', function (Browser $browser) use ($user) { + $browser->assertElementsCount('tbody tr', 1) + ->assertVisible('tbody tr:first-child.text-secondary') + ->with('tbody tr:first-child', function (Browser $browser) use ($user) { + $browser->assertSeeIn('td:nth-child(1) span', $user->email) + ->assertSeeIn('td:nth-child(2) span', $user->id) + ->assertVisible('td:nth-child(3)') + ->assertTextRegExp('td:nth-child(3)', '/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/') + ->assertVisible('td:nth-child(4)') + ->assertTextRegExp('td:nth-child(4)', '/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/'); + }); + }); + }); + } } diff --git a/src/tests/Browser/Admin/UserFinancesTest.php b/src/tests/Browser/Admin/UserFinancesTest.php --- a/src/tests/Browser/Admin/UserFinancesTest.php +++ b/src/tests/Browser/Admin/UserFinancesTest.php @@ -122,10 +122,13 @@ // Now we go to Ned's info page, he's a controller on John's wallet $this->browse(function (Browser $browser) { $ned = $this->getTestUser('ned@kolab.org'); + $wallet = $ned->wallets()->first(); + $wallet->balance = 0; + $wallet->save(); $page = new UserPage($ned->id); $browser->click('@nav #tab-users') - ->click('@user-users tbody tr:nth-child(3) td:first-child a') + ->click('@user-users tbody tr:nth-child(4) td:first-child a') ->on($page) ->with('@user-finances', function (Browser $browser) { $browser->waitUntilMissing('.app-loader') diff --git a/src/tests/Browser/Admin/UserTest.php b/src/tests/Browser/Admin/UserTest.php --- a/src/tests/Browser/Admin/UserTest.php +++ b/src/tests/Browser/Admin/UserTest.php @@ -102,7 +102,7 @@ ->assertSeeIn('.row:nth-child(6) label', 'External email') ->assertMissing('.row:nth-child(6) #external_email a') ->assertSeeIn('.row:nth-child(7) label', 'Country') - ->assertSeeIn('.row:nth-child(7) #country', 'United States of America'); + ->assertSeeIn('.row:nth-child(7) #country', 'United States'); }); // Some tabs are loaded in background, wait a second @@ -198,7 +198,7 @@ ->assertSeeIn('.row:nth-child(8) label', 'Address') ->assertSeeIn('.row:nth-child(8) #billing_address', $john->getSetting('billing_address')) ->assertSeeIn('.row:nth-child(9) label', 'Country') - ->assertSeeIn('.row:nth-child(9) #country', 'United States of America'); + ->assertSeeIn('.row:nth-child(9) #country', 'United States'); }); // Some tabs are loaded in background, wait a second @@ -243,16 +243,18 @@ }); // Assert Users tab - $browser->assertSeeIn('@nav #tab-users', 'Users (3)') + $browser->assertSeeIn('@nav #tab-users', 'Users (4)') ->click('@nav #tab-users') ->with('@user-users table', function (Browser $browser) { - $browser->assertElementsCount('tbody tr', 3) + $browser->assertElementsCount('tbody tr', 4) ->assertSeeIn('tbody tr:nth-child(1) td:first-child a', 'jack@kolab.org') ->assertVisible('tbody tr:nth-child(1) td:first-child svg.text-success') ->assertSeeIn('tbody tr:nth-child(2) td:first-child a', 'joe@kolab.org') ->assertVisible('tbody tr:nth-child(2) td:first-child svg.text-success') - ->assertSeeIn('tbody tr:nth-child(3) td:first-child a', 'ned@kolab.org') + ->assertSeeIn('tbody tr:nth-child(3) td:first-child span', 'john@kolab.org') ->assertVisible('tbody tr:nth-child(3) td:first-child svg.text-success') + ->assertSeeIn('tbody tr:nth-child(4) td:first-child a', 'ned@kolab.org') + ->assertVisible('tbody tr:nth-child(4) td:first-child svg.text-success') ->assertMissing('tfoot'); }); }); @@ -262,7 +264,7 @@ $ned = $this->getTestUser('ned@kolab.org'); $page = new UserPage($ned->id); - $browser->click('@user-users tbody tr:nth-child(3) td:first-child a') + $browser->click('@user-users tbody tr:nth-child(4) td:first-child a') ->on($page); // Assert main info box content diff --git a/src/tests/Browser/DomainTest.php b/src/tests/Browser/DomainTest.php --- a/src/tests/Browser/DomainTest.php +++ b/src/tests/Browser/DomainTest.php @@ -65,14 +65,9 @@ $browser->assertSeeIn('pre', $domain->namespace) ->assertSeeIn('pre', $domain->hash()) ->click('button') - ->assertToast(Toast::TYPE_ERROR, 'Domain ownership verification failed.'); - - // Make sure the domain is confirmed now - $domain->status |= Domain::STATUS_CONFIRMED; - $domain->save(); - - $browser->click('button') ->assertToast(Toast::TYPE_SUCCESS, 'Domain verified successfully.'); + + // TODO: Test scenario when a domain confirmation failed }) ->whenAvailable('@config', function ($browser) use ($domain) { $browser->assertSeeIn('pre', $domain->namespace); diff --git a/src/tests/Browser/StatusTest.php b/src/tests/Browser/StatusTest.php --- a/src/tests/Browser/StatusTest.php +++ b/src/tests/Browser/StatusTest.php @@ -54,16 +54,20 @@ { // Unconfirmed domain and user $domain = Domain::where('namespace', 'kolab.org')->first(); + if ($domain->isConfirmed()) { $domain->status ^= Domain::STATUS_CONFIRMED; $domain->save(); } $john = $this->getTestUser('john@kolab.org'); + $john->created_at = Carbon::now(); + if ($john->isImapReady()) { $john->status ^= User::STATUS_IMAP_READY; } + $john->save(); $this->browse(function ($browser) use ($john, $domain) { @@ -113,10 +117,13 @@ $domain->status ^= Domain::STATUS_CONFIRMED; $domain->save(); } + $john->created_at = Carbon::now()->subSeconds(3600); + if ($john->isImapReady()) { $john->status ^= User::STATUS_IMAP_READY; } + $john->save(); $this->browse(function ($browser) use ($john, $domain) { @@ -153,6 +160,18 @@ $domain->status = Domain::STATUS_NEW | Domain::STATUS_ACTIVE | Domain::STATUS_LDAP_READY; $domain->save(); + // side-step + $this->assertFalse($domain->isNew()); + $this->assertTrue($domain->isActive()); + $this->assertTrue($domain->isLdapReady()); + $this->assertTrue($domain->isExternal()); + + $this->assertFalse($domain->isHosted()); + $this->assertFalse($domain->isConfirmed()); + $this->assertFalse($domain->isVerified()); + $this->assertFalse($domain->isSuspended()); + $this->assertFalse($domain->isDeleted()); + $this->browse(function ($browser) use ($domain) { // Test auto-refresh $browser->on(new Dashboard()) diff --git a/src/tests/Feature/Backends/LDAPTest.php b/src/tests/Feature/Backends/LDAPTest.php --- a/src/tests/Feature/Backends/LDAPTest.php +++ b/src/tests/Feature/Backends/LDAPTest.php @@ -221,6 +221,24 @@ $this->assertSame(null, LDAP::getUser($user->email)); } + /** + * Test handling errors on user creation + * + * @group ldap + */ + public function testCreateUserException(): void + { + $this->expectException(\Exception::class); + $this->expectExceptionMessageMatches('/Failed to create user/'); + + $user = new User([ + 'email' => 'test-non-existing-ldap@non-existing.org', + 'status' => User::STATUS_ACTIVE, + ]); + + LDAP::createUser($user); + } + /** * Test handling update of a non-existing domain * diff --git a/src/tests/Feature/BillingTest.php b/src/tests/Feature/BillingTest.php --- a/src/tests/Feature/BillingTest.php +++ b/src/tests/Feature/BillingTest.php @@ -82,7 +82,10 @@ */ public function testFullTrial(): void { - $this->backdateEntitlements($this->wallet->entitlements, Carbon::now()->subMonthsWithoutOverflow(1)); + $this->backdateEntitlements( + $this->wallet->entitlements, + Carbon::now()->subMonthsWithoutOverflow(1) + ); $this->assertEquals(999, $this->wallet->expectedCharges()); } diff --git a/src/tests/Feature/Console/UserForceDeleteTest.php b/src/tests/Feature/Console/UserForceDeleteTest.php new file mode 100644 --- /dev/null +++ b/src/tests/Feature/Console/UserForceDeleteTest.php @@ -0,0 +1,98 @@ +deleteTestUser('user@force-delete.com'); + $this->deleteTestDomain('force-delete.com'); + } + + /** + * {@inheritDoc} + */ + public function tearDown(): void + { + $this->deleteTestUser('user@force-delete.com'); + $this->deleteTestDomain('force-delete.com'); + + parent::tearDown(); + } + + /** + * Test the command + */ + public function testHandle(): void + { + // Non-existing user + $this->artisan('user:force-delete unknown@unknown.org') + ->assertExitCode(1); + + Queue::fake(); + $user = $this->getTestUser('user@force-delete.com'); + $domain = $this->getTestDomain('force-delete.com', [ + 'status' => \App\Domain::STATUS_NEW, + 'type' => \App\Domain::TYPE_HOSTED, + ]); + $package_kolab = \App\Package::where('title', 'kolab')->first(); + $package_domain = \App\Package::where('title', 'domain-hosting')->first(); + $user->assignPackage($package_kolab); + $domain->assignPackage($package_domain, $user); + $wallet = $user->wallets()->first(); + $entitlements = $wallet->entitlements->pluck('id')->all(); + + $this->assertCount(5, $entitlements); + + // Non-deleted user + $this->artisan('user:force-delete user@force-delete.com') + ->assertExitCode(1); + + $user->delete(); + + $this->assertTrue($user->trashed()); + $this->assertTrue($domain->fresh()->trashed()); + + // Deleted user + $this->artisan('user:force-delete user@force-delete.com') + ->assertExitCode(0); + + $this->assertCount( + 0, + \App\User::withTrashed()->where('email', 'user@force-delete.com')->get() + ); + $this->assertCount( + 0, + \App\Domain::withTrashed()->where('namespace', 'force-delete.com')->get() + ); + $this->assertCount( + 0, + \App\Wallet::where('id', $wallet->id)->get() + ); + $this->assertCount( + 0, + \App\Entitlement::withTrashed()->where('wallet_id', $wallet->id)->get() + ); + $this->assertCount( + 0, + \App\Entitlement::withTrashed()->where('entitleable_id', $user->id)->get() + ); + $this->assertCount( + 0, + \App\Transaction::whereIn('object_id', $entitlements) + ->where('object_type', \App\Entitlement::class) + ->get() + ); + + // TODO: Test that it also deletes users in a group account + } +} diff --git a/src/tests/Feature/Controller/Admin/UsersTest.php b/src/tests/Feature/Controller/Admin/UsersTest.php --- a/src/tests/Feature/Controller/Admin/UsersTest.php +++ b/src/tests/Feature/Controller/Admin/UsersTest.php @@ -18,6 +18,8 @@ self::useAdminUrl(); $this->deleteTestUser('UsersControllerTest1@userscontroller.com'); + $this->deleteTestUser('test@testsearch.com'); + $this->deleteTestDomain('testsearch.com'); $jack = $this->getTestUser('jack@kolab.org'); $jack->setSetting('external_email', null); @@ -29,6 +31,8 @@ public function tearDown(): void { $this->deleteTestUser('UsersControllerTest1@userscontroller.com'); + $this->deleteTestUser('test@testsearch.com'); + $this->deleteTestDomain('testsearch.com'); $jack = $this->getTestUser('jack@kolab.org'); $jack->setSetting('external_email', null); @@ -146,6 +150,48 @@ $this->assertSame(0, $json['count']); $this->assertCount(0, $json['list']); + + // Deleted users/domains + $domain = $this->getTestDomain('testsearch.com', ['type' => \App\Domain::TYPE_EXTERNAL]); + $user = $this->getTestUser('test@testsearch.com'); + $plan = \App\Plan::where('title', 'group')->first(); + $user->assignPlan($plan, $domain); + $user->setAliases(['alias@testsearch.com']); + Queue::fake(); + $user->delete(); + + $response = $this->actingAs($admin)->get("api/v4/users?search=test@testsearch.com"); + $response->assertStatus(200); + + $json = $response->json(); + + $this->assertSame(1, $json['count']); + $this->assertCount(1, $json['list']); + $this->assertSame($user->id, $json['list'][0]['id']); + $this->assertSame($user->email, $json['list'][0]['email']); + $this->assertTrue($json['list'][0]['isDeleted']); + + $response = $this->actingAs($admin)->get("api/v4/users?search=alias@testsearch.com"); + $response->assertStatus(200); + + $json = $response->json(); + + $this->assertSame(1, $json['count']); + $this->assertCount(1, $json['list']); + $this->assertSame($user->id, $json['list'][0]['id']); + $this->assertSame($user->email, $json['list'][0]['email']); + $this->assertTrue($json['list'][0]['isDeleted']); + + $response = $this->actingAs($admin)->get("api/v4/users?search=testsearch.com"); + $response->assertStatus(200); + + $json = $response->json(); + + $this->assertSame(1, $json['count']); + $this->assertCount(1, $json['list']); + $this->assertSame($user->id, $json['list'][0]['id']); + $this->assertSame($user->email, $json['list'][0]['email']); + $this->assertTrue($json['list'][0]['isDeleted']); } /** diff --git a/src/tests/Feature/Controller/AuthTest.php b/src/tests/Feature/Controller/AuthTest.php --- a/src/tests/Feature/Controller/AuthTest.php +++ b/src/tests/Feature/Controller/AuthTest.php @@ -101,10 +101,26 @@ $this->assertSame('Invalid username or password.', $json['message']); // Valid user+password + $user = $this->getTestUser('john@kolab.org'); $post = ['email' => 'john@kolab.org', 'password' => 'simple123']; $response = $this->post("api/auth/login", $post); $json = $response->json(); + $response->assertStatus(200); + $this->assertTrue(!empty($json['access_token'])); + $this->assertEquals(\config('jwt.ttl') * 60, $json['expires_in']); + $this->assertEquals('bearer', $json['token_type']); + $this->assertEquals($user->id, $json['id']); + $this->assertEquals($user->email, $json['email']); + $this->assertTrue(is_array($json['statusInfo'])); + $this->assertTrue(is_array($json['settings'])); + $this->assertTrue(is_array($json['aliases'])); + + // Valid user+password (upper-case) + $post = ['email' => 'John@Kolab.org', 'password' => 'simple123']; + $response = $this->post("api/auth/login", $post); + $json = $response->json(); + $response->assertStatus(200); $this->assertTrue(!empty($json['access_token'])); $this->assertEquals(\config('jwt.ttl') * 60, $json['expires_in']); diff --git a/src/tests/Feature/Controller/DomainsTest.php b/src/tests/Feature/Controller/DomainsTest.php --- a/src/tests/Feature/Controller/DomainsTest.php +++ b/src/tests/Feature/Controller/DomainsTest.php @@ -229,14 +229,14 @@ $json = $response->json(); $this->assertTrue($json['isVerified']); - $this->assertFalse($json['isReady']); + $this->assertTrue($json['isReady']); $this->assertCount(4, $json['process']); $this->assertSame('domain-verified', $json['process'][2]['label']); $this->assertSame(true, $json['process'][2]['state']); $this->assertSame('domain-confirmed', $json['process'][3]['label']); - $this->assertSame(false, $json['process'][3]['state']); - $this->assertSame('error', $json['status']); - $this->assertSame('Failed to verify an ownership of a domain.', $json['message']); + $this->assertSame(true, $json['process'][3]['state']); + $this->assertSame('success', $json['status']); + $this->assertSame('Setup process finished successfully.', $json['message']); // TODO: Test completing all process steps } diff --git a/src/tests/Feature/Controller/PasswordResetTest.php b/src/tests/Feature/Controller/PasswordResetTest.php --- a/src/tests/Feature/Controller/PasswordResetTest.php +++ b/src/tests/Feature/Controller/PasswordResetTest.php @@ -304,20 +304,24 @@ $json = $response->json(); $response->assertStatus(200); - $this->assertCount(4, $json); $this->assertSame('success', $json['status']); $this->assertSame('bearer', $json['token_type']); $this->assertTrue(!empty($json['expires_in']) && is_int($json['expires_in']) && $json['expires_in'] > 0); $this->assertNotEmpty($json['access_token']); + $this->assertSame($user->email, $json['email']); + $this->assertSame($user->id, $json['id']); - Queue::assertPushed(\App\Jobs\UserUpdate::class, 1); - Queue::assertPushed(\App\Jobs\UserUpdate::class, function ($job) use ($user) { - $job_user = TestCase::getObjectProperty($job, 'user'); + Queue::assertPushed(\App\Jobs\User\UpdateJob::class, 1); - return $job_user->id == $user->id - && $job_user->email == $user->email - && $job_user->password_ldap != $user->password_ldap; - }); + Queue::assertPushed( + \App\Jobs\User\UpdateJob::class, + function ($job) use ($user) { + $userEmail = TestCase::getObjectProperty($job, 'userEmail'); + $userId = TestCase::getObjectProperty($job, 'userId'); + + return $userEmail == $user->email && $userId == $user->id; + } + ); // Check if the code has been removed $this->assertNull(VerificationCode::find($code->code)); diff --git a/src/tests/Feature/Controller/PaymentsMollieTest.php b/src/tests/Feature/Controller/PaymentsMollieTest.php --- a/src/tests/Feature/Controller/PaymentsMollieTest.php +++ b/src/tests/Feature/Controller/PaymentsMollieTest.php @@ -34,8 +34,12 @@ Payment::where('wallet_id', $wallet->id)->delete(); Wallet::where('id', $wallet->id)->update(['balance' => 0]); WalletSetting::where('wallet_id', $wallet->id)->delete(); - Transaction::where('object_id', $wallet->id) - ->where('type', Transaction::WALLET_CREDIT)->delete(); + $types = [ + Transaction::WALLET_CREDIT, + Transaction::WALLET_REFUND, + Transaction::WALLET_CHARGEBACK, + ]; + Transaction::where('object_id', $wallet->id)->whereIn('type', $types)->delete(); } /** @@ -48,8 +52,12 @@ Payment::where('wallet_id', $wallet->id)->delete(); Wallet::where('id', $wallet->id)->update(['balance' => 0]); WalletSetting::where('wallet_id', $wallet->id)->delete(); - Transaction::where('object_id', $wallet->id) - ->where('type', Transaction::WALLET_CREDIT)->delete(); + $types = [ + Transaction::WALLET_CREDIT, + Transaction::WALLET_REFUND, + Transaction::WALLET_CHARGEBACK, + ]; + Transaction::where('object_id', $wallet->id)->whereIn('type', $types)->delete(); parent::tearDown(); } @@ -569,7 +577,162 @@ return $job_payment->id === $payment->id; }); - $responseStack = $this->unmockMollie(); + $this->unmockMollie(); + } + + /** + * Test refund/chargeback handling by the webhook + * + * @group mollie + */ + public function testRefundAndChargeback(): void + { + Bus::fake(); + + $user = $this->getTestUser('john@kolab.org'); + $wallet = $user->wallets()->first(); + $wallet->transactions()->delete(); + + $mollie = PaymentProvider::factory('mollie'); + + // Create a paid payment + $payment = Payment::create([ + 'id' => 'tr_123456', + 'status' => PaymentProvider::STATUS_PAID, + 'amount' => 123, + 'type' => PaymentProvider::TYPE_ONEOFF, + 'wallet_id' => $wallet->id, + 'provider' => 'mollie', + 'description' => 'test', + ]); + + // Test handling a refund by the webhook + + $mollie_response1 = [ + "resource" => "payment", + "id" => $payment->id, + "status" => "paid", + // Status is not enough, paidAt is used to distinguish the state + "paidAt" => date('c'), + "mode" => "test", + "_links" => [ + "refunds" => [ + "href" => "https://api.mollie.com/v2/payments/{$payment->id}/refunds", + "type" => "application/hal+json" + ] + ] + ]; + + $mollie_response2 = [ + "count" => 1, + "_links" => [], + "_embedded" => [ + "refunds" => [ + [ + "resource" => "refund", + "id" => "re_123456", + "status" => \Mollie\Api\Types\RefundStatus::STATUS_REFUNDED, + "paymentId" => $payment->id, + "description" => "refund desc", + "amount" => [ + "currency" => "CHF", + "value" => "1.01", + ], + ] + ] + ] + ]; + + // We'll trigger the webhook with payment id and use mocking for + // requests to the Mollie payments API. + $responseStack = $this->mockMollie(); + $responseStack->append(new Response(200, [], json_encode($mollie_response1))); + $responseStack->append(new Response(200, [], json_encode($mollie_response2))); + + $post = ['id' => $payment->id]; + $response = $this->post("api/webhooks/payment/mollie", $post); + $response->assertStatus(200); + + $wallet->refresh(); + + $this->assertEquals(-101, $wallet->balance); + + $transactions = $wallet->transactions()->where('type', Transaction::WALLET_REFUND)->get(); + + $this->assertCount(1, $transactions); + $this->assertSame(101, $transactions[0]->amount); + $this->assertSame(Transaction::WALLET_REFUND, $transactions[0]->type); + $this->assertSame("refund desc", $transactions[0]->description); + + $payments = $wallet->payments()->where('id', 're_123456')->get(); + + $this->assertCount(1, $payments); + $this->assertSame(-101, $payments[0]->amount); + $this->assertSame(PaymentProvider::STATUS_PAID, $payments[0]->status); + $this->assertSame(PaymentProvider::TYPE_REFUND, $payments[0]->type); + $this->assertSame("mollie", $payments[0]->provider); + $this->assertSame("refund desc", $payments[0]->description); + + // Test handling a chargeback by the webhook + + $mollie_response1["_links"] = [ + "chargebacks" => [ + "href" => "https://api.mollie.com/v2/payments/{$payment->id}/chargebacks", + "type" => "application/hal+json" + ] + ]; + + $mollie_response2 = [ + "count" => 1, + "_links" => [], + "_embedded" => [ + "chargebacks" => [ + [ + "resource" => "chargeback", + "id" => "chb_123456", + "paymentId" => $payment->id, + "amount" => [ + "currency" => "CHF", + "value" => "0.15", + ], + ] + ] + ] + ]; + + // We'll trigger the webhook with payment id and use mocking for + // requests to the Mollie payments API. + $responseStack = $this->mockMollie(); + $responseStack->append(new Response(200, [], json_encode($mollie_response1))); + $responseStack->append(new Response(200, [], json_encode($mollie_response2))); + + $post = ['id' => $payment->id]; + $response = $this->post("api/webhooks/payment/mollie", $post); + $response->assertStatus(200); + + $wallet->refresh(); + + $this->assertEquals(-116, $wallet->balance); + + $transactions = $wallet->transactions()->where('type', Transaction::WALLET_CHARGEBACK)->get(); + + $this->assertCount(1, $transactions); + $this->assertSame(15, $transactions[0]->amount); + $this->assertSame(Transaction::WALLET_CHARGEBACK, $transactions[0]->type); + $this->assertSame('', $transactions[0]->description); + + $payments = $wallet->payments()->where('id', 'chb_123456')->get(); + + $this->assertCount(1, $payments); + $this->assertSame(-15, $payments[0]->amount); + $this->assertSame(PaymentProvider::STATUS_PAID, $payments[0]->status); + $this->assertSame(PaymentProvider::TYPE_CHARGEBACK, $payments[0]->type); + $this->assertSame("mollie", $payments[0]->provider); + $this->assertSame('', $payments[0]->description); + + Bus::assertNotDispatched(\App\Jobs\PaymentEmail::class); + + $this->unmockMollie(); } /** diff --git a/src/tests/Feature/Controller/PaymentsStripeTest.php b/src/tests/Feature/Controller/PaymentsStripeTest.php --- a/src/tests/Feature/Controller/PaymentsStripeTest.php +++ b/src/tests/Feature/Controller/PaymentsStripeTest.php @@ -457,6 +457,8 @@ */ public function testTopUpAndWebhook(): void { + $this->markTestIncomplete(); + Bus::fake(); $user = $this->getTestUser('john@kolab.org'); @@ -539,6 +541,7 @@ $wallet->setSetting('mandate_disabled', null); $wallet->balance = -2050; $wallet->save(); + $result = PaymentsController::topUpWallet($wallet); $this->assertFalse($result); $this->assertCount(1, $wallet->payments()->get()); diff --git a/src/tests/Feature/Controller/SignupTest.php b/src/tests/Feature/Controller/SignupTest.php --- a/src/tests/Feature/Controller/SignupTest.php +++ b/src/tests/Feature/Controller/SignupTest.php @@ -475,18 +475,21 @@ $json = $response->json(); $response->assertStatus(200); - $this->assertCount(4, $json); $this->assertSame('success', $json['status']); $this->assertSame('bearer', $json['token_type']); $this->assertTrue(!empty($json['expires_in']) && is_int($json['expires_in']) && $json['expires_in'] > 0); $this->assertNotEmpty($json['access_token']); + $this->assertSame($identity, $json['email']); - Queue::assertPushed(\App\Jobs\UserCreate::class, 1); - Queue::assertPushed(\App\Jobs\UserCreate::class, function ($job) use ($data) { - $job_user = TestCase::getObjectProperty($job, 'user'); + Queue::assertPushed(\App\Jobs\User\CreateJob::class, 1); - return $job_user->email === \strtolower($data['login'] . '@' . $data['domain']); - }); + Queue::assertPushed( + \App\Jobs\User\CreateJob::class, + function ($job) use ($data) { + $userEmail = TestCase::getObjectProperty($job, 'userEmail'); + return $userEmail === \strtolower($data['login'] . '@' . $data['domain']); + } + ); // Check if the code has been removed $this->assertNull(SignupCode::where('code', $result['code'])->first()); @@ -586,25 +589,33 @@ $result = $response->json(); $response->assertStatus(200); - $this->assertCount(4, $result); $this->assertSame('success', $result['status']); $this->assertSame('bearer', $result['token_type']); $this->assertTrue(!empty($result['expires_in']) && is_int($result['expires_in']) && $result['expires_in'] > 0); $this->assertNotEmpty($result['access_token']); + $this->assertSame("$login@$domain", $result['email']); - Queue::assertPushed(\App\Jobs\DomainCreate::class, 1); - Queue::assertPushed(\App\Jobs\DomainCreate::class, function ($job) use ($domain) { - $job_domain = TestCase::getObjectProperty($job, 'domain'); + Queue::assertPushed(\App\Jobs\Domain\CreateJob::class, 1); - return $job_domain->namespace === $domain; - }); + Queue::assertPushed( + \App\Jobs\Domain\CreateJob::class, + function ($job) use ($domain) { + $domainNamespace = TestCase::getObjectProperty($job, 'domainNamespace'); + + return $domainNamespace === $domain; + } + ); - Queue::assertPushed(\App\Jobs\UserCreate::class, 1); - Queue::assertPushed(\App\Jobs\UserCreate::class, function ($job) use ($data) { - $job_user = TestCase::getObjectProperty($job, 'user'); + Queue::assertPushed(\App\Jobs\User\CreateJob::class, 1); - return $job_user->email === $data['login'] . '@' . $data['domain']; - }); + Queue::assertPushed( + \App\Jobs\User\CreateJob::class, + function ($job) use ($data) { + $userEmail = TestCase::getObjectProperty($job, 'userEmail'); + + return $userEmail === $data['login'] . '@' . $data['domain']; + } + ); // Check if the code has been removed $this->assertNull(SignupCode::find($code->id)); diff --git a/src/tests/Feature/Controller/WalletsTest.php b/src/tests/Feature/Controller/WalletsTest.php --- a/src/tests/Feature/Controller/WalletsTest.php +++ b/src/tests/Feature/Controller/WalletsTest.php @@ -64,7 +64,10 @@ $this->assertSame('You are out of credit, top up your balance now.', $notice); - // User/entitlements created today, balance=-9,99 CHF (monthly cost) + // User/entitlements created slightly more than a month ago, balance=9,99 CHF (monthly) + $wallet->owner->created_at = Carbon::now()->subMonthsWithoutOverflow(1)->subDays(1); + $wallet->owner->save(); + $wallet->balance = 999; $notice = $method->invoke($controller, $wallet); @@ -86,7 +89,7 @@ public function testReceiptDownload(): void { $user = $this->getTestUser('wallets-controller@kolabnow.com'); - $john = $this->getTestUser('john@klab.org'); + $john = $this->getTestUser('john@kolab.org'); $wallet = $user->wallets()->first(); // Unauth access not allowed @@ -129,7 +132,7 @@ public function testReceipts(): void { $user = $this->getTestUser('wallets-controller@kolabnow.com'); - $john = $this->getTestUser('john@klab.org'); + $john = $this->getTestUser('john@kolab.org'); $wallet = $user->wallets()->first(); $wallet->payments()->delete(); @@ -206,6 +209,7 @@ $this->assertSame('CHF', $json['currency']); $this->assertSame($wallet->balance, $json['balance']); $this->assertTrue(empty($json['description'])); + // TODO: This assertion does not work after a longer while from seeding $this->assertTrue(!empty($json['notice'])); } @@ -217,7 +221,7 @@ $package_kolab = \App\Package::where('title', 'kolab')->first(); $user = $this->getTestUser('wallets-controller@kolabnow.com'); $user->assignPackage($package_kolab); - $john = $this->getTestUser('john@klab.org'); + $john = $this->getTestUser('john@kolab.org'); $wallet = $user->wallets()->first(); // Unauth access not allowed diff --git a/src/tests/Feature/Documents/ReceiptTest.php b/src/tests/Feature/Documents/ReceiptTest.php --- a/src/tests/Feature/Documents/ReceiptTest.php +++ b/src/tests/Feature/Documents/ReceiptTest.php @@ -13,6 +13,15 @@ class ReceiptTest extends TestCase { + private $paymentIDs = ['AAA1', 'AAA2', 'AAA3', 'AAA4', 'AAA5', 'AAA6', 'AAA7']; + + public function setUp(): void + { + parent::setUp(); + + Payment::whereIn('id', $this->paymentIDs)->delete(); + } + /** * {@inheritDoc} */ @@ -20,6 +29,8 @@ { $this->deleteTestUser('receipt-test@kolabnow.com'); + Payment::whereIn('id', $this->paymentIDs)->delete(); + parent::tearDown(); } @@ -51,7 +62,7 @@ // The main table content $content = $dom->getElementById('content'); $records = $content->getElementsByTagName('tr'); - $this->assertCount(5, $records); + $this->assertCount(7, $records); $headerCells = $records[0]->getElementsByTagName('th'); $this->assertCount(3, $headerCells); @@ -70,13 +81,23 @@ $this->assertSame('0,01 CHF', $this->getNodeContent($cells[2])); $cells = $records[3]->getElementsByTagName('td'); $this->assertCount(3, $cells); - $this->assertSame('2020-05-31', $this->getNodeContent($cells[0])); + $this->assertSame('2020-05-21', $this->getNodeContent($cells[0])); $this->assertSame("$appName Services", $this->getNodeContent($cells[1])); $this->assertSame('1,00 CHF', $this->getNodeContent($cells[2])); - $summaryCells = $records[4]->getElementsByTagName('td'); + $cells = $records[4]->getElementsByTagName('td'); + $this->assertCount(3, $cells); + $this->assertSame('2020-05-30', $this->getNodeContent($cells[0])); + $this->assertSame("Refund", $this->getNodeContent($cells[1])); + $this->assertSame('-1,00 CHF', $this->getNodeContent($cells[2])); + $cells = $records[5]->getElementsByTagName('td'); + $this->assertCount(3, $cells); + $this->assertSame('2020-05-31', $this->getNodeContent($cells[0])); + $this->assertSame("Chargeback", $this->getNodeContent($cells[1])); + $this->assertSame('-0,10 CHF', $this->getNodeContent($cells[2])); + $summaryCells = $records[6]->getElementsByTagName('td'); $this->assertCount(2, $summaryCells); $this->assertSame('Total', $this->getNodeContent($summaryCells[0])); - $this->assertSame('13,35 CHF', $this->getNodeContent($summaryCells[1])); + $this->assertSame('12,25 CHF', $this->getNodeContent($summaryCells[1])); // Customer data $customer = $dom->getElementById('customer'); @@ -116,7 +137,7 @@ // The main table content $content = $dom->getElementById('content'); $records = $content->getElementsByTagName('tr'); - $this->assertCount(7, $records); + $this->assertCount(9, $records); $cells = $records[1]->getElementsByTagName('td'); $this->assertCount(3, $cells); @@ -130,21 +151,31 @@ $this->assertSame('0,01 CHF', $this->getNodeContent($cells[2])); $cells = $records[3]->getElementsByTagName('td'); $this->assertCount(3, $cells); - $this->assertSame('2020-05-31', $this->getNodeContent($cells[0])); + $this->assertSame('2020-05-21', $this->getNodeContent($cells[0])); $this->assertSame("$appName Services", $this->getNodeContent($cells[1])); $this->assertSame('0,92 CHF', $this->getNodeContent($cells[2])); - $subtotalCells = $records[4]->getElementsByTagName('td'); + $cells = $records[4]->getElementsByTagName('td'); + $this->assertCount(3, $cells); + $this->assertSame('2020-05-30', $this->getNodeContent($cells[0])); + $this->assertSame("Refund", $this->getNodeContent($cells[1])); + $this->assertSame('-0,92 CHF', $this->getNodeContent($cells[2])); + $cells = $records[5]->getElementsByTagName('td'); + $this->assertCount(3, $cells); + $this->assertSame('2020-05-31', $this->getNodeContent($cells[0])); + $this->assertSame("Chargeback", $this->getNodeContent($cells[1])); + $this->assertSame('-0,09 CHF', $this->getNodeContent($cells[2])); + $subtotalCells = $records[6]->getElementsByTagName('td'); $this->assertCount(2, $subtotalCells); $this->assertSame('Subtotal', $this->getNodeContent($subtotalCells[0])); - $this->assertSame('12,32 CHF', $this->getNodeContent($subtotalCells[1])); - $vatCells = $records[5]->getElementsByTagName('td'); + $this->assertSame('11,31 CHF', $this->getNodeContent($subtotalCells[1])); + $vatCells = $records[7]->getElementsByTagName('td'); $this->assertCount(2, $vatCells); $this->assertSame('VAT (7.7%)', $this->getNodeContent($vatCells[0])); - $this->assertSame('1,03 CHF', $this->getNodeContent($vatCells[1])); - $totalCells = $records[6]->getElementsByTagName('td'); + $this->assertSame('0,94 CHF', $this->getNodeContent($vatCells[1])); + $totalCells = $records[8]->getElementsByTagName('td'); $this->assertCount(2, $totalCells); $this->assertSame('Total', $this->getNodeContent($totalCells[0])); - $this->assertSame('13,35 CHF', $this->getNodeContent($totalCells[1])); + $this->assertSame('12,25 CHF', $this->getNodeContent($totalCells[1])); } /** @@ -185,7 +216,7 @@ // Create two payments out of the 2020-05 period // and three in it, plus one in the period but unpaid, - // and one with amount 0 + // and one with amount 0, and an extra refund and chanrgeback $payment = Payment::create([ 'id' => 'AAA1', @@ -235,7 +266,7 @@ $payment->updated_at = Carbon::create(2020, 5, 1, 0, 0, 0); $payment->save(); - // ... so we expect the last three on the receipt + // ... so we expect the five three on the receipt $payment = Payment::create([ 'id' => 'AAA5', 'status' => PaymentProvider::STATUS_PAID, @@ -269,6 +300,30 @@ 'provider' => 'stripe', 'amount' => 100, ]); + $payment->updated_at = Carbon::create(2020, 5, 21, 23, 59, 0); + $payment->save(); + + $payment = Payment::create([ + 'id' => 'ref1', + 'status' => PaymentProvider::STATUS_PAID, + 'type' => PaymentProvider::TYPE_REFUND, + 'description' => 'refund desc', + 'wallet_id' => $wallet->id, + 'provider' => 'stripe', + 'amount' => -100, + ]); + $payment->updated_at = Carbon::create(2020, 5, 30, 23, 59, 0); + $payment->save(); + + $payment = Payment::create([ + 'id' => 'chback1', + 'status' => PaymentProvider::STATUS_PAID, + 'type' => PaymentProvider::TYPE_CHARGEBACK, + 'description' => '', + 'wallet_id' => $wallet->id, + 'provider' => 'stripe', + 'amount' => -10, + ]); $payment->updated_at = Carbon::create(2020, 5, 31, 23, 59, 0); $payment->save(); diff --git a/src/tests/Feature/DomainTest.php b/src/tests/Feature/DomainTest.php --- a/src/tests/Feature/DomainTest.php +++ b/src/tests/Feature/DomainTest.php @@ -47,6 +47,27 @@ parent::tearDown(); } + /** + * Test domain create/creating observer + */ + public function testCreate(): void + { + Queue::fake(); + + $domain = Domain::create([ + 'namespace' => 'GMAIL.COM', + 'status' => Domain::STATUS_NEW, + 'type' => Domain::TYPE_EXTERNAL, + ]); + + $result = Domain::where('namespace', 'gmail.com')->first(); + + $this->assertSame('gmail.com', $result->namespace); + $this->assertSame($domain->id, $result->id); + $this->assertSame($domain->type, $result->type); + $this->assertSame(Domain::STATUS_NEW, $result->status); + } + /** * Test domain creating jobs */ @@ -62,19 +83,20 @@ 'type' => Domain::TYPE_EXTERNAL, ]); - Queue::assertPushed(\App\Jobs\DomainCreate::class, 1); + Queue::assertPushed(\App\Jobs\Domain\CreateJob::class, 1); Queue::assertPushed( - \App\Jobs\DomainCreate::class, + \App\Jobs\Domain\CreateJob::class, function ($job) use ($domain) { - $job_domain = TestCase::getObjectProperty($job, 'domain'); + $domainId = TestCase::getObjectProperty($job, 'domainId'); + $domainNamespace = TestCase::getObjectProperty($job, 'domainNamespace'); - return $job_domain->id === $domain->id && - $job_domain->namespace === $domain->namespace; + return $domainId === $domain->id && + $domainNamespace === $domain->namespace; } ); - $job = new \App\Jobs\DomainCreate($domain); + $job = new \App\Jobs\Domain\CreateJob($domain->id); $job->handle(); } @@ -187,7 +209,7 @@ $this->assertFalse($domain->fresh()->isDeleted()); // Delete the domain for real - $job = new \App\Jobs\DomainDelete($domain->id); + $job = new \App\Jobs\Domain\DeleteJob($domain->id); $job->handle(); $this->assertTrue(Domain::withTrashed()->where('id', $domain->id)->first()->isDeleted()); diff --git a/src/tests/Feature/EntitlementTest.php b/src/tests/Feature/EntitlementTest.php --- a/src/tests/Feature/EntitlementTest.php +++ b/src/tests/Feature/EntitlementTest.php @@ -150,18 +150,32 @@ $wallet = $user->wallets()->first(); - $this->backdateEntitlements($user->entitlements, Carbon::now()->subWeeks(7)); + $backdate = Carbon::now()->subWeeks(7); + $this->backdateEntitlements($user->entitlements, $backdate); $charge = $wallet->chargeEntitlements(); - $this->assertTrue($wallet->balance < 0); + $this->assertSame(-1099, $wallet->balance); $balance = $wallet->balance; + $discount = \App\Discount::where('discount', 30)->first(); + $wallet->discount()->associate($discount); + $wallet->save(); $user->removeSku($storage, 4); - // we expect the wallet to have been charged. - $this->assertTrue($wallet->fresh()->balance < $balance); + // we expect the wallet to have been charged for ~3 weeks of use of + // 4 deleted storage entitlements, it should also take discount into account + $backdate->addMonthsWithoutOverflow(1); + $diffInDays = $backdate->diffInDays(Carbon::now()); + + // entitlements-num * cost * discount * days-in-month + $max = intval(4 * 25 * 0.7 * $diffInDays / 28); + $min = intval(4 * 25 * 0.7 * $diffInDays / 31); + + $wallet->refresh(); + $this->assertTrue($wallet->balance >= $balance - $max); + $this->assertTrue($wallet->balance <= $balance - $min); $transactions = \App\Transaction::where('object_id', $wallet->id) ->where('object_type', \App\Wallet::class)->get(); diff --git a/src/tests/Feature/Jobs/DomainCreateTest.php b/src/tests/Feature/Jobs/DomainCreateTest.php --- a/src/tests/Feature/Jobs/DomainCreateTest.php +++ b/src/tests/Feature/Jobs/DomainCreateTest.php @@ -2,7 +2,6 @@ namespace Tests\Feature\Jobs; -use App\Jobs\DomainCreate; use App\Domain; use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Queue; @@ -48,20 +47,21 @@ Queue::fake(); Queue::assertNothingPushed(); - $job = new DomainCreate($domain); + $job = new \App\Jobs\Domain\CreateJob($domain->id); $job->handle(); $this->assertTrue($domain->fresh()->isLdapReady()); - Queue::assertPushed(\App\Jobs\DomainVerify::class, 1); + Queue::assertPushed(\App\Jobs\Domain\VerifyJob::class, 1); Queue::assertPushed( - \App\Jobs\DomainVerify::class, + \App\Jobs\Domain\VerifyJob::class, function ($job) use ($domain) { - $job_domain = TestCase::getObjectProperty($job, 'domain'); + $domainId = TestCase::getObjectProperty($job, 'domainId'); + $domainNamespace = TestCase::getObjectProperty($job, 'domainNamespace'); - return $job_domain->id === $domain->id && - $job_domain->namespace === $domain->namespace; + return $domainId === $domain->id && + $domainNamespace === $domain->namespace; } ); } diff --git a/src/tests/Feature/Jobs/DomainVerifyTest.php b/src/tests/Feature/Jobs/DomainVerifyTest.php --- a/src/tests/Feature/Jobs/DomainVerifyTest.php +++ b/src/tests/Feature/Jobs/DomainVerifyTest.php @@ -2,7 +2,6 @@ namespace Tests\Feature\Jobs; -use App\Jobs\DomainVerify; use App\Domain; use Illuminate\Support\Facades\Mail; use Tests\TestCase; @@ -45,7 +44,7 @@ $this->assertFalse($domain->isVerified()); - $job = new DomainVerify($domain); + $job = new \App\Jobs\Domain\VerifyJob($domain->id); $job->handle(); $this->assertTrue($domain->fresh()->isVerified()); @@ -68,7 +67,7 @@ $this->assertFalse($domain->isVerified()); - $job = new DomainVerify($domain); + $job = new \App\Jobs\Domain\VerifyJob($domain->id); $job->handle(); $this->assertFalse($domain->fresh()->isVerified()); diff --git a/src/tests/Feature/Jobs/PaymentEmailTest.php b/src/tests/Feature/Jobs/PaymentEmailTest.php --- a/src/tests/Feature/Jobs/PaymentEmailTest.php +++ b/src/tests/Feature/Jobs/PaymentEmailTest.php @@ -69,8 +69,8 @@ Mail::assertSent(PaymentSuccess::class, 1); // Assert the mail was sent to the user's email - Mail::assertSent(PaymentSuccess::class, function ($mail) use ($user) { - return $mail->hasTo($user->email) && $mail->hasCc('ext@email.tld'); + Mail::assertSent(PaymentSuccess::class, function ($mail) { + return $mail->hasTo('ext@email.tld') && !$mail->hasCc('ext@email.tld'); }); $payment->status = PaymentProvider::STATUS_FAILED; @@ -83,8 +83,8 @@ Mail::assertSent(PaymentFailure::class, 1); // Assert the mail was sent to the user's email - Mail::assertSent(PaymentFailure::class, function ($mail) use ($user) { - return $mail->hasTo($user->email) && $mail->hasCc('ext@email.tld'); + Mail::assertSent(PaymentFailure::class, function ($mail) { + return $mail->hasTo('ext@email.tld') && !$mail->hasCc('ext@email.tld'); }); $payment->status = PaymentProvider::STATUS_EXPIRED; diff --git a/src/tests/Feature/Jobs/PaymentMandateDisabledEmailTest.php b/src/tests/Feature/Jobs/PaymentMandateDisabledEmailTest.php --- a/src/tests/Feature/Jobs/PaymentMandateDisabledEmailTest.php +++ b/src/tests/Feature/Jobs/PaymentMandateDisabledEmailTest.php @@ -56,8 +56,8 @@ Mail::assertSent(PaymentMandateDisabled::class, 1); // Assert the mail was sent to the user's email - Mail::assertSent(PaymentMandateDisabled::class, function ($mail) use ($user) { - return $mail->hasTo($user->email) && $mail->hasCc('ext@email.tld'); + Mail::assertSent(PaymentMandateDisabled::class, function ($mail) { + return $mail->hasTo('ext@email.tld') && !$mail->hasCc('ext@email.tld'); }); } } diff --git a/src/tests/Feature/Jobs/UserCreateTest.php b/src/tests/Feature/Jobs/UserCreateTest.php --- a/src/tests/Feature/Jobs/UserCreateTest.php +++ b/src/tests/Feature/Jobs/UserCreateTest.php @@ -2,7 +2,7 @@ namespace Tests\Feature\Jobs; -use App\Jobs\UserCreate; +use App\User; use Tests\TestCase; class UserCreateTest extends TestCase @@ -35,9 +35,44 @@ $this->assertFalse($user->isLdapReady()); - $job = new UserCreate($user); + $job = new \App\Jobs\User\CreateJob($user->id); $job->handle(); $this->assertTrue($user->fresh()->isLdapReady()); + $this->assertFalse($job->hasFailed()); + + // Test job failures + $job = new \App\Jobs\User\CreateJob($user->id); + $job->handle(); + + $this->assertTrue($job->hasFailed()); + $this->assertSame("User {$user->id} is already marked as ldap-ready.", $job->failureMessage); + + $user->status |= User::STATUS_DELETED; + $user->save(); + + $job = new \App\Jobs\User\CreateJob($user->id); + $job->handle(); + + $this->assertTrue($job->hasFailed()); + $this->assertSame("User {$user->id} is marked as deleted.", $job->failureMessage); + + $user->status ^= User::STATUS_DELETED; + $user->save(); + $user->delete(); + + $job = new \App\Jobs\User\CreateJob($user->id); + $job->handle(); + + $this->assertTrue($job->hasFailed()); + $this->assertSame("User {$user->id} is actually deleted.", $job->failureMessage); + + // TODO: Test failures on domain sanity checks + + $job = new \App\Jobs\User\CreateJob(123); + $job->handle(); + + $this->assertTrue($job->hasFailed()); + $this->assertSame("User 123 could not be found in the database.", $job->failureMessage); } } diff --git a/src/tests/Feature/Jobs/UserUpdateTest.php b/src/tests/Feature/Jobs/UserUpdateTest.php --- a/src/tests/Feature/Jobs/UserUpdateTest.php +++ b/src/tests/Feature/Jobs/UserUpdateTest.php @@ -3,7 +3,6 @@ namespace Tests\Feature\Jobs; use App\Backends\LDAP; -use App\Jobs\UserUpdate; use Illuminate\Support\Facades\Queue; use Tests\TestCase; @@ -42,7 +41,7 @@ $user = $this->getTestUser('new-job-user@' . \config('app.domain')); // Create the user in LDAP - $job = new \App\Jobs\UserCreate($user); + $job = new \App\Jobs\User\CreateJob($user->id); $job->handle(); // Test setting two aliases @@ -50,9 +49,10 @@ 'new-job-user1@' . \config('app.domain'), 'new-job-user2@' . \config('app.domain'), ]; + $user->setAliases($aliases); - $job = new UserUpdate($user->fresh()); + $job = new \App\Jobs\User\UpdateJob($user->id); $job->handle(); $ldap_user = LDAP::getUser('new-job-user@' . \config('app.domain')); @@ -63,9 +63,10 @@ $aliases = [ 'new-job-user1@' . \config('app.domain'), ]; + $user->setAliases($aliases); - $job = new UserUpdate($user->fresh()); + $job = new \App\Jobs\User\UpdateJob($user->id); $job->handle(); $ldap_user = LDAP::getUser('new-job-user@' . \config('app.domain')); @@ -76,7 +77,7 @@ $aliases = []; $user->setAliases($aliases); - $job = new UserUpdate($user->fresh()); + $job = new \App\Jobs\User\UpdateJob($user->id); $job->handle(); $ldap_user = LDAP::getUser('new-job-user@' . \config('app.domain')); diff --git a/src/tests/Feature/Jobs/UserVerifyTest.php b/src/tests/Feature/Jobs/UserVerifyTest.php --- a/src/tests/Feature/Jobs/UserVerifyTest.php +++ b/src/tests/Feature/Jobs/UserVerifyTest.php @@ -2,8 +2,6 @@ namespace Tests\Feature\Jobs; -use App\Jobs\UserCreate; -use App\Jobs\UserVerify; use App\User; use Illuminate\Support\Facades\Queue; use Tests\TestCase; @@ -44,6 +42,7 @@ Queue::fake(); $user = $this->getTestUser('ned@kolab.org'); + if ($user->isImapReady()) { $user->status ^= User::STATUS_IMAP_READY; $user->save(); @@ -52,7 +51,7 @@ $this->assertFalse($user->isImapReady()); for ($i = 0; $i < 10; $i++) { - $job = new UserVerify($user); + $job = new \App\Jobs\User\VerifyJob($user->id); $job->handle(); if ($user->fresh()->isImapReady()) { diff --git a/src/tests/Feature/Jobs/WalletCheckTest.php b/src/tests/Feature/Jobs/WalletCheckTest.php --- a/src/tests/Feature/Jobs/WalletCheckTest.php +++ b/src/tests/Feature/Jobs/WalletCheckTest.php @@ -115,10 +115,36 @@ } /** - * Test job handle, reminder notification + * Test job handle, top-up before reminder notification * * @depends testHandleInitial */ + public function testHandleBeforeReminder(): void + { + Mail::fake(); + + $user = $this->getTestUser('ned@kolab.org'); + $wallet = $user->wallets()->first(); + $now = Carbon::now(); + + // Balance turned negative 7-1 days ago + $wallet->setSetting('balance_negative_since', $now->subDays(7 - 1)->toDateTimeString()); + + $job = new WalletCheck($wallet); + $res = $job->handle(); + + Mail::assertNothingSent(); + + // TODO: Test that it actually executed the topUpWallet() + $this->assertSame(WalletCheck::THRESHOLD_BEFORE_REMINDER, $res); + $this->assertFalse($user->fresh()->isSuspended()); + } + + /** + * Test job handle, reminder notification + * + * @depends testHandleBeforeReminder + */ public function testHandleReminder(): void { Mail::fake(); @@ -149,10 +175,37 @@ } /** - * Test job handle, account suspending + * Test job handle, top-up wallet before account suspending * * @depends testHandleReminder */ + public function testHandleBeforeSuspended(): void + { + Mail::fake(); + + $user = $this->getTestUser('ned@kolab.org'); + $wallet = $user->wallets()->first(); + $now = Carbon::now(); + + // Balance turned negative 7+14-1 days ago + $days = 7 + 14 - 1; + $wallet->setSetting('balance_negative_since', $now->subDays($days)->toDateTimeString()); + + $job = new WalletCheck($wallet); + $res = $job->handle(); + + Mail::assertNothingSent(); + + // TODO: Test that it actually executed the topUpWallet() + $this->assertSame(WalletCheck::THRESHOLD_BEFORE_SUSPEND, $res); + $this->assertFalse($user->fresh()->isSuspended()); + } + + /** + * Test job handle, account suspending + * + * @depends testHandleBeforeSuspended + */ public function testHandleSuspended(): void { Mail::fake(); diff --git a/src/tests/Feature/UserTest.php b/src/tests/Feature/UserTest.php --- a/src/tests/Feature/UserTest.php +++ b/src/tests/Feature/UserTest.php @@ -55,50 +55,10 @@ $this->markTestIncomplete(); } - /** - * Verify user creation process - */ - public function testUserCreateJob(): void - { - // Fake the queue, assert that no jobs were pushed... - Queue::fake(); - Queue::assertNothingPushed(); - - $user = User::create([ - 'email' => 'user-test@' . \config('app.domain') - ]); - - Queue::assertPushed(\App\Jobs\UserCreate::class, 1); - Queue::assertPushed(\App\Jobs\UserCreate::class, function ($job) use ($user) { - $job_user = TestCase::getObjectProperty($job, 'user'); - - return $job_user->id === $user->id - && $job_user->email === $user->email; - }); - - Queue::assertPushedWithChain(\App\Jobs\UserCreate::class, [ - \App\Jobs\UserVerify::class, - ]); -/* - FIXME: Looks like we can't really do detailed assertions on chained jobs - Another thing to consider is if we maybe should run these jobs - independently (not chained) and make sure there's no race-condition - in status update - - Queue::assertPushed(\App\Jobs\UserVerify::class, 1); - Queue::assertPushed(\App\Jobs\UserVerify::class, function ($job) use ($user) { - $job_user = TestCase::getObjectProperty($job, 'user'); - - return $job_user->id === $user->id - && $job_user->email === $user->email; - }); -*/ - } - /** * Verify a wallet assigned a controller is among the accounts of the assignee. */ - public function testListUserAccounts(): void + public function testAccounts(): void { $userA = $this->getTestUser('UserAccountA@UserAccount.com'); $userB = $this->getTestUser('UserAccountB@UserAccount.com'); @@ -114,11 +74,6 @@ $this->assertTrue($userB->accounts()->get()[0]->id === $userA->wallets()->get()[0]->id); } - public function testAccounts(): void - { - $this->markTestIncomplete(); - } - public function testCanDelete(): void { $this->markTestIncomplete(); @@ -134,6 +89,73 @@ $this->markTestIncomplete(); } + /** + * Test user create/creating observer + */ + public function testCreate(): void + { + Queue::fake(); + + $domain = \config('app.domain'); + + $user = User::create(['email' => 'USER-test@' . \strtoupper($domain)]); + + $result = User::where('email', 'user-test@' . $domain)->first(); + + $this->assertSame('user-test@' . $domain, $result->email); + $this->assertSame($user->id, $result->id); + $this->assertSame(User::STATUS_NEW | User::STATUS_ACTIVE, $result->status); + } + + /** + * Verify user creation process + */ + public function testCreateJobs(): void + { + // Fake the queue, assert that no jobs were pushed... + Queue::fake(); + Queue::assertNothingPushed(); + + $user = User::create([ + 'email' => 'user-test@' . \config('app.domain') + ]); + + Queue::assertPushed(\App\Jobs\User\CreateJob::class, 1); + + Queue::assertPushed( + \App\Jobs\User\CreateJob::class, + function ($job) use ($user) { + $userEmail = TestCase::getObjectProperty($job, 'userEmail'); + $userId = TestCase::getObjectProperty($job, 'userId'); + + return $userEmail === $user->email + && $userId === $user->id; + } + ); + + Queue::assertPushedWithChain( + \App\Jobs\User\CreateJob::class, + [ + \App\Jobs\User\VerifyJob::class, + ] + ); +/* + FIXME: Looks like we can't really do detailed assertions on chained jobs + Another thing to consider is if we maybe should run these jobs + independently (not chained) and make sure there's no race-condition + in status update + + Queue::assertPushed(\App\Jobs\User\VerifyJob::class, 1); + Queue::assertPushed(\App\Jobs\User\VerifyJob::class, function ($job) use ($user) { + $userEmail = TestCase::getObjectProperty($job, 'userEmail'); + $userId = TestCase::getObjectProperty($job, 'userId'); + + return $userEmail === $user->email + && $userId === $user->id; + }); +*/ + } + /** * Tests for User::domains() */ @@ -204,7 +226,7 @@ $this->assertFalse($user->fresh()->isDeleted()); // Delete the user for real - $job = new \App\Jobs\UserDelete($id); + $job = new \App\Jobs\User\DeleteJob($id); $job->handle(); $this->assertTrue(User::withTrashed()->where('id', $id)->first()->isDeleted()); @@ -341,7 +363,7 @@ // Add an alias $user->setAliases(['UserAlias1@UserAccount.com']); - Queue::assertPushed(\App\Jobs\UserUpdate::class, 1); + Queue::assertPushed(\App\Jobs\User\UpdateJob::class, 1); $aliases = $user->aliases()->get(); $this->assertCount(1, $aliases); @@ -350,7 +372,7 @@ // Add another alias $user->setAliases(['UserAlias1@UserAccount.com', 'UserAlias2@UserAccount.com']); - Queue::assertPushed(\App\Jobs\UserUpdate::class, 2); + Queue::assertPushed(\App\Jobs\User\UpdateJob::class, 2); $aliases = $user->aliases()->orderBy('alias')->get(); $this->assertCount(2, $aliases); @@ -360,7 +382,7 @@ // Remove an alias $user->setAliases(['UserAlias1@UserAccount.com']); - Queue::assertPushed(\App\Jobs\UserUpdate::class, 3); + Queue::assertPushed(\App\Jobs\User\UpdateJob::class, 3); $aliases = $user->aliases()->get(); $this->assertCount(1, $aliases); @@ -369,7 +391,7 @@ // Remove all aliases $user->setAliases([]); - Queue::assertPushed(\App\Jobs\UserUpdate::class, 4); + Queue::assertPushed(\App\Jobs\User\UpdateJob::class, 4); $this->assertCount(0, $user->aliases()->get()); @@ -418,7 +440,7 @@ $user = $this->getTestUser('UserAccountA@UserAccount.com'); - Queue::assertPushed(\App\Jobs\UserUpdate::class, 0); + Queue::assertPushed(\App\Jobs\User\UpdateJob::class, 0); // Test default settings // Note: Technicly this tests UserObserver::created() behavior @@ -432,7 +454,7 @@ // Add a setting $user->setSetting('first_name', 'Firstname'); - Queue::assertPushed(\App\Jobs\UserUpdate::class, 1); + Queue::assertPushed(\App\Jobs\User\UpdateJob::class, 1); // Note: We test both current user as well as fresh user object // to make sure cache works as expected @@ -442,7 +464,7 @@ // Update a setting $user->setSetting('first_name', 'Firstname1'); - Queue::assertPushed(\App\Jobs\UserUpdate::class, 2); + Queue::assertPushed(\App\Jobs\User\UpdateJob::class, 2); // Note: We test both current user as well as fresh user object // to make sure cache works as expected @@ -452,7 +474,7 @@ // Delete a setting (null) $user->setSetting('first_name', null); - Queue::assertPushed(\App\Jobs\UserUpdate::class, 3); + Queue::assertPushed(\App\Jobs\User\UpdateJob::class, 3); // Note: We test both current user as well as fresh user object // to make sure cache works as expected @@ -463,7 +485,7 @@ $user->setSetting('first_name', 'Firstname1'); $user->setSetting('first_name', ''); - Queue::assertPushed(\App\Jobs\UserUpdate::class, 5); + Queue::assertPushed(\App\Jobs\User\UpdateJob::class, 5); // Note: We test both current user as well as fresh user object // to make sure cache works as expected @@ -478,7 +500,7 @@ ]); // TODO: This really should create a single UserUpdate job, not 3 - Queue::assertPushed(\App\Jobs\UserUpdate::class, 7); + Queue::assertPushed(\App\Jobs\User\UpdateJob::class, 7); // Note: We test both current user as well as fresh user object // to make sure cache works as expected diff --git a/src/tests/Feature/WalletTest.php b/src/tests/Feature/WalletTest.php --- a/src/tests/Feature/WalletTest.php +++ b/src/tests/Feature/WalletTest.php @@ -44,8 +44,6 @@ parent::tearDown(); } - - /** * Test that turning wallet balance from negative to positive * unsuspends the account @@ -88,7 +86,10 @@ // User/entitlements created today, balance=0 $until = $wallet->balanceLastsUntil(); - $this->assertSame(Carbon::now()->toDateString(), $until->toDateString()); + $this->assertSame( + Carbon::now()->addMonthsWithoutOverflow(1)->toDateString(), + $until->toDateString() + ); // User/entitlements created today, balance=-10 CHF $wallet->balance = -1000; @@ -103,7 +104,7 @@ $daysInLastMonth = \App\Utils::daysInLastMonth(); $this->assertSame( - Carbon::now()->addDays($daysInLastMonth)->toDateString(), + Carbon::now()->addMonthsWithoutOverflow(1)->addDays($daysInLastMonth)->toDateString(), $until->toDateString() ); diff --git a/src/tests/Functional/Methods/DomainTest.php b/src/tests/Functional/Methods/DomainTest.php new file mode 100644 --- /dev/null +++ b/src/tests/Functional/Methods/DomainTest.php @@ -0,0 +1,115 @@ +domain = $this->getTestDomain( + 'test.domain', + [ + 'status' => \App\Domain::STATUS_CONFIRMED | \App\Domain::STATUS_VERIFIED, + 'type' => \App\Domain::TYPE_EXTERNAL + ] + ); + } + + public function tearDown(): void + { + $this->deleteTestDomain('test.domain'); + + parent::tearDown(); + } + + /** + * Verify we can suspend an active domain. + */ + public function testSuspendForActiveDomain() + { + Queue::fake(); + + $this->domain->status |= \App\Domain::STATUS_ACTIVE; + + $this->assertFalse($this->domain->isSuspended()); + $this->assertTrue($this->domain->isActive()); + + $this->domain->suspend(); + + $this->assertTrue($this->domain->isSuspended()); + $this->assertFalse($this->domain->isActive()); + } + + /** + * Verify we can unsuspend a suspended domain + */ + public function testUnsuspendForSuspendedDomain() + { + Queue::fake(); + + $this->domain->status |= \App\Domain::STATUS_SUSPENDED; + + $this->assertTrue($this->domain->isSuspended()); + $this->assertFalse($this->domain->isActive()); + + $this->domain->unsuspend(); + + $this->assertFalse($this->domain->isSuspended()); + $this->assertTrue($this->domain->isActive()); + } + + /** + * Verify we can unsuspend a suspended domain that wasn't confirmed + */ + public function testUnsuspendForSuspendedUnconfirmedDomain() + { + Queue::fake(); + + $this->domain->status = \App\Domain::STATUS_NEW | \App\Domain::STATUS_SUSPENDED; + + $this->assertTrue($this->domain->isNew()); + $this->assertTrue($this->domain->isSuspended()); + $this->assertFalse($this->domain->isActive()); + $this->assertFalse($this->domain->isConfirmed()); + $this->assertFalse($this->domain->isVerified()); + + $this->domain->unsuspend(); + + $this->assertTrue($this->domain->isNew()); + $this->assertFalse($this->domain->isSuspended()); + $this->assertFalse($this->domain->isActive()); + $this->assertFalse($this->domain->isConfirmed()); + $this->assertFalse($this->domain->isVerified()); + } + + /** + * Verify we can unsuspend a suspended domain that was verified but not confirmed + */ + public function testUnsuspendForSuspendedVerifiedUnconfirmedDomain() + { + Queue::fake(); + + $this->domain->status = \App\Domain::STATUS_NEW | \App\Domain::STATUS_SUSPENDED | \App\Domain::STATUS_VERIFIED; + + $this->assertTrue($this->domain->isNew()); + $this->assertTrue($this->domain->isSuspended()); + $this->assertFalse($this->domain->isActive()); + $this->assertFalse($this->domain->isConfirmed()); + $this->assertTrue($this->domain->isVerified()); + + $this->domain->unsuspend(); + + $this->assertTrue($this->domain->isNew()); + $this->assertFalse($this->domain->isSuspended()); + $this->assertFalse($this->domain->isActive()); + $this->assertFalse($this->domain->isConfirmed()); + $this->assertTrue($this->domain->isVerified()); + } +} diff --git a/src/tests/TestCase.php b/src/tests/TestCase.php --- a/src/tests/TestCase.php +++ b/src/tests/TestCase.php @@ -14,6 +14,10 @@ $entitlement->created_at = $targetDate; $entitlement->updated_at = $targetDate; $entitlement->save(); + + $owner = $entitlement->wallet->owner; + $owner->created_at = $targetDate; + $owner->save(); } } diff --git a/src/tests/TestCaseTrait.php b/src/tests/TestCaseTrait.php --- a/src/tests/TestCaseTrait.php +++ b/src/tests/TestCaseTrait.php @@ -117,7 +117,7 @@ return; } - $job = new \App\Jobs\DomainDelete($domain->id); + $job = new \App\Jobs\Domain\DeleteJob($domain->id); $job->handle(); $domain->forceDelete(); @@ -133,7 +133,7 @@ return; } - $job = new \App\Jobs\UserDelete($user->id); + $job = new \App\Jobs\User\DeleteJob($user->id); $job->handle(); $user->forceDelete(); diff --git a/src/tests/Unit/DomainTest.php b/src/tests/Unit/DomainTest.php --- a/src/tests/Unit/DomainTest.php +++ b/src/tests/Unit/DomainTest.php @@ -24,38 +24,59 @@ $domains = \App\Utils::powerSet($statuses); - foreach ($domains as $domain_statuses) { + foreach ($domains as $domainStatuses) { $domain = new Domain( [ 'namespace' => 'test.com', - 'status' => \array_sum($domain_statuses), + 'status' => \array_sum($domainStatuses), 'type' => Domain::TYPE_EXTERNAL ] ); - $this->assertTrue($domain->isNew() === in_array(Domain::STATUS_NEW, $domain_statuses)); - $this->assertTrue($domain->isActive() === in_array(Domain::STATUS_ACTIVE, $domain_statuses)); - $this->assertTrue($domain->isConfirmed() === in_array(Domain::STATUS_CONFIRMED, $domain_statuses)); - $this->assertTrue($domain->isSuspended() === in_array(Domain::STATUS_SUSPENDED, $domain_statuses)); - $this->assertTrue($domain->isDeleted() === in_array(Domain::STATUS_DELETED, $domain_statuses)); - $this->assertTrue($domain->isLdapReady() === in_array(Domain::STATUS_LDAP_READY, $domain_statuses)); - $this->assertTrue($domain->isVerified() === in_array(Domain::STATUS_VERIFIED, $domain_statuses)); - } - } + $domainStatuses = []; - /** - * Test setStatusAttribute exception - */ - public function testDomainStatusInvalid(): void - { - $this->expectException(\Exception::class); - - $domain = new Domain( - [ - 'namespace' => 'test.com', - 'status' => 1234567, - ] - ); + foreach ($statuses as $status) { + if ($domain->status & $status) { + $domainStatuses[] = $status; + } + } + + $this->assertSame($domain->status, \array_sum($domainStatuses)); + + // either one is true, but not both + $this->assertSame( + $domain->isNew() === in_array(Domain::STATUS_NEW, $domainStatuses), + $domain->isActive() === in_array(Domain::STATUS_ACTIVE, $domainStatuses) + ); + + $this->assertTrue( + $domain->isNew() === in_array(Domain::STATUS_NEW, $domainStatuses) + ); + + $this->assertTrue( + $domain->isActive() === in_array(Domain::STATUS_ACTIVE, $domainStatuses) + ); + + $this->assertTrue( + $domain->isConfirmed() === in_array(Domain::STATUS_CONFIRMED, $domainStatuses) + ); + + $this->assertTrue( + $domain->isSuspended() === in_array(Domain::STATUS_SUSPENDED, $domainStatuses) + ); + + $this->assertTrue( + $domain->isDeleted() === in_array(Domain::STATUS_DELETED, $domainStatuses) + ); + + $this->assertTrue( + $domain->isLdapReady() === in_array(Domain::STATUS_LDAP_READY, $domainStatuses) + ); + + $this->assertTrue( + $domain->isVerified() === in_array(Domain::STATUS_VERIFIED, $domainStatuses) + ); + } } /** diff --git a/src/tests/Unit/Methods/DomainTest.php b/src/tests/Unit/Methods/DomainTest.php new file mode 100644 --- /dev/null +++ b/src/tests/Unit/Methods/DomainTest.php @@ -0,0 +1,161 @@ +domain = new \App\Domain(); + } + + /** + * Test lower-casing namespace attribute. + */ + public function testSetNamespaceAttributeLowercases() + { + $this->domain = new \App\Domain(); + + $this->domain->namespace = 'UPPERCASE'; + + $this->assertTrue($this->domain->namespace === 'uppercase'); // @phpstan-ignore-line + } + + /** + * Test setting the status to something invalid + */ + public function testSetStatusAttributeInvalid() + { + $this->expectException(\Exception::class); + + $this->domain->status = 123456; + } + + /** + * Test public domain. + */ + public function testSetStatusAttributeOnPublicDomain() + { + $this->domain->{'type'} = \App\Domain::TYPE_PUBLIC; + + $this->domain->status = 115; + + $this->assertTrue($this->domain->status == 115); + } + + /** + * Test status mutations + */ + public function testSetStatusAttributeActiveMakesForNotNew() + { + $this->domain->status = \App\Domain::STATUS_NEW; + + $this->assertTrue($this->domain->isNew()); + $this->assertFalse($this->domain->isActive()); + + $this->domain->status |= \App\Domain::STATUS_ACTIVE; + + $this->assertFalse($this->domain->isNew()); + $this->assertTrue($this->domain->isActive()); + } + + /** + * Verify setting confirmed sets verified. + */ + public function testSetStatusAttributeConfirmedMakesForVerfied() + { + $this->domain->status = \App\Domain::STATUS_CONFIRMED; + + $this->assertTrue($this->domain->isConfirmed()); + $this->assertTrue($this->domain->isVerified()); + } + + /** + * Verify setting confirmed sets active. + */ + public function testSetStatusAttributeConfirmedMakesForActive() + { + $this->domain->status = \App\Domain::STATUS_CONFIRMED; + + $this->assertTrue($this->domain->isConfirmed()); + $this->assertTrue($this->domain->isActive()); + } + + /** + * Verify setting deleted drops active. + */ + public function testSetStatusAttributeDeletedVoidsActive() + { + $this->domain->status = \App\Domain::STATUS_ACTIVE; + + $this->assertTrue($this->domain->isActive()); + $this->assertFalse($this->domain->isNew()); + $this->assertFalse($this->domain->isDeleted()); + + $this->domain->status |= \App\Domain::STATUS_DELETED; + + $this->assertFalse($this->domain->isActive()); + $this->assertFalse($this->domain->isNew()); + $this->assertTrue($this->domain->isDeleted()); + } + + /** + * Verify setting suspended drops active. + */ + public function testSetStatusAttributeSuspendedVoidsActive() + { + $this->domain->status = \App\Domain::STATUS_ACTIVE; + + $this->assertTrue($this->domain->isActive()); + $this->assertFalse($this->domain->isSuspended()); + + $this->domain->status |= \App\Domain::STATUS_SUSPENDED; + + $this->assertFalse($this->domain->isActive()); + $this->assertTrue($this->domain->isSuspended()); + } + + /** + * Verify we can suspend a suspended domain without disaster. + * + * This doesn't change anything to trigger a save. + */ + public function testSuspendForSuspendedDomain() + { + $this->domain->status = \App\Domain::STATUS_ACTIVE; + + $this->domain->status |= \App\Domain::STATUS_SUSPENDED; + + $this->assertTrue($this->domain->isSuspended()); + $this->assertFalse($this->domain->isActive()); + + $this->domain->suspend(); + + $this->assertTrue($this->domain->isSuspended()); + $this->assertFalse($this->domain->isActive()); + } + + /** + * Verify we can unsuspend an active (unsuspended) domain + * + * This doesn't change anything to trigger a save. + */ + public function testUnsuspendForActiveDomain() + { + $this->domain->status = \App\Domain::STATUS_ACTIVE; + + $this->assertFalse($this->domain->isSuspended()); + $this->assertTrue($this->domain->isActive()); + + $this->domain->unsuspend(); + + $this->assertFalse($this->domain->isSuspended()); + $this->assertTrue($this->domain->isActive()); + } +} diff --git a/src/tests/Unit/UtilsTest.php b/src/tests/Unit/UtilsTest.php --- a/src/tests/Unit/UtilsTest.php +++ b/src/tests/Unit/UtilsTest.php @@ -9,10 +9,8 @@ { /** * Test for Utils::powerSet() - * - * @return void */ - public function testPowerSet() + public function testPowerSet(): void { $set = []; @@ -54,12 +52,37 @@ $this->assertTrue(in_array(["a1", "a2", "a3"], $result)); } + /** + * Test for Utils::serviceUrl() + */ + public function testServiceUrl(): void + { + $public_href = 'https://public.url/cockpit'; + $local_href = 'https://local.url/cockpit'; + + \config([ + 'app.url' => $local_href, + 'app.public_url' => '', + ]); + + $this->assertSame($local_href, Utils::serviceUrl('')); + $this->assertSame($local_href . '/unknown', Utils::serviceUrl('unknown')); + $this->assertSame($local_href . '/unknown', Utils::serviceUrl('/unknown')); + + \config([ + 'app.url' => $local_href, + 'app.public_url' => $public_href, + ]); + + $this->assertSame($public_href, Utils::serviceUrl('')); + $this->assertSame($public_href . '/unknown', Utils::serviceUrl('unknown')); + $this->assertSame($public_href . '/unknown', Utils::serviceUrl('/unknown')); + } + /** * Test for Utils::uuidInt() - * - * @return void */ - public function testUuidInt() + public function testUuidInt(): void { $result = Utils::uuidInt(); @@ -69,10 +92,8 @@ /** * Test for Utils::uuidStr() - * - * @return void */ - public function testUuidStr() + public function testUuidStr(): void { $result = Utils::uuidStr();