diff --git a/.angular-cli.json b/.angular-cli.json deleted file mode 100644 index 3cc20c7..0000000 --- a/.angular-cli.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "project": { - "name": "ludos-data" - }, - "apps": [ - { - "root": "src", - "outDir": "dist", - "assets": [ - "assets", - "favicon.ico" - ], - "index": "index.html", - "main": "main.ts", - "polyfills": "polyfills.ts", - "test": "test.ts", - "tsconfig": "tsconfig.app.json", - "testTsconfig": "tsconfig.spec.json", - "prefix": "app", - "styles": [ - "styles.css" - ], - "scripts": [ - - ], - "environmentSource": "environments/environment.ts", - "environments": { - "dev": "environments/environment.ts", - "prod": "environments/environment.prod.ts" - } - } - ], - "e2e": { - "protractor": { - "config": "./protractor.conf.js" - } - }, - "lint": [ - { - "project": "src/tsconfig.app.json", - "exclude": "**/node_modules/**" - }, - { - "project": "src/tsconfig.spec.json", - "exclude": "**/node_modules/**" - }, - { - "project": "e2e/tsconfig.e2e.json", - "exclude": "**/node_modules/**" - } - ], - "test": { - "karma": { - "config": "./karma.conf.js" - } - }, - "defaults": { - "styleExt": "css", - "component": {} - } -} diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..56e18c9 --- /dev/null +++ b/.env.example @@ -0,0 +1,33 @@ +# Copy to .env and fill in. .env is gitignored — never commit real secrets. +# +# The 2018 version of this project committed its live database password to the +# repository, which is why it now has to be treated as compromised. Keep secrets +# in .env, and keep .env out of git. + +# --- Required -------------------------------------------------------------- +# JWT signing key. Minimum 32 characters; the API refuses to start without it. +# Generate one with: openssl rand -base64 48 +JWT_KEY= + +# --- First-run seeding ------------------------------------------------------ +# On a database with no users, the API creates this account and imports the 105 +# games recovered from the 2018 MySQL dump. Once a user exists, this is ignored. +# Password rules: 12+ chars, upper, lower and a digit. +SEED_USERNAME=ckoch +SEED_EMAIL=you@example.com +SEED_PASSWORD= + +# Set to false once you are past first run, or to start with an empty library. +SEED_ENABLED=true + +# --- Optional --------------------------------------------------------------- +# Host port the web UI is published on. +WEB_PORT=8080 + +# Token lifetime in minutes. Default is 12 hours; there is no refresh flow, so +# expiry returns you to the login form. +JWT_LIFETIME_MINUTES=720 + +JWT_ISSUER=LudosData +JWT_AUDIENCE=LudosData +CORS_ORIGIN=http://localhost:8080 diff --git a/.gitignore b/.gitignore index 8298940..92459cc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,48 +1,39 @@ -# See http://help.github.com/ignore-files/ for more about ignoring files. +# Secrets — never commit. The 2018 version of this project committed live +# database credentials, which is why they now have to be treated as compromised. +.env +*.env +!.env.example -# compiled output -/dist -/dist-server -/tmp -/out-tsc +# Runtime state +data/ +uploads/ +*.db +*.db-shm +*.db-wal -# dependencies -/node_modules +# --- Frontend --- +node_modules/ +frontend/dist/ +frontend/.angular/ +npm-debug.log* +yarn-error.log* +testem.log -# IDEs and editors -/.idea +# --- Backend --- +[Bb]in/ +[Oo]bj/ +*.user +.vs/ + +# --- Editors / OS --- +.idea/ .project .classpath -.c9/ -*.launch -.settings/ *.sublime-workspace - -# IDE - VSCode .vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json !.vscode/extensions.json - -# misc -/.sass-cache -/connect.lock -/coverage -/libpeerconnection.log -npm-debug.log -testem.log -/typings - -# e2e -/e2e/*.js -/e2e/*.map - -# System Files +!.vscode/launch.json +!.vscode/tasks.json .DS_Store Thumbs.db -src/app/game-grid/game-grid.component.html -src/app/game-grid/game-grid.component.html -src/app/game-grid/game-grid.component.ts -src/app/game-grid/game-grid.component.html -src/app/games.service.ts +*.swp diff --git a/README.md b/README.md index 904add8..84b344a 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,173 @@ # LudosData -This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.7.0. +A personal video game library: catalogue what you own, what you've dumped, +played and finished. -## Development server +Originally built in 2018 on Angular 5 + PHP + MySQL. Rebuilt in 2026 on +**Angular 22** and **ASP.NET Core 10** with **SQLite**, running in Docker. -Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. +--- -## Code scaffolding +## Quick start -Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. +```bash +cp .env.example .env +# Generate a signing key and put it in .env as JWT_KEY: +openssl rand -base64 48 +# Also set SEED_USERNAME / SEED_EMAIL / SEED_PASSWORD for the first account. -## Build +docker compose up --build +``` -Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build. +Then open and sign in with the seed credentials. -## Running unit tests +On first run the API creates that account and imports the **105 games** recovered +from the 2018 database dump. Seeding only happens while the database has no users. -Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). +> **Password rules:** 12+ characters, with an uppercase, a lowercase and a digit. +> The API refuses to start if `JWT_KEY` is missing or shorter than 32 characters — +> that is deliberate, so a misconfigured deployment fails loudly instead of +> signing tokens with a guessable key. -## Running end-to-end tests +--- -Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). +## Layout -## Further help +``` +backend/ ASP.NET Core 10 Web API (C#) + src/LudosData.Api/ + Domain/ Game, AppUser + Data/ DbContext, migrations, seeder, games.json + Auth/ JWT options, token service + Controllers/ auth, games, images + Services/ image storage +frontend/ Angular 22 SPA + src/app/ + core/ models, services, guard, HTTP interceptor + features/ login, register, game-grid, game-edit, account + shared/ toolbar, confirm dialog +archive/ the original 2018 MySQL dump, for provenance +``` -To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). +Everything stateful lives in one Docker volume (`ludos-data`): the SQLite file, +uploaded box art, and the Data Protection keys. Back that volume up and you have +backed up the whole application. + +--- + +## Development + +Host tooling (Node 24, .NET 10) is installed via Homebrew. `dotnet-ef` needs +`~/.dotnet/tools` on `PATH`, which `~/.bashrc.d/dotnet.sh` sets up. + +```bash +# API on http://localhost:5099 +cd backend/src/LudosData.Api +Jwt__Key="a-dev-key-of-at-least-32-characters!!" dotnet run + +# SPA on http://localhost:4200, proxying /api and /uploads to :5099 +cd frontend +npm start +``` + +```bash +cd frontend && npm test # vitest +cd backend && dotnet build # 0 warnings expected +``` + +### Database changes + +```bash +cd backend/src/LudosData.Api +dotnet ef migrations add --output-dir Data/Migrations +``` + +Migrations are applied automatically at startup. + +--- + +## API + +All `/api/games` and `/api/images` routes require `Authorization: Bearer `. + +| Method | Route | Notes | +| --- | --- | --- | +| `POST` | `/api/auth/register` | Returns a token; signs the new user straight in | +| `POST` | `/api/auth/login` | Returns `{ token, expiresAt, user }` | +| `GET` | `/api/auth/me` | Current user | +| `GET` | `/api/auth/available?userName=` / `?email=` | Returns only a boolean | +| `GET` | `/api/games` | `search, system, genre, own, dumped, played, finished, page, pageSize, sort, dir` | +| `GET` | `/api/games/{id}` | | +| `GET` | `/api/games/facets` | Distinct systems and genres, for filter dropdowns | +| `POST` | `/api/games` | | +| `PUT` | `/api/games/{id}` | | +| `DELETE` | `/api/games/{id}` | | +| `POST` | `/api/images` | multipart `file`; re-encodes to WebP | +| `GET` | `/health` | Anonymous | + +**Ownership is always taken from the JWT subject, never from the request.** A game +belonging to another user returns `404`, not `403`, so the response does not +confirm that the id exists. + +--- + +## Security notes + +### Rotate the old database password + +The 2018 code committed live MySQL credentials to this repository +(`interfaceServices/dbConfig.php`, and again in four other files). They are in git +history. **That password must be considered compromised and rotated**, regardless +of this rewrite. Removing the files does not remove them from history. + +The new stack keeps secrets in `.env`, which is gitignored. + +### What was fixed in the rewrite + +The old backend was ~16,700 lines of PHP, of which ~16,400 were vendored +third-party code — four near-identical copies of `php-crud-api` plus +`class.upload.php`. Only ~150 lines were application logic. These problems were +not carried across: + +| Old behaviour | Now | +| --- | --- | +| Two endpoints exposed unauthenticated CRUD over every table | Every data route requires a valid token | +| Client chose whose rows to read (`filter[]=userId,eq,N`) | Owner comes from the JWT subject, server-side | +| Login hardcoded to a single username | Any registered user can sign in | +| `crypt()` with one global salt, silently truncating passwords to 8 chars | ASP.NET Core Identity (PBKDF2, per-user salt) | +| JWT secret was the literal string `"testing"`, tokens never expired | Key required from config, 12-hour expiry | +| Token passed in the query string | `Authorization: Bearer` header | +| Uploads anonymous, path built from the client filename | Authenticated, server-generated name, per-user folder, must decode as an image | +| `Access-Control-Allow-Origin: *` | Explicit origin allowlist | + +Passwords could not be migrated — the old hashes are unrecoverable by design. + +### Known accepted risk + +`npm audit` reports a moderate advisory in `@hono/node-server`, reached +transitively through `@angular/cli`'s MCP server feature. It is: + +- **dev-only** — `npm audit --omit=dev` reports 0 vulnerabilities, and it is not in the browser bundle +- a **Windows-only** path traversal, on a Linux-only toolchain here + +`npm audit fix --force` would downgrade Angular CLI to 21.0.4, a breaking change. +Overriding the dependency means forcing a major bump the MCP SDK does not accept +(`^1.19.9`). Left as-is deliberately; revisit when Angular CLI updates the SDK. + +--- + +## Notable version facts (as of 2026-08) + +- **Angular 22.1** is **zoneless** — there is no `zone.js` in the dependency tree. + Component state must be signal-based for change detection to see it. +- **`@angular/animations` is deprecated in v22**; Material 22 no longer depends on + it. There is no `provideAnimations()` in `app.config.ts`, and a test pins that + Material still renders without one. +- Unit tests run on **Vitest**, not Karma/Jasmine. +- Fonts and Material icons are bundled from `node_modules`, so the app makes no + third-party requests at runtime. +- The backend pins two transitive packages (`Microsoft.OpenApi`, + `SQLitePCLRaw.lib.e_sqlite3`) to clear high-severity advisories. See the comment + in `LudosData.Api.csproj`. +- Image processing uses **SkiaSharp**, not ImageSharp: ImageSharp v4 requires a + paid licence key at build time. diff --git a/archive/README.md b/archive/README.md new file mode 100644 index 0000000..de2a893 --- /dev/null +++ b/archive/README.md @@ -0,0 +1,18 @@ +# Archive + +`lazypugn_LudosData_2018-03-14_20-31-02.sql.zip` is the original MySQL dump of the +2018 database, kept for provenance. + +It contains a single `games` table with 105 rows. It predates the multi-user work +that was in progress when the project was last touched, so it has **no `users` +table and no `userId` column** — every game in it is unowned. + +It has been converted to +[`backend/src/LudosData.Api/Data/Seed/games.json`](../backend/src/LudosData.Api/Data/Seed/games.json), +which the API imports on first run. Nothing reads the zip at runtime. + +Conversion notes: +- `id` was dropped; the new table assigns its own keys. +- Empty strings became `null`, matching the new nullable columns. +- `tinyint(1)` flags became booleans. +- `Art` and `Description` were empty on every row, so no images were migrated. diff --git a/archive/lazypugn_LudosData_2018-03-14_20-31-02.sql.zip b/archive/lazypugn_LudosData_2018-03-14_20-31-02.sql.zip new file mode 100644 index 0000000..1531196 Binary files /dev/null and b/archive/lazypugn_LudosData_2018-03-14_20-31-02.sql.zip differ diff --git a/authlogin/README.md b/authlogin/README.md deleted file mode 100644 index 1113f5f..0000000 --- a/authlogin/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# PHP-API-AUTH - -Single file PHP script that adds authentication to a [PHP-CRUD-API](https://github.com/mevdschee/php-crud-api) project. - -## Requirements - - - PHP 5.3 or higher - -## Simple username + password - -On API server - -- login.html is loaded -- sends username + password via POST to "api.php/" -- api.php (POST on "/" gets hijacked by auth.php) is loaded -- sends back csrf token + http-only session cookie -- call API as: api.php?csrf=\[csrf token] (session cookie is sent automatically) -- (when using Angular2 or Vue2 the CSRF token is sent automatically) - -## With authentication server - -On authentication server - -- login_token.html is loaded -- sends username + password via POST to "login_token.php" -- login_token.php is loaded -- sends token via POST to "api.php/" - -On API server - -- api.php (POST on "/" gets hijacked by auth.php) is loaded -- sends back csrf token + http-only session cookie -- call API as: api.php?csrf=\[csrf token] (session cookie is sent automatically) -- (when using Angular2 or Vue2 the CSRF token is sent automatically) diff --git a/authlogin/api.php b/authlogin/api.php deleted file mode 100644 index 5bab1c5..0000000 --- a/authlogin/api.php +++ /dev/null @@ -1,36 +0,0 @@ -'someVeryLongPassPhraseChangeMe', -)); -if ($auth->executeCommand()) exit(0); -if (empty($_SESSION['user']) || !$auth->hasValidCsrfToken()) { - header('HTTP/1.0 401 Unauthorized'); - exit(0); -} -*/ - -// for form+session based authentication (see "login.html"): - -require 'auth.php'; -$auth = new PHP_API_AUTH(array( - 'authenticator'=>function($user,$pass){ if ($user=='admin' && $pass=='admin') $_SESSION['user']=$user; } -)); -if ($auth->executeCommand()) exit(0); -if (empty($_SESSION['user']) || !$auth->hasValidCsrfToken()) { - header('HTTP/1.0 401 Unauthorized'); -exit(0); -} - -// include your api code here: -// -// see: https://github.com/mevdschee/php-crud-api -// -// placeholder for testing: -// echo 'Access granted!'; diff --git a/authlogin/auth.php b/authlogin/auth.php deleted file mode 100644 index f068394..0000000 --- a/authlogin/auth.php +++ /dev/null @@ -1,223 +0,0 @@ -settings = compact('verb', 'path', 'username', 'password', 'token', 'authenticator', 'method', 'request', 'post', 'origin', 'time', 'leeway', 'ttl', 'algorithm', 'secret', 'allow_origin'); - } - - - protected function retrieveInput($post) { - $input = (object)array(); - $data = trim(file_get_contents($post)); - if (strlen($data)>0) { - if ($data[0]=='{') { - $input = json_decode($data); - } else { - parse_str($data, $input); - $input = (object)$input; - } - } - return $input; - } - - protected function generateToken($claims,$time,$ttl,$algorithm,$secret) { - $algorithms = array('HS256'=>'sha256','HS384'=>'sha384','HS512'=>'sha512'); - $header = array(); - $header['typ']='JWT'; - $header['alg']=$algorithm; - $token = array(); - $token[0] = rtrim(strtr(base64_encode(json_encode((object)$header)),'+/','-_'),'='); - $claims['iat'] = $time; - $claims['exp'] = $time + $ttl; - $token[1] = rtrim(strtr(base64_encode(json_encode((object)$claims)),'+/','-_'),'='); - if (!isset($algorithms[$algorithm])) return false; - $hmac = $algorithms[$algorithm]; - $signature = hash_hmac($hmac,"$token[0].$token[1]",$secret,true); - $token[2] = rtrim(strtr(base64_encode($signature),'+/','-_'),'='); - return implode('.',$token); - } - - protected function getVerifiedClaims($token,$time,$leeway,$ttl,$algorithm,$secret) { - $algorithms = array('HS256'=>'sha256','HS384'=>'sha384','HS512'=>'sha512'); - if (!isset($algorithms[$algorithm])) return false; - $hmac = $algorithms[$algorithm]; - $token = explode('.',$token); - if (count($token)<3) return false; - $header = json_decode(base64_decode(strtr($token[0],'-_','+/')),true); - if (!$secret) return false; - if ($header['typ']!='JWT') return false; - if ($header['alg']!=$algorithm) return false; - $signature = bin2hex(base64_decode(strtr($token[2],'-_','+/'))); - if ($signature!=hash_hmac($hmac,"$token[0].$token[1]",$secret)) return false; - $claims = json_decode(base64_decode(strtr($token[1],'-_','+/')),true); - if (!$claims) return false; - if (isset($claims['nbf']) && $time+$leeway<$claims['nbf']) return false; - if (isset($claims['iat']) && $time+$leeway<$claims['iat']) return false; - if (isset($claims['exp']) && $time-$leeway>$claims['exp']) return false; - if (isset($claims['iat']) && !isset($claims['exp'])) { - if ($time-$leeway>$claims['iat']+$ttl) return false; - } - return $claims; - } - - protected function allowOrigin($origin,$allowOrigins) { - if (isset($_SERVER['REQUEST_METHOD'])) { - header('Access-Control-Allow-Credentials: true'); - header('Access-Control-Expose-Headers: X-XSRF-TOKEN'); - foreach (explode(',',$allowOrigins) as $o) { - if (preg_match('/^'.str_replace('\*','.*',preg_quote(strtolower(trim($o)))).'$/',$origin)) { - header('Access-Control-Allow-Origin: '.$origin); - break; - } - } - } - } - - protected function headersCommand() { - $headers = array(); - $headers[]='Access-Control-Allow-Headers: Content-Type, X-XSRF-TOKEN'; - $headers[]='Access-Control-Allow-Methods: OPTIONS, GET, PUT, POST, DELETE, PATCH'; - $headers[]='Access-Control-Allow-Credentials: true'; - $headers[]='Access-Control-Max-Age: 1728000'; - if (isset($_SERVER['REQUEST_METHOD'])) { - foreach ($headers as $header) header($header); - } else { - echo json_encode($headers); - } - } - - public function hasValidCsrfToken() { - $csrf = isset($_SESSION['csrf'])?$_SESSION['csrf']:false; - if (!$csrf) return false; - $get = isset($_GET['csrf'])?$_GET['csrf']:false; - $header = isset($_SERVER['HTTP_X_XSRF_TOKEN'])?$_SERVER['HTTP_X_XSRF_TOKEN']:false; - return ($get == $csrf) || ($header == $csrf); - } - - public function executeCommand() { - extract($this->settings); - if ($origin) { - $this->allowOrigin($origin,$allow_origin); - } - if ($method=='OPTIONS') { - $this->headersCommand(); - return true; - } - $no_session = $authenticator && $secret; - if (!$no_session) { - ini_set('session.cookie_httponly', 1); - session_start(); - if (!isset($_SESSION['csrf'])) { - if (function_exists('random_int')) $_SESSION['csrf'] = 'N'.random_int(0,PHP_INT_MAX); - else $_SESSION['csrf'] = 'N'.rand(0,PHP_INT_MAX); - } - } - if ($method==$verb && trim($path,'/')==$request) { - $input = $this->retrieveInput($post); - if ($authenticator && isset($input->$username) && isset($input->$password)) { - $authenticator($input->$username,$input->$password); - if ($no_session) { - echo json_encode($this->generateToken($_SESSION,$time,$ttl,$algorithm,$secret)); - } else { - session_regenerate_id(); - setcookie('XSRF-TOKEN',$_SESSION['csrf'],0,'/'); - header('X-XSRF-TOKEN: '.$_SESSION['csrf']); - echo json_encode($_SESSION['csrf']); - } - } elseif ($secret && isset($input->$token)) { - $claims = $this->getVerifiedClaims($input->$token,$time,$leeway,$ttl,$algorithm,$secret); - if ($claims) { - foreach ($claims as $key=>$value) { - $_SESSION[$key] = $value; - } - session_regenerate_id(); - setcookie('XSRF-TOKEN',$_SESSION['csrf'],0,'/'); - header('X-XSRF-TOKEN: '.$_SESSION['csrf']); - echo json_encode($_SESSION['csrf']); - } - } else { - if (!$no_session) { - session_destroy(); - } - } - return true; - } - return false; - } -} diff --git a/authlogin/login.html b/authlogin/login.html deleted file mode 100644 index 27e5624..0000000 --- a/authlogin/login.html +++ /dev/null @@ -1,5 +0,0 @@ -
- - - -
diff --git a/authlogin/loginInterface.php b/authlogin/loginInterface.php deleted file mode 100644 index f02b8db..0000000 --- a/authlogin/loginInterface.php +++ /dev/null @@ -1,2787 +0,0 @@ -function($user,$pass){ if ($user=='admin' && $pass=='admin') $_SESSION['user']=$user; } -)); -if ($auth->executeCommand()) exit(0); -if (empty($_SESSION['user']) || !$auth->hasValidCsrfToken()) { - header('HTTP/1.0 401 Unauthorized'); -exit(0); -} - - -interface DatabaseInterface { - public function getSql($name); - public function connect($hostname,$username,$password,$database,$port,$socket,$charset); - public function query($sql,$params=array()); - public function fetchAssoc($result); - public function fetchRow($result); - public function insertId($result); - public function affectedRows($result); - public function close($result); - public function fetchFields($table); - public function addLimitToSql($sql,$limit,$offset); - public function likeEscape($string); - public function isNumericType($field); - public function isBinaryType($field); - public function isGeometryType($field); - public function isJsonType($field); - public function getDefaultCharset(); - public function beginTransaction(); - public function commitTransaction(); - public function rollbackTransaction(); - public function jsonEncode($object); - public function jsonDecode($string); -} - -class MySQL implements DatabaseInterface { - - protected $db; - protected $queries; - - public function __construct() { - $this->queries = array( - 'list_tables'=>'SELECT - "TABLE_NAME","TABLE_COMMENT" - FROM - "INFORMATION_SCHEMA"."TABLES" - WHERE - "TABLE_SCHEMA" = ?', - 'reflect_table'=>'SELECT - "TABLE_NAME" - FROM - "INFORMATION_SCHEMA"."TABLES" - WHERE - "TABLE_NAME" COLLATE \'utf8_bin\' = ? AND - "TABLE_SCHEMA" = ?', - 'reflect_pk'=>'SELECT - "COLUMN_NAME" - FROM - "INFORMATION_SCHEMA"."COLUMNS" - WHERE - "COLUMN_KEY" = \'PRI\' AND - "TABLE_NAME" = ? AND - "TABLE_SCHEMA" = ?', - 'reflect_belongs_to'=>'SELECT - "TABLE_NAME","COLUMN_NAME", - "REFERENCED_TABLE_NAME","REFERENCED_COLUMN_NAME" - FROM - "INFORMATION_SCHEMA"."KEY_COLUMN_USAGE" - WHERE - "TABLE_NAME" COLLATE \'utf8_bin\' = ? AND - "REFERENCED_TABLE_NAME" COLLATE \'utf8_bin\' IN ? AND - "TABLE_SCHEMA" = ? AND - "REFERENCED_TABLE_SCHEMA" = ?', - 'reflect_has_many'=>'SELECT - "TABLE_NAME","COLUMN_NAME", - "REFERENCED_TABLE_NAME","REFERENCED_COLUMN_NAME" - FROM - "INFORMATION_SCHEMA"."KEY_COLUMN_USAGE" - WHERE - "TABLE_NAME" COLLATE \'utf8_bin\' IN ? AND - "REFERENCED_TABLE_NAME" COLLATE \'utf8_bin\' = ? AND - "TABLE_SCHEMA" = ? AND - "REFERENCED_TABLE_SCHEMA" = ?', - 'reflect_habtm'=>'SELECT - k1."TABLE_NAME", k1."COLUMN_NAME", - k1."REFERENCED_TABLE_NAME", k1."REFERENCED_COLUMN_NAME", - k2."TABLE_NAME", k2."COLUMN_NAME", - k2."REFERENCED_TABLE_NAME", k2."REFERENCED_COLUMN_NAME" - FROM - "INFORMATION_SCHEMA"."KEY_COLUMN_USAGE" k1, - "INFORMATION_SCHEMA"."KEY_COLUMN_USAGE" k2 - WHERE - k1."TABLE_SCHEMA" = ? AND - k2."TABLE_SCHEMA" = ? AND - k1."REFERENCED_TABLE_SCHEMA" = ? AND - k2."REFERENCED_TABLE_SCHEMA" = ? AND - k1."TABLE_NAME" COLLATE \'utf8_bin\' = k2."TABLE_NAME" COLLATE \'utf8_bin\' AND - k1."REFERENCED_TABLE_NAME" COLLATE \'utf8_bin\' = ? AND - k2."REFERENCED_TABLE_NAME" COLLATE \'utf8_bin\' IN ?', - 'reflect_columns'=> 'SELECT - "COLUMN_NAME", "COLUMN_DEFAULT", "IS_NULLABLE", "DATA_TYPE", "CHARACTER_MAXIMUM_LENGTH" - FROM - "INFORMATION_SCHEMA"."COLUMNS" - WHERE - "TABLE_NAME" = ? AND - "TABLE_SCHEMA" = ? - ORDER BY - "ORDINAL_POSITION"' - ); - } - - public function getSql($name) { - return isset($this->queries[$name])?$this->queries[$name]:false; - } - - public function connect($hostname,$username,$password,$database,$port,$socket,$charset) { - $db = mysqli_init(); - if (defined('MYSQLI_OPT_INT_AND_FLOAT_NATIVE')) { - mysqli_options($db,MYSQLI_OPT_INT_AND_FLOAT_NATIVE,true); - } - $success = mysqli_real_connect($db,$hostname,$username,$password,$database,$port,$socket,MYSQLI_CLIENT_FOUND_ROWS); - if (!$success) { - throw new \Exception('Connect failed. '.mysqli_connect_error()); - } - if (!mysqli_set_charset($db,$charset)) { - throw new \Exception('Error setting charset. '.mysqli_error($db)); - } - if (!mysqli_query($db,'SET SESSION sql_mode = \'ANSI_QUOTES\';')) { - throw new \Exception('Error setting ANSI quotes. '.mysqli_error($db)); - } - $this->db = $db; - } - - public function query($sql,$params=array()) { - $db = $this->db; - $sql = preg_replace_callback('/\!|\?/', function ($matches) use (&$db,&$params) { - $param = array_shift($params); - if ($matches[0]=='!') { - $key = preg_replace('/[^a-zA-Z0-9\-_=<> ]/','',is_object($param)?$param->key:$param); - if (is_object($param) && $param->type=='hex') { - return "HEX(\"$key\") as \"$key\""; - } - if (is_object($param) && $param->type=='wkt') { - return "ST_AsText(\"$key\") as \"$key\""; - } - return '"'.$key.'"'; - } else { - if (is_array($param)) return '('.implode(',',array_map(function($v) use (&$db) { - return "'".mysqli_real_escape_string($db,$v)."'"; - },$param)).')'; - if (is_object($param) && $param->type=='hex') { - return "x'".$param->value."'"; - } - if (is_object($param) && $param->type=='wkt') { - return "ST_GeomFromText('".mysqli_real_escape_string($db,$param->value)."')"; - } - if ($param===null) return 'NULL'; - return "'".mysqli_real_escape_string($db,$param)."'"; - } - }, $sql); - //if (!strpos($sql,'INFORMATION_SCHEMA')) echo "\n$sql\n"; - //if (!strpos($sql,'INFORMATION_SCHEMA')) file_put_contents('log.txt',"\n$sql\n",FILE_APPEND); - return mysqli_query($db,$sql); - } - - public function fetchAssoc($result) { - return mysqli_fetch_assoc($result); - } - - public function fetchRow($result) { - return mysqli_fetch_row($result); - } - - public function insertId($result) { - return mysqli_insert_id($this->db); - } - - public function affectedRows($result) { - return mysqli_affected_rows($this->db); - } - - public function close($result) { - return mysqli_free_result($result); - } - - public function fetchFields($table) { - $result = $this->query('SELECT * FROM ! WHERE 1=2;',array($table)); - return mysqli_fetch_fields($result); - } - - public function addLimitToSql($sql,$limit,$offset) { - return "$sql LIMIT $limit OFFSET $offset"; - } - - public function likeEscape($string) { - return addcslashes($string,'%_'); - } - - public function convertFilter($field, $comparator, $value) { - return false; - } - - public function isNumericType($field) { - return in_array($field->type,array(1,2,3,4,5,6,8,9)); - } - - public function isBinaryType($field) { - //echo "$field->name: $field->type ($field->flags)\n"; - return (($field->flags & 128) && (($field->type>=249 && $field->type<=252) || ($field->type>=253 && $field->type<=254 && $field->charsetnr==63))); - } - - public function isGeometryType($field) { - return ($field->type==255); - } - - public function isJsonType($field) { - return ($field->type==245); - } - - public function getDefaultCharset() { - return 'utf8'; - } - - public function beginTransaction() { - mysqli_query($this->db,'BEGIN'); - //return mysqli_begin_transaction($this->db); - } - - public function commitTransaction() { - mysqli_query($this->db,'COMMIT'); - //return mysqli_commit($this->db); - } - - public function rollbackTransaction() { - mysqli_query($this->db,'ROLLBACK'); - //return mysqli_rollback($this->db); - } - - public function jsonEncode($object) { - return json_encode($object); - } - - public function jsonDecode($string) { - return json_decode($string); - } -} - -class PostgreSQL implements DatabaseInterface { - - protected $db; - protected $queries; - - public function __construct() { - $this->queries = array( - 'list_tables'=>'select - "table_name",\'\' as "table_comment" - from - "information_schema"."tables" - where - "table_schema" = \'public\' and - "table_catalog" = ?', - 'reflect_table'=>'select - "table_name" - from - "information_schema"."tables" - where - "table_name" = ? and - "table_schema" = \'public\' and - "table_catalog" = ?', - 'reflect_pk'=>'select - "column_name" - from - "information_schema"."table_constraints" tc, - "information_schema"."key_column_usage" ku - where - tc."constraint_type" = \'PRIMARY KEY\' and - tc."constraint_name" = ku."constraint_name" and - ku."table_name" = ? and - ku."table_schema" = \'public\' and - ku."table_catalog" = ?', - 'reflect_belongs_to'=>'select - cu1."table_name",cu1."column_name", - cu2."table_name",cu2."column_name" - from - "information_schema".referential_constraints rc, - "information_schema".key_column_usage cu1, - "information_schema".key_column_usage cu2 - where - cu1."constraint_name" = rc."constraint_name" and - cu2."constraint_name" = rc."unique_constraint_name" and - cu1."table_name" = ? and - cu2."table_name" in ? and - cu1."table_schema" = \'public\' and - cu2."table_schema" = \'public\' and - cu1."table_catalog" = ? and - cu2."table_catalog" = ?', - 'reflect_has_many'=>'select - cu1."table_name",cu1."column_name", - cu2."table_name",cu2."column_name" - from - "information_schema".referential_constraints rc, - "information_schema".key_column_usage cu1, - "information_schema".key_column_usage cu2 - where - cu1."constraint_name" = rc."constraint_name" and - cu2."constraint_name" = rc."unique_constraint_name" and - cu1."table_name" in ? and - cu2."table_name" = ? and - cu1."table_schema" = \'public\' and - cu2."table_schema" = \'public\' and - cu1."table_catalog" = ? and - cu2."table_catalog" = ?', - 'reflect_habtm'=>'select - cua1."table_name",cua1."column_name", - cua2."table_name",cua2."column_name", - cub1."table_name",cub1."column_name", - cub2."table_name",cub2."column_name" - from - "information_schema".referential_constraints rca, - "information_schema".referential_constraints rcb, - "information_schema".key_column_usage cua1, - "information_schema".key_column_usage cua2, - "information_schema".key_column_usage cub1, - "information_schema".key_column_usage cub2 - where - cua1."constraint_name" = rca."constraint_name" and - cua2."constraint_name" = rca."unique_constraint_name" and - cub1."constraint_name" = rcb."constraint_name" and - cub2."constraint_name" = rcb."unique_constraint_name" and - cua1."table_catalog" = ? and - cub1."table_catalog" = ? and - cua2."table_catalog" = ? and - cub2."table_catalog" = ? and - cua1."table_schema" = \'public\' and - cub1."table_schema" = \'public\' and - cua2."table_schema" = \'public\' and - cub2."table_schema" = \'public\' and - cua1."table_name" = cub1."table_name" and - cua2."table_name" = ? and - cub2."table_name" in ?', - 'reflect_columns'=> 'select - "column_name", "column_default", "is_nullable", "data_type", "character_maximum_length" - from - "information_schema"."columns" - where - "table_name" = ? and - "table_schema" = \'public\' and - "table_catalog" = ? - order by - "ordinal_position"' - ); - } - - public function getSql($name) { - return isset($this->queries[$name])?$this->queries[$name]:false; - } - - public function connect($hostname,$username,$password,$database,$port,$socket,$charset) { - $e = function ($v) { return str_replace(array('\'','\\'),array('\\\'','\\\\'),$v); }; - $conn_string = ''; - if ($hostname || $socket) { - if ($socket) $hostname = $e($socket); - else $hostname = $e($hostname); - $conn_string.= " host='$hostname'"; - } - if ($port) { - $port = ($port+0); - $conn_string.= " port='$port'"; - } - if ($database) { - $database = $e($database); - $conn_string.= " dbname='$database'"; - } - if ($username) { - $username = $e($username); - $conn_string.= " user='$username'"; - } - if ($password) { - $password = $e($password); - $conn_string.= " password='$password'"; - } - if ($charset) { - $charset = $e($charset); - $conn_string.= " options='--client_encoding=$charset'"; - } - $db = pg_connect($conn_string); - $this->db = $db; - } - - public function query($sql,$params=array()) { - $db = $this->db; - $sql = preg_replace_callback('/\!|\?/', function ($matches) use (&$db,&$params) { - $param = array_shift($params); - if ($matches[0]=='!') { - $key = preg_replace('/[^a-zA-Z0-9\-_=<> ]/','',is_object($param)?$param->key:$param); - if (is_object($param) && $param->type=='hex') { - return "encode(\"$key\",'hex') as \"$key\""; - } - if (is_object($param) && $param->type=='wkt') { - return "ST_AsText(\"$key\") as \"$key\""; - } - return '"'.$key.'"'; - } else { - if (is_array($param)) return '('.implode(',',array_map(function($v) use (&$db) { - return "'".pg_escape_string($db,$v)."'"; - },$param)).')'; - if (is_object($param) && $param->type=='hex') { - return "'\x".$param->value."'"; - } - if (is_object($param) && $param->type=='wkt') { - return "ST_GeomFromText('".pg_escape_string($db,$param->value)."')"; - } - if ($param===null) return 'NULL'; - return "'".pg_escape_string($db,$param)."'"; - } - }, $sql); - if (strtoupper(substr($sql,0,6))=='INSERT') { - $sql .= ' RETURNING id;'; - } - //echo "\n$sql\n"; - return @pg_query($db,$sql); - } - - public function fetchAssoc($result) { - return pg_fetch_assoc($result); - } - - public function fetchRow($result) { - return pg_fetch_row($result); - } - - public function insertId($result) { - list($id) = pg_fetch_row($result); - return (int)$id; - } - - public function affectedRows($result) { - return pg_affected_rows($result); - } - - public function close($result) { - return pg_free_result($result); - } - - public function fetchFields($table) { - $result = $this->query('SELECT * FROM ! WHERE 1=2;',array($table)); - $keys = array(); - for($i=0;$itype, array('int2', 'int4', 'int8', 'float4', 'float8')); - } - - public function isBinaryType($field) { - return $field->type == 'bytea'; - } - - public function isGeometryType($field) { - return $field->type == 'geometry'; - } - - public function isJsonType($field) { - return in_array($field->type,array('json','jsonb')); - } - - public function getDefaultCharset() { - return 'UTF8'; - } - - public function beginTransaction() { - return $this->query('BEGIN'); - } - - public function commitTransaction() { - return $this->query('COMMIT'); - } - - public function rollbackTransaction() { - return $this->query('ROLLBACK'); - } - - public function jsonEncode($object) { - return json_encode($object); - } - - public function jsonDecode($string) { - return json_decode($string); - } -} - -class SQLServer implements DatabaseInterface { - - protected $db; - protected $queries; - - public function __construct() { - $this->queries = array( - 'list_tables'=>'SELECT - "TABLE_NAME",\'\' as "TABLE_COMMENT" - FROM - "INFORMATION_SCHEMA"."TABLES" - WHERE - "TABLE_CATALOG" = ?', - 'reflect_table'=>'SELECT - "TABLE_NAME" - FROM - "INFORMATION_SCHEMA"."TABLES" - WHERE - "TABLE_NAME" = ? AND - "TABLE_CATALOG" = ?', - 'reflect_pk'=>'SELECT - "COLUMN_NAME" - FROM - "INFORMATION_SCHEMA"."TABLE_CONSTRAINTS" tc, - "INFORMATION_SCHEMA"."KEY_COLUMN_USAGE" ku - WHERE - tc."CONSTRAINT_TYPE" = \'PRIMARY KEY\' AND - tc."CONSTRAINT_NAME" = ku."CONSTRAINT_NAME" AND - ku."TABLE_NAME" = ? AND - ku."TABLE_CATALOG" = ?', - 'reflect_belongs_to'=>'SELECT - cu1."TABLE_NAME",cu1."COLUMN_NAME", - cu2."TABLE_NAME",cu2."COLUMN_NAME" - FROM - "INFORMATION_SCHEMA".REFERENTIAL_CONSTRAINTS rc, - "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cu1, - "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cu2 - WHERE - cu1."CONSTRAINT_NAME" = rc."CONSTRAINT_NAME" AND - cu2."CONSTRAINT_NAME" = rc."UNIQUE_CONSTRAINT_NAME" AND - cu1."TABLE_NAME" = ? AND - cu2."TABLE_NAME" IN ? AND - cu1."TABLE_CATALOG" = ? AND - cu2."TABLE_CATALOG" = ?', - 'reflect_has_many'=>'SELECT - cu1."TABLE_NAME",cu1."COLUMN_NAME", - cu2."TABLE_NAME",cu2."COLUMN_NAME" - FROM - "INFORMATION_SCHEMA".REFERENTIAL_CONSTRAINTS rc, - "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cu1, - "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cu2 - WHERE - cu1."CONSTRAINT_NAME" = rc."CONSTRAINT_NAME" AND - cu2."CONSTRAINT_NAME" = rc."UNIQUE_CONSTRAINT_NAME" AND - cu1."TABLE_NAME" IN ? AND - cu2."TABLE_NAME" = ? AND - cu1."TABLE_CATALOG" = ? AND - cu2."TABLE_CATALOG" = ?', - 'reflect_habtm'=>'SELECT - cua1."TABLE_NAME",cua1."COLUMN_NAME", - cua2."TABLE_NAME",cua2."COLUMN_NAME", - cub1."TABLE_NAME",cub1."COLUMN_NAME", - cub2."TABLE_NAME",cub2."COLUMN_NAME" - FROM - "INFORMATION_SCHEMA".REFERENTIAL_CONSTRAINTS rca, - "INFORMATION_SCHEMA".REFERENTIAL_CONSTRAINTS rcb, - "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cua1, - "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cua2, - "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cub1, - "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cub2 - WHERE - cua1."CONSTRAINT_NAME" = rca."CONSTRAINT_NAME" AND - cua2."CONSTRAINT_NAME" = rca."UNIQUE_CONSTRAINT_NAME" AND - cub1."CONSTRAINT_NAME" = rcb."CONSTRAINT_NAME" AND - cub2."CONSTRAINT_NAME" = rcb."UNIQUE_CONSTRAINT_NAME" AND - cua1."TABLE_CATALOG" = ? AND - cub1."TABLE_CATALOG" = ? AND - cua2."TABLE_CATALOG" = ? AND - cub2."TABLE_CATALOG" = ? AND - cua1."TABLE_NAME" = cub1."TABLE_NAME" AND - cua2."TABLE_NAME" = ? AND - cub2."TABLE_NAME" IN ?', - 'reflect_columns'=> 'SELECT - "COLUMN_NAME", "COLUMN_DEFAULT", "IS_NULLABLE", "DATA_TYPE", "CHARACTER_MAXIMUM_LENGTH" - FROM - "INFORMATION_SCHEMA"."COLUMNS" - WHERE - "TABLE_NAME" LIKE ? AND - "TABLE_CATALOG" = ? - ORDER BY - "ORDINAL_POSITION"' - ); - } - - public function getSql($name) { - return isset($this->queries[$name])?$this->queries[$name]:false; - } - - public function connect($hostname,$username,$password,$database,$port,$socket,$charset) { - $connectionInfo = array(); - if ($port) $hostname.=','.$port; - if ($username) $connectionInfo['UID']=$username; - if ($password) $connectionInfo['PWD']=$password; - if ($database) $connectionInfo['Database']=$database; - if ($charset) $connectionInfo['CharacterSet']=$charset; - $connectionInfo['QuotedId']=1; - $connectionInfo['ReturnDatesAsStrings']=1; - - $db = sqlsrv_connect($hostname, $connectionInfo); - if (!$db) { - throw new \Exception('Connect failed. '.print_r( sqlsrv_errors(), true)); - } - if ($socket) { - throw new \Exception('Socket connection is not supported.'); - } - $this->db = $db; - } - - public function query($sql,$params=array()) { - $args = array(); - $db = $this->db; - $sql = preg_replace_callback('/\!|\?/', function ($matches) use (&$db,&$params,&$args) { - static $i=-1; - $i++; - $param = $params[$i]; - if ($matches[0]=='!') { - $key = preg_replace('/[^a-zA-Z0-9\-_=<> ]/','',is_object($param)?$param->key:$param); - if (is_object($param) && $param->type=='hex') { - return "CONVERT(varchar(max), \"$key\", 2) as \"$key\""; - } - if (is_object($param) && $param->type=='wkt') { - return "\"$key\".STAsText() as \"$key\""; - } - return '"'.$key.'"'; - } else { - // This is workaround because SQLSRV cannot accept NULL in a param - if ($matches[0]=='?' && is_null($param)) { - return 'NULL'; - } - if (is_array($param)) { - $args = array_merge($args,$param); - return '('.implode(',',str_split(str_repeat('?',count($param)))).')'; - } - if (is_object($param) && $param->type=='hex') { - $args[] = $param->value; - return 'CONVERT(VARBINARY(MAX),?,2)'; - } - if (is_object($param) && $param->type=='wkt') { - $args[] = $param->value; - return 'geometry::STGeomFromText(?,0)'; - } - $args[] = $param; - return '?'; - } - }, $sql); - //var_dump($params); - //echo "\n$sql\n"; - //var_dump($args); - //file_put_contents('sql.txt',"\n$sql\n".var_export($args,true)."\n",FILE_APPEND); - if (strtoupper(substr($sql,0,6))=='INSERT') { - $sql .= ';SELECT SCOPE_IDENTITY()'; - } - return sqlsrv_query($db,$sql,$args)?:null; - } - - public function fetchAssoc($result) { - return sqlsrv_fetch_array($result, SQLSRV_FETCH_ASSOC); - } - - public function fetchRow($result) { - return sqlsrv_fetch_array($result, SQLSRV_FETCH_NUMERIC); - } - - public function insertId($result) { - sqlsrv_next_result($result); - sqlsrv_fetch($result); - return (int)sqlsrv_get_field($result, 0); - } - - public function affectedRows($result) { - return sqlsrv_rows_affected($result); - } - - public function close($result) { - return sqlsrv_free_stmt($result); - } - - public function fetchFields($table) { - $result = $this->query('SELECT * FROM ! WHERE 1=2;',array($table)); - //var_dump(sqlsrv_field_metadata($result)); - return array_map(function($a){ - $p = array(); - foreach ($a as $k=>$v) { - $p[strtolower($k)] = $v; - } - return (object)$p; - },sqlsrv_field_metadata($result)); - } - - public function addLimitToSql($sql,$limit,$offset) { - return "$sql OFFSET $offset ROWS FETCH NEXT $limit ROWS ONLY"; - } - - public function likeEscape($string) { - return str_replace(array('%','_'),array('[%]','[_]'),$string); - } - - public function convertFilter($field, $comparator, $value) { - $comparator = strtolower($comparator); - if ($comparator[0]!='n') { - switch ($comparator) { - case 'sco': return array('!.STContains(geometry::STGeomFromText(?,0))=1',$field,$value); - case 'scr': return array('!.STCrosses(geometry::STGeomFromText(?,0))=1',$field,$value); - case 'sdi': return array('!.STDisjoint(geometry::STGeomFromText(?,0))=1',$field,$value); - case 'seq': return array('!.STEquals(geometry::STGeomFromText(?,0))=1',$field,$value); - case 'sin': return array('!.STIntersects(geometry::STGeomFromText(?,0))=1',$field,$value); - case 'sov': return array('!.STOverlaps(geometry::STGeomFromText(?,0))=1',$field,$value); - case 'sto': return array('!.STTouches(geometry::STGeomFromText(?,0))=1',$field,$value); - case 'swi': return array('!.STWithin(geometry::STGeomFromText(?,0))=1',$field,$value); - case 'sic': return array('!.STIsClosed()=1',$field); - case 'sis': return array('!.STIsSimple()=1',$field); - case 'siv': return array('!.STIsValid()=1',$field); - } - } else { - switch ($comparator) { - case 'nsco': return array('!.STContains(geometry::STGeomFromText(?,0))=0',$field,$value); - case 'nscr': return array('!.STCrosses(geometry::STGeomFromText(?,0))=0',$field,$value); - case 'nsdi': return array('!.STDisjoint(geometry::STGeomFromText(?,0))=0',$field,$value); - case 'nseq': return array('!.STEquals(geometry::STGeomFromText(?,0))=0',$field,$value); - case 'nsin': return array('!.STIntersects(geometry::STGeomFromText(?,0))=0',$field,$value); - case 'nsov': return array('!.STOverlaps(geometry::STGeomFromText(?,0))=0',$field,$value); - case 'nsto': return array('!.STTouches(geometry::STGeomFromText(?,0))=0',$field,$value); - case 'nswi': return array('!.STWithin(geometry::STGeomFromText(?,0))=0',$field,$value); - case 'nsic': return array('!.STIsClosed()=0',$field); - case 'nsis': return array('!.STIsSimple()=0',$field); - case 'nsiv': return array('!.STIsValid()=0',$field); - } - } - return false; - } - - public function isNumericType($field) { - return in_array($field->type,array(-6,-5,4,5,2,6,7)); - } - - public function isBinaryType($field) { - return ($field->type>=-4 && $field->type<=-2); - } - - public function isGeometryType($field) { - return ($field->type==-151); - } - - public function isJsonType($field) { - return ($field->type==-152); - } - - public function getDefaultCharset() { - return 'UTF-8'; - } - - public function beginTransaction() { - return sqlsrv_begin_transaction($this->db); - } - - public function commitTransaction() { - return sqlsrv_commit($this->db); - } - - public function rollbackTransaction() { - return sqlsrv_rollback($this->db); - } - - public function jsonEncode($object) { - $a = $object; - $d = new DOMDocument(); - $c = $d->createElement("root"); - $d->appendChild($c); - $t = function($v) { - $type = gettype($v); - switch($type) { - case 'integer': return 'number'; - case 'double': return 'number'; - default: return strtolower($type); - } - }; - $f = function($f,$c,$a,$s=false) use ($t,$d) { - $c->setAttribute('type', $t($a)); - if ($t($a) != 'array' && $t($a) != 'object') { - if ($t($a) == 'boolean') { - $c->appendChild($d->createTextNode($a?'true':'false')); - } else { - $c->appendChild($d->createTextNode($a)); - } - } else { - foreach($a as $k=>$v) { - if ($k == '__type' && $t($a) == 'object') { - $c->setAttribute('__type', $v); - } else { - if ($t($v) == 'object') { - $ch = $c->appendChild($d->createElementNS(null, $s ? 'item' : $k)); - $f($f, $ch, $v); - } else if ($t($v) == 'array') { - $ch = $c->appendChild($d->createElementNS(null, $s ? 'item' : $k)); - $f($f, $ch, $v, true); - } else { - $va = $d->createElementNS(null, $s ? 'item' : $k); - if ($t($v) == 'boolean') { - $va->appendChild($d->createTextNode($v?'true':'false')); - } else { - $va->appendChild($d->createTextNode($v)); - } - $ch = $c->appendChild($va); - $ch->setAttribute('type', $t($v)); - } - } - } - } - }; - $f($f,$c,$a,$t($a)=='array'); - return $d->saveXML($d->documentElement); - } - - public function jsonDecode($string) { - $a = dom_import_simplexml(simplexml_load_string($string)); - $t = function($v) { - return $v->getAttribute('type'); - }; - $f = function($f,$a) use ($t) { - $c = null; - if ($t($a)=='null') { - $c = null; - } else if ($t($a)=='boolean') { - $b = substr(strtolower($a->textContent),0,1); - $c = in_array($b,array('1','t')); - } else if ($t($a)=='number') { - $c = $a->textContent+0; - } else if ($t($a)=='string') { - $c = $a->textContent; - } else if ($t($a)=='object') { - $c = array(); - if ($a->getAttribute('__type')) { - $c['__type'] = $a->getAttribute('__type'); - } - for ($i=0;$i<$a->childNodes->length;$i++) { - $v = $a->childNodes[$i]; - $c[$v->nodeName] = $f($f,$v); - } - $c = (object)$c; - } else if ($t($a)=='array') { - $c = array(); - for ($i=0;$i<$a->childNodes->length;$i++) { - $v = $a->childNodes[$i]; - $c[$i] = $f($f,$v); - } - } - return $c; - }; - $c = $f($f,$a); - return $c; - } -} - -class SQLite implements DatabaseInterface { - - protected $db; - protected $queries; - - public function __construct() { - $this->queries = array( - 'list_tables'=>'SELECT - "name", "" - FROM - "sys/tables"', - 'reflect_table'=>'SELECT - "name" - FROM - "sys/tables" - WHERE - "name"=?', - 'reflect_pk'=>'SELECT - "name" - FROM - "sys/columns" - WHERE - "pk"=1 AND - "self"=?', - 'reflect_belongs_to'=>'SELECT - "self", "from", - "table", "to" - FROM - "sys/foreign_keys" - WHERE - "self" = ? AND - "table" IN ? AND - ? like "%" AND - ? like "%"', - 'reflect_has_many'=>'SELECT - "self", "from", - "table", "to" - FROM - "sys/foreign_keys" - WHERE - "self" IN ? AND - "table" = ? AND - ? like "%" AND - ? like "%"', - 'reflect_habtm'=>'SELECT - k1."self", k1."from", - k1."table", k1."to", - k2."self", k2."from", - k2."table", k2."to" - FROM - "sys/foreign_keys" k1, - "sys/foreign_keys" k2 - WHERE - ? like "%" AND - ? like "%" AND - ? like "%" AND - ? like "%" AND - k1."self" = k2."self" AND - k1."table" = ? AND - k2."table" IN ?', - 'reflect_columns'=> 'SELECT - "name", "dflt_value", case when "notnull"==1 then \'no\' else \'yes\' end as "nullable", "type", 2147483647 - FROM - "sys/columns" - WHERE - "self"=? - ORDER BY - "cid"' - ); - } - - public function getSql($name) { - return isset($this->queries[$name])?$this->queries[$name]:false; - } - - public function connect($hostname,$username,$password,$database,$port,$socket,$charset) { - $this->db = new SQLite3($database); - // optimizations - $this->db->querySingle('PRAGMA synchronous = NORMAL'); - $this->db->querySingle('PRAGMA foreign_keys = on'); - $reflection = $this->db->querySingle('SELECT name FROM sqlite_master WHERE type = "table" and name like "sys/%"'); - if (!$reflection) { - //create reflection tables - $this->query('CREATE table "sys/version" ("version" integer)'); - $this->query('CREATE table "sys/tables" ("name" text)'); - $this->query('CREATE table "sys/columns" ("self" text,"cid" integer,"name" text,"type" integer,"notnull" integer,"dflt_value" integer,"pk" integer)'); - $this->query('CREATE table "sys/foreign_keys" ("self" text,"id" integer,"seq" integer,"table" text,"from" text,"to" text,"on_update" text,"on_delete" text,"match" text)'); - } - $version = $this->db->querySingle('pragma schema_version'); - if ($version != $this->db->querySingle('SELECT "version" from "sys/version"')) { - // reflection may take a while - set_time_limit(3600); - // update version data - $this->query('DELETE FROM "sys/version"'); - $this->query('INSERT into "sys/version" ("version") VALUES (?)',array($version)); - // update tables data - $this->query('DELETE FROM "sys/tables"'); - $result = $this->query('SELECT * FROM sqlite_master WHERE (type = "table" or type = "view") and name not like "sys/%" and name<>"sqlite_sequence"'); - $tables = array(); - while ($row = $this->fetchAssoc($result)) { - $tables[] = $row['name']; - $this->query('INSERT into "sys/tables" ("name") VALUES (?)',array($row['name'])); - } - // update columns and foreign_keys data - $this->query('DELETE FROM "sys/columns"'); - $this->query('DELETE FROM "sys/foreign_keys"'); - foreach ($tables as $table) { - $result = $this->query('pragma table_info(!)',array($table)); - while ($row = $this->fetchRow($result)) { - array_unshift($row, $table); - $this->query('INSERT into "sys/columns" ("self","cid","name","type","notnull","dflt_value","pk") VALUES (?,?,?,?,?,?,?)',$row); - } - $result = $this->query('pragma foreign_key_list(!)',array($table)); - while ($row = $this->fetchRow($result)) { - array_unshift($row, $table); - $this->query('INSERT into "sys/foreign_keys" ("self","id","seq","table","from","to","on_update","on_delete","match") VALUES (?,?,?,?,?,?,?,?,?)',$row); - } - } - } - } - - public function query($sql,$params=array()) { - $db = $this->db; - $sql = preg_replace_callback('/\!|\?/', function ($matches) use (&$db,&$params) { - $param = array_shift($params); - if ($matches[0]=='!') { - $key = preg_replace('/[^a-zA-Z0-9\-_=<> ]/','',is_object($param)?$param->key:$param); - return '"'.$key.'"'; - } else { - if (is_array($param)) return '('.implode(',',array_map(function($v) use (&$db) { - return "'".$db->escapeString($v)."'"; - },$param)).')'; - if (is_object($param) && $param->type=='hex') { - return "'".$db->escapeString($param->value)."'"; - } - if (is_object($param) && $param->type=='wkt') { - return "'".$db->escapeString($param->value)."'"; - } - if ($param===null) return 'NULL'; - return "'".$db->escapeString($param)."'"; - } - }, $sql); - //echo "\n$sql\n"; - try { $result=$db->query($sql); } catch(\Exception $e) { $result=null; } - return $result; - } - - public function fetchAssoc($result) { - return $result->fetchArray(SQLITE3_ASSOC); - } - - public function fetchRow($result) { - return $result->fetchArray(SQLITE3_NUM); - } - - public function insertId($result) { - return $this->db->lastInsertRowID(); - } - - public function affectedRows($result) { - return $this->db->changes(); - } - - public function close($result) { - return $result->finalize(); - } - - public function fetchFields($table) { - $result = $this->query('SELECT * FROM "sys/columns" WHERE "self"=?;',array($table)); - $fields = array(); - while ($row = $this->fetchAssoc($result)){ - $fields[strtolower($row['name'])] = (object)$row; - } - return $fields; - } - - public function addLimitToSql($sql,$limit,$offset) { - return "$sql LIMIT $limit OFFSET $offset"; - } - - public function likeEscape($string) { - return addcslashes($string,'%_'); - } - - public function convertFilter($field, $comparator, $value) { - return false; - } - - public function isNumericType($field) { - return in_array($field->type,array('integer','real')); - } - - public function isBinaryType($field) { - return (substr($field->type,0,4)=='data'); - } - - public function isGeometryType($field) { - return in_array($field->type,array('geometry')); - } - - public function isJsonType($field) { - return in_array($field->type,array('json','jsonb')); - } - - public function getDefaultCharset() { - return 'utf8'; - } - - public function beginTransaction() { - return $this->query('BEGIN'); - } - - public function commitTransaction() { - return $this->query('COMMIT'); - } - - public function rollbackTransaction() { - return $this->query('ROLLBACK'); - } - - public function jsonEncode($object) { - return json_encode($object); - } - - public function jsonDecode($string) { - return json_decode($string); - } -} - -class PHP_CRUD_API { - - protected $db; - protected $settings; - - protected function mapMethodToAction($method,$key) { - switch ($method) { - case 'OPTIONS': return 'headers'; - case 'GET': return ($key===false)?'list':'read'; - case 'PUT': return 'update'; - case 'POST': return 'create'; - case 'DELETE': return 'delete'; - case 'PATCH': return 'increment'; - default: $this->exitWith404('method'); - } - return false; - } - - protected function parseRequestParameter(&$request,$characters) { - if ($request==='') return false; - $pos = strpos($request,'/'); - $value = $pos?substr($request,0,$pos):$request; - $request = $pos?substr($request,$pos+1):''; - if (!$characters) return $value; - return preg_replace("/[^$characters]/",'',$value); - } - - protected function parseGetParameter($get,$name,$characters) { - $value = isset($get[$name])?$get[$name]:false; - return $characters?preg_replace("/[^$characters]/",'',$value):$value; - } - - protected function parseGetParameterArray($get,$name,$characters) { - $values = isset($get[$name])?$get[$name]:false; - if (!is_array($values)) $values = array($values); - if ($characters) { - foreach ($values as &$value) { - $value = preg_replace("/[^$characters]/",'',$value); - } - } - return $values; - } - - protected function applyBeforeHandler(&$action,&$database,&$table,&$ids,&$callback,&$inputs) { - if (is_callable($callback,true)) { - $max = count($ids)?:count($inputs); - $values = array('action'=>$action,'database'=>$database,'table'=>$table); - for ($i=0;$i<$max;$i++) { - $action = $values['action']; - $database = $values['database']; - $table = $values['table']; - if (!isset($ids[$i])) $ids[$i] = false; - if (!isset($inputs[$i])) $inputs[$i] = false; - $callback($action,$database,$table,$ids[$i],$inputs[$i]); - } - } - } - - protected function applyAfterHandler($parameters,$outputs) { - $callback = $parameters['after']; - if (is_callable($callback,true)) { - $action = $parameters['action']; - $database = $parameters['database']; - $table = $parameters['tables'][0]; - $ids = $parameters['key'][0]; - $inputs = $parameters['inputs']; - $max = max(count($ids),count($inputs)); - for ($i=0;$i<$max;$i++) { - $id = isset($ids[$i])?$ids[$i]:false; - $input = isset($inputs[$i])?$inputs[$i]:false; - $output = is_array($outputs)?$outputs[$i]:$outputs; - $callback($action,$database,$table,$id,$input,$output); - } - } - } - - protected function applyTableAuthorizer($callback,$action,$database,&$tables) { - if (is_callable($callback,true)) foreach ($tables as $i=>$table) { - if (!$callback($action,$database,$table)) { - unset($tables[$i]); - } - } - } - - protected function applyRecordFilter($callback,$action,$database,$tables,&$filters) { - if (is_callable($callback,true)) foreach ($tables as $i=>$table) { - $this->addFilters($filters,$table,array($table=>'and'),$callback($action,$database,$table)); - } - } - - protected function applyTenancyFunction($callback,$action,$database,$fields,&$filters) { - if (is_callable($callback,true)) foreach ($fields as $table=>$keys) { - foreach ($keys as $field) { - $v = $callback($action,$database,$table,$field->name); - if ($v!==null) { - if (is_array($v)) $this->addFilter($filters,$table,'and',$field->name,'in',implode(',',$v)); - else $this->addFilter($filters,$table,'and',$field->name,'eq',$v); - } - } - } - } - - protected function applyColumnAuthorizer($callback,$action,$database,&$fields) { - if (is_callable($callback,true)) foreach ($fields as $table=>$keys) { - foreach ($keys as $field) { - if (!$callback($action,$database,$table,$field->name)) { - unset($fields[$table][$field->name]); - } - } - } - } - - protected function applyInputTenancy($callback,$action,$database,$table,&$input,$keys) { - if (is_callable($callback,true)) foreach ($keys as $key=>$field) { - $v = $callback($action,$database,$table,$key); - if ($v!==null && (isset($input->$key) || $action=='create')) { - if (is_array($v)) { - if (!count($v)) { - $input->$key = null; - } elseif (!isset($input->$key)) { - $input->$key = $v[0]; - } elseif (!in_array($input->$key,$v)) { - $input->$key = null; - } - } else { - $input->$key = $v; - } - } - } - } - - protected function applyInputSanitizer($callback,$action,$database,$table,&$input,$keys) { - if (is_callable($callback,true)) foreach ((array)$input as $key=>$value) { - if (isset($keys[$key])) { - $input->$key = $callback($action,$database,$table,$key,$keys[$key]->type,$value); - } - } - } - - protected function applyInputValidator($callback,$action,$database,$table,$input,$keys,$context) { - $errors = array(); - if (is_callable($callback,true)) foreach ((array)$input as $key=>$value) { - if (isset($keys[$key])) { - $error = $callback($action,$database,$table,$key,$keys[$key]->type,$value,$context); - if ($error!==true && $error!==null) $errors[$key] = $error; - } - } - if (!empty($errors)) $this->exitWith422($errors); - } - - protected function processTableAndIncludeParameters($database,$table,$include,$action) { - $blacklist = array('information_schema','mysql','sys','pg_catalog'); - if (in_array(strtolower($database), $blacklist)) return array(); - $table_list = array(); - if ($result = $this->db->query($this->db->getSql('reflect_table'),array($table,$database))) { - while ($row = $this->db->fetchRow($result)) $table_list[] = $row[0]; - $this->db->close($result); - } - if (empty($table_list)) $this->exitWith404('entity'); - if ($action=='list') { - foreach (explode(',',$include) as $table) { - if ($result = $this->db->query($this->db->getSql('reflect_table'),array($table,$database))) { - while ($row = $this->db->fetchRow($result)) $table_list[] = $row[0]; - $this->db->close($result); - } - } - } - return $table_list; - } - - protected function exitWith404($type) { - if (isset($_SERVER['REQUEST_METHOD'])) { - header('Content-Type:',true,404); - die("Not found ($type)"); - } else { - throw new \Exception("Not found ($type)"); - } - } - - protected function exitWith400($type) { - if (isset($_SERVER['REQUEST_METHOD'])) { - header('Content-Type:',true,400); - die("The request could not be understood by the server due to malformed syntax. The client SHOULD NOT repeat the request without modifications. ($type)"); - } else { - throw new \Exception("Bad request ($type)"); - } - } - - protected function exitWith422($object) { - if (isset($_SERVER['REQUEST_METHOD'])) { - header('Content-Type:',true,422); - die(json_encode($object)); - } else { - throw new \Exception(json_encode($object)); - } - } - - protected function headersCommand($parameters) { - $headers = array(); - $headers[]='Access-Control-Allow-Headers: Content-Type, X-XSRF-TOKEN'; - $headers[]='Access-Control-Allow-Methods: OPTIONS, GET, PUT, POST, DELETE, PATCH'; - $headers[]='Access-Control-Allow-Credentials: true'; - $headers[]='Access-Control-Max-Age: 1728000'; - if (isset($_SERVER['REQUEST_METHOD'])) { - foreach ($headers as $header) header($header); - } else { - echo json_encode($headers); - } - return false; - } - - protected function startOutput() { - if (isset($_SERVER['REQUEST_METHOD'])) { - header('Content-Type: application/json; charset=utf-8'); - } - } - - protected function findPrimaryKeys($table,$database) { - $fields = array(); - if ($result = $this->db->query($this->db->getSql('reflect_pk'),array($table,$database))) { - while ($row = $this->db->fetchRow($result)) { - $fields[] = $row[0]; - } - $this->db->close($result); - } - return $fields; - } - - protected function processKeyParameter($key,$tables,$database) { - if ($key===false) return false; - $fields = $this->findPrimaryKeys($tables[0],$database); - if (count($fields)!=1) $this->exitWith404('1pk'); - return array(explode(',',$key),$fields[0]); - } - - protected function processOrderingsParameter($orderings) { - if (!$orderings) return false; - foreach ($orderings as &$order) { - $order = explode(',',$order,2); - if (count($order)<2) $order[1]='ASC'; - if (!strlen($order[0])) return false; - $direction = strtoupper($order[1]); - if (in_array($direction,array('ASC','DESC'))) { - $order[1] = $direction; - } - } - return $orderings; - } - - protected function convertFilter($field, $comparator, $value) { - $result = $this->db->convertFilter($field,$comparator,$value); - if ($result) return $result; - // default behavior - $comparator = strtolower($comparator); - if ($comparator[0]!='n') { - if (strlen($comparator)==2) { - switch ($comparator) { - case 'cs': return array('! LIKE ?',$field,'%'.$this->db->likeEscape($value).'%'); - case 'sw': return array('! LIKE ?',$field,$this->db->likeEscape($value).'%'); - case 'ew': return array('! LIKE ?',$field,'%'.$this->db->likeEscape($value)); - case 'eq': return array('! = ?',$field,$value); - case 'lt': return array('! < ?',$field,$value); - case 'le': return array('! <= ?',$field,$value); - case 'ge': return array('! >= ?',$field,$value); - case 'gt': return array('! > ?',$field,$value); - case 'bt': - $v = explode(',',$value); - if (count($v)<2) return false; - return array('! BETWEEN ? AND ?',$field,$v[0],$v[1]); - case 'in': return array('! IN ?',$field,explode(',',$value)); - case 'is': return array('! IS NULL',$field); - } - } else { - switch ($comparator) { - case 'sco': return array('ST_Contains(!,ST_GeomFromText(?))=TRUE',$field,$value); - case 'scr': return array('ST_Crosses(!,ST_GeomFromText(?))=TRUE',$field,$value); - case 'sdi': return array('ST_Disjoint(!,ST_GeomFromText(?))=TRUE',$field,$value); - case 'seq': return array('ST_Equals(!,ST_GeomFromText(?))=TRUE',$field,$value); - case 'sin': return array('ST_Intersects(!,ST_GeomFromText(?))=TRUE',$field,$value); - case 'sov': return array('ST_Overlaps(!,ST_GeomFromText(?))=TRUE',$field,$value); - case 'sto': return array('ST_Touches(!,ST_GeomFromText(?))=TRUE',$field,$value); - case 'swi': return array('ST_Within(!,ST_GeomFromText(?))=TRUE',$field,$value); - case 'sic': return array('ST_IsClosed(!)=TRUE',$field); - case 'sis': return array('ST_IsSimple(!)=TRUE',$field); - case 'siv': return array('ST_IsValid(!)=TRUE',$field); - } - } - } else { - if (strlen($comparator)==2) { - switch ($comparator) { - case 'ne': return $this->convertFilter($field, 'neq', $value); // deprecated - case 'ni': return $this->convertFilter($field, 'nin', $value); // deprecated - case 'no': return $this->convertFilter($field, 'nis', $value); // deprecated - } - } elseif (strlen($comparator)==3) { - switch ($comparator) { - case 'ncs': return array('! NOT LIKE ?',$field,'%'.$this->db->likeEscape($value).'%'); - case 'nsw': return array('! NOT LIKE ?',$field,$this->db->likeEscape($value).'%'); - case 'new': return array('! NOT LIKE ?',$field,'%'.$this->db->likeEscape($value)); - case 'neq': return array('! <> ?',$field,$value); - case 'nlt': return array('! >= ?',$field,$value); - case 'nle': return array('! > ?',$field,$value); - case 'nge': return array('! < ?',$field,$value); - case 'ngt': return array('! <= ?',$field,$value); - case 'nbt': - $v = explode(',',$value); - if (count($v)<2) return false; - return array('! NOT BETWEEN ? AND ?',$field,$v[0],$v[1]); - case 'nin': return array('! NOT IN ?',$field,explode(',',$value)); - case 'nis': return array('! IS NOT NULL',$field); - } - } else { - switch ($comparator) { - case 'nsco': return array('ST_Contains(!,ST_GeomFromText(?))=FALSE',$field,$value); - case 'nscr': return array('ST_Crosses(!,ST_GeomFromText(?))=FALSE',$field,$value); - case 'nsdi': return array('ST_Disjoint(!,ST_GeomFromText(?))=FALSE',$field,$value); - case 'nseq': return array('ST_Equals(!,ST_GeomFromText(?))=FALSE',$field,$value); - case 'nsin': return array('ST_Intersects(!,ST_GeomFromText(?))=FALSE',$field,$value); - case 'nsov': return array('ST_Overlaps(!,ST_GeomFromText(?))=FALSE',$field,$value); - case 'nsto': return array('ST_Touches(!,ST_GeomFromText(?))=FALSE',$field,$value); - case 'nswi': return array('ST_Within(!,ST_GeomFromText(?))=FALSE',$field,$value); - case 'nsic': return array('ST_IsClosed(!)=FALSE',$field); - case 'nsis': return array('ST_IsSimple(!)=FALSE',$field); - case 'nsiv': return array('ST_IsValid(!)=FALSE',$field); - } - } - } - return false; - } - - public function addFilter(&$filters,$table,$and,$field,$comparator,$value) { - if (!isset($filters[$table])) $filters[$table] = array(); - if (!isset($filters[$table][$and])) $filters[$table][$and] = array(); - $filter = $this->convertFilter($field,$comparator,$value); - if ($filter) $filters[$table][$and][] = $filter; - } - - public function addFilters(&$filters,$table,$satisfy,$filterStrings) { - if ($filterStrings) { - for ($i=0;$i=2) { - if (strpos($parts[0],'.')) list($t,$f) = explode('.',$parts[0],2); - else list($t,$f) = array($table,$parts[0]); - $comparator = $parts[1]; - $value = isset($parts[2])?$parts[2]:null; - $and = isset($satisfy[$t])?$satisfy[$t]:'and'; - $this->addFilter($filters,$t,$and,$f,$comparator,$value); - } - } - } - } - - protected function processSatisfyParameter($tables,$satisfyString) { - $satisfy = array(); - foreach (explode(',',$satisfyString) as $str) { - if (strpos($str,'.')) list($t,$s) = explode('.',$str,2); - else list($t,$s) = array($tables[0],$str); - $and = ($s && strtolower($s)=='any')?'or':'and'; - $satisfy[$t] = $and; - } - return $satisfy; - } - - protected function processFiltersParameter($tables,$satisfy,$filterStrings) { - $filters = array(); - $this->addFilters($filters,$tables[0],$satisfy,$filterStrings); - return $filters; - } - - protected function processPageParameter($page) { - if (!$page) return false; - $page = explode(',',$page,2); - if (count($page)<2) $page[1]=20; - $page[0] = ($page[0]-1)*$page[1]; - return $page; - } - - protected function retrieveObject($key,$fields,$filters,$tables) { - if (!$key) return false; - $table = $tables[0]; - $params = array(); - $sql = 'SELECT '; - $this->convertOutputs($sql,$params,$fields[$table]); - $sql .= ' FROM !'; - $params[] = $table; - $this->addFilter($filters,$table,'and',$key[1],'eq',$key[0][0]); - $this->addWhereFromFilters($filters[$table],$sql,$params); - $object = null; - if ($result = $this->db->query($sql,$params)) { - $object = $this->fetchAssoc($result,$fields[$table]); - $this->db->close($result); - } - return $object; - } - - protected function retrieveObjects($key,$fields,$filters,$tables) { - $keyField = $key[1]; - $keys = $key[0]; - $rows = array(); - foreach ($keys as $key) { - $result = $this->retrieveObject(array(array($key),$keyField),$fields,$filters,$tables); - if ($result===null) { - return null; - } - $rows[] = $result; - } - return $rows; - } - - protected function createObject($input,$tables) { - if (!$input) return false; - $input = (array)$input; - - - /* START: Crypt password and userId. */ - - $date = new DateTime(); - $id = $date->getTimestamp() . $input['userName']; - - $passwordSalt = "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"; - - $hashedPassword = crypt( $password, $passwordSalt ); - $hashedId = crypt( $id, $passwordSalt ); - - $input['password'] = $hashedPassword; - $input['userId'] = $hashedId; - - /* END: Crypt password and userId. */ - - $keys = implode(',',str_split(str_repeat('!', count($input)))); - $values = implode(',',str_split(str_repeat('?', count($input)))); - $params = array_merge(array_keys($input),array_values($input)); - array_unshift($params, $tables[0]); - $result = $this->db->query('INSERT INTO ! ('.$keys.') VALUES ('.$values.')',$params); - if (!$result) return null; - $insertId = $this->db->insertId($result); - return $insertId; - } - - protected function createObjects($inputs,$tables) { - - if (!$inputs) return false; - $ids = array(); - $this->db->beginTransaction(); - foreach ($inputs as $input) { - $result = $this->createObject($input,$tables); - if ($result===null) { - $this->db->rollbackTransaction(); - return null; - } - $ids[] = $result; - } - $this->db->commitTransaction(); - return $ids; - } - - protected function updateObject($key,$input,$filters,$tables) { - if (!$input) return null; - $input = (array)$input; - $table = $tables[0]; - $sql = 'UPDATE ! SET '; - $params = array($table); - foreach (array_keys($input) as $j=>$k) { - if ($j) $sql .= ','; - $v = $input[$k]; - $sql .= '!=?'; - $params[] = $k; - $params[] = $v; - } - $this->addFilter($filters,$table,'and',$key[1],'eq',$key[0][0]); - $this->addWhereFromFilters($filters[$table],$sql,$params); - $result = $this->db->query($sql,$params); - if (!$result) return null; - return $this->db->affectedRows($result); - } - - protected function updateObjects($key,$inputs,$filters,$tables) { - if (!$inputs) return null; - $keyField = $key[1]; - $keys = $key[0]; - if (count(array_filter($inputs))!=count(array_filter($keys))) { - $this->exitWith404('subject'); - } - $rows = array(); - $this->db->beginTransaction(); - foreach ($inputs as $i=>$input) { - $result = $this->updateObject(array(array($keys[$i]),$keyField),$input,$filters,$tables); - if ($result===null) { - $this->db->rollbackTransaction(); - return null; - } - $rows[] = $result; - } - $this->db->commitTransaction(); - return $rows; - } - - protected function deleteObject($key,$filters,$tables) { - $table = $tables[0]; - $sql = 'DELETE FROM !'; - $params = array($table); - $this->addFilter($filters,$table,'and',$key[1],'eq',$key[0][0]); - $this->addWhereFromFilters($filters[$table],$sql,$params); - $result = $this->db->query($sql,$params); - if (!$result) return null; - return $this->db->affectedRows($result); - } - - protected function deleteObjects($key,$filters,$tables) { - $keyField = $key[1]; - $keys = $key[0]; - $rows = array(); - $this->db->beginTransaction(); - foreach ($keys as $key) { - $result = $this->deleteObject(array(array($key),$keyField),$filters,$tables); - if ($result===null) { - $this->db->rollbackTransaction(); - return null; - } - $rows[] = $result; - } - $this->db->commitTransaction(); - return $rows; - } - - protected function incrementObject($key,$input,$filters,$tables,$fields) { - if (!$input) return null; - $input = (array)$input; - $table = $tables[0]; - $sql = 'UPDATE ! SET '; - $params = array($table); - foreach (array_keys($input) as $j=>$k) { - if ($j) $sql .= ','; - $v = $input[$k]; - if ($this->db->isNumericType($fields[$table][$k])) { - $sql .= '!=!+?'; - $params[] = $k; - $params[] = $k; - $params[] = $v; - } else { - $sql .= '!=!'; - $params[] = $k; - $params[] = $k; - } - } - $this->addFilter($filters,$table,'and',$key[1],'eq',$key[0][0]); - $this->addWhereFromFilters($filters[$table],$sql,$params); - $result = $this->db->query($sql,$params); - if (!$result) return null; - return $this->db->affectedRows($result); - } - - protected function incrementObjects($key,$inputs,$filters,$tables,$fields) { - if (!$inputs) return null; - $keyField = $key[1]; - $keys = $key[0]; - if (count(array_filter($inputs))!=count(array_filter($keys))) { - $this->exitWith404('subject'); - } - $rows = array(); - $this->db->beginTransaction(); - foreach ($inputs as $i=>$input) { - $result = $this->incrementObject(array(array($keys[$i]),$keyField),$input,$filters,$tables,$fields); - if ($result===null) { - $this->db->rollbackTransaction(); - return null; - } - $rows[] = $result; - } - $this->db->commitTransaction(); - return $rows; - } - - protected function findRelations($tables,$database,$auto_include) { - $tableset = array(); - $collect = array(); - $select = array(); - - while (count($tables)>1) { - $table0 = array_shift($tables); - $tableset[] = $table0; - - $result = $this->db->query($this->db->getSql('reflect_belongs_to'),array($table0,$tables,$database,$database)); - while ($row = $this->db->fetchRow($result)) { - if (!$auto_include && !in_array($row[0],array_merge($tables,$tableset))) continue; - $collect[$row[0]][$row[1]]=array(); - $select[$row[2]][$row[3]]=array($row[0],$row[1]); - if (!in_array($row[0],$tableset)) $tableset[] = $row[0]; - } - $result = $this->db->query($this->db->getSql('reflect_has_many'),array($tables,$table0,$database,$database)); - while ($row = $this->db->fetchRow($result)) { - if (!$auto_include && !in_array($row[2],array_merge($tables,$tableset))) continue; - $collect[$row[2]][$row[3]]=array(); - $select[$row[0]][$row[1]]=array($row[2],$row[3]); - if (!in_array($row[2],$tableset)) $tableset[] = $row[2]; - } - $result = $this->db->query($this->db->getSql('reflect_habtm'),array($database,$database,$database,$database,$table0,$tables)); - while ($row = $this->db->fetchRow($result)) { - if (!$auto_include && !in_array($row[2],array_merge($tables,$tableset))) continue; - if (!$auto_include && !in_array($row[4],array_merge($tables,$tableset))) continue; - $collect[$row[2]][$row[3]]=array(); - $select[$row[0]][$row[1]]=array($row[2],$row[3]); - $collect[$row[4]][$row[5]]=array(); - $select[$row[6]][$row[7]]=array($row[4],$row[5]); - if (!in_array($row[2],$tableset)) $tableset[] = $row[2]; - if (!in_array($row[4],$tableset)) $tableset[] = $row[4]; - } - } - $tableset[] = array_shift($tables); - $tableset = array_unique($tableset); - return array($tableset,$collect,$select); - } - - protected function retrieveInputs($data) { - $data = trim($data, " \t\n\r"); - if (strlen($data)==0) { - $input = false; - } else if ($data[0]=='{' || $data[0]=='[') { - $input = json_decode($data); - $causeCode = json_last_error(); - if ($causeCode !== JSON_ERROR_NONE) { - $errorString = "Error decoding input JSON. json_last_error code: " . $causeCode; - $this->exitWith400($errorString); - } - } else { - parse_str($data, $input); - foreach ($input as $key => $value) { - if (substr($key,-9)=='__is_null') { - $input[substr($key,0,-9)] = null; - unset($input[$key]); - } - } - $input = (object)$input; - } - return is_array($input)?$input:array($input); - } - - protected function getRelationShipColumns($select) { - $keep = array(); - foreach ($select as $table=>$keys) { - foreach ($keys as $key=>$other) { - if (!isset($keep[$table])) $keep[$table] = array(); - $keep[$table][$key]=true; - list($table2,$key2) = $other; - if (!isset($keep[$table2])) $keep[$table2] = array(); - $keep[$table2][$key2]=true; - } - } - return $keep; - } - - protected function findFields($tables,$columns,$exclude,$select,$database) { - $fields = array(); - if ($select && ($columns || $exclude)) { - $keep = $this->getRelationShipColumns($select); - } else { - $keep = false; - } - foreach ($tables as $i=>$table) { - $fields[$table] = $this->findTableFields($table,$database); - $fields[$table] = $this->filterFieldsByColumns($fields[$table],$columns,$keep,$i==0,$table); - $fields[$table] = $this->filterFieldsByExclude($fields[$table],$exclude,$keep,$i==0,$table); - } - return $fields; - } - - protected function filterFieldsByColumns($fields,$columns,$keep,$first,$table) { - if ($columns) { - $columns = explode(',',$columns); - foreach (array_keys($fields) as $key) { - $delete = true; - foreach ($columns as $column) { - if (strpos($column,'.')) { - if ($column=="$table.$key" || $column=="$table.*") { - $delete = false; - } - } elseif ($first) { - if ($column==$key || $column=="*") { - $delete = false; - } - } - } - if ($delete && !isset($keep[$table][$key])) { - unset($fields[$key]); - } - } - } - return $fields; - } - - protected function filterFieldsByExclude($fields,$exclude,$keep,$first,$table) { - if ($exclude) { - $columns = explode(',',$exclude); - foreach (array_keys($fields) as $key) { - $delete = false; - foreach ($columns as $column) { - if (strpos($column,'.')) { - if ($column=="$table.$key" || $column=="$table.*") { - $delete = true; - } - } elseif ($first) { - if ($column==$key || $column=="*") { - $delete = true; - } - } - } - if ($delete && !isset($keep[$table][$key])) { - unset($fields[$key]); - } - } - } - return $fields; - } - - protected function findTableFields($table,$database) { - $fields = array(); - foreach ($this->db->fetchFields($table) as $field) { - $fields[$field->name] = $field; - } - return $fields; - } - - protected function filterInputByFields($input,$fields) { - if ($fields) foreach (array_keys((array)$input) as $key) { - if (!isset($fields[$key])) { - unset($input->$key); - } - } - return $input; - } - - protected function convertInputs(&$input,$fields) { - foreach ($fields as $key=>$field) { - if (isset($input->$key) && $input->$key && $this->db->isBinaryType($field)) { - $value = $input->$key; - $value = str_pad(strtr($value, '-_', '+/'), ceil(strlen($value) / 4) * 4, '=', STR_PAD_RIGHT); - $input->$key = (object)array('type'=>'hex','value'=>bin2hex(base64_decode($value))); - } - if (isset($input->$key) && $input->$key && $this->db->isGeometryType($field)) { - $input->$key = (object)array('type'=>'wkt','value'=>$input->$key); - } - if (isset($input->$key) && $input->$key && $this->db->isJsonType($field)) { - $input->$key = $this->db->jsonEncode($input->$key); - } - } - } - - protected function convertOutputs(&$sql, &$params, $fields) { - $sql .= implode(',',str_split(str_repeat('!',count($fields)))); - foreach ($fields as $key=>$field) { - if ($this->db->isBinaryType($field)) { - $params[] = (object)array('type'=>'hex','key'=>$key); - } - else if ($this->db->isGeometryType($field)) { - $params[] = (object)array('type'=>'wkt','key'=>$key); - } - else { - $params[] = $key; - } - } - } - - protected function convertTypes($result,&$values,&$fields) { - foreach ($values as $i=>$v) { - if (is_string($v)) { - if ($this->db->isNumericType($fields[$i])) { - $values[$i] = $v + 0; - } - else if ($this->db->isBinaryType($fields[$i])) { - $values[$i] = base64_encode(pack("H*",$v)); - } - else if ($this->db->isJsonType($fields[$i])) { - $values[$i] = $this->db->jsonDecode($v); - } - } - } - } - - protected function fetchAssoc($result,$fields=false) { - $values = $this->db->fetchAssoc($result); - if ($values && $fields) { - $this->convertTypes($result,$values,$fields); - } - return $values; - } - - protected function fetchRow($result,$fields=false) { - $values = $this->db->fetchRow($result,$fields); - if ($values && $fields) { - $fields = array_values($fields); - $this->convertTypes($result,$values,$fields); - } - return $values; - } - - protected function getParameters($settings) { - extract($settings); - - $table = $this->parseRequestParameter($request, 'a-zA-Z0-9\-_'); - $key = $this->parseRequestParameter($request, 'a-zA-Z0-9\-_,'); // auto-increment or uuid - $action = $this->mapMethodToAction($method,$key); - $include = $this->parseGetParameter($get, 'include', 'a-zA-Z0-9\-_,'); - $page = $this->parseGetParameter($get, 'page', '0-9,'); - $filters = $this->parseGetParameterArray($get, 'filter', false); - $satisfy = $this->parseGetParameter($get, 'satisfy', 'a-zA-Z0-9\-_,.'); - $columns = $this->parseGetParameter($get, 'columns', 'a-zA-Z0-9\-_,.*'); - $exclude = $this->parseGetParameter($get, 'exclude', 'a-zA-Z0-9\-_,.*'); - $orderings = $this->parseGetParameterArray($get, 'order', 'a-zA-Z0-9\-_,'); - $transform = $this->parseGetParameter($get, 'transform', 't1'); - - $tables = $this->processTableAndIncludeParameters($database,$table,$include,$action); - $key = $this->processKeyParameter($key,$tables,$database); - $satisfy = $this->processSatisfyParameter($tables,$satisfy); - $filters = $this->processFiltersParameter($tables,$satisfy,$filters); - $page = $this->processPageParameter($page); - $orderings = $this->processOrderingsParameter($orderings); - - // reflection - list($tables,$collect,$select) = $this->findRelations($tables,$database,$auto_include); - $fields = $this->findFields($tables,$columns,$exclude,$select,$database); - - // permissions - if ($table_authorizer) $this->applyTableAuthorizer($table_authorizer,$action,$database,$tables); - if (!isset($tables[0])) $this->exitWith404('entity'); - if ($record_filter) $this->applyRecordFilter($record_filter,$action,$database,$tables,$filters); - if ($tenancy_function) $this->applyTenancyFunction($tenancy_function,$action,$database,$fields,$filters); - if ($column_authorizer) $this->applyColumnAuthorizer($column_authorizer,$action,$database,$fields); - - // input - $inputs = $this->retrieveInputs($post); - foreach ($inputs as $k=>$context) { - $input = $this->filterInputByFields($context,$fields[$tables[0]]); - - if ($tenancy_function) $this->applyInputTenancy($tenancy_function,$action,$database,$tables[0],$input,$fields[$tables[0]]); - if ($input_sanitizer) $this->applyInputSanitizer($input_sanitizer,$action,$database,$tables[0],$input,$fields[$tables[0]]); - if ($input_validator) $this->applyInputValidator($input_validator,$action,$database,$tables[0],$input,$fields[$tables[0]],$context); - - $this->convertInputs($input,$fields[$tables[0]]); - $inputs[$k] = $input; - } - - if ($before) { - $this->applyBeforeHandler($action,$database,$tables[0],$key[0],$before,$inputs); - } - - return compact('action','database','tables','key','page','filters','fields','orderings','transform','inputs','collect','select','before','after'); - } - - protected function addWhereFromFilters($filters,&$sql,&$params) { - $first = true; - if (isset($filters['or'])) { - $first = false; - $sql .= ' WHERE ('; - foreach ($filters['or'] as $i=>$filter) { - $sql .= $i==0?'':' OR '; - $sql .= $filter[0]; - for ($i=1;$i$filter) { - $sql .= $first?' WHERE ':' AND '; - $sql .= $filter[0]; - for ($i=1;$i$ordering) { - $sql .= $i==0?' ORDER BY ':', '; - $sql .= '! '.$ordering[1]; - $params[] = $ordering[0]; - } - } - - protected function listCommandInternal($parameters) { - extract($parameters); - echo '{'; - $table = array_shift($tables); - // first table - $count = false; - echo '"'.$table.'":{'; - if (is_array($orderings) && is_array($page)) { - $params = array(); - $sql = 'SELECT COUNT(*) FROM !'; - $params[] = $table; - if (isset($filters[$table])) { - $this->addWhereFromFilters($filters[$table],$sql,$params); - } - if ($result = $this->db->query($sql,$params)) { - while ($pages = $this->db->fetchRow($result)) { - $count = (int)$pages[0]; - } - } - } - $params = array(); - $sql = 'SELECT '; - $this->convertOutputs($sql,$params,$fields[$table]); - $sql .= ' FROM !'; - $params[] = $table; - if (isset($filters[$table])) { - $this->addWhereFromFilters($filters[$table],$sql,$params); - } - if (is_array($orderings)) { - $this->addOrderByFromOrderings($orderings,$sql,$params); - } - if (is_array($orderings) && is_array($page)) { - $sql = $this->db->addLimitToSql($sql,$page[1],$page[0]); - } - if ($result = $this->db->query($sql,$params)) { - echo '"columns":'; - $keys = array_keys($fields[$table]); - echo json_encode($keys); - $keys = array_flip($keys); - echo ',"records":['; - $first_row = true; - while ($row = $this->fetchRow($result,$fields[$table])) { - if ($first_row) $first_row = false; - else echo ','; - if (isset($collect[$table])) { - foreach (array_keys($collect[$table]) as $field) { - $collect[$table][$field][] = $row[$keys[$field]]; - } - } - echo json_encode($row); - } - $this->db->close($result); - echo ']'; - if ($count) echo ','; - } - if ($count) echo '"results":'.$count; - echo '}'; - // other tables - foreach ($tables as $t=>$table) { - echo ','; - echo '"'.$table.'":{'; - $params = array(); - $sql = 'SELECT '; - $this->convertOutputs($sql,$params,$fields[$table]); - $sql .= ' FROM !'; - $params[] = $table; - if (isset($select[$table])) { - echo '"relations":{'; - $first_row = true; - foreach ($select[$table] as $field => $path) { - $values = $collect[$path[0]][$path[1]]; - if ($values) { - $this->addFilter($filters,$table,'and',$field,'in',implode(',',$values)); - } - if ($first_row) $first_row = false; - else echo ','; - echo '"'.$field.'":"'.implode('.',$path).'"'; - } - echo '}'; - } - if (isset($filters[$table])) { - $this->addWhereFromFilters($filters[$table],$sql,$params); - } - if ($result = $this->db->query($sql,$params)) { - if (isset($select[$table])) echo ','; - echo '"columns":'; - $keys = array_keys($fields[$table]); - echo json_encode($keys); - $keys = array_flip($keys); - echo ',"records":['; - $first_row = true; - while ($row = $this->fetchRow($result,$fields[$table])) { - if ($first_row) $first_row = false; - else echo ','; - if (isset($collect[$table])) { - foreach (array_keys($collect[$table]) as $field) { - $collect[$table][$field][]=$row[$keys[$field]]; - } - } - echo json_encode($row); - } - $this->db->close($result); - echo ']'; - } - echo '}'; - } - echo '}'; - } - - protected function readCommand($parameters) { - extract($parameters); - if (count($key[0])>1) $object = $this->retrieveObjects($key,$fields,$filters,$tables); - else $object = $this->retrieveObject($key,$fields,$filters,$tables); - if (!$object) $this->exitWith404('object'); - $this->startOutput(); - echo json_encode($object); - return false; - } - - protected function createCommand($parameters) { - extract($parameters); - if (!$inputs || !$inputs[0]) $this->exitWith404('input'); - if (count($inputs)>1) return $this->createObjects($inputs,$tables); - return $this->createObject($inputs[0],$tables); - - } - - protected function updateCommand($parameters) { - extract($parameters); - if (!$inputs || !$inputs[0]) $this->exitWith404('subject'); - if (count($inputs)>1) return $this->updateObjects($key,$inputs,$filters,$tables); - return $this->updateObject($key,$inputs[0],$filters,$tables); - } - - protected function deleteCommand($parameters) { - extract($parameters); - if (count($key[0])>1) return $this->deleteObjects($key,$filters,$tables); - return $this->deleteObject($key,$filters,$tables); - } - - protected function incrementCommand($parameters) { - extract($parameters); - if (!$inputs || !$inputs[0]) $this->exitWith404('subject'); - if (count($inputs)>1) return $this->incrementObjects($key,$inputs,$filters,$tables,$fields); - return $this->incrementObject($key,$inputs[0],$filters,$tables,$fields); - } - - protected function listCommand($parameters) { - extract($parameters); - $this->startOutput(); - if ($transform) { - ob_start(); - } - $this->listCommandInternal($parameters); - if ($transform) { - $content = ob_get_contents(); - ob_end_clean(); - $data = json_decode($content,true); - echo json_encode(self::php_crud_api_transform($data)); - } - return false; - } - - protected function retrievePostData() { - if ($_FILES) { - $files = array(); - foreach ($_FILES as $name => $file) { - foreach ($file as $key => $value) { - switch ($key) { - case 'tmp_name': $files[$name] = $value?base64_encode(file_get_contents($value)):''; break; - default: $files[$name.'_'.$key] = $value; - } - } - } - return http_build_query(array_merge($files,$_POST)); - } - return file_get_contents('php://input'); - } - - public function __construct($config) { - extract($config); - - // initialize - $dbengine = isset($dbengine)?$dbengine:null; - $hostname = isset($hostname)?$hostname:null; - $username = isset($username)?$username:null; - $password = isset($password)?$password:null; - $database = isset($database)?$database:null; - $port = isset($port)?$port:null; - $socket = isset($socket)?$socket:null; - $charset = isset($charset)?$charset:null; - - $table_authorizer = isset($table_authorizer)?$table_authorizer:null; - $record_filter = isset($record_filter)?$record_filter:null; - $column_authorizer = isset($column_authorizer)?$column_authorizer:null; - $tenancy_function = isset($tenancy_function)?$tenancy_function:null; - $input_sanitizer = isset($input_sanitizer)?$input_sanitizer:null; - $input_validator = isset($input_validator)?$input_validator:null; - $auto_include = isset($auto_include)?$auto_include:null; - $allow_origin = isset($allow_origin)?$allow_origin:null; - $before = isset($before)?$before:null; - $after = isset($after)?$after:null; - - $db = isset($db)?$db:null; - $method = isset($method)?$method:null; - $request = isset($request)?$request:null; - $get = isset($get)?$get:null; - $post = isset($post)?$post:null; - $origin = isset($origin)?$origin:null; - - // defaults - if (!$dbengine) { - $dbengine = 'MySQL'; - } - if (!$method) { - $method = $_SERVER['REQUEST_METHOD']; - } - if (!$request) { - $request = isset($_SERVER['PATH_INFO'])?$_SERVER['PATH_INFO']:''; - if (!$request) { - $request = isset($_SERVER['ORIG_PATH_INFO'])?$_SERVER['ORIG_PATH_INFO']:''; - $request = $request!=$_SERVER['SCRIPT_NAME']?$request:''; - } - } - if (!$get) { - $get = $_GET; - } - if (!$post) { - $post = $this->retrievePostData(); - } - if (!$origin) { - $origin = isset($_SERVER['HTTP_ORIGIN'])?$_SERVER['HTTP_ORIGIN']:''; - } - - // connect - $request = trim($request,'/'); - if (!$database) { - $database = $this->parseRequestParameter($request, 'a-zA-Z0-9\-_'); - } - if (!$db) { - $db = new $dbengine(); - if (!$charset) { - $charset = $db->getDefaultCharset(); - } - $db->connect($hostname,$username,$password,$database,$port,$socket,$charset); - } - if ($auto_include===null) { - $auto_include = true; - } - if ($allow_origin===null) { - $allow_origin = '*'; - } - - $this->db = $db; - $this->settings = compact('method', 'request', 'get', 'post', 'origin', 'database', 'table_authorizer', 'record_filter', 'column_authorizer', 'tenancy_function', 'input_sanitizer', 'input_validator', 'before', 'after', 'auto_include', 'allow_origin'); - } - - public static function php_crud_api_transform(&$tables) { - $get_objects = function (&$tables,$table_name,$where_index=false,$match_value=false) use (&$get_objects) { - $objects = array(); - if (isset($tables[$table_name]['records'])) { - foreach ($tables[$table_name]['records'] as $record) { - if ($where_index===false || $record[$where_index]==$match_value) { - $object = array(); - foreach ($tables[$table_name]['columns'] as $index=>$column) { - $object[$column] = $record[$index]; - foreach ($tables as $relation=>$reltable) { - if (isset($reltable['relations'])) { - foreach ($reltable['relations'] as $key=>$target) { - if ($target == "$table_name.$column") { - $column_indices = array_flip($reltable['columns']); - $object[$relation] = $get_objects($tables,$relation,$column_indices[$key],$record[$index]); - } - } - } - } - } - $objects[] = $object; - } - } - } - return $objects; - }; - $tree = array(); - foreach ($tables as $name=>$table) { - if (!isset($table['relations'])) { - $tree[$name] = $get_objects($tables,$name); - if (isset($table['results'])) { - $tree['_results'] = $table['results']; - } - } - } - return $tree; - } - - protected function swagger($settings) { - extract($settings); - - $tables = array(); - if ($result = $this->db->query($this->db->getSql('list_tables'),array($database))) { - while ($row = $this->db->fetchRow($result)) { - $table = array( - 'name'=>$row[0], - 'comments'=>$row[1], - 'root_actions'=>array( - array('name'=>'list','method'=>'get'), - array('name'=>'create','method'=>'post'), - ), - 'id_actions'=>array( - array('name'=>'read','method'=>'get'), - array('name'=>'update','method'=>'put'), - array('name'=>'delete','method'=>'delete'), - array('name'=>'increment','method'=>'patch'), - ), - ); - $tables[] = $table; - } - $this->db->close($result); - } - - $table_names = array_map(function($v){ return $v['name'];},$tables); - foreach ($tables as $t=>$table) { - $table_list = array($table['name']); - $table_fields = $this->findFields($table_list,false,false,false,$database); - - // extensions - $result = $this->db->query($this->db->getSql('reflect_belongs_to'),array($table_list[0],$table_names,$database,$database)); - while ($row = $this->db->fetchRow($result)) { - $table_fields[$table['name']][$row[1]]->references=array($row[2],$row[3]); - } - $result = $this->db->query($this->db->getSql('reflect_has_many'),array($table_names,$table_list[0],$database,$database)); - while ($row = $this->db->fetchRow($result)) { - $table_fields[$table['name']][$row[3]]->referenced[]=array($row[0],$row[1]); - } - $primaryKeys = $this->findPrimaryKeys($table_list[0],$database); - foreach ($primaryKeys as $primaryKey) { - $table_fields[$table['name']][$primaryKey]->primaryKey = true; - } - $result = $this->db->query($this->db->getSql('reflect_columns'),array($table_list[0],$database)); - while ($row = $this->db->fetchRow($result)) { - $table_fields[$table['name']][$row[0]]->required = strtolower($row[2])=='no' && $row[1]===null; - $table_fields[$table['name']][$row[0]]->{'x-nullable'} = strtolower($row[2])=='yes'; - $table_fields[$table['name']][$row[0]]->{'x-dbtype'} = $row[3]; - if ($this->db->isNumericType($table_fields[$table['name']][$row[0]])) { - if (strpos(strtolower($table_fields[$table['name']][$row[0]]->{'x-dbtype'}),'int')!==false) { - $table_fields[$table['name']][$row[0]]->type = 'integer'; - if ($row[1]!==null) $table_fields[$table['name']][$row[0]]->default = (int)$row[1]; - } else { - $table_fields[$table['name']][$row[0]]->type = 'number'; - if ($row[1]!==null) $table_fields[$table['name']][$row[0]]->default = (float)$row[1]; - } - } else { - if ($this->db->isBinaryType($table_fields[$table['name']][$row[0]])) { - $table_fields[$table['name']][$row[0]]->format = 'byte'; - } else if ($this->db->isGeometryType($table_fields[$table['name']][$row[0]])) { - $table_fields[$table['name']][$row[0]]->format = 'wkt'; - } else if ($this->db->isJsonType($table_fields[$table['name']][$row[0]])) { - $table_fields[$table['name']][$row[0]]->format = 'json'; - } - $table_fields[$table['name']][$row[0]]->type = 'string'; - if ($row[1]!==null) $table_fields[$table['name']][$row[0]]->default = $row[1]; - if ($row[4]!==null) $table_fields[$table['name']][$row[0]]->maxLength = (int)$row[4]; - } - } - - foreach (array('root_actions','id_actions') as $path) { - foreach ($table[$path] as $i=>$action) { - $table_list = array($table['name']); - $fields = $table_fields; - if ($table_authorizer) $this->applyTableAuthorizer($table_authorizer,$action['name'],$database,$table_list); - if ($column_authorizer) $this->applyColumnAuthorizer($column_authorizer,$action['name'],$database,$fields); - if (!$table_list || !$fields[$table['name']]) $tables[$t][$path][$i] = false; - else $tables[$t][$path][$i]['fields'] = $fields[$table['name']]; - } - // remove unauthorized tables and tables without fields - $tables[$t][$path] = array_values(array_filter($tables[$t][$path])); - } - if (!$tables[$t]['root_actions']&&!$tables[$t]['id_actions']) $tables[$t] = false; - } - $tables = array_merge(array_filter($tables)); - //var_dump($tables);die(); - - header('Content-Type: application/json; charset=utf-8'); - echo '{"swagger":"2.0",'; - echo '"info":{'; - echo '"title":"'.$database.'",'; - echo '"description":"API generated with [PHP-CRUD-API](https://github.com/mevdschee/php-crud-api)",'; - echo '"version":"1.0.0"'; - echo '},'; - echo '"host":"'.$_SERVER['HTTP_HOST'].'",'; - echo '"basePath":"'.$_SERVER['SCRIPT_NAME'].'",'; - echo '"schemes":["http'.((!empty($_SERVER['HTTPS'])&&$_SERVER['HTTPS']!=='off')?'s':'').'"],'; - echo '"consumes":["application/json"],'; - echo '"produces":["application/json"],'; - echo '"tags":['; - foreach ($tables as $i=>$table) { - if ($i>0) echo ','; - echo '{'; - echo '"name":"'.$table['name'].'",'; - echo '"description":"'.$table['comments'].'"'; - echo '}'; - } - echo '],'; - echo '"paths":{'; - foreach ($tables as $i=>$table) { - if ($table['root_actions']) { - if ($i>0) echo ','; - echo '"/'.$table['name'].'":{'; - foreach ($table['root_actions'] as $j=>$action) { - if ($j>0) echo ','; - echo '"'.$action['method'].'":{'; - echo '"tags":["'.$table['name'].'"],'; - echo '"summary":"'.ucfirst($action['name']).'",'; - if ($action['name']=='list') { - echo '"parameters":['; - echo '{'; - echo '"name":"exclude",'; - echo '"in":"query",'; - echo '"description":"One or more related entities (comma separated).",'; - echo '"required":false,'; - echo '"type":"string"'; - echo '},'; - echo '{'; - echo '"name":"include",'; - echo '"in":"query",'; - echo '"description":"One or more related entities (comma separated).",'; - echo '"required":false,'; - echo '"type":"string"'; - echo '},'; - echo '{'; - echo '"name":"order",'; - echo '"in":"query",'; - echo '"description":"Column you want to sort on and the sort direction (comma separated). Example: id,desc",'; - echo '"required":false,'; - echo '"type":"string"'; - echo '},'; - echo '{'; - echo '"name":"page",'; - echo '"in":"query",'; - echo '"description":"Page number and page size (comma separated). NB: You cannot use \"page\" without \"order\"! Example: 1,10",'; - echo '"required":false,'; - echo '"type":"string"'; - echo '},'; - echo '{'; - echo '"name":"transform",'; - echo '"in":"query",'; - echo '"description":"Transform the records to object format. NB: This can also be done client-side in JavaScript!",'; - echo '"required":false,'; - echo '"type":"boolean"'; - echo '},'; - echo '{'; - echo '"name":"columns",'; - echo '"in":"query",'; - echo '"description":"The table columns you want to retrieve (comma separated). Example: posts.*,categories.name",'; - echo '"required":false,'; - echo '"type":"string"'; - echo '},'; - echo '{'; - echo '"name":"filter[]",'; - echo '"in":"query",'; - echo '"description":"Filters to be applied. Each filter consists of a column, an operator and a value (comma separated). Example: id,eq,1",'; - echo '"required":false,'; - echo '"type":"array",'; - echo '"collectionFormat":"multi",'; - echo '"items":{"type":"string"}'; - echo '},'; - echo '{'; - echo '"name":"satisfy",'; - echo '"in":"query",'; - echo '"description":"Should all filters match (default)? Or any?",'; - echo '"required":false,'; - echo '"type":"string",'; - echo '"enum":["any"]'; - echo '}'; - echo '],'; - echo '"responses":{'; - echo '"200":{'; - echo '"description":"An array of '.$table['name'].'",'; - echo '"schema":{'; - echo '"type": "object",'; - echo '"properties": {'; - echo '"'.$table['name'].'": {'; - echo '"type":"array",'; - echo '"items":{'; - echo '"type": "object",'; - echo '"properties": {'; - foreach (array_keys($action['fields']) as $k=>$field) { - if ($k>0) echo ','; - echo '"'.$field.'": {'; - echo '"type": '.json_encode($action['fields'][$field]->type); - if (isset($action['fields'][$field]->format)) { - echo ',"format": '.json_encode($action['fields'][$field]->format); - } - echo ',"x-dbtype": '.json_encode($action['fields'][$field]->{'x-dbtype'}); - echo ',"x-nullable": '.json_encode($action['fields'][$field]->{'x-nullable'}); - if (isset($action['fields'][$field]->maxLength) && $action['fields'][$field]->maxLength>0) { - echo ',"maxLength": '.json_encode($action['fields'][$field]->maxLength); - } - if (isset($action['fields'][$field]->default)) { - echo ',"default": '.json_encode($action['fields'][$field]->default); - } - if (isset($action['fields'][$field]->referenced)) { - echo ',"x-referenced": '.json_encode($action['fields'][$field]->referenced); - } - if (isset($action['fields'][$field]->references)) { - echo ',"x-references": '.json_encode($action['fields'][$field]->references); - } - if (isset($action['fields'][$field]->primaryKey)) { - echo ',"x-primary-key": true'; - } - echo '}'; - } - echo '}'; //properties - echo '}'; //items - echo '}'; //table - echo '}'; //properties - echo '}'; //schema - echo '}'; //200 - echo '}'; //responses - } - if ($action['name']=='create') { - echo '"parameters":[{'; - echo '"name":"item",'; - echo '"in":"body",'; - echo '"description":"Item to create.",'; - echo '"required":true,'; - echo '"schema":{'; - echo '"type": "object",'; - $required_fields = array_keys(array_filter($action['fields'],function($f){ return $f->required; })); - if (count($required_fields) > 0) { - echo '"required":'.json_encode($required_fields).','; - } - echo '"properties": {'; - foreach (array_keys($action['fields']) as $k=>$field) { - if ($k>0) echo ','; - echo '"'.$field.'": {'; - echo '"type": '.json_encode($action['fields'][$field]->type); - if (isset($action['fields'][$field]->format)) { - echo ',"format": '.json_encode($action['fields'][$field]->format); - } - echo ',"x-dbtype": '.json_encode($action['fields'][$field]->{'x-dbtype'}); - echo ',"x-nullable": '.json_encode($action['fields'][$field]->{'x-nullable'}); - if (isset($action['fields'][$field]->maxLength)) { - echo ',"maxLength": '.json_encode($action['fields'][$field]->maxLength); - } - if (isset($action['fields'][$field]->default)) { - echo ',"default": '.json_encode($action['fields'][$field]->default); - } - if (isset($action['fields'][$field]->referenced)) { - echo ',"x-referenced": '.json_encode($action['fields'][$field]->referenced); - } - if (isset($action['fields'][$field]->references)) { - echo ',"x-references": '.json_encode($action['fields'][$field]->references); - } - if (isset($action['fields'][$field]->primaryKey)) { - echo ',"x-primary-key": true'; - } - echo '}'; - } - echo '}'; //properties - echo '}'; //schema - echo '}],'; - echo '"responses":{'; - echo '"200":{'; - echo '"description":"Identifier of created item.",'; - echo '"schema":{'; - echo '"type":"integer"'; - echo '}';//schema - echo '}';//200 - echo '}';//responses - } - echo '}';//method - } - echo '}'; - } - if ($table['id_actions']) { - if ($i>0 || $table['root_actions']) echo ','; - echo '"/'.$table['name'].'/{id}":{'; - foreach ($table['id_actions'] as $j=>$action) { - if ($j>0) echo ','; - echo '"'.$action['method'].'":{'; - echo '"tags":["'.$table['name'].'"],'; - echo '"summary":"'.ucfirst($action['name']).'",'; - echo '"parameters":['; - echo '{'; - echo '"name":"id",'; - echo '"in":"path",'; - echo '"description":"Identifier for item.",'; - echo '"required":true,'; - echo '"type":"string"'; - echo '}'; - if ($action['name']=='update' || $action['name']=='increment') { - echo ',{'; - echo '"name":"item",'; - echo '"in":"body",'; - echo '"description":"Properties of item to update.",'; - echo '"required":true,'; - echo '"schema":{'; - echo '"type": "object",'; - $required_fields = array_keys(array_filter($action['fields'],function($f){ return $f->required; })); - if (count($required_fields) > 0) { - echo '"required":'.json_encode($required_fields).','; - } - echo '"properties": {'; - foreach (array_keys($action['fields']) as $k=>$field) { - if ($k>0) echo ','; - echo '"'.$field.'": {'; - echo '"type": '.json_encode($action['fields'][$field]->type); - if (isset($action['fields'][$field]->format)) { - echo ',"format": '.json_encode($action['fields'][$field]->format); - } - echo ',"x-dbtype": '.json_encode($action['fields'][$field]->{'x-dbtype'}); - echo ',"x-nullable": '.json_encode($action['fields'][$field]->{'x-nullable'}); - if (isset($action['fields'][$field]->maxLength)) { - echo ',"maxLength": '.json_encode($action['fields'][$field]->maxLength); - } - if (isset($action['fields'][$field]->default)) { - echo ',"default": '.json_encode($action['fields'][$field]->default); - } - if (isset($action['fields'][$field]->referenced)) { - echo ',"x-referenced": '.json_encode($action['fields'][$field]->referenced); - } - if (isset($action['fields'][$field]->references)) { - echo ',"x-references": '.json_encode($action['fields'][$field]->references); - } - if (isset($action['fields'][$field]->primaryKey)) { - echo ',"x-primary-key": true'; - } - echo '}'; - } - echo '}'; //properties - echo '}'; //schema - echo '}'; - } - echo '],'; - if ($action['name']=='read') { - echo '"responses":{'; - echo '"200":{'; - echo '"description":"The requested item.",'; - echo '"schema":{'; - echo '"type": "object",'; - echo '"properties": {'; - foreach (array_keys($action['fields']) as $k=>$field) { - if ($k>0) echo ','; - echo '"'.$field.'": {'; - echo '"type": '.json_encode($action['fields'][$field]->type); - if (isset($action['fields'][$field]->format)) { - echo ',"format": '.json_encode($action['fields'][$field]->format); - } - echo ',"x-dbtype": '.json_encode($action['fields'][$field]->{'x-dbtype'}); - echo ',"x-nullable": '.json_encode($action['fields'][$field]->{'x-nullable'}); - if (isset($action['fields'][$field]->maxLength)) { - echo ',"maxLength": '.json_encode($action['fields'][$field]->maxLength); - } - if (isset($action['fields'][$field]->default)) { - echo ',"default": '.json_encode($action['fields'][$field]->default); - } - if (isset($action['fields'][$field]->referenced)) { - echo ',"x-referenced": '.json_encode($action['fields'][$field]->referenced); - } - if (isset($action['fields'][$field]->references)) { - echo ',"x-references": '.json_encode($action['fields'][$field]->references); - } - if (isset($action['fields'][$field]->primaryKey)) { - echo ',"x-primary-key": true'; - } - echo '}'; - } - echo '}'; //properties - echo '}'; //schema - echo '}'; - echo '}'; - } else { - echo '"responses":{'; - echo '"200":{'; - echo '"description":"Number of affected rows.",'; - echo '"schema":{'; - echo '"type":"integer"'; - echo '}'; - echo '}'; - echo '}'; - } - echo '}'; - } - echo '}'; - } - } - echo '}'; - echo '}'; - } - - protected function allowOrigin($origin,$allowOrigins) { - if (isset($_SERVER['REQUEST_METHOD'])) { - header('Access-Control-Allow-Credentials: true'); - foreach (explode(',',$allowOrigins) as $o) { - if (preg_match('/^'.str_replace('\*','.*',preg_quote(strtolower(trim($o)))).'$/',$origin)) { - header('Access-Control-Allow-Origin: '.$origin); - break; - } - } - } - } - - public function executeCommand() { - if ($this->settings['origin']) { - $this->allowOrigin($this->settings['origin'],$this->settings['allow_origin']); - } - if (!$this->settings['request']) { - $this->swagger($this->settings); - } else { - $parameters = $this->getParameters($this->settings); - switch($parameters['action']){ - case 'list': $output = $this->listCommand($parameters); break; - case 'read': $output = $this->readCommand($parameters); break; - case 'create': $output = $this->createCommand($parameters); break; - case 'update': $output = $this->updateCommand($parameters); break; - case 'delete': $output = $this->deleteCommand($parameters); break; - case 'increment': $output = $this->incrementCommand($parameters); break; - case 'headers': $output = $this->headersCommand($parameters); break; - default: $output = false; - } - if ($output!==false) { - $this->startOutput(); - echo json_encode($output); - } - if ($parameters['after']) { - $this->applyAfterHandler($parameters,$output); - } - } - } -} - -// require 'auth.php'; // from the PHP-API-AUTH project, see: https://github.com/mevdschee/php-api-auth - -// uncomment the lines below for token+session based authentication (see "login_token.html" + "login_token.php"): - -// $auth = new PHP_API_AUTH(array( -// 'secret'=>'someVeryLongPassPhraseChangeMe', -// )); -// if ($auth->executeCommand()) exit(0); -// if (empty($_SESSION['user']) || !$auth->hasValidCsrfToken()) { -// header('HTTP/1.0 401 Unauthorized'); -// exit(0); -// } - -// uncomment the lines below for form+session based authentication (see "login.html"): - -// $auth = new PHP_API_AUTH(array( -// 'authenticator'=>function($user,$pass){ $_SESSION['user']=($user=='admin' && $pass=='admin'); } -// )); -// if ($auth->executeCommand()) exit(0); -// if (empty($_SESSION['user']) || !$auth->hasValidCsrfToken()) { -// header('HTTP/1.0 401 Unauthorized'); -// exit(0); -// } - -// uncomment the lines below when running in stand-alone mode: - - $api = new PHP_CRUD_API(array( - 'dbengine'=>'MySQL', - 'hostname'=>'localhost', - 'username'=>'lazyp_workadmin', - 'password'=>'GH5fZF0iCtLnHLrz', - 'database'=>'LudosData', - 'charset'=>'utf8mb4' - )); - $api->executeCommand(); - -// For Microsoft SQL Server 2012 use: - -// $api = new PHP_CRUD_API(array( -// 'dbengine'=>'SQLServer', -// 'hostname'=>'(local)', -// 'username'=>'', -// 'password'=>'', -// 'database'=>'xxx', -// 'charset'=>'UTF-8' -// )); -// $api->executeCommand(); - -// For PostgreSQL 9 use: - -// $api = new PHP_CRUD_API(array( -// 'dbengine'=>'PostgreSQL', -// 'hostname'=>'localhost', -// 'username'=>'xxx', -// 'password'=>'xxx', -// 'database'=>'xxx', -// 'charset'=>'UTF8' -// )); -// $api->executeCommand(); - -// For SQLite 3 use: - -// $api = new PHP_CRUD_API(array( -// 'dbengine'=>'SQLite', -// 'database'=>'data/blog.db', -// )); -// $api->executeCommand(); diff --git a/authlogin/login_token.html b/authlogin/login_token.html deleted file mode 100644 index 900b9e2..0000000 --- a/authlogin/login_token.html +++ /dev/null @@ -1,5 +0,0 @@ -
- - - -
diff --git a/authlogin/login_token.php b/authlogin/login_token.php deleted file mode 100644 index 2e3c1b9..0000000 --- a/authlogin/login_token.php +++ /dev/null @@ -1,13 +0,0 @@ -
-'someVeryLongPassPhraseChangeMe', - 'authenticator'=>function($user,$pass){ if ($user=='admin' && $pass=='admin') $_SESSION['user']=$user; } -)); -$auth->executeCommand(); -?>/> - -
diff --git a/authlogin/logout.html b/authlogin/logout.html deleted file mode 100644 index de841ba..0000000 --- a/authlogin/logout.html +++ /dev/null @@ -1,3 +0,0 @@ -
- -
diff --git a/backend/.dockerignore b/backend/.dockerignore new file mode 100644 index 0000000..d143a2e --- /dev/null +++ b/backend/.dockerignore @@ -0,0 +1,7 @@ +**/bin/ +**/obj/ +**/data/ +**/uploads/ +**/*.user +**/.vs/ +**/.vscode/ diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..116e2cc --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,50 @@ +# syntax=docker/dockerfile:1 + +# ---- build ---------------------------------------------------------------- +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build +WORKDIR /src + +# Restore against the project file alone so the layer caches across code edits. +COPY src/LudosData.Api/LudosData.Api.csproj src/LudosData.Api/ +RUN dotnet restore src/LudosData.Api/LudosData.Api.csproj + +COPY src/ src/ +RUN dotnet publish src/LudosData.Api/LudosData.Api.csproj \ + -c Release \ + -o /app/publish \ + --no-restore \ + /p:UseAppHost=false + +# ---- runtime -------------------------------------------------------------- +FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime +WORKDIR /app + +# The runtime image ships neither curl nor wget, so the container healthcheck +# below has nothing to probe with unless one is added. +RUN apt-get update \ + && apt-get install -y --no-install-recommends curl \ + && rm -rf /var/lib/apt/lists/* + +# Run as the non-root user the base image already ships with. +ENV ASPNETCORE_HTTP_PORTS=8080 \ + DOTNET_RUNNING_IN_CONTAINER=true \ + ConnectionStrings__Default="Data Source=/data/ludos.db" \ + Uploads__RootPath=/data/uploads + +COPY --from=build /app/publish . + +# Writable mount point for the SQLite file and uploaded art. Declared as a volume +# so an unmounted run still persists for the life of the container rather than +# failing to open the database. +RUN mkdir -p /data/uploads && chown -R $APP_UID:$APP_UID /data +VOLUME ["/data"] + +USER $APP_UID +EXPOSE 8080 + +# Probes the app's own health endpoint, so "healthy" means it is actually +# serving requests rather than merely that the process exists. +HEALTHCHECK --interval=30s --timeout=3s --start-period=15s --retries=3 \ + CMD curl -fsS http://localhost:8080/health || exit 1 + +ENTRYPOINT ["dotnet", "LudosData.Api.dll"] diff --git a/backend/LudosData.slnx b/backend/LudosData.slnx new file mode 100644 index 0000000..00978ca --- /dev/null +++ b/backend/LudosData.slnx @@ -0,0 +1,5 @@ + + + + + diff --git a/backend/global.json b/backend/global.json new file mode 100644 index 0000000..545c92c --- /dev/null +++ b/backend/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "10.0.302", + "rollForward": "latestFeature" + } +} diff --git a/backend/src/LudosData.Api/Auth/JwtOptions.cs b/backend/src/LudosData.Api/Auth/JwtOptions.cs new file mode 100644 index 0000000..62fc45b --- /dev/null +++ b/backend/src/LudosData.Api/Auth/JwtOptions.cs @@ -0,0 +1,27 @@ +using System.ComponentModel.DataAnnotations; + +namespace LudosData.Api.Auth; + +public class JwtOptions +{ + public const string SectionName = "Jwt"; + + /// + /// HMAC-SHA256 signing key. Supplied via the JWT__KEY environment variable — + /// there is deliberately no default, so a misconfigured deployment fails to + /// start rather than signing tokens with a guessable key. + /// + [Required(AllowEmptyStrings = false)] + [MinLength(32, ErrorMessage = "Jwt:Key must be at least 32 characters.")] + public string Key { get; set; } = string.Empty; + + [Required] public string Issuer { get; set; } = "LudosData"; + [Required] public string Audience { get; set; } = "LudosData"; + + /// + /// Access token lifetime. Twelve hours suits a single-user library app; there + /// is no refresh token flow, so expiry sends the user back to the login form. + /// + [Range(1, 24 * 60 * 7)] + public int LifetimeMinutes { get; set; } = 720; +} diff --git a/backend/src/LudosData.Api/Auth/TokenService.cs b/backend/src/LudosData.Api/Auth/TokenService.cs new file mode 100644 index 0000000..060aaf0 --- /dev/null +++ b/backend/src/LudosData.Api/Auth/TokenService.cs @@ -0,0 +1,65 @@ +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using LudosData.Api.Domain; +using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Tokens; + +namespace LudosData.Api.Auth; + +public interface ITokenService +{ + (string Token, DateTimeOffset ExpiresAt) CreateAccessToken(AppUser user); +} + +public class TokenService(IOptions options) : ITokenService +{ + private readonly JwtOptions _options = options.Value; + + public (string Token, DateTimeOffset ExpiresAt) CreateAccessToken(AppUser user) + { + var expiresAt = DateTimeOffset.UtcNow.AddMinutes(_options.LifetimeMinutes); + + var claims = new List + { + // The subject is the only thing authorization trusts. Ownership checks + // read it server-side; the client cannot influence which rows it sees. + new(JwtRegisteredClaimNames.Sub, user.Id), + new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), + new(ClaimTypes.NameIdentifier, user.Id), + }; + + if (!string.IsNullOrEmpty(user.UserName)) + { + claims.Add(new Claim(JwtRegisteredClaimNames.UniqueName, user.UserName)); + } + + if (!string.IsNullOrEmpty(user.Email)) + { + claims.Add(new Claim(JwtRegisteredClaimNames.Email, user.Email)); + } + + var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_options.Key)); + var token = new JwtSecurityToken( + issuer: _options.Issuer, + audience: _options.Audience, + claims: claims, + notBefore: DateTime.UtcNow, + expires: expiresAt.UtcDateTime, + signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256)); + + return (new JwtSecurityTokenHandler().WriteToken(token), expiresAt); + } +} + +public static class ClaimsPrincipalExtensions +{ + /// + /// The authenticated user's id. Throws rather than returning null: every call + /// site sits behind [Authorize], so a missing subject is a bug, not a branch. + /// + public static string GetUserId(this ClaimsPrincipal principal) => + principal.FindFirstValue(ClaimTypes.NameIdentifier) + ?? principal.FindFirstValue(JwtRegisteredClaimNames.Sub) + ?? throw new InvalidOperationException("Authenticated principal has no subject claim."); +} diff --git a/backend/src/LudosData.Api/Contracts/AuthContracts.cs b/backend/src/LudosData.Api/Contracts/AuthContracts.cs new file mode 100644 index 0000000..3ea4f08 --- /dev/null +++ b/backend/src/LudosData.Api/Contracts/AuthContracts.cs @@ -0,0 +1,36 @@ +using System.ComponentModel.DataAnnotations; + +namespace LudosData.Api.Contracts; + +public record RegisterRequest +{ + [Required, MinLength(3), MaxLength(50)] + public string UserName { get; init; } = string.Empty; + + [Required, EmailAddress, MaxLength(256)] + public string Email { get; init; } = string.Empty; + + [Required, MinLength(12), MaxLength(128)] + public string Password { get; init; } = string.Empty; + + [MaxLength(100)] public string? FirstName { get; init; } + [MaxLength(100)] public string? LastName { get; init; } +} + +public record LoginRequest +{ + [Required] public string UserName { get; init; } = string.Empty; + [Required] public string Password { get; init; } = string.Empty; +} + +public record UserResponse( + string Id, + string UserName, + string? Email, + string? FirstName, + string? LastName, + string? Art); + +public record AuthResponse(string Token, DateTimeOffset ExpiresAt, UserResponse User); + +public record AvailabilityResponse(bool Available); diff --git a/backend/src/LudosData.Api/Contracts/GameContracts.cs b/backend/src/LudosData.Api/Contracts/GameContracts.cs new file mode 100644 index 0000000..98a4a64 --- /dev/null +++ b/backend/src/LudosData.Api/Contracts/GameContracts.cs @@ -0,0 +1,86 @@ +using System.ComponentModel.DataAnnotations; + +namespace LudosData.Api.Contracts; + +/// A page of results plus the totals the paginator needs. +public record PagedResult(IReadOnlyList Items, int Page, int PageSize, int Total) +{ + public int TotalPages => PageSize > 0 ? (int)Math.Ceiling(Total / (double)PageSize) : 0; +} + +/// +/// A game as returned to the client. Art is the stored filename; ArtUrl +/// is the ready-to-use URL built server-side, so the client never has to +/// string-concatenate upload paths the way the old grid did. +/// +public record GameResponse( + int Id, + string Title, + string? System, + string? Genre, + string? Year, + string? Developer, + string? Publisher, + string? Art, + string? ArtUrl, + string? Description, + bool Own, + bool Dumped, + bool Played, + bool Finished, + DateTimeOffset CreatedAt, + DateTimeOffset UpdatedAt); + +/// +/// Create/update payload. Deliberately has no Id and no OwnerId — the route supplies +/// the former and the JWT the latter, so neither can be spoofed by the client. +/// +public record GameRequest +{ + [Required(AllowEmptyStrings = false), MaxLength(200)] + public string Title { get; init; } = string.Empty; + + [MaxLength(50)] public string? System { get; init; } + [MaxLength(50)] public string? Genre { get; init; } + [MaxLength(50)] public string? Year { get; init; } + [MaxLength(100)] public string? Developer { get; init; } + [MaxLength(100)] public string? Publisher { get; init; } + [MaxLength(200)] public string? Art { get; init; } + [MaxLength(10_000)] public string? Description { get; init; } + + public bool Own { get; init; } + public bool Dumped { get; init; } + public bool Played { get; init; } + public bool Finished { get; init; } +} + +/// Query string for the library list, bound from [FromQuery]. +public record GameQuery +{ + /// Free-text match against title, developer and publisher. + public string? Search { get; init; } + + public string? System { get; init; } + public string? Genre { get; init; } + + public bool? Own { get; init; } + public bool? Dumped { get; init; } + public bool? Played { get; init; } + public bool? Finished { get; init; } + + [Range(1, int.MaxValue)] public int Page { get; init; } = 1; + + /// Capped at 100 to keep a hostile or buggy client from asking for everything. + [Range(1, 100)] public int PageSize { get; init; } = 20; + + /// One of: title, system, genre, year, developer, publisher, created, updated. + public string Sort { get; init; } = "title"; + + /// "asc" or "desc". + public string Dir { get; init; } = "asc"; +} + +/// Distinct values present in the user's library, for filter dropdowns. +public record FacetsResponse(IReadOnlyList Systems, IReadOnlyList Genres); + +public record UploadResponse(string FileName, string Url); diff --git a/backend/src/LudosData.Api/Controllers/AuthController.cs b/backend/src/LudosData.Api/Controllers/AuthController.cs new file mode 100644 index 0000000..f199166 --- /dev/null +++ b/backend/src/LudosData.Api/Controllers/AuthController.cs @@ -0,0 +1,118 @@ +using LudosData.Api.Auth; +using LudosData.Api.Contracts; +using LudosData.Api.Domain; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; + +namespace LudosData.Api.Controllers; + +[ApiController] +[Route("api/auth")] +public class AuthController( + UserManager userManager, + SignInManager signInManager, + ITokenService tokenService, + ILogger logger) : ControllerBase +{ + [HttpPost("register")] + [AllowAnonymous] + public async Task> Register(RegisterRequest request) + { + var user = new AppUser + { + UserName = request.UserName, + Email = request.Email, + FirstName = request.FirstName, + LastName = request.LastName, + }; + + var result = await userManager.CreateAsync(user, request.Password); + if (!result.Succeeded) + { + foreach (var error in result.Errors) + { + ModelState.AddModelError(error.Code, error.Description); + } + + return ValidationProblem(ModelState); + } + + logger.LogInformation("Registered user {UserName}", user.UserName); + return Ok(BuildAuthResponse(user)); + } + + [HttpPost("login")] + [AllowAnonymous] + public async Task> Login(LoginRequest request) + { + var user = await userManager.FindByNameAsync(request.UserName); + if (user is null) + { + // Same response as a bad password, so this endpoint cannot be used to + // enumerate which usernames exist. + return Unauthorized(new ProblemDetails { Title = "Invalid username or password." }); + } + + var result = await signInManager.CheckPasswordSignInAsync(user, request.Password, lockoutOnFailure: true); + + if (result.IsLockedOut) + { + return StatusCode(StatusCodes.Status423Locked, + new ProblemDetails { Title = "Account temporarily locked after too many failed attempts." }); + } + + if (!result.Succeeded) + { + return Unauthorized(new ProblemDetails { Title = "Invalid username or password." }); + } + + return Ok(BuildAuthResponse(user)); + } + + [HttpGet("me")] + [Authorize] + public async Task> Me() + { + var user = await userManager.FindByIdAsync(User.GetUserId()); + return user is null ? Unauthorized() : Ok(ToUserResponse(user)); + } + + /// + /// Availability check for the registration form. Replaces the old approach of + /// querying the users table through the generic CRUD endpoint, which exposed + /// every user column to anonymous callers; this returns only a boolean. + /// + [HttpGet("available")] + [AllowAnonymous] + public async Task> Available( + [FromQuery] string? userName, + [FromQuery] string? email) + { + if (!string.IsNullOrWhiteSpace(userName)) + { + return Ok(new AvailabilityResponse(await userManager.FindByNameAsync(userName) is null)); + } + + if (!string.IsNullOrWhiteSpace(email)) + { + return Ok(new AvailabilityResponse(await userManager.FindByEmailAsync(email) is null)); + } + + return BadRequest(new ProblemDetails { Title = "Provide either userName or email." }); + } + + private AuthResponse BuildAuthResponse(AppUser user) + { + var (token, expiresAt) = tokenService.CreateAccessToken(user); + return new AuthResponse(token, expiresAt, ToUserResponse(user)); + } + + private static UserResponse ToUserResponse(AppUser user) => new( + user.Id, + user.UserName ?? string.Empty, + user.Email, + user.FirstName, + user.LastName, + user.Art); +} diff --git a/backend/src/LudosData.Api/Controllers/GamesController.cs b/backend/src/LudosData.Api/Controllers/GamesController.cs new file mode 100644 index 0000000..8d86afd --- /dev/null +++ b/backend/src/LudosData.Api/Controllers/GamesController.cs @@ -0,0 +1,179 @@ +using LudosData.Api.Auth; +using LudosData.Api.Contracts; +using LudosData.Api.Data; +using LudosData.Api.Domain; +using LudosData.Api.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace LudosData.Api.Controllers; + +/// +/// The user's game library. +/// +/// Every query starts from Where(g => g.OwnerId == currentUserId), taken from +/// the JWT subject. The old API took the owner id from a client-supplied query +/// parameter (filter[]=userId,eq,N), which meant any valid token could read +/// any other user's library by editing the number. +/// +[ApiController] +[Route("api/games")] +[Authorize] +public class GamesController( + LudosDbContext db, + IImageStorage images, + ILogger logger) : ControllerBase +{ + [HttpGet] + public async Task>> List([FromQuery] GameQuery query, CancellationToken ct) + { + var ownerId = User.GetUserId(); + var q = db.Games.AsNoTracking().Where(g => g.OwnerId == ownerId); + + if (!string.IsNullOrWhiteSpace(query.Search)) + { + var term = query.Search.Trim(); + q = q.Where(g => + EF.Functions.Like(g.Title, $"%{term}%") || + (g.Developer != null && EF.Functions.Like(g.Developer, $"%{term}%")) || + (g.Publisher != null && EF.Functions.Like(g.Publisher, $"%{term}%"))); + } + + if (!string.IsNullOrWhiteSpace(query.System)) q = q.Where(g => g.System == query.System); + if (!string.IsNullOrWhiteSpace(query.Genre)) q = q.Where(g => g.Genre == query.Genre); + + if (query.Own is { } own) q = q.Where(g => g.Own == own); + if (query.Dumped is { } dumped) q = q.Where(g => g.Dumped == dumped); + if (query.Played is { } played) q = q.Where(g => g.Played == played); + if (query.Finished is { } finished) q = q.Where(g => g.Finished == finished); + + var total = await q.CountAsync(ct); + + q = ApplySort(q, query.Sort, query.Dir); + + var items = await q + .Skip((query.Page - 1) * query.PageSize) + .Take(query.PageSize) + .ToListAsync(ct); + + return Ok(new PagedResult( + items.Select(g => ToResponse(g, ownerId)).ToList(), + query.Page, + query.PageSize, + total)); + } + + [HttpGet("{id:int}")] + public async Task> Get(int id, CancellationToken ct) + { + var ownerId = User.GetUserId(); + var game = await db.Games.AsNoTracking() + .FirstOrDefaultAsync(g => g.Id == id && g.OwnerId == ownerId, ct); + + // A game belonging to someone else is reported as 404, not 403 — the + // response should not confirm that the id exists. + return game is null ? NotFound() : Ok(ToResponse(game, ownerId)); + } + + [HttpGet("facets")] + public async Task> Facets(CancellationToken ct) + { + var ownerId = User.GetUserId(); + var mine = db.Games.AsNoTracking().Where(g => g.OwnerId == ownerId); + + var systems = await mine + .Where(g => g.System != null && g.System != "") + .Select(g => g.System!) + .Distinct().OrderBy(s => s).ToListAsync(ct); + + var genres = await mine + .Where(g => g.Genre != null && g.Genre != "") + .Select(g => g.Genre!) + .Distinct().OrderBy(s => s).ToListAsync(ct); + + return Ok(new FacetsResponse(systems, genres)); + } + + [HttpPost] + public async Task> Create(GameRequest request, CancellationToken ct) + { + var ownerId = User.GetUserId(); + + var game = new Game { OwnerId = ownerId }; + Apply(request, game); + + db.Games.Add(game); + await db.SaveChangesAsync(ct); + + logger.LogInformation("User {OwnerId} created game {GameId}", ownerId, game.Id); + return CreatedAtAction(nameof(Get), new { id = game.Id }, ToResponse(game, ownerId)); + } + + [HttpPut("{id:int}")] + public async Task> Update(int id, GameRequest request, CancellationToken ct) + { + var ownerId = User.GetUserId(); + var game = await db.Games.FirstOrDefaultAsync(g => g.Id == id && g.OwnerId == ownerId, ct); + if (game is null) return NotFound(); + + Apply(request, game); + await db.SaveChangesAsync(ct); + + return Ok(ToResponse(game, ownerId)); + } + + [HttpDelete("{id:int}")] + public async Task Delete(int id, CancellationToken ct) + { + var ownerId = User.GetUserId(); + var game = await db.Games.FirstOrDefaultAsync(g => g.Id == id && g.OwnerId == ownerId, ct); + if (game is null) return NotFound(); + + db.Games.Remove(game); + await db.SaveChangesAsync(ct); + + logger.LogInformation("User {OwnerId} deleted game {GameId}", ownerId, id); + return NoContent(); + } + + private static IQueryable ApplySort(IQueryable q, string sort, string dir) + { + var descending = string.Equals(dir, "desc", StringComparison.OrdinalIgnoreCase); + + // Allow-list rather than reflecting over the string, so the sort parameter + // cannot reach the query shape in any way the API does not define. + return (sort?.ToLowerInvariant()) switch + { + "system" => descending ? q.OrderByDescending(g => g.System) : q.OrderBy(g => g.System), + "genre" => descending ? q.OrderByDescending(g => g.Genre) : q.OrderBy(g => g.Genre), + "year" => descending ? q.OrderByDescending(g => g.Year) : q.OrderBy(g => g.Year), + "developer" => descending ? q.OrderByDescending(g => g.Developer) : q.OrderBy(g => g.Developer), + "publisher" => descending ? q.OrderByDescending(g => g.Publisher) : q.OrderBy(g => g.Publisher), + "created" => descending ? q.OrderByDescending(g => g.CreatedAt) : q.OrderBy(g => g.CreatedAt), + "updated" => descending ? q.OrderByDescending(g => g.UpdatedAt) : q.OrderBy(g => g.UpdatedAt), + _ => descending ? q.OrderByDescending(g => g.Title) : q.OrderBy(g => g.Title), + }; + } + + private static void Apply(GameRequest request, Game game) + { + game.Title = request.Title.Trim(); + game.System = request.System?.Trim(); + game.Genre = request.Genre?.Trim(); + game.Year = request.Year?.Trim(); + game.Developer = request.Developer?.Trim(); + game.Publisher = request.Publisher?.Trim(); + game.Art = request.Art?.Trim(); + game.Description = request.Description; + game.Own = request.Own; + game.Dumped = request.Dumped; + game.Played = request.Played; + game.Finished = request.Finished; + } + + private GameResponse ToResponse(Game g, string ownerId) => new( + g.Id, g.Title, g.System, g.Genre, g.Year, g.Developer, g.Publisher, + g.Art, images.BuildUrl(ownerId, g.Art), g.Description, + g.Own, g.Dumped, g.Played, g.Finished, g.CreatedAt, g.UpdatedAt); +} diff --git a/backend/src/LudosData.Api/Controllers/ImagesController.cs b/backend/src/LudosData.Api/Controllers/ImagesController.cs new file mode 100644 index 0000000..bc3bac0 --- /dev/null +++ b/backend/src/LudosData.Api/Controllers/ImagesController.cs @@ -0,0 +1,63 @@ +using LudosData.Api.Auth; +using LudosData.Api.Contracts; +using LudosData.Api.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; + +namespace LudosData.Api.Controllers; + +/// +/// Box art uploads. +/// +/// The PHP original accepted anonymous uploads, wrote every file into one hardcoded +/// "ckoch" folder, and built the destination path from the client-supplied filename. +/// This requires authentication, files land in the caller's own folder, and the +/// stored name is generated server-side. +/// +[ApiController] +[Route("api/images")] +[Authorize] +public class ImagesController( + IImageStorage images, + IOptions options, + ILogger logger) : ControllerBase +{ + private readonly ImageStorageOptions _options = options.Value; + + [HttpPost] + [RequestSizeLimit(6 * 1024 * 1024)] + public async Task> Upload(IFormFile file, CancellationToken ct) + { + if (file is null || file.Length == 0) + { + return BadRequest(new ProblemDetails { Title = "No file was uploaded." }); + } + + if (file.Length > _options.MaxBytes) + { + return BadRequest(new ProblemDetails + { + Title = $"File is larger than the {_options.MaxBytes / (1024 * 1024)} MB limit.", + }); + } + + var ownerId = User.GetUserId(); + + try + { + await using var stream = file.OpenReadStream(); + var fileName = await images.SaveAsync(stream, ownerId, ct); + + return Ok(new UploadResponse(fileName, images.BuildUrl(ownerId, fileName)!)); + } + catch (Exception ex) when (ex is not OperationCanceledException) + { + // The most common cause is bytes that are not a decodable image. The + // detail is logged but not returned, so probing does not reveal the + // internals of the decoder. + logger.LogWarning(ex, "Rejected upload from user {OwnerId}", ownerId); + return BadRequest(new ProblemDetails { Title = "The file could not be read as an image." }); + } + } +} diff --git a/backend/src/LudosData.Api/Data/DbSeeder.cs b/backend/src/LudosData.Api/Data/DbSeeder.cs new file mode 100644 index 0000000..3cafbd7 --- /dev/null +++ b/backend/src/LudosData.Api/Data/DbSeeder.cs @@ -0,0 +1,136 @@ +using System.Text.Json; +using LudosData.Api.Domain; +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; + +namespace LudosData.Api.Data; + +public class SeedOptions +{ + public const string SectionName = "Seed"; + + /// When false, migrations still run but no user or games are created. + public bool Enabled { get; set; } = true; + + public string UserName { get; set; } = string.Empty; + public string Email { get; set; } = string.Empty; + public string Password { get; set; } = string.Empty; +} + +/// +/// Applies migrations and, on a genuinely empty database, creates the initial user +/// and imports the 105 games recovered from the 2018 MySQL dump. +/// +/// The dump predates the multi-user work, so it has no users table and no per-game +/// owner: every imported game is assigned to the seed user. +/// +public static class DbSeeder +{ + public static async Task MigrateAndSeedAsync(IServiceProvider services, CancellationToken ct = default) + { + using var scope = services.CreateScope(); + var sp = scope.ServiceProvider; + + var logger = sp.GetRequiredService().CreateLogger("DbSeeder"); + var db = sp.GetRequiredService(); + + await db.Database.MigrateAsync(ct); + + var options = sp.GetRequiredService>().Value; + if (!options.Enabled) + { + logger.LogInformation("Seeding disabled; skipping."); + return; + } + + if (await db.Users.AnyAsync(ct)) + { + logger.LogInformation("Database already has users; skipping seed."); + return; + } + + if (string.IsNullOrWhiteSpace(options.UserName) || string.IsNullOrWhiteSpace(options.Password)) + { + logger.LogWarning( + "Seeding is enabled but Seed:UserName / Seed:Password are not set, so no initial user was " + + "created and the {Count} games from the 2018 dump were not imported. Set SEED__USERNAME, " + + "SEED__EMAIL and SEED__PASSWORD and restart, or register a user and import manually.", + await CountSeedGamesAsync(ct)); + return; + } + + var userManager = sp.GetRequiredService>(); + + var user = new AppUser + { + UserName = options.UserName, + Email = string.IsNullOrWhiteSpace(options.Email) ? $"{options.UserName}@localhost" : options.Email, + EmailConfirmed = true, + }; + + var created = await userManager.CreateAsync(user, options.Password); + if (!created.Succeeded) + { + var errors = string.Join("; ", created.Errors.Select(e => e.Description)); + logger.LogError("Could not create the seed user: {Errors}", errors); + return; + } + + var games = await LoadSeedGamesAsync(ct); + foreach (var game in games) + { + game.OwnerId = user.Id; + db.Games.Add(game); + } + + await db.SaveChangesAsync(ct); + logger.LogInformation( + "Seeded user {UserName} with {Count} games from the 2018 dump.", user.UserName, games.Count); + } + + private static async Task> LoadSeedGamesAsync(CancellationToken ct) + { + var path = Path.Combine(AppContext.BaseDirectory, "Data", "Seed", "games.json"); + if (!File.Exists(path)) + { + return []; + } + + await using var stream = File.OpenRead(path); + var records = await JsonSerializer.DeserializeAsync>( + stream, new JsonSerializerOptions(JsonSerializerDefaults.Web), ct) ?? []; + + return records.Select(r => new Game + { + Title = r.Title, + System = r.System, + Genre = r.Genre, + Year = r.Year, + Developer = r.Developer, + Publisher = r.Publisher, + Art = r.Art, + Description = r.Description, + Own = r.Own, + Dumped = r.Dumped, + Played = r.Played, + Finished = r.Finished, + }).ToList(); + } + + private static async Task CountSeedGamesAsync(CancellationToken ct) => + (await LoadSeedGamesAsync(ct)).Count; + + private sealed record SeedGame( + string Title, + string? System, + string? Genre, + string? Year, + string? Developer, + string? Publisher, + string? Art, + string? Description, + bool Own, + bool Dumped, + bool Played, + bool Finished); +} diff --git a/backend/src/LudosData.Api/Data/LudosDbContext.cs b/backend/src/LudosData.Api/Data/LudosDbContext.cs new file mode 100644 index 0000000..e708696 --- /dev/null +++ b/backend/src/LudosData.Api/Data/LudosDbContext.cs @@ -0,0 +1,60 @@ +using LudosData.Api.Domain; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + +namespace LudosData.Api.Data; + +public class LudosDbContext(DbContextOptions options) + : IdentityDbContext(options) +{ + public DbSet Games => Set(); + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + + builder.Entity(game => + { + game.HasOne(g => g.Owner) + .WithMany(u => u.Games) + .HasForeignKey(g => g.OwnerId) + .OnDelete(DeleteBehavior.Cascade); + + // Every list query filters by owner first, then narrows or sorts on + // these columns, so they lead the composite indexes. + game.HasIndex(g => new { g.OwnerId, g.Title }); + game.HasIndex(g => new { g.OwnerId, g.System }); + game.HasIndex(g => new { g.OwnerId, g.Genre }); + }); + } + + public override int SaveChanges() + { + StampTimestamps(); + return base.SaveChanges(); + } + + public override Task SaveChangesAsync(CancellationToken cancellationToken = default) + { + StampTimestamps(); + return base.SaveChangesAsync(cancellationToken); + } + + private void StampTimestamps() + { + var now = DateTimeOffset.UtcNow; + + foreach (var entry in ChangeTracker.Entries()) + { + if (entry.State == EntityState.Added) + { + entry.Entity.CreatedAt = now; + entry.Entity.UpdatedAt = now; + } + else if (entry.State == EntityState.Modified) + { + entry.Entity.UpdatedAt = now; + } + } + } +} diff --git a/backend/src/LudosData.Api/Data/Migrations/20260803220157_InitialCreate.Designer.cs b/backend/src/LudosData.Api/Data/Migrations/20260803220157_InitialCreate.Designer.cs new file mode 100644 index 0000000..680a13f --- /dev/null +++ b/backend/src/LudosData.Api/Data/Migrations/20260803220157_InitialCreate.Designer.cs @@ -0,0 +1,367 @@ +// +using System; +using LudosData.Api.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace LudosData.Api.Data.Migrations +{ + [DbContext(typeof(LudosDbContext))] + [Migration("20260803220157_InitialCreate")] + partial class InitialCreate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.10"); + + modelBuilder.Entity("LudosData.Api.Domain.AppUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("Art") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("FirstName") + .HasColumnType("TEXT"); + + b.Property("LastName") + .HasColumnType("TEXT"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("LudosData.Api.Domain.Game", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Art") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("Developer") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Dumped") + .HasColumnType("INTEGER"); + + b.Property("Finished") + .HasColumnType("INTEGER"); + + b.Property("Genre") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Own") + .HasColumnType("INTEGER"); + + b.Property("OwnerId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Played") + .HasColumnType("INTEGER"); + + b.Property("Publisher") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("System") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Year") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OwnerId", "Genre"); + + b.HasIndex("OwnerId", "System"); + + b.HasIndex("OwnerId", "Title"); + + b.ToTable("Games"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("LudosData.Api.Domain.Game", b => + { + b.HasOne("LudosData.Api.Domain.AppUser", "Owner") + .WithMany("Games") + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("LudosData.Api.Domain.AppUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("LudosData.Api.Domain.AppUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LudosData.Api.Domain.AppUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("LudosData.Api.Domain.AppUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("LudosData.Api.Domain.AppUser", b => + { + b.Navigation("Games"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/src/LudosData.Api/Data/Migrations/20260803220157_InitialCreate.cs b/backend/src/LudosData.Api/Data/Migrations/20260803220157_InitialCreate.cs new file mode 100644 index 0000000..8d8b2cc --- /dev/null +++ b/backend/src/LudosData.Api/Data/Migrations/20260803220157_InitialCreate.cs @@ -0,0 +1,277 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace LudosData.Api.Data.Migrations +{ + /// + public partial class InitialCreate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "TEXT", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + FirstName = table.Column(type: "TEXT", nullable: true), + LastName = table.Column(type: "TEXT", nullable: true), + Art = table.Column(type: "TEXT", nullable: true), + CreatedAt = table.Column(type: "TEXT", nullable: false), + UserName = table.Column(type: "TEXT", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "TEXT", maxLength: 256, nullable: true), + Email = table.Column(type: "TEXT", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "TEXT", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "INTEGER", nullable: false), + PasswordHash = table.Column(type: "TEXT", nullable: true), + SecurityStamp = table.Column(type: "TEXT", nullable: true), + ConcurrencyStamp = table.Column(type: "TEXT", nullable: true), + PhoneNumber = table.Column(type: "TEXT", nullable: true), + PhoneNumberConfirmed = table.Column(type: "INTEGER", nullable: false), + TwoFactorEnabled = table.Column(type: "INTEGER", nullable: false), + LockoutEnd = table.Column(type: "TEXT", nullable: true), + LockoutEnabled = table.Column(type: "INTEGER", nullable: false), + AccessFailedCount = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + RoleId = table.Column(type: "TEXT", nullable: false), + ClaimType = table.Column(type: "TEXT", nullable: true), + ClaimValue = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + UserId = table.Column(type: "TEXT", nullable: false), + ClaimType = table.Column(type: "TEXT", nullable: true), + ClaimValue = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "TEXT", nullable: false), + ProviderKey = table.Column(type: "TEXT", nullable: false), + ProviderDisplayName = table.Column(type: "TEXT", nullable: true), + UserId = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "TEXT", nullable: false), + RoleId = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "TEXT", nullable: false), + LoginProvider = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", nullable: false), + Value = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Games", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Title = table.Column(type: "TEXT", maxLength: 200, nullable: false), + System = table.Column(type: "TEXT", maxLength: 50, nullable: true), + Genre = table.Column(type: "TEXT", maxLength: 50, nullable: true), + Year = table.Column(type: "TEXT", maxLength: 50, nullable: true), + Developer = table.Column(type: "TEXT", maxLength: 100, nullable: true), + Publisher = table.Column(type: "TEXT", maxLength: 100, nullable: true), + Art = table.Column(type: "TEXT", maxLength: 200, nullable: true), + Description = table.Column(type: "TEXT", nullable: true), + Own = table.Column(type: "INTEGER", nullable: false), + Dumped = table.Column(type: "INTEGER", nullable: false), + Played = table.Column(type: "INTEGER", nullable: false), + Finished = table.Column(type: "INTEGER", nullable: false), + OwnerId = table.Column(type: "TEXT", nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Games", x => x.Id); + table.ForeignKey( + name: "FK_Games_AspNetUsers_OwnerId", + column: x => x.OwnerId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Games_OwnerId_Genre", + table: "Games", + columns: new[] { "OwnerId", "Genre" }); + + migrationBuilder.CreateIndex( + name: "IX_Games_OwnerId_System", + table: "Games", + columns: new[] { "OwnerId", "System" }); + + migrationBuilder.CreateIndex( + name: "IX_Games_OwnerId_Title", + table: "Games", + columns: new[] { "OwnerId", "Title" }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "Games"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/backend/src/LudosData.Api/Data/Migrations/LudosDbContextModelSnapshot.cs b/backend/src/LudosData.Api/Data/Migrations/LudosDbContextModelSnapshot.cs new file mode 100644 index 0000000..584305e --- /dev/null +++ b/backend/src/LudosData.Api/Data/Migrations/LudosDbContextModelSnapshot.cs @@ -0,0 +1,364 @@ +// +using System; +using LudosData.Api.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace LudosData.Api.Data.Migrations +{ + [DbContext(typeof(LudosDbContext))] + partial class LudosDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.10"); + + modelBuilder.Entity("LudosData.Api.Domain.AppUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("Art") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("FirstName") + .HasColumnType("TEXT"); + + b.Property("LastName") + .HasColumnType("TEXT"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("LudosData.Api.Domain.Game", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Art") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("Developer") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Dumped") + .HasColumnType("INTEGER"); + + b.Property("Finished") + .HasColumnType("INTEGER"); + + b.Property("Genre") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Own") + .HasColumnType("INTEGER"); + + b.Property("OwnerId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Played") + .HasColumnType("INTEGER"); + + b.Property("Publisher") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("System") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Year") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OwnerId", "Genre"); + + b.HasIndex("OwnerId", "System"); + + b.HasIndex("OwnerId", "Title"); + + b.ToTable("Games"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("LudosData.Api.Domain.Game", b => + { + b.HasOne("LudosData.Api.Domain.AppUser", "Owner") + .WithMany("Games") + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("LudosData.Api.Domain.AppUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("LudosData.Api.Domain.AppUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LudosData.Api.Domain.AppUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("LudosData.Api.Domain.AppUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("LudosData.Api.Domain.AppUser", b => + { + b.Navigation("Games"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/src/LudosData.Api/Data/Seed/games.json b/backend/src/LudosData.Api/Data/Seed/games.json new file mode 100644 index 0000000..24fded3 --- /dev/null +++ b/backend/src/LudosData.Api/Data/Seed/games.json @@ -0,0 +1,1472 @@ +[ + { + "system": "PS2", + "title": "Armored Core 3", + "genre": "action", + "year": "2002", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": false, + "finished": false + }, + { + "system": "360", + "title": "Armored Core V", + "genre": "action", + "year": "2012", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": false, + "finished": false + }, + { + "system": "PS1", + "title": "Armored Core: Master of Arena", + "genre": "action", + "year": "1999", + "developer": "FromSoftware", + "publisher": "PlayStation", + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": true, + "finished": false + }, + { + "system": "N64", + "title": "Army Men: Sarge's Heroes", + "genre": "action", + "year": "1999", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": false, + "finished": false + }, + { + "system": "N64", + "title": "Banjo-Kazooie", + "genre": "adventure", + "year": "1998", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": true, + "finished": false + }, + { + "system": "N64", + "title": "Banjo-Tooie", + "genre": "adventure", + "year": "2000", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": false, + "finished": false + }, + { + "system": "SNES", + "title": "Battle Clash", + "genre": "lightgun", + "year": null, + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": false, + "finished": false + }, + { + "system": "N64", + "title": "BattleTanx", + "genre": "fighter", + "year": "1998", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": true, + "finished": false + }, + { + "system": "N64", + "title": "Beetle Adventure Racing", + "genre": "racing", + "year": "1999", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": true, + "finished": false + }, + { + "system": "360", + "title": "Blue Dragon", + "genre": "rpg", + "year": "2006", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": true, + "finished": false + }, + { + "system": "SNES", + "title": "Brett Hull Hocky 95", + "genre": "sports", + "year": null, + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": false, + "finished": false + }, + { + "system": "360", + "title": "Brink", + "genre": "fps", + "year": "2011", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": false, + "finished": false + }, + { + "system": "N64", + "title": "Castlevania", + "genre": "action", + "year": "1999", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": true, + "finished": false + }, + { + "system": "PS1", + "title": "Chrono Cross", + "genre": "rpg", + "year": "1999", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": false, + "finished": false + }, + { + "system": "DS", + "title": "Chrono Trigger", + "genre": "rpg", + "year": null, + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": true, + "finished": false + }, + { + "system": "N64", + "title": "Diddy Kong Racing", + "genre": "racing", + "year": "1997", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": true, + "finished": false + }, + { + "system": "N64", + "title": "Donkey Kong 64", + "genre": "adventure", + "year": "1999", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": false, + "finished": false + }, + { + "system": "SNES", + "title": "Donkey Kong Country", + "genre": "platformer", + "year": null, + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": true, + "finished": true + }, + { + "system": "GB", + "title": "Donkey Kong Country", + "genre": "platformer", + "year": null, + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": true, + "finished": false + }, + { + "system": "GBA", + "title": "Donkey Kong Country", + "genre": "platformer", + "year": null, + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": true, + "finished": false + }, + { + "system": "SNES", + "title": "Donkey Kong Country 2: Diddy's Kong Quest", + "genre": "platformer", + "year": null, + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": true, + "finished": false + }, + { + "system": "GB", + "title": "Donkey Kong Country 2: Diddy's Kong Quest", + "genre": "platformer", + "year": null, + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": true, + "finished": false + }, + { + "system": "GBA", + "title": "Donkey Kong Country 2: Diddy's Kong Quest", + "genre": "platformer", + "year": null, + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": true, + "finished": false + }, + { + "system": "SNES", + "title": "Donkey Kong Country 3: Dixie Kong's Double Trouble!", + "genre": "platformer", + "year": null, + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": true, + "finished": false + }, + { + "system": "GBA", + "title": "Donkey Kong Country 3: Dixie Kong's Double Trouble!", + "genre": "platformer", + "year": null, + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": true, + "finished": false + }, + { + "system": "DS", + "title": "Donkey Kong Country Returns", + "genre": "platformer", + "year": "2010", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": true, + "finished": false + }, + { + "system": "WII", + "title": "Donkey Kong Country Returns", + "genre": "platformer", + "year": "2010", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": true, + "finished": true + }, + { + "system": "PS2", + "title": "Dragon Ball Z Budokai", + "genre": "fighter", + "year": "2002", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": false, + "finished": false + }, + { + "system": "WII", + "title": "Epic Mickey", + "genre": "adventure", + "year": "2010", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": false, + "finished": false + }, + { + "system": "PSP", + "title": "Final Fantasy", + "genre": "rpg", + "year": null, + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": false, + "finished": false + }, + { + "system": "SNES", + "title": "Final Fantasy 2", + "genre": "rpg", + "year": null, + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": true, + "finished": false + }, + { + "system": "GC", + "title": "Final Fantasy Crystal Chronicles", + "genre": "rpg", + "year": "2003", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": true, + "finished": false + }, + { + "system": "PSP", + "title": "Final Fantasy II", + "genre": "rpg", + "year": null, + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": false, + "finished": false + }, + { + "system": "PS1", + "title": "Final Fantasy V", + "genre": "rpg", + "year": null, + "developer": "Square", + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": true, + "finished": false + }, + { + "system": "PS1", + "title": "Final Fantasy VI", + "genre": "rpg", + "year": null, + "developer": null, + "publisher": "Square", + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": true, + "finished": false + }, + { + "system": "PS1", + "title": "Final Fantasy VIII", + "genre": "rpg", + "year": "1999", + "developer": null, + "publisher": "SquareSoft", + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": true, + "finished": true + }, + { + "system": "PS2", + "title": "Final Fantasy X", + "genre": "rpg", + "year": "2001", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": true, + "finished": true + }, + { + "system": "PS2", + "title": "Final Fantasy X-2", + "genre": "rpg", + "year": "2003", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": true, + "finished": false + }, + { + "system": "PS2", + "title": "Final Fantasy XII", + "genre": "rpg", + "year": "2006", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": false, + "finished": false + }, + { + "system": "360", + "title": "Gears of War 2", + "genre": "fps", + "year": "2008", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": false, + "finished": false + }, + { + "system": "GBA", + "title": "Golden Sun", + "genre": "rpg", + "year": "2001", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": true, + "finished": true + }, + { + "system": "DS", + "title": "Golden Sun: Dark Dawn", + "genre": "rpg", + "year": "2010", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": true, + "finished": false + }, + { + "system": "GBA", + "title": "Golden Sun: The Lost Age", + "genre": "rpg", + "year": "2002", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": false, + "finished": false + }, + { + "system": "PS1", + "title": "Grand Theft Auto 2", + "genre": "action", + "year": "1999", + "developer": "Rockstar", + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": false, + "finished": false + }, + { + "system": "PS1", + "title": "Grandia", + "genre": "rpg", + "year": "1997", + "developer": "Game Arts", + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": false, + "finished": false + }, + { + "system": "PS1", + "title": "Gundam: The Battle Master 2", + "genre": "fighter", + "year": "1998", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": true, + "finished": false + }, + { + "system": "360", + "title": "Halo 3", + "genre": "fps", + "year": "2007", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": true, + "finished": false + }, + { + "system": "360", + "title": "Halo 4", + "genre": "fps", + "year": "2012", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": false, + "finished": false + }, + { + "system": "360", + "title": "Halo: Combat Evolved Anniversary", + "genre": "fps", + "year": "2011", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": false, + "finished": false + }, + { + "system": "PS2", + "title": "Harvest Moon: A Wonderful Life", + "genre": "rpg", + "year": "2003", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": false, + "finished": false + }, + { + "system": "GB", + "title": "Hot Wheels Stunt Track Driver", + "genre": "racing", + "year": "1998", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": true, + "finished": false + }, + { + "system": "PS2", + "title": "Kingdom Hearts", + "genre": "rpg", + "year": "2002", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": false, + "finished": false + }, + { + "system": "PS2", + "title": "Kingdom Hearts: Chain of Memories", + "genre": "rpg", + "year": "2004", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": false, + "finished": false + }, + { + "system": "360", + "title": "Lost Planet: Extreme Condition", + "genre": "fps", + "year": "2006", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": false, + "finished": false + }, + { + "system": "GBA", + "title": "Love Hina: Advance", + "genre": "simulation", + "year": "2001", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": true, + "finished": false + }, + { + "system": "WII", + "title": "Mario Kart Wii", + "genre": "racing", + "year": "2008", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": true, + "finished": true + }, + { + "system": "NES", + "title": "Metroid", + "genre": "platformer", + "year": "1986", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": false, + "finished": false + }, + { + "system": "PS2", + "title": "Mobile Suit Gundam: Federation vs. Zeon", + "genre": "action", + "year": "2001", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": false, + "finished": false + }, + { + "system": "PS2", + "title": "Mobile Suit Gundam: Journey to Jaburo", + "genre": "action", + "year": "2000", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": true, + "finished": false + }, + { + "system": "PS2", + "title": "Mobile Suit Gundam: Zeonic Front", + "genre": "strategy", + "year": "2001", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": true, + "finished": false + }, + { + "system": "SNES", + "title": "Mortal Kombat", + "genre": "fighter", + "year": null, + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": false, + "finished": false + }, + { + "system": "PS2", + "title": "Mortal Kombat: Deadly Alliance", + "genre": "fighter", + "year": "2002", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": true, + "finished": false + }, + { + "system": "PS2", + "title": "MS Saga: A New Dawn", + "genre": "rpg", + "year": "2005", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": true, + "finished": false + }, + { + "system": "PS2", + "title": "Need for Speed: ProStreet", + "genre": "racing", + "year": "2007", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": false, + "finished": false + }, + { + "system": "PS2", + "title": "Oni", + "genre": "action", + "year": "2001", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": true, + "finished": true + }, + { + "system": "N64", + "title": "Perfect Dark", + "genre": "fps", + "year": "2000", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": true, + "finished": false + }, + { + "system": "360", + "title": "Perfect Dark Zero", + "genre": "fps", + "year": "2007", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": true, + "finished": false + }, + { + "system": "GC", + "title": "Phantasy Star Online", + "genre": "rpg", + "year": "2000", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": true, + "finished": false + }, + { + "system": "GB", + "title": "Pokemon Yellow", + "genre": "rpg", + "year": "1998", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": true, + "finished": false + }, + { + "system": "N64", + "title": "Pokémon Stadium", + "genre": "fighter", + "year": "1998", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": true, + "finished": true + }, + { + "system": "GB", + "title": "Pokémon Trading Card Game", + "genre": "card", + "year": "1996", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": true, + "finished": false + }, + { + "system": "PS2", + "title": "Ratchet & Clank: Size Matters", + "genre": "adventure", + "year": "2007", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": false, + "finished": false + }, + { + "system": "PS2", + "title": "Red Faction", + "genre": "fps", + "year": "2001", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": false, + "finished": false + }, + { + "system": "WII", + "title": "Resident Evil: The Darkside Chronicles", + "genre": "lightgun", + "year": "2009", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": true, + "finished": true + }, + { + "system": "SNES", + "title": "Roger Clemens MVP Baseball", + "genre": "sports", + "year": null, + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": false, + "finished": false + }, + { + "system": "PS2", + "title": "SD Gundam Force: Showdown!", + "genre": "fighter", + "year": null, + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": false, + "finished": false + }, + { + "system": "SNES", + "title": "SimCity", + "genre": "strategy", + "year": null, + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": false, + "finished": false + }, + { + "system": "PS2", + "title": "Soulcalibur II", + "genre": "fighter", + "year": "2002", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": true, + "finished": false + }, + { + "system": "PS1", + "title": "Spyro 2: Ripto's Rage!", + "genre": "adventure", + "year": "1999", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": false, + "finished": false + }, + { + "system": "PS1", + "title": "Spyro the Dragon", + "genre": "adventure", + "year": "1998", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": true, + "finished": false + }, + { + "system": "GBA", + "title": "Spyro: Season of Ice", + "genre": "adventure", + "year": "2001", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": true, + "finished": false + }, + { + "system": "NES", + "title": "Super Mario Bros. 3", + "genre": "platformer", + "year": "1988", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": false, + "finished": false + }, + { + "system": "WII", + "title": "Super Mario Galaxy", + "genre": "adventure", + "year": "2007", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": false, + "finished": false + }, + { + "system": "GC", + "title": "Super Mario Sunshine", + "genre": "adventure", + "year": "2002", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": true, + "finished": false + }, + { + "system": "WII", + "title": "Super Paper Mario", + "genre": "platformer", + "year": "2007", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": true, + "finished": false + }, + { + "system": "WII", + "title": "Tales of Symphonia: Dawn of the New World", + "genre": "rpg", + "year": "2008", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": false, + "finished": false + }, + { + "system": "PS2", + "title": "The Bouncer", + "genre": "fighter", + "year": "2000", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": false, + "finished": false + }, + { + "system": "360", + "title": "The Elder Scrolls V: Skyrim", + "genre": "rpg", + "year": "2011", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": false, + "finished": false + }, + { + "system": "PS1", + "title": "The Legend of Dragoon", + "genre": "rpg", + "year": "1999", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": true, + "finished": true + }, + { + "system": "N64", + "title": "The Legend of Zelda: Ocarina of Time", + "genre": "adventure", + "year": "1998", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": true, + "finished": false + }, + { + "system": "PS2", + "title": "The Sims", + "genre": "simulation", + "year": "2000", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": true, + "finished": true + }, + { + "system": "PS2", + "title": "The Sims 2", + "genre": "simulation", + "year": "2004", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": true, + "finished": true + }, + { + "system": "PS2", + "title": "The Sims 2 Pets", + "genre": "simulation", + "year": "2006", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": false, + "finished": false + }, + { + "system": "PS1", + "title": "Tomorrow Never Dies", + "genre": "action", + "year": "1999", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": true, + "finished": false + }, + { + "system": "N64", + "title": "Tony Hawk's Pro Skater", + "genre": "sports", + "year": "1999", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": false, + "finished": false + }, + { + "system": "PS2", + "title": "Tony Hawk's Pro Skater 4", + "genre": "sports", + "year": "2002", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": false, + "finished": false + }, + { + "system": "PS2", + "title": "Tony Hawk's Underground", + "genre": "sports", + "year": "2003", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": false, + "finished": false + }, + { + "system": "PS2", + "title": "Unreal Tournament", + "genre": "fps", + "year": "1999", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": false, + "finished": false + }, + { + "system": "N64", + "title": "Wave Race 64", + "genre": "sports", + "year": "1996", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": true, + "finished": false + }, + { + "system": "SNES", + "title": "WWF Super Wrestlemania", + "genre": "sports", + "year": null, + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": false, + "finished": false + }, + { + "system": "N64", + "title": "WWF War Zone", + "genre": "sports", + "year": "1998", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": false, + "finished": false + }, + { + "system": "PS2", + "title": "Xenosaga Episode I", + "genre": "rpg", + "year": "2002", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": true, + "finished": false + }, + { + "system": "PS2", + "title": "XIII", + "genre": "fps", + "year": "2003", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": false, + "played": false, + "finished": false + }, + { + "system": "SNES", + "title": "Yoshi's Island", + "genre": "platformer", + "year": null, + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": true, + "finished": false + }, + { + "system": "N64", + "title": "Yoshi's Story", + "genre": "platformer", + "year": "1997", + "developer": null, + "publisher": null, + "art": null, + "description": null, + "own": true, + "dumped": true, + "played": false, + "finished": true + } +] diff --git a/backend/src/LudosData.Api/Domain/AppUser.cs b/backend/src/LudosData.Api/Domain/AppUser.cs new file mode 100644 index 0000000..ea005ed --- /dev/null +++ b/backend/src/LudosData.Api/Domain/AppUser.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore.Identity; + +namespace LudosData.Api.Domain; + +/// +/// Application user. Extends IdentityUser, which supplies Id, UserName, Email, +/// PasswordHash (PBKDF2 with a per-user salt), lockout and security stamp. +/// +public class AppUser : IdentityUser +{ + public string? FirstName { get; set; } + public string? LastName { get; set; } + + /// Filename of the user's avatar, relative to their upload folder. + public string? Art { get; set; } + + public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow; + + public ICollection Games { get; set; } = new List(); +} diff --git a/backend/src/LudosData.Api/Domain/Game.cs b/backend/src/LudosData.Api/Domain/Game.cs new file mode 100644 index 0000000..2ce6467 --- /dev/null +++ b/backend/src/LudosData.Api/Domain/Game.cs @@ -0,0 +1,60 @@ +using System.ComponentModel.DataAnnotations; + +namespace LudosData.Api.Domain; + +/// +/// A single entry in a user's game library. Mirrors the columns of the original +/// MySQL `games` table so the 2018 dump imports without transformation, with the +/// addition of ownership and audit fields. +/// +public class Game +{ + public int Id { get; set; } + + [Required] + [MaxLength(200)] + public string Title { get; set; } = string.Empty; + + /// Console/platform the game runs on, e.g. "SNES", "PS2". + [MaxLength(50)] + public string? System { get; set; } + + [MaxLength(50)] + public string? Genre { get; set; } + + /// + /// Release year. Kept as a string rather than an int: the original column was + /// varchar(50) and holds values like "" and "1996" — some entries were never + /// filled in, and a few real-world cases want ranges. + /// + [MaxLength(50)] + public string? Year { get; set; } + + [MaxLength(100)] + public string? Developer { get; set; } + + [MaxLength(100)] + public string? Publisher { get; set; } + + /// Filename of the uploaded box art, relative to the owner's upload folder. + [MaxLength(200)] + public string? Art { get; set; } + + public string? Description { get; set; } + + public bool Own { get; set; } + public bool Dumped { get; set; } + public bool Played { get; set; } + public bool Finished { get; set; } + + /// + /// Owning user. Every query is filtered on this server-side, from the JWT subject — + /// it is never accepted from the client. + /// + [Required] + public string OwnerId { get; set; } = string.Empty; + public AppUser? Owner { get; set; } + + public DateTimeOffset CreatedAt { get; set; } + public DateTimeOffset UpdatedAt { get; set; } +} diff --git a/backend/src/LudosData.Api/LudosData.Api.csproj b/backend/src/LudosData.Api/LudosData.Api.csproj new file mode 100644 index 0000000..3d95551 --- /dev/null +++ b/backend/src/LudosData.Api/LudosData.Api.csproj @@ -0,0 +1,39 @@ + + + + net10.0 + enable + enable + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + + + + + + diff --git a/backend/src/LudosData.Api/Program.cs b/backend/src/LudosData.Api/Program.cs new file mode 100644 index 0000000..ba8f29b --- /dev/null +++ b/backend/src/LudosData.Api/Program.cs @@ -0,0 +1,168 @@ +using System.Text; +using LudosData.Api.Auth; +using LudosData.Api.Data; +using LudosData.Api.Domain; +using LudosData.Api.Services; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.DataProtection; +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Tokens; + +var builder = WebApplication.CreateBuilder(args); + +// --------------------------------------------------------------------------- +// Options +// --------------------------------------------------------------------------- +builder.Services.AddOptions() + .Bind(builder.Configuration.GetSection(JwtOptions.SectionName)) + .ValidateDataAnnotations() + // Validating on start means a deployment with a missing or too-short signing + // key fails immediately and loudly, instead of issuing weak tokens. + .ValidateOnStart(); + +builder.Services.Configure( + builder.Configuration.GetSection(ImageStorageOptions.SectionName)); +builder.Services.Configure( + builder.Configuration.GetSection(SeedOptions.SectionName)); + +// --------------------------------------------------------------------------- +// Data +// --------------------------------------------------------------------------- +var connectionString = builder.Configuration.GetConnectionString("Default") + ?? "Data Source=data/ludos.db"; + +// Make sure the SQLite file's directory exists before EF tries to open it. +var dataSource = new Microsoft.Data.Sqlite.SqliteConnectionStringBuilder(connectionString).DataSource; +var dataDirectory = Path.GetDirectoryName(Path.GetFullPath(dataSource)); +if (!string.IsNullOrEmpty(dataDirectory)) +{ + Directory.CreateDirectory(dataDirectory); +} + +builder.Services.AddDbContext(options => options.UseSqlite(connectionString)); + +// Keep Data Protection keys on the same persistent volume as the database. +// Without this they live in the container filesystem and are regenerated on +// every restart, which silently invalidates Identity-issued tokens such as +// password-reset and email-confirmation links. +var keysDirectory = builder.Configuration["DataProtection:KeysPath"] + ?? Path.Combine(dataDirectory ?? ".", "keys"); +Directory.CreateDirectory(keysDirectory); + +builder.Services + .AddDataProtection() + .PersistKeysToFileSystem(new DirectoryInfo(keysDirectory)) + .SetApplicationName("LudosData"); + +// --------------------------------------------------------------------------- +// Identity + JWT +// --------------------------------------------------------------------------- +builder.Services + .AddIdentityCore(options => + { + options.User.RequireUniqueEmail = true; + + options.Password.RequiredLength = 12; + options.Password.RequireDigit = true; + options.Password.RequireLowercase = true; + options.Password.RequireUppercase = true; + options.Password.RequireNonAlphanumeric = false; + + options.Lockout.MaxFailedAccessAttempts = 10; + options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(15); + }) + .AddSignInManager() + .AddEntityFrameworkStores(); + +var jwtSection = builder.Configuration.GetSection(JwtOptions.SectionName); +var signingKey = jwtSection["Key"] ?? string.Empty; + +builder.Services + .AddAuthentication(options => + { + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + }) + .AddJwtBearer(options => + { + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidateAudience = true, + ValidateLifetime = true, + ValidateIssuerSigningKey = true, + ValidIssuer = jwtSection["Issuer"] ?? "LudosData", + ValidAudience = jwtSection["Audience"] ?? "LudosData", + // A real key is required by JwtOptions validation on start; this + // placeholder only exists so DI can build before that check runs. + IssuerSigningKey = new SymmetricSecurityKey( + Encoding.UTF8.GetBytes(signingKey.Length >= 32 ? signingKey : new string('0', 32))), + ClockSkew = TimeSpan.FromMinutes(1), + }; + }); + +builder.Services.AddAuthorization(); + +// --------------------------------------------------------------------------- +// Application services +// --------------------------------------------------------------------------- +builder.Services.AddScoped(); +builder.Services.AddSingleton(); + +builder.Services.AddControllers(); +builder.Services.AddProblemDetails(); +builder.Services.AddOpenApi(); + +const string SpaCorsPolicy = "spa"; +var allowedOrigins = builder.Configuration.GetSection("Cors:AllowedOrigins").Get() + ?? ["http://localhost:4200"]; + +builder.Services.AddCors(options => options.AddPolicy(SpaCorsPolicy, policy => policy + // Explicit origins, not AllowAnyOrigin. The old API sent + // `Access-Control-Allow-Origin: *` on every response. + .WithOrigins(allowedOrigins) + .AllowAnyHeader() + .AllowAnyMethod())); + +builder.Services.AddHealthChecks(); + +var app = builder.Build(); + +// --------------------------------------------------------------------------- +// Pipeline +// --------------------------------------------------------------------------- +app.UseExceptionHandler(); +app.UseStatusCodePages(); + +if (app.Environment.IsDevelopment()) +{ + app.MapOpenApi(); +} + +app.UseCors(SpaCorsPolicy); + +// Serve uploaded box art from the configured folder (a Docker volume in +// production) rather than from wwwroot, so user content and app files stay apart. +var uploadOptions = app.Services.GetRequiredService>().Value; +var uploadRoot = Path.GetFullPath(uploadOptions.RootPath); +Directory.CreateDirectory(uploadRoot); + +app.UseStaticFiles(new StaticFileOptions +{ + FileProvider = new PhysicalFileProvider(uploadRoot), + RequestPath = uploadOptions.RequestPath, + ServeUnknownFileTypes = false, +}); + +app.UseAuthentication(); +app.UseAuthorization(); + +app.MapControllers(); +app.MapHealthChecks("/health").AllowAnonymous(); + +await DbSeeder.MigrateAndSeedAsync(app.Services); + +app.Run(); diff --git a/backend/src/LudosData.Api/Properties/launchSettings.json b/backend/src/LudosData.Api/Properties/launchSettings.json new file mode 100644 index 0000000..9a4ffcf --- /dev/null +++ b/backend/src/LudosData.Api/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5044", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7008;http://localhost:5044", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/backend/src/LudosData.Api/Services/ImageStorage.cs b/backend/src/LudosData.Api/Services/ImageStorage.cs new file mode 100644 index 0000000..4f34fb9 --- /dev/null +++ b/backend/src/LudosData.Api/Services/ImageStorage.cs @@ -0,0 +1,115 @@ +using Microsoft.Extensions.Options; +using SkiaSharp; + +namespace LudosData.Api.Services; + +public interface IImageStorage +{ + Task SaveAsync(Stream source, string ownerId, CancellationToken ct = default); + string? BuildUrl(string ownerId, string? fileName); +} + +public class ImageStorageOptions +{ + public const string SectionName = "Uploads"; + + /// Filesystem root for uploads. In Docker this is a mounted volume. + public string RootPath { get; set; } = "uploads"; + + /// Public URL prefix these files are served under. + public string RequestPath { get; set; } = "/uploads"; + + /// Max accepted upload size. Enforced again at the endpoint. + public long MaxBytes { get; set; } = 5 * 1024 * 1024; + + /// Stored images are downscaled to at most this width, preserving aspect. + public int MaxWidth { get; set; } = 500; + + /// WebP quality, 1-100. + public int Quality { get; set; } = 82; +} + +/// +/// Stores box art on disk as WebP, re-encoded from whatever was uploaded. +/// +/// Unlike the PHP version this replaces, the uploaded filename is never used to +/// build the destination path — the name is generated server-side and the +/// extension is fixed, so a crafted filename cannot traverse directories or land +/// an executable in a served folder. Decoding the bytes and re-encoding them also +/// means only pixel data survives: any payload smuggled in metadata is dropped. +/// +public class ImageStorage( + IOptions options, + ILogger logger) : IImageStorage +{ + private readonly ImageStorageOptions _options = options.Value; + + public async Task SaveAsync(Stream source, string ownerId, CancellationToken ct = default) + { + // Buffer first: SKBitmap.Decode wants a seekable stream, and the caller's + // request stream is not. The endpoint has already bounded the length. + using var buffer = new MemoryStream(); + await source.CopyToAsync(buffer, ct); + buffer.Position = 0; + + // Decoding is the real content check — anything Skia cannot parse as an + // image returns null here, before a byte is persisted. + using var decoded = SKBitmap.Decode(buffer) + ?? throw new InvalidDataException("The uploaded bytes are not a decodable image."); + + using var final = Downscale(decoded); + + var directory = DirectoryFor(ownerId); + Directory.CreateDirectory(directory); + + var fileName = $"{Guid.NewGuid():N}.webp"; + var fullPath = Path.Combine(directory, fileName); + + using (var image = SKImage.FromBitmap(final)) + using (var data = image.Encode(SKEncodedImageFormat.Webp, _options.Quality)) + { + if (data is null) + { + throw new InvalidDataException("The image could not be encoded as WebP."); + } + + await using var output = File.Create(fullPath); + data.SaveTo(output); + } + + logger.LogInformation("Stored upload {FileName} for user {OwnerId}", fileName, ownerId); + return fileName; + } + + public string? BuildUrl(string ownerId, string? fileName) => + string.IsNullOrWhiteSpace(fileName) + ? null + : $"{_options.RequestPath}/{ownerId}/{fileName}"; + + private SKBitmap Downscale(SKBitmap source) + { + if (source.Width <= _options.MaxWidth) + { + return source.Copy(); + } + + var height = (int)Math.Round(source.Height * (_options.MaxWidth / (double)source.Width)); + var info = new SKImageInfo(_options.MaxWidth, Math.Max(1, height)); + + return source.Resize(info, new SKSamplingOptions(SKFilterMode.Linear, SKMipmapMode.Linear)) + ?? throw new InvalidDataException("The image could not be resized."); + } + + private string DirectoryFor(string ownerId) + { + // ownerId is an Identity-generated GUID string, but this is defence in + // depth: only the bare filename component is ever joined onto the root. + var safeOwner = Path.GetFileName(ownerId); + if (string.IsNullOrWhiteSpace(safeOwner)) + { + throw new ArgumentException("Invalid owner id.", nameof(ownerId)); + } + + return Path.Combine(Path.GetFullPath(_options.RootPath), safeOwner); + } +} diff --git a/backend/src/LudosData.Api/appsettings.Development.json b/backend/src/LudosData.Api/appsettings.Development.json new file mode 100644 index 0000000..ff66ba6 --- /dev/null +++ b/backend/src/LudosData.Api/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/backend/src/LudosData.Api/appsettings.json b/backend/src/LudosData.Api/appsettings.json new file mode 100644 index 0000000..5d05586 --- /dev/null +++ b/backend/src/LudosData.Api/appsettings.json @@ -0,0 +1,29 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "Default": "Data Source=data/ludos.db" + }, + "Jwt": { + "Issuer": "LudosData", + "Audience": "LudosData", + "LifetimeMinutes": 720 + }, + "Uploads": { + "RootPath": "uploads", + "RequestPath": "/uploads", + "MaxBytes": 5242880, + "MaxWidth": 500 + }, + "Cors": { + "AllowedOrigins": [ "http://localhost:4200", "http://localhost:8080" ] + }, + "Seed": { + "Enabled": true + } +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..bd42116 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,69 @@ +# LudosData — full stack. +# +# cp .env.example .env # then edit the secrets +# docker compose up --build +# +# The SPA is served by nginx on http://localhost:8080, which also reverse-proxies +# /api and /uploads to the API container. Because everything is same-origin in +# this setup, the browser never issues a cross-origin request and CORS is not in +# play at all — the API's CORS policy only matters for `ng serve` on :4200. + +services: + api: + build: + context: ./backend + image: ludosdata-api + restart: unless-stopped + environment: + ASPNETCORE_ENVIRONMENT: Production + + # Required. Startup fails loudly if this is missing or under 32 chars. + Jwt__Key: ${JWT_KEY:?JWT_KEY is required — see .env.example} + Jwt__Issuer: ${JWT_ISSUER:-LudosData} + Jwt__Audience: ${JWT_AUDIENCE:-LudosData} + Jwt__LifetimeMinutes: ${JWT_LIFETIME_MINUTES:-720} + + # Creates the first account and imports the 105 games from the 2018 dump, + # but only while the database has no users at all. + Seed__Enabled: ${SEED_ENABLED:-true} + Seed__UserName: ${SEED_USERNAME:-} + Seed__Email: ${SEED_EMAIL:-} + Seed__Password: ${SEED_PASSWORD:-} + + # Only consulted when the SPA is served from somewhere other than nginx. + Cors__AllowedOrigins__0: ${CORS_ORIGIN:-http://localhost:8080} + Cors__AllowedOrigins__1: http://localhost:4200 + volumes: + # SQLite file and uploaded box art. This is the only stateful thing in the + # stack — back this volume up and you have backed up everything. + - ludos-data:/data + expose: + - "8080" + healthcheck: + test: ["CMD-SHELL", "curl -fsS http://localhost:8080/health || exit 1"] + interval: 15s + timeout: 3s + retries: 5 + start_period: 20s + + web: + build: + context: ./frontend + image: ludosdata-web + restart: unless-stopped + depends_on: + api: + condition: service_healthy + ports: + - "${WEB_PORT:-8080}:8080" + healthcheck: + # 127.0.0.1, not localhost: nginx listens on IPv4 only, and BusyBox wget + # resolves localhost to ::1 first and gets connection-refused. + test: ["CMD-SHELL", "wget -q --spider http://127.0.0.1:8080/ || exit 1"] + interval: 15s + timeout: 3s + retries: 5 + start_period: 10s + +volumes: + ludos-data: diff --git a/e2e/app.e2e-spec.ts b/e2e/app.e2e-spec.ts deleted file mode 100644 index 8d7775f..0000000 --- a/e2e/app.e2e-spec.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { AppPage } from './app.po'; - -describe('ludos-data App', () => { - let page: AppPage; - - beforeEach(() => { - page = new AppPage(); - }); - - it('should display welcome message', () => { - page.navigateTo(); - expect(page.getParagraphText()).toEqual('Welcome to app!'); - }); -}); diff --git a/e2e/app.po.ts b/e2e/app.po.ts deleted file mode 100644 index 82ea75b..0000000 --- a/e2e/app.po.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { browser, by, element } from 'protractor'; - -export class AppPage { - navigateTo() { - return browser.get('/'); - } - - getParagraphText() { - return element(by.css('app-root h1')).getText(); - } -} diff --git a/e2e/tsconfig.e2e.json b/e2e/tsconfig.e2e.json deleted file mode 100644 index 1d9e5ed..0000000 --- a/e2e/tsconfig.e2e.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "outDir": "../out-tsc/e2e", - "baseUrl": "./", - "module": "commonjs", - "target": "es5", - "types": [ - "jasmine", - "jasminewd2", - "node" - ] - } -} diff --git a/frontend/.dockerignore b/frontend/.dockerignore new file mode 100644 index 0000000..06b5797 --- /dev/null +++ b/frontend/.dockerignore @@ -0,0 +1,5 @@ +node_modules/ +dist/ +.angular/ +.vscode/ +*.log diff --git a/frontend/.editorconfig b/frontend/.editorconfig new file mode 100644 index 0000000..f166060 --- /dev/null +++ b/frontend/.editorconfig @@ -0,0 +1,17 @@ +# Editor configuration, see https://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.ts] +quote_type = single +ij_typescript_use_double_quotes = false + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..854acd5 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,44 @@ +# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files. + +# Compiled output +/dist +/tmp +/out-tsc +/bazel-out + +# Node +/node_modules +npm-debug.log +yarn-error.log + +# IDEs and editors +.idea/ +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# Visual Studio Code +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/mcp.json +.history/* + +# Miscellaneous +/.angular/cache +.sass-cache/ +/connect.lock +/coverage +/libpeerconnection.log +testem.log +/typings +__screenshots__/ + +# System files +.DS_Store +Thumbs.db diff --git a/frontend/.prettierrc b/frontend/.prettierrc new file mode 100644 index 0000000..d6c16d7 --- /dev/null +++ b/frontend/.prettierrc @@ -0,0 +1,12 @@ +{ + "printWidth": 100, + "singleQuote": true, + "overrides": [ + { + "files": "*.html", + "options": { + "parser": "angular" + } + } + ] +} diff --git a/frontend/.vscode/extensions.json b/frontend/.vscode/extensions.json new file mode 100644 index 0000000..77b3745 --- /dev/null +++ b/frontend/.vscode/extensions.json @@ -0,0 +1,4 @@ +{ + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846 + "recommendations": ["angular.ng-template"] +} diff --git a/frontend/.vscode/launch.json b/frontend/.vscode/launch.json new file mode 100644 index 0000000..925af83 --- /dev/null +++ b/frontend/.vscode/launch.json @@ -0,0 +1,20 @@ +{ + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "ng serve", + "type": "chrome", + "request": "launch", + "preLaunchTask": "npm: start", + "url": "http://localhost:4200/" + }, + { + "name": "ng test", + "type": "chrome", + "request": "launch", + "preLaunchTask": "npm: test", + "url": "http://localhost:9876/debug.html" + } + ] +} diff --git a/frontend/.vscode/tasks.json b/frontend/.vscode/tasks.json new file mode 100644 index 0000000..244306f --- /dev/null +++ b/frontend/.vscode/tasks.json @@ -0,0 +1,42 @@ +{ + // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "start", + "isBackground": true, + "problemMatcher": { + "owner": "typescript", + "pattern": "$tsc", + "background": { + "activeOnStart": true, + "beginsPattern": { + "regexp": "Changes detected" + }, + "endsPattern": { + "regexp": "bundle generation (complete|failed)" + } + } + } + }, + { + "type": "npm", + "script": "test", + "isBackground": true, + "problemMatcher": { + "owner": "typescript", + "pattern": "$tsc", + "background": { + "activeOnStart": true, + "beginsPattern": { + "regexp": "Changes detected" + }, + "endsPattern": { + "regexp": "bundle generation (complete|failed)" + } + } + } + } + ] +} diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..475e5e3 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,26 @@ +# syntax=docker/dockerfile:1 + +# ---- build ---------------------------------------------------------------- +# Pinned to the same Node major the project declares in package.json engines. +FROM node:24-alpine AS build +WORKDIR /app + +# npm ci against the lockfile alone, so this layer caches across source edits. +COPY package.json package-lock.json ./ +RUN npm ci + +COPY . . +RUN npm run build:prod + +# ---- runtime -------------------------------------------------------------- +# Unprivileged nginx: listens on 8080 and runs as a non-root user out of the box. +FROM nginxinc/nginx-unprivileged:alpine AS runtime + +COPY --chown=nginx:nginx nginx.conf /etc/nginx/conf.d/default.conf +COPY --chown=nginx:nginx security-headers.conf /etc/nginx/snippets/security-headers.conf +COPY --from=build --chown=nginx:nginx /app/dist/ludos-web/browser /usr/share/nginx/html + +EXPOSE 8080 + +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD ["wget", "-q", "--spider", "http://127.0.0.1:8080/"] diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..6579eea --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,59 @@ +# LudosWeb + +This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 22.1.2. + +## Development server + +To start a local development server, run: + +```bash +ng serve +``` + +Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files. + +## Code scaffolding + +Angular CLI includes powerful code scaffolding tools. To generate a new component, run: + +```bash +ng generate component component-name +``` + +For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run: + +```bash +ng generate --help +``` + +## Building + +To build the project run: + +```bash +ng build +``` + +This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed. + +## Running unit tests + +To execute unit tests with the [Vitest](https://vitest.dev/) test runner, use the following command: + +```bash +ng test +``` + +## Running end-to-end tests + +For end-to-end (e2e) testing, run: + +```bash +ng e2e +``` + +Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs. + +## Additional Resources + +For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page. diff --git a/frontend/angular.json b/frontend/angular.json new file mode 100644 index 0000000..44aa777 --- /dev/null +++ b/frontend/angular.json @@ -0,0 +1,85 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "cli": { + "packageManager": "npm" + }, + "newProjectRoot": "projects", + "projects": { + "ludos-web": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" + } + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular/build:application", + "options": { + "browser": "src/main.ts", + "tsConfig": "tsconfig.app.json", + "inlineStyleLanguage": "scss", + "assets": [ + { + "glob": "**/*", + "input": "public" + } + ], + "styles": [ + "@fontsource/roboto/400.css", + "@fontsource/roboto/500.css", + "@fontsource/roboto/700.css", + "material-icons/iconfont/filled.css", + "src/styles.scss" + ] + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kB", + "maximumError": "1MB" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "4kB", + "maximumError": "8kB" + } + ], + "outputHashing": "all" + }, + "development": { + "optimization": false, + "extractLicenses": false, + "sourceMap": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular/build:dev-server", + "configurations": { + "production": { + "buildTarget": "ludos-web:build:production" + }, + "development": { + "buildTarget": "ludos-web:build:development" + } + }, + "defaultConfiguration": "development", + "options": { + "proxyConfig": "proxy.conf.json" + } + }, + "test": { + "builder": "@angular/build:unit-test" + } + } + } + } +} \ No newline at end of file diff --git a/frontend/nginx.conf b/frontend/nginx.conf new file mode 100644 index 0000000..30fcd4f --- /dev/null +++ b/frontend/nginx.conf @@ -0,0 +1,71 @@ +# Serves the built Angular bundle and reverse-proxies the API, so the browser +# sees a single origin and never makes a cross-origin request. + +server { + listen 8080; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + # Client uploads are capped server-side too; this stops oversized bodies + # from being buffered all the way to the API first. + client_max_body_size 6m; + + gzip on; + gzip_types text/css application/javascript application/json image/svg+xml; + gzip_min_length 1024; + + # Do not advertise the exact nginx version. + server_tokens off; + + include /etc/nginx/snippets/security-headers.conf; + + # Hashed build assets are immutable, so they can be cached hard. + location ~* \.(?:js|css|woff2?|ttf|eot|svg|png|jpg|jpeg|gif|webp|ico)$ { + include /etc/nginx/snippets/security-headers.conf; + # add_header alone, not `expires`: using both emits two Cache-Control + # headers with overlapping directives. + add_header Cache-Control "public, max-age=31536000, immutable" always; + try_files $uri =404; + } + + location /api/ { + proxy_pass http://api:8080; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # Uploaded box art, served straight off the API's volume. + # + # `^~` matters: without it, a regex location wins over a prefix location, so + # /uploads//.webp would fall into the static-asset block above and + # 404 against nginx's own filesystem instead of being proxied. + location ^~ /uploads/ { + include /etc/nginx/snippets/security-headers.conf; + proxy_pass http://api:8080; + proxy_http_version 1.1; + proxy_set_header Host $host; + add_header Cache-Control "public, max-age=2592000" always; + } + + location /health { + proxy_pass http://api:8080/health; + } + + # index.html must never be cached, or clients keep booting old bundles. + # Declared before `location /` so the internal rewrite below lands here. + location = /index.html { + include /etc/nginx/snippets/security-headers.conf; + add_header Cache-Control "no-store, no-cache, must-revalidate" always; + } + + # Angular owns routing: any unknown path returns index.html so a deep link + # or a refresh on /games/12 does not 404. + location / { + try_files $uri $uri/ /index.html; + } +} diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..0af5eea --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,7953 @@ +{ + "name": "ludos-web", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "ludos-web", + "version": "0.0.0", + "dependencies": { + "@angular/cdk": "^22.1.0", + "@angular/common": "^22.1.0", + "@angular/compiler": "^22.1.0", + "@angular/core": "^22.1.0", + "@angular/forms": "^22.1.0", + "@angular/material": "^22.1.0", + "@angular/platform-browser": "^22.1.0", + "@angular/router": "^22.1.0", + "@fontsource/roboto": "^5.3.0", + "material-icons": "^1.13.14", + "rxjs": "~7.8.0", + "tslib": "^2.3.0" + }, + "devDependencies": { + "@angular/build": "^22.1.2", + "@angular/cli": "^22.1.2", + "@angular/compiler-cli": "^22.1.0", + "jsdom": "^28.0.0", + "prettier": "^3.8.1", + "typescript": "~6.0.2", + "vitest": "^4.0.8" + } + }, + "node_modules/@acemir/cssom": { + "version": "0.9.31", + "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.31.tgz", + "integrity": "sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@angular-devkit/architect": { + "version": "0.2201.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2201.2.tgz", + "integrity": "sha512-RRG3JA3hPH0ypbDIyquZt9DDTP5pOMPgqQ/iLSkok1MZdKiOgpk6FGfXCa1ei72SwlX7lnJdq94d6WWdqpbyKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "22.1.2", + "rxjs": "7.8.2" + }, + "bin": { + "architect": "bin/cli.js" + }, + "engines": { + "node": "^22.22.3 || ^24.15.0 || >=26.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/core": { + "version": "22.1.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-22.1.2.tgz", + "integrity": "sha512-tF1oEE7KPs8I08HJQmH5e4GkLUB3+MXXy8t6gMJULaLFxZYP9K1oXRFLappMpdm9OIbEXOChk23hrho0By9aYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "8.20.0", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.5", + "rxjs": "7.8.2", + "source-map": "0.7.6" + }, + "engines": { + "node": "^22.22.3 || ^24.15.0 || >=26.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^5.0.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/schematics": { + "version": "22.1.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-22.1.2.tgz", + "integrity": "sha512-Lw6NvW5rfMUl/2dsuWY8l6wlfWCuYBzCYSSqqliLPDco0doGzBliHwY9uxuzuUKZgOl5TvuVyvEo0t3o4Jj4GA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "22.1.2", + "jsonc-parser": "3.3.1", + "magic-string": "1.0.0", + "ora": "9.4.1", + "rxjs": "7.8.2" + }, + "engines": { + "node": "^22.22.3 || ^24.15.0 || >=26.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular/build": { + "version": "22.1.2", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-22.1.2.tgz", + "integrity": "sha512-DE/3o17JTel4EBt2BA4DqJYeBBuz5Ef/kf1jL9YZTyJu4SrLr/HI79K14jFr0VRIxzcqG92FdIzfLDxbOesQsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "2.3.0", + "@angular-devkit/architect": "0.2201.2", + "@babel/core": "8.0.1", + "@babel/helper-annotate-as-pure": "8.0.0", + "@babel/helper-split-export-declaration": "7.24.7", + "@inquirer/confirm": "6.1.1", + "@vitejs/plugin-basic-ssl": "2.3.0", + "beasties": "0.4.3", + "browserslist": "^4.26.0", + "esbuild": "0.28.1", + "https-proxy-agent": "9.1.0", + "jsonc-parser": "3.3.1", + "listr2": "10.2.2", + "magic-string": "1.0.0", + "mrmime": "2.0.1", + "oxc-parser": "0.142.0", + "parse5-html-rewriting-stream": "8.0.1", + "picomatch": "4.0.5", + "piscina": "5.2.0", + "rolldown": "1.2.0", + "sass": "1.101.0", + "semver": "7.8.5", + "source-map-support": "0.5.21", + "tinyglobby": "0.2.17", + "vite": "8.1.5", + "watchpack": "2.5.2" + }, + "engines": { + "node": "^22.22.3 || ^24.15.0 || >=26.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "optionalDependencies": { + "lmdb": "3.5.6" + }, + "peerDependencies": { + "@angular/compiler": "^22.0.0", + "@angular/compiler-cli": "^22.0.0", + "@angular/core": "^22.0.0", + "@angular/localize": "^22.0.0", + "@angular/platform-browser": "^22.0.0", + "@angular/platform-server": "^22.0.0", + "@angular/service-worker": "^22.0.0", + "@angular/ssr": "^22.1.2", + "istanbul-lib-instrument": "^6.0.0", + "karma": "^6.4.0", + "less": "^4.2.0", + "ng-packagr": "^22.0.0", + "postcss": "^8.4.0", + "rollup": "^4.0.0", + "tailwindcss": "^2.0.0 || ^3.0.0 || ^4.0.0", + "tslib": "^2.3.0", + "typescript": ">=6.0 <6.1", + "vitest": "^4.0.8" + }, + "peerDependenciesMeta": { + "@angular/core": { + "optional": true + }, + "@angular/localize": { + "optional": true + }, + "@angular/platform-browser": { + "optional": true + }, + "@angular/platform-server": { + "optional": true + }, + "@angular/service-worker": { + "optional": true + }, + "@angular/ssr": { + "optional": true + }, + "istanbul-lib-instrument": { + "optional": true + }, + "karma": { + "optional": true + }, + "less": { + "optional": true + }, + "ng-packagr": { + "optional": true + }, + "postcss": { + "optional": true + }, + "rollup": { + "optional": true + }, + "tailwindcss": { + "optional": true + }, + "vitest": { + "optional": true + } + } + }, + "node_modules/@angular/build/node_modules/listr2": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-10.2.2.tgz", + "integrity": "sha512-JtNtbZj8q5BnDMR7trpwvwk3RIrANtIVzEUm8w7amp6xelLgyuq+4WZoTH913XaQAoH/cNdYhaNzBPA2U3xbDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^5.2.0", + "eventemitter3": "^5.0.4", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^10.0.0" + }, + "engines": { + "node": ">=22.13.0" + } + }, + "node_modules/@angular/cdk": { + "version": "22.1.0", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-22.1.0.tgz", + "integrity": "sha512-yfQug47CZ+51mHy0ZLWzi6F/YHfsAF2Z7Jxo3JLM7Aj3Er47hHB8VnrNERwK5tBLs0bE5DoEIm59ga/MI3fVGg==", + "license": "MIT", + "dependencies": { + "parse5": "^8.0.0", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": "^22.0.0 || ^23.0.0", + "@angular/core": "^22.0.0 || ^23.0.0", + "@angular/platform-browser": "^22.0.0 || ^23.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/cli": { + "version": "22.1.2", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-22.1.2.tgz", + "integrity": "sha512-gzB+iuZzB507DAkZb9s5+Jw8QRzOBolUhHEuAKH74xF6oWlEP5JdexfTgti45SjXaKKqeYpODJFnUmSQQJRhxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/architect": "0.2201.2", + "@angular-devkit/core": "22.1.2", + "@angular-devkit/schematics": "22.1.2", + "@inquirer/prompts": "8.5.2", + "@listr2/prompt-adapter-inquirer": "4.2.4", + "@modelcontextprotocol/sdk": "1.29.0", + "@schematics/angular": "22.1.2", + "jsonc-parser": "3.3.1", + "listr2": "10.2.2", + "npm-package-arg": "14.0.0", + "parse5-html-rewriting-stream": "8.0.1", + "semver": "7.8.5", + "yargs": "18.0.0", + "zod": "4.4.3" + }, + "bin": { + "ng": "bin/ng.js" + }, + "engines": { + "node": "^22.22.3 || ^24.15.0 || >=26.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular/cli/node_modules/listr2": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-10.2.2.tgz", + "integrity": "sha512-JtNtbZj8q5BnDMR7trpwvwk3RIrANtIVzEUm8w7amp6xelLgyuq+4WZoTH913XaQAoH/cNdYhaNzBPA2U3xbDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^5.2.0", + "eventemitter3": "^5.0.4", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^10.0.0" + }, + "engines": { + "node": ">=22.13.0" + } + }, + "node_modules/@angular/common": { + "version": "22.1.0", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-22.1.0.tgz", + "integrity": "sha512-67L8AS00egxwEKnoMhNDxy+TY+eKOwvwa+os0Odq8nLm7+Qh7JnMVeub8hfncpenOFqlC/RUjO2W9H7Gd2veNA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^22.22.3 || ^24.15.0 || >=26.0.0" + }, + "peerDependencies": { + "@angular/core": "22.1.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/compiler": { + "version": "22.1.0", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-22.1.0.tgz", + "integrity": "sha512-WCmuPnuXgqnqrkbrwqQRyldi1k3rlzNLVDl8ntINF7XWuJh0KfQLEkRK0FCCmBztWJkbGug4RBVnKTWlKhRzCQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^22.22.3 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/@angular/compiler-cli": { + "version": "22.1.0", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-22.1.0.tgz", + "integrity": "sha512-jL89dbzkrV8AeLaxedBgT7ErMnbfi2dvwDJuCrUgm3eCNfbcOpGbNxzH+wDgvbRg2Lhj11/u3JPbW50xA6rvvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "8.0.1", + "@jridgewell/sourcemap-codec": "^1.4.14", + "chokidar": "^5.0.0", + "convert-source-map": "^1.5.1", + "reflect-metadata": "^0.2.0", + "semver": "^7.0.0", + "tslib": "^2.3.0", + "yargs": "^18.0.0" + }, + "bin": { + "ng-xi18n": "bundles/src/bin/ng_xi18n.js", + "ngc": "bundles/src/bin/ngc.js" + }, + "engines": { + "node": "^22.22.3 || ^24.15.0 || >=26.0.0" + }, + "peerDependencies": { + "@angular/compiler": "22.1.0", + "typescript": ">=6.0 <6.1" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@angular/core": { + "version": "22.1.0", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-22.1.0.tgz", + "integrity": "sha512-X5UaMuOCI4HAvSQIs3QtM+5e0Cni16DRaHUIL3BIBd4ZQNnSH3pZ25TsKQ8Jlu/3hAQ9rzV278kNQcecooGJ7g==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^22.22.3 || ^24.15.0 || >=26.0.0" + }, + "peerDependencies": { + "@angular/compiler": "22.1.0", + "rxjs": "^6.5.3 || ^7.4.0", + "zone.js": "~0.15.0 || ~0.16.0" + }, + "peerDependenciesMeta": { + "@angular/compiler": { + "optional": true + }, + "zone.js": { + "optional": true + } + } + }, + "node_modules/@angular/forms": { + "version": "22.1.0", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-22.1.0.tgz", + "integrity": "sha512-nWlSM/pPp78Sx/fBM/tFEgZxdfZe50LkCE2/hkO22Fi1UM2maGc43LDsu/s6l0q9hFep4Wj+xa30KXDBS7Cn8A==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "tslib": "^2.3.0", + "zod": "^4.0.10" + }, + "engines": { + "node": "^22.22.3 || ^24.15.0 || >=26.0.0" + }, + "peerDependencies": { + "@angular/common": "22.1.0", + "@angular/core": "22.1.0", + "@angular/platform-browser": "22.1.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/material": { + "version": "22.1.0", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-22.1.0.tgz", + "integrity": "sha512-i3Os8JZg4DejgNTtw8GCLQbbZ8+lv8h/ym6PxRwmYNpegmyGiVSKAZ2JBkEs5f1pmMaTppuG3MF6mXei4xi6Wg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/cdk": "22.1.0", + "@angular/common": "^22.0.0 || ^23.0.0", + "@angular/core": "^22.0.0 || ^23.0.0", + "@angular/forms": "^22.0.0 || ^23.0.0", + "@angular/platform-browser": "^22.0.0 || ^23.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/platform-browser": { + "version": "22.1.0", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-22.1.0.tgz", + "integrity": "sha512-gqUYDUiPfwbaLYdH8WLnOLl3feo3OcNpnMO08HBHaUdi4TLNkC28xwa9fC6ANyYD22QZ5A3abSg8fmR6upWMwg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^22.22.3 || ^24.15.0 || >=26.0.0" + }, + "peerDependencies": { + "@angular/animations": "22.1.0", + "@angular/common": "22.1.0", + "@angular/core": "22.1.0" + }, + "peerDependenciesMeta": { + "@angular/animations": { + "optional": true + } + } + }, + "node_modules/@angular/router": { + "version": "22.1.0", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-22.1.0.tgz", + "integrity": "sha512-42Bs0g+tV2gE70Lqnt+VD/+DWbvWwQcg8QgXkTIu3A504tYknrZG/wmvki2AJGyZhmyQ46B4pfXLG4WDP8MFSA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^22.22.3 || ^24.15.0 || >=26.0.0" + }, + "peerDependencies": { + "@angular/common": "22.1.0", + "@angular/core": "22.1.0", + "@angular/platform-browser": "22.1.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "5.1.11", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz", + "integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/generational-cache": "^1.0.1", + "@csstools/css-calc": "^3.2.0", + "@csstools/css-color-parser": "^4.1.0", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.8.1.tgz", + "integrity": "sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.1.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.6" + } + }, + "node_modules/@asamuzakjp/generational-cache": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz", + "integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/code-frame": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-8.0.0.tgz", + "integrity": "sha512-dYYg153EyN2Ekbqw2zAsbd6/JR+9N2SEoC7YV2GyyqMM7x9bLDTjBD6XBhSMLH0wtIVyJj03jWNriQhaN+eoCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^8.0.0", + "js-tokens": "^10.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-8.0.0.tgz", + "integrity": "sha512-DOjnob/cXOUgDOozCDeq/aK2p5y8dUIVdf6tNhEV1HQRd6I8aQ4f4fbtHRVEvb6lP3BGomrKHiS8ICAASSVQSw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/core": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-8.0.1.tgz", + "integrity": "sha512-5FgxM4dLQpMJHSiVATk8foW263dVHQHBVpXYiimNECVWG01f4nFyEbQixeT6Mwvg7TayREJ2gpKl3o2RoMdnqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^8.0.0", + "@babel/generator": "^8.0.0", + "@babel/helper-compilation-targets": "^8.0.0", + "@babel/helpers": "^8.0.0", + "@babel/parser": "^8.0.0", + "@babel/template": "^8.0.0", + "@babel/traverse": "^8.0.0", + "@babel/types": "^8.0.0", + "@types/gensync": "^1.0.5", + "convert-source-map": "^2.0.0", + "empathic": "^2.0.1", + "gensync": "^1.0.0-beta.2", + "import-meta-resolve": "^4.2.0", + "json5": "^2.2.3", + "obug": "^2.1.1", + "semver": "^7.7.3" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/generator": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-8.0.0.tgz", + "integrity": "sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^8.0.0", + "@babel/types": "^8.0.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "@types/jsesc": "^2.5.0", + "jsesc": "^3.0.2" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-8.0.0.tgz", + "integrity": "sha512-NSpMkMsvvZqzThJ0p1B02cbtA2ObEyfBvq950bmNkyxsxvcxwhvvCB036rKhlEnuBBo30bOrk13u3FzlKSoRrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^8.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-8.0.0.tgz", + "integrity": "sha512-JwculLABZvyPvyLBpwU/E/IbH2uM3mnxNtIJpxnIfb24y1PrdVxK5Dqjle4DpgqpGRnwgC7G8IkzPdSXZrO1Ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^8.0.0", + "@babel/helper-validator-option": "^8.0.0", + "browserslist": "^4.24.0", + "lru-cache": "^11.0.0", + "semver": "^7.7.3" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-8.0.0.tgz", + "integrity": "sha512-lLozHOM6sWWlxNo8CYqHy4MBZeTvHXNgVPBfPOGsjPKUzHC2Az9QwB6gxdQmpwHl6GlQtbGgS+lj5887guDiLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration/node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration/node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration/node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz", + "integrity": "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.4.tgz", + "integrity": "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-8.0.0.tgz", + "integrity": "sha512-U4Dybxh4WESWHt5XhBeExi4DrY0/DNK1aHpQbsrQXCUbFHuMweT0TpLEWKvaraV2Y6fS+ZXunsZ8zIuZIgvF2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helpers": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-8.0.0.tgz", + "integrity": "sha512-wfbi91pM3py96oIiJEz7qIpyXDytgr9zQC1HEWwlGNVRAEmItuU/0a41ZUKu1sJGyhhOIpc4t5vk4PYzt8wpsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^8.0.0", + "@babel/types": "^8.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/parser": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.4.tgz", + "integrity": "sha512-srpptsAkEbbNIC/q8nT7o+m6CQe8CJUTV/t7MYc9NnWlgYVtHOb7JH6SorxMhN0kuRJjVqXbKClG6xSbPtzz+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^8.0.4" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/template": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-8.0.0.tgz", + "integrity": "sha512-eAD0QW/AlbamBbw0FeGiwasbCVPq5ncW0HNVyLP3B9czqLyh4gvw+5JTSNt6le9+ziAU7mqDZsKTHf3jTb4chQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^8.0.0", + "@babel/parser": "^8.0.0", + "@babel/types": "^8.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/traverse": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-8.0.4.tgz", + "integrity": "sha512-bZnmqzGG8UZneG1lLxBoWIH0G6Gr1D846Yu4/3XnY6FhCndMR49u26nTY08u/dAxWmLWF9vGQOuC+84FfIUoeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^8.0.0", + "@babel/generator": "^8.0.0", + "@babel/helper-globals": "^8.0.0", + "@babel/parser": "^8.0.4", + "@babel/template": "^8.0.0", + "@babel/types": "^8.0.4", + "obug": "^2.1.1" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/types": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.4.tgz", + "integrity": "sha512-eY+Yn3dCqTGmyiq2QRU66lA5FL8lqqqvecHt0fF3uHONIa7ToYsaCiWV8lOKqAs0Rb2SjixiKFROngnulPtt2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^8.0.0", + "@babel/helper-validator-identifier": "^8.0.4" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.0.tgz", + "integrity": "sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@csstools/css-calc": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.3.0.tgz", + "integrity": "sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.10.tgz", + "integrity": "sha512-UZhQLIUyJaaMepqehrCODwCg2KW25vFvLWBmqYFaPclYvvxzj/sG8LBOhBFCp11i9uE7t1EyS+RAoV9tztPFyw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^6.1.0", + "@csstools/css-calc": "^3.3.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.7.tgz", + "integrity": "sha512-fQ+05118eQS1cofO3aJpB5efgpBZMvIzwr/sbC8kDLVA5XLG8q1kJV5yzrUAI1f7lvhPnm8fgIjzFB8/O/5Dig==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.2.tgz", + "integrity": "sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.2.tgz", + "integrity": "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@exodus/bytes": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz", + "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, + "node_modules/@fontsource/roboto": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.3.0.tgz", + "integrity": "sha512-BapRJOWYP+LZ21zp+wBQjfpPYKRoxc4LspJ/RLuI+HSMBD5u/X4O+ESDrSvEqDSy0rAl7GwBJ+09mdc16cVQ1Q==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@harperfast/extended-iterable": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@harperfast/extended-iterable/-/extended-iterable-1.0.3.tgz", + "integrity": "sha512-sSAYhQca3rDWtQUHSAPeO7axFIUJOI6hn1gjRC5APVE1a90tuyT8f5WIgRsFhhWA7htNkju2veB9eWL6YHi/Lw==", + "dev": true, + "license": "Apache-2.0", + "optional": true + }, + "node_modules/@hono/node-server": { + "version": "1.19.17", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.17.tgz", + "integrity": "sha512-dSneS5qhiauZWGDCeK4o695Xd9nUNjviSZCMQrj10eetr8Uln1ucn6bbphOM6UynAMMtNIzZNSpL9vnASJwrPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@inquirer/ansi": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-2.0.7.tgz", + "integrity": "sha512-3eTuUO1vH2cZm2ZKHeQxnOqlTi9EfZDGgIe3BL3I4u+rJHocr9Fz86M4fjYABPvFnQG/gGK551HqDiIcETwU6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + } + }, + "node_modules/@inquirer/checkbox": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-5.2.1.tgz", + "integrity": "sha512-b6xmA/VlTe0ZgDQHDui+Nav470u7u49nRd8/iuhOcQPO9Ch7lGuogydhi2VOmNlZ+zXcM8IcPuNSwQcdJaF/kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.7", + "@inquirer/core": "^11.2.1", + "@inquirer/figures": "^2.0.7", + "@inquirer/type": "^4.0.7" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/confirm": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-6.1.1.tgz", + "integrity": "sha512-eb8DBZcz/2qHWQda4rk2JiQk5h9QV/cVHi1yjt0f69WFZMRFn0sJTye3EAP8icut8UDMjQPsaH5KbcOogefrFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.2.1", + "@inquirer/type": "^4.0.7" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "11.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-11.2.1.tgz", + "integrity": "sha512-Qd6GJT1yVyrZZCfN8W2qKF5ApmqryXRhRKCuip8h01x2w/esJQ2XIYc6f9abMIHgKQdBfFTSOdbHRLAhuM09UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.7", + "@inquirer/figures": "^2.0.7", + "@inquirer/type": "^4.0.7", + "cli-width": "^4.1.0", + "fast-wrap-ansi": "^0.2.0", + "mute-stream": "^3.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/editor": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-5.2.2.tgz", + "integrity": "sha512-ZRVd/oD+sYsUd5zVm0NflqEzlqfYCyHNsqkHl2oWXEUHs12tCbcSFi+wVFEvD8+LGRaMUsVrE7qeo6lSG/S1Vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.2.1", + "@inquirer/external-editor": "^3.0.3", + "@inquirer/type": "^4.0.7" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/expand": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-5.1.1.tgz", + "integrity": "sha512-YmQpenjbFSHAK3sOd44puHh3V1KXXr+JiNpUztoSQ4drLh2rTVzTap/YtlAVu/5xavifIlBfNEzJ/neZJ1a/1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.2.1", + "@inquirer/type": "^4.0.7" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/external-editor": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-3.0.3.tgz", + "integrity": "sha512-6thf5I8q7lZwzGLAxPaaGEREEkZ3nyePPDQ1oyobblxmEE8mqTLguScP7pDjUTAibiyb4hfXl+qjUEJ+di/aNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.2" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/figures": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-2.0.7.tgz", + "integrity": "sha512-aJ8TBPOGB6f/2qziPfElISTCEd5XOYTFckA2SGjhNmiKzfK/u4ot3v0DUzGVdUnKjN10EqnnEPck36BkyfLnJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + } + }, + "node_modules/@inquirer/input": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-5.1.2.tgz", + "integrity": "sha512-9K/DDBSQpOyZSkt6sOVP9Vo0TR7atX2kuILsUu0x3wVcVbe97lJwIJKMLdMw25tDYuXl/qp6erT0Xs1rfmcfZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.2.1", + "@inquirer/type": "^4.0.7" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/number": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-4.1.1.tgz", + "integrity": "sha512-XF4IXAbPnGPgw0wsbC/i2tPcyfdZgDpUlhsqU0SfT4IRIGWha6Xm9VRgN5yYxJq+jnyXlfXI/nQ3ulfk0iEICA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.2.1", + "@inquirer/type": "^4.0.7" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/password": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-5.1.1.tgz", + "integrity": "sha512-3XBfF7DAsp5qeDsvN5Rd1HmbNokVvEQoUM0QLrRcybC9nX96w3Pbmu7qUsb3IT3J3jBvs2+mTXaKHOUsgHMLzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.7", + "@inquirer/core": "^11.2.1", + "@inquirer/type": "^4.0.7" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/prompts": { + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-8.5.2.tgz", + "integrity": "sha512-IYR/3C/paEVVQYQvdDlFZVjRCJVYHHON0XXMH91KO9GSxs0TdKYWlUdvfQl2EfAHDxUaN3IBffkE/BDTh5nJ6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^5.2.1", + "@inquirer/confirm": "^6.1.1", + "@inquirer/editor": "^5.2.2", + "@inquirer/expand": "^5.1.1", + "@inquirer/input": "^5.1.2", + "@inquirer/number": "^4.1.1", + "@inquirer/password": "^5.1.1", + "@inquirer/rawlist": "^5.3.1", + "@inquirer/search": "^4.2.1", + "@inquirer/select": "^5.2.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/rawlist": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-5.3.1.tgz", + "integrity": "sha512-QqdTqQddL3qPX/PPrjobpsO25NZ4dWXgTLenrR445L2ptLEYE6Z+PD5c5CNDJNx4ugRgELAIpSIJxZaO2jJ2Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.2.1", + "@inquirer/type": "^4.0.7" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/search": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-4.2.1.tgz", + "integrity": "sha512-xJj8QWKRSrfKoBIITLZK61dD3zwo0Rz11fgDImku30/Oe81zMdIdGgrLY2h6RkJ+KZ/GhNYIRMKnH/62qBTA5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.2.1", + "@inquirer/figures": "^2.0.7", + "@inquirer/type": "^4.0.7" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/select": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-5.2.1.tgz", + "integrity": "sha512-FlDndEUww8m7BfukO2nJa25vhD+H5jxxCv4oGioKqzyWz3nPHhhw4LKdYRSlXuAx7DsdWia7iyaBPKKS95Evfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.7", + "@inquirer/core": "^11.2.1", + "@inquirer/figures": "^2.0.7", + "@inquirer/type": "^4.0.7" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/type": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.0.7.tgz", + "integrity": "sha512-t28inv14nMQ1PhKpsJPY+kEs/c00qzeCOS2gTNRyTjG5d6qsVA2fItxW4hkvGZ5lvanGLdtCzVIx5dwdRpN1+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@listr2/prompt-adapter-inquirer": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@listr2/prompt-adapter-inquirer/-/prompt-adapter-inquirer-4.2.4.tgz", + "integrity": "sha512-/KRI2DMD7JGSYaREF0Ygl7AefJ/2ase4Gc5cBiKqT5l4tFjsSJfhFGcc5nSkgl0Sp9LkCQNzl/cqbVJYP2L3dw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/type": "^4.0.6" + }, + "engines": { + "node": ">=22.13.0" + }, + "peerDependencies": { + "@inquirer/prompts": ">= 3 < 9", + "listr2": "10.2.1" + } + }, + "node_modules/@lmdb/lmdb-darwin-arm64": { + "version": "3.5.6", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-3.5.6.tgz", + "integrity": "sha512-mY5FG4TjPAkY4P0w+OhHaUka5mDh2TX2WKYIwuKzJ1zeW3VvRgxdam/lGJTquI+bthTx5CSHDW+BAQCnNAzkEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@lmdb/lmdb-darwin-x64": { + "version": "3.5.6", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-3.5.6.tgz", + "integrity": "sha512-foa+pwitysO8k+xhs7psBFfTKnVgR69NlZRRTHaFVDqphh7AdGpLeyRzKw/ofatr/sN6TiHRRW6mmop0ZrrppQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@lmdb/lmdb-linux-arm": { + "version": "3.5.6", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-3.5.6.tgz", + "integrity": "sha512-QR4YRyR5h5Z8eGXrNQjiyo2NNDfqi3tCc9dQG5Is1blCt+qWw1ZoBWhlWAr5d+jshkifMIJjVHzHGKbkKzF8Tw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-linux-arm64": { + "version": "3.5.6", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-3.5.6.tgz", + "integrity": "sha512-HmiyFFdJa38s1heCMSooSPaBSFTHJ3C+ERPp28xAPlDX1YiALJVOgbry065nXd8Y7KISWjnw05zpG1RX8IfftA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-linux-x64": { + "version": "3.5.6", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-3.5.6.tgz", + "integrity": "sha512-ADzCuCF2cTNiX9kDScqcz1fjnAkxPpQNneV3KFTdV3wWtVlI2sTGzySoMTgDpinkMMFj1NTJlxA6XR8fwc4hlA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-win32-arm64": { + "version": "3.5.6", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-arm64/-/lmdb-win32-arm64-3.5.6.tgz", + "integrity": "sha512-J7A9aEQsQiv0TYtBGL7NDIPp2lOS8nnl+zm4sWZm1xlsTTaQ4PgD096Adzdrk27rw3UxCkDXdCUa4ax41oztBQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@lmdb/lmdb-win32-x64": { + "version": "3.5.6", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-3.5.6.tgz", + "integrity": "sha512-1g7G0knRX2iV/voDu54yxrGqw5Dk0w2oIYb7dgJq8IkOi+m7wbD8Q3QpPFjh0C01G58S88dqGn03len6UPCXsg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.4.tgz", + "integrity": "sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.4.tgz", + "integrity": "sha512-zExlW9zUJKZH/tOtVMttwjKa4Xm/3KcNjnE3dPN92uCktwavMxpgCA3MoJK/DOnTWsQgo224OaST27/mPNAf+w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.4.tgz", + "integrity": "sha512-Tg3yX65f5GbtXLkrYEHE5oibZG9epyYWas7FogTTEJeDEF9JlXJzKgXaNhT3UXlTOeA+AfZpYZYZ0uPj7Cfquw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.4.tgz", + "integrity": "sha512-dgX0P/9wGPJeHFBG+ZmhgE6bmtMt7NP5CRBGyyktpopdk/mW4POnrpQsSLtKI1dwpc+pPLuXHDh6vvskyQE/sw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.4.tgz", + "integrity": "sha512-8TNXMEjJc3QEy7R/x1INhgiU+XakDAFUzBhaz7+Rbrs8NH5UQeHQxxmzsSBJGyV6I1jW79undiQm8tOI+D+8FQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.4.tgz", + "integrity": "sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@napi-rs/nice": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice/-/nice-1.1.1.tgz", + "integrity": "sha512-xJIPs+bYuc9ASBl+cvGsKbGrJmS6fAKaSZCnT0lhahT5rhA2VVy9/EcIgd2JhtEuFOJNx7UHNn/qiTPTY4nrQw==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@napi-rs/nice-android-arm-eabi": "1.1.1", + "@napi-rs/nice-android-arm64": "1.1.1", + "@napi-rs/nice-darwin-arm64": "1.1.1", + "@napi-rs/nice-darwin-x64": "1.1.1", + "@napi-rs/nice-freebsd-x64": "1.1.1", + "@napi-rs/nice-linux-arm-gnueabihf": "1.1.1", + "@napi-rs/nice-linux-arm64-gnu": "1.1.1", + "@napi-rs/nice-linux-arm64-musl": "1.1.1", + "@napi-rs/nice-linux-ppc64-gnu": "1.1.1", + "@napi-rs/nice-linux-riscv64-gnu": "1.1.1", + "@napi-rs/nice-linux-s390x-gnu": "1.1.1", + "@napi-rs/nice-linux-x64-gnu": "1.1.1", + "@napi-rs/nice-linux-x64-musl": "1.1.1", + "@napi-rs/nice-openharmony-arm64": "1.1.1", + "@napi-rs/nice-win32-arm64-msvc": "1.1.1", + "@napi-rs/nice-win32-ia32-msvc": "1.1.1", + "@napi-rs/nice-win32-x64-msvc": "1.1.1" + } + }, + "node_modules/@napi-rs/nice-android-arm-eabi": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm-eabi/-/nice-android-arm-eabi-1.1.1.tgz", + "integrity": "sha512-kjirL3N6TnRPv5iuHw36wnucNqXAO46dzK9oPb0wj076R5Xm8PfUVA9nAFB5ZNMmfJQJVKACAPd/Z2KYMppthw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-android-arm64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm64/-/nice-android-arm64-1.1.1.tgz", + "integrity": "sha512-blG0i7dXgbInN5urONoUCNf+DUEAavRffrO7fZSeoRMJc5qD+BJeNcpr54msPF6qfDD6kzs9AQJogZvT2KD5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-darwin-arm64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-arm64/-/nice-darwin-arm64-1.1.1.tgz", + "integrity": "sha512-s/E7w45NaLqTGuOjC2p96pct4jRfo61xb9bU1unM/MJ/RFkKlJyJDx7OJI/O0ll/hrfpqKopuAFDV8yo0hfT7A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-darwin-x64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-x64/-/nice-darwin-x64-1.1.1.tgz", + "integrity": "sha512-dGoEBnVpsdcC+oHHmW1LRK5eiyzLwdgNQq3BmZIav+9/5WTZwBYX7r5ZkQC07Nxd3KHOCkgbHSh4wPkH1N1LiQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-freebsd-x64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-freebsd-x64/-/nice-freebsd-x64-1.1.1.tgz", + "integrity": "sha512-kHv4kEHAylMYmlNwcQcDtXjklYp4FCf0b05E+0h6nDHsZ+F0bDe04U/tXNOqrx5CmIAth4vwfkjjUmp4c4JktQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm-gnueabihf": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm-gnueabihf/-/nice-linux-arm-gnueabihf-1.1.1.tgz", + "integrity": "sha512-E1t7K0efyKXZDoZg1LzCOLxgolxV58HCkaEkEvIYQx12ht2pa8hoBo+4OB3qh7e+QiBlp1SRf+voWUZFxyhyqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-gnu/-/nice-linux-arm64-gnu-1.1.1.tgz", + "integrity": "sha512-CIKLA12DTIZlmTaaKhQP88R3Xao+gyJxNWEn04wZwC2wmRapNnxCUZkVwggInMJvtVElA+D4ZzOU5sX4jV+SmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm64-musl": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-musl/-/nice-linux-arm64-musl-1.1.1.tgz", + "integrity": "sha512-+2Rzdb3nTIYZ0YJF43qf2twhqOCkiSrHx2Pg6DJaCPYhhaxbLcdlV8hCRMHghQ+EtZQWGNcS2xF4KxBhSGeutg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-ppc64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-ppc64-gnu/-/nice-linux-ppc64-gnu-1.1.1.tgz", + "integrity": "sha512-4FS8oc0GeHpwvv4tKciKkw3Y4jKsL7FRhaOeiPei0X9T4Jd619wHNe4xCLmN2EMgZoeGg+Q7GY7BsvwKpL22Tg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-riscv64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-riscv64-gnu/-/nice-linux-riscv64-gnu-1.1.1.tgz", + "integrity": "sha512-HU0nw9uD4FO/oGCCk409tCi5IzIZpH2agE6nN4fqpwVlCn5BOq0MS1dXGjXaG17JaAvrlpV5ZeyZwSon10XOXw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-s390x-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-s390x-gnu/-/nice-linux-s390x-gnu-1.1.1.tgz", + "integrity": "sha512-2YqKJWWl24EwrX0DzCQgPLKQBxYDdBxOHot1KWEq7aY2uYeX+Uvtv4I8xFVVygJDgf6/92h9N3Y43WPx8+PAgQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-x64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-gnu/-/nice-linux-x64-gnu-1.1.1.tgz", + "integrity": "sha512-/gaNz3R92t+dcrfCw/96pDopcmec7oCcAQ3l/M+Zxr82KT4DljD37CpgrnXV+pJC263JkW572pdbP3hP+KjcIg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-x64-musl": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-musl/-/nice-linux-x64-musl-1.1.1.tgz", + "integrity": "sha512-xScCGnyj/oppsNPMnevsBe3pvNaoK7FGvMjT35riz9YdhB2WtTG47ZlbxtOLpjeO9SqqQ2J2igCmz6IJOD5JYw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-openharmony-arm64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-openharmony-arm64/-/nice-openharmony-arm64-1.1.1.tgz", + "integrity": "sha512-6uJPRVwVCLDeoOaNyeiW0gp2kFIM4r7PL2MczdZQHkFi9gVlgm+Vn+V6nTWRcu856mJ2WjYJiumEajfSm7arPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-arm64-msvc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-arm64-msvc/-/nice-win32-arm64-msvc-1.1.1.tgz", + "integrity": "sha512-uoTb4eAvM5B2aj/z8j+Nv8OttPf2m+HVx3UjA5jcFxASvNhQriyCQF1OB1lHL43ZhW+VwZlgvjmP5qF3+59atA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-ia32-msvc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-ia32-msvc/-/nice-win32-ia32-msvc-1.1.1.tgz", + "integrity": "sha512-CNQqlQT9MwuCsg1Vd/oKXiuH+TcsSPJmlAFc5frFyX/KkOh0UpBLEj7aoY656d5UKZQMQFP7vJNa1DNUNORvug==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-x64-msvc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-x64-msvc/-/nice-win32-x64-msvc-1.1.1.tgz", + "integrity": "sha512-vB+4G/jBQCAh0jelMTY3+kgFy00Hlx2f2/1zjMoH821IbplbWZOkLiTYXQkygNTzQJTq5cvwBDgn2ppHD+bglQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.2.tgz", + "integrity": "sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.3", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.3" + } + }, + "node_modules/@oxc-parser/binding-android-arm-eabi": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm-eabi/-/binding-android-arm-eabi-0.142.0.tgz", + "integrity": "sha512-ZiRGDutGsv1G6bL/ozy/koC0Sv39T1DqyoC4KD1DOy9ZoACm1O5UWhEK2c02Qdk+4lfLVkvFa/mQ0fm/4h1BtQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-android-arm64": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.142.0.tgz", + "integrity": "sha512-WZkvGRLNQTz8lR9zP5nLjUdlroRCopBu3g9zF1p/laE6DzT1UbQo8Rdz5MWhaJUPYg/6gp+jo7HUgsyKaN1FtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-darwin-arm64": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.142.0.tgz", + "integrity": "sha512-l4khS8LQOOVYsGRVARo1gSaCT/aBSceUVXgtovWc2+drnxVuDr082WA3OCHVdVzIz5JIrP/y9CWsSKxBDNmYGg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-darwin-x64": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.142.0.tgz", + "integrity": "sha512-QBsNF3nqlXmcH2B1YOPqQYmCJoy4HuIjUxGbBO/k5JAJUl68ghU2psRY2zPk+RyBaWqKP/qfL4oaFgEMCdwskA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-freebsd-x64": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.142.0.tgz", + "integrity": "sha512-b7Q7m4Cqc6XqNhri3R+QhU+GVy646Pn+bkdhrDdWym/Fdi0ZUa+d73H9dm5H91JtbtAQ/z1d8XKMW3oOV8a4tQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm-gnueabihf": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.142.0.tgz", + "integrity": "sha512-3riVS5IhdH3uCZj1Y9ftDQlR0dvLsIlw/edrRqk8JhgNd5K0XSs+UBtgh50N13CAlW9/TXj6sVGXaKNBocd0Yg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm-musleabihf": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.142.0.tgz", + "integrity": "sha512-NmXUOpgpTSkhl795TiXmWppTwmSJ92RC1qvD6e4XOF+slgmo3e6Ah+kEu+6AN8s7NAOEwqGmir58MgSQSWmBSA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm64-gnu": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.142.0.tgz", + "integrity": "sha512-gc0EXsKtXgerujmU2Bql3u1L1HsSQ2774R83idq/FoNMPVV/RY/1ErFsvnit7KoiP/sLvzQixeUo4Ut0ic0wmw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm64-musl": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.142.0.tgz", + "integrity": "sha512-F2XvmWSE0uWpie+jHKKIFgdVOe9ypGhkEZxKx5DuW215K6cbAC274yYaPkcM7EqY4Df3Weyhpcz3lsURyH2LVg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-ppc64-gnu": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.142.0.tgz", + "integrity": "sha512-wLMbT21U/QxknQsk+VvNF0b9D2/aGWhcaQQQ+VYlE8FwD5+GoWZIPPXNzyHmkYyhm0KB3itL+TBavjMatqNnYA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-riscv64-gnu": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.142.0.tgz", + "integrity": "sha512-+G8F/4ckwT7FCJV4H2bt09xEzJbjNCfuL4Sp1AYNaFtFMVtgIGMuJlteT82U+K0UIZ/DzAR/LDlMFnEuajG7Kw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-riscv64-musl": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.142.0.tgz", + "integrity": "sha512-hTsHtTLxMAfCo+rpF5K3qZJKW2NpPN/CHd4mYB3y7XlSdspHkd2gehDIofP64AacA9nWQw2tY3O7wR6UY8IVOA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-s390x-gnu": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.142.0.tgz", + "integrity": "sha512-6y7qYY3TCUDYjqswImdTGl92y+KA/80twALegQPN27kfY+bG7Ib1+L3jbmrCZQx6wrVnai9IPsEZp07I0hx7JQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-x64-gnu": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.142.0.tgz", + "integrity": "sha512-i69kAWU+2LgoH5bR+zWiiu+UzAw7Oxkwv7COeJTeY19pn4e70nKQcr9Pm6cL2Z0Z54d+gl9qADlK/0yyuCPiBA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-x64-musl": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.142.0.tgz", + "integrity": "sha512-4SQs678MmjYVrmhAgCWD4o0vpaFszXw9xLX5p2Z9MMFcltxiLkA88wQjh80YHjPrXtpyZ2CWI5m+1yNKM0m2Pw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-openharmony-arm64": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-openharmony-arm64/-/binding-openharmony-arm64-0.142.0.tgz", + "integrity": "sha512-YHpx9N7Ln3a++Tc8rv+H7mrK1zyJQOAwCFg8LZ3lTs1T5afGWeZrLPhPT9HLnIwSjCyJqPWVMIrMxbjcmBr2oQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-wasm32-wasi": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.142.0.tgz", + "integrity": "sha512-3pLDyY3+oogW73RM5uehNgAiR/Xfb7fvO2Q1Z1gIqZ2+50XDVQmBVlRkHXZTU4gKnQHpwETNsYQVsJ3joVB2iA==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.2", + "@emnapi/runtime": "1.11.2", + "@napi-rs/wasm-runtime": "^1.1.6" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-win32-arm64-msvc": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.142.0.tgz", + "integrity": "sha512-Had/VeVY28Oyb0K+Q4FV8KCzoBycIh93oDK6pCbya9lkzdq+ikMHMgBubsdqqlybjJmQRawCQRrnBRHyQwYvcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-win32-ia32-msvc": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.142.0.tgz", + "integrity": "sha512-GGi3+YphVHavvgs6gum2UXoNCqzHAmPt/nXkn8ZQZstV2Q1qZD1Mn8fz/nWrDkefHQtrG/+1/XrbMxsBTo6Svw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-win32-x64-msvc": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.142.0.tgz", + "integrity": "sha512-Ny/Wv4Us1LGC/ljwNTp+Hx3r/pH15EFfeDF0p+n898gt+TtRd6C9SccHcuUhDiNTb8s5tt7jdeAMDRQZ4Vq6hg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.142.0.tgz", + "integrity": "sha512-7W+2q5AKQVU36fkaryontrHn3YDt1RyUYXatw9i5H8ocYe2sPKSFB6eS8WNPeRKiN1qAWWZUPm7gwFzJGrccqQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.6.0.tgz", + "integrity": "sha512-7FNeNl8NCE7aINx7WXiKQrPYZWC/hvrTsmk6zmxbI7LTXE7hVek/n8AfVgpe2y82zl3w0HvCHN0bVKMBoJcC0w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.6.0", + "@parcel/watcher-darwin-arm64": "2.6.0", + "@parcel/watcher-darwin-x64": "2.6.0", + "@parcel/watcher-freebsd-x64": "2.6.0", + "@parcel/watcher-linux-arm-glibc": "2.6.0", + "@parcel/watcher-linux-arm-musl": "2.6.0", + "@parcel/watcher-linux-arm64-glibc": "2.6.0", + "@parcel/watcher-linux-arm64-musl": "2.6.0", + "@parcel/watcher-linux-x64-glibc": "2.6.0", + "@parcel/watcher-linux-x64-musl": "2.6.0", + "@parcel/watcher-win32-arm64": "2.6.0", + "@parcel/watcher-win32-x64": "2.6.0" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.6.0.tgz", + "integrity": "sha512-trgpLSCKRC/huFjXX/Smh+0sWe4+YtKfktIToiMl59ghz7z+qkH6kMvNnUbLyRs9N11t8l4svSCs1+5B3rOAhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.6.0.tgz", + "integrity": "sha512-Y3QV0gl7Q1zbfueunkWIERICbEojQFCgpyG7YqOGNFLsckXyI1xu9mAIUpKY9QBYzBtSkN8dBPwd3yiAO9ovMw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.6.0.tgz", + "integrity": "sha512-Ohv6OpzhUfKYD7Beb8kDvG0jbIxORCYY1JRdZnaBtnjjkJxgD7ZVL0nw2sCYd0yTMKTvz3nnTnOF3cDifK+kvw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.6.0.tgz", + "integrity": "sha512-5HmXvDgs8VK+74jF9y9/2FE3/OnlcKmc56tjmSrEuZjpSZOGL+fvAu+HKJBdPs9uwoP2hE6TlSUpXZ/C5jUFmQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.6.0.tgz", + "integrity": "sha512-Ps/hui3A+vMbjdqlqAowK2ZL8+BO8dBjxeWXj6npTBs3jx4wWmbPpaLuqwrQrSqIVMCnpWo238bJ1U37GhQOYg==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.6.0.tgz", + "integrity": "sha512-9c6AUHgHoG+IY88MRIHupztQiQnrbqHYQjkM2btA+Bf/wQnQMuiD0Wfk1EVv3TlNT3x41uU71rn6E4xh/+zvkw==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.6.0.tgz", + "integrity": "sha512-yHRqS2owEXe6Hic9z6Mh1ECsCd+ODVOGvZDyciqRd21+v+o+DnXMOrw50DSpIG2sb8GPEaPPmfeCAWKPJdq46g==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.6.0.tgz", + "integrity": "sha512-WhB2e/V7rqdHHWZusBSPuy5Ei8S6lSz6FE5TKKQz5h3a0O+C+mhY7vxU9b/stqvMb8beLnPY82ZrFTLKs+SrKA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.6.0.tgz", + "integrity": "sha512-ulGE6x6Oz6iAwg75T8YQSoguBWasniIbX+QWpaYPcCnDOpdWX3k+4xbEYPZVLxOuoJI+svJJPD3sEj8G7lrQ3A==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.6.0.tgz", + "integrity": "sha512-tkBYKt7YQrjIJWYDnto2YgO8MRkjlMTSNoRHzsXinBqbLdeOM3L32wPZJvIZxqaLMfSlS/4sUjH/6STVP/XDLw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.6.0.tgz", + "integrity": "sha512-gIZAP23jaHjGWasY/TY6yL7NHFClf0Ga7FN+iINvk+KN94rhm94lYZhFsbYFNcA04/onvGD9kKmiJLJB2HbNwQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.6.0.tgz", + "integrity": "sha512-cA+/pXV2YkfxlIcXOQ5fSWqAzzPyD78/x5qbK/I0vUkrlYHA8TIz+MXjAbGouguKVSI4bOmkTSJ1/poVSsgt+A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher/node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.0.tgz", + "integrity": "sha512-9yB1l95IrJuNGDFdOYe79vdApdz6WWBCObE+rQ2LUliYUlcyFwSYIb2xb5/Ifw7dAtMy2ZqNyd8QTSOc7duAKw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.0.tgz", + "integrity": "sha512-pexNaW9ACLUOaBITOpU6qVu4VrsOFIjTv6bzgu0YUATo4eUJx0V605PxwZfndpPOn0ilqGqvGQ0M8UW0IE24jg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.0.tgz", + "integrity": "sha512-NqKYaq0355ZmNMG4QGpxtEDxsc7tGDhjhCm4PpE0cwnBW+5Il95LJyq414niEiaKLVjnVHBEjSo1wngKxJNiFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.0.tgz", + "integrity": "sha512-3vPoHzh6eBTz9IbB0/qZdSr0Qeks2echn+I4cHu2joV74VriPDdldswksEDzrl1mBB+oPRi+67+3Ib59paxIPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.0.tgz", + "integrity": "sha512-E6NNefZ1bUVmKJq2tJkf45J4Zyczj7qm9rUT7NY+Xo2474Y13qWAwc2tvBt0BAVbmtXR1llkxXg0Ou1jbDf2SQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.0.tgz", + "integrity": "sha512-D+TgkdgM1vu+7/Fpf8+v0ARW+RXEP9Ccazgm8zQ4JFFd9Q7SrYQ2TakU5S5ihazQDgpKyAgZDOcIFsvoHmTZ8w==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.0.tgz", + "integrity": "sha512-wUqdwJBbAv0APN87GecstdMUtLjjNTs0hBALpxETD73mccFxdmt/XeizXDtN5RAlBwNKmI+Tg+blect2G+8IeQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.0.tgz", + "integrity": "sha512-9DtF35qR9/NrfhM4oxLplCzVVjE+KKm8Pjemi0i/sdhAWkUasjmSo8WTTubNJClhSHCfyk2yeyoXDQEDPtDAAw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.0.tgz", + "integrity": "sha512-RzuHrBh8X8Hntd2N4VR02QGEciq/9JhcZoTpR/Cee6otRrlILGCf3cg2ygHuih+ZebUnWmMrDX6ITI85btO6rQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.0.tgz", + "integrity": "sha512-MK7L0018jjh1jR3mh21G2j1zAVcpscJBlPo2z19pRjv2XOYGRhaV4LyiD8HO6nCDdZln9IFgCMIV5yt4E3klGQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.0.tgz", + "integrity": "sha512-gyrxLQ9NfGb/9LoVnC4kb9miUghw1mghnkfYvNHSnVIXriabnfgGPUP4RLcJm87q3KgYz4FYUG8IDiWUT+CpSw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.0.tgz", + "integrity": "sha512-/6VFMQGRmrhP77KXDC+StIxGzcNp5JOIyYtw0CQ8gPlzhpiIRucYfoM5FaFamHd5BJYIdH86yfP46l1p3WdrFA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.2.0.tgz", + "integrity": "sha512-rwdbUL465kisF24WEJLvP3JrEG6E5GRuIHt5wpMwHGERtHe4Wm2CIvtf5gTBgr2tGOHKh5NdKEAFS2VkOPE91g==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.2", + "@emnapi/runtime": "1.11.2", + "@napi-rs/wasm-runtime": "^1.1.6" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.0.tgz", + "integrity": "sha512-+5suHwRiKGmhwyUaNT8a5QbrBvLFh2DbO910TEmGRH1aSxwrCezodvGQnulv4uiWEIv1Kq4ypRsJ5+O+ry1DiA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.0.tgz", + "integrity": "sha512-WfFv6/qGufotqBSBzBYwgpCkJBk8Nj7697LL9vTz/XWc67e0r3oewu8iMRwQj3AUL45GVD7wVsPjCsAAtW66Wg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@schematics/angular": { + "version": "22.1.2", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-22.1.2.tgz", + "integrity": "sha512-52udja/QGSNH5geSnL4JWFOEfx8M7tqf7LNXz8byjki4VshVOkKHTawQcL4YbJfo3MfwwXm4AsoadMOk8EST2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "22.1.2", + "@angular-devkit/schematics": "22.1.2", + "jsonc-parser": "3.3.1", + "typescript": "6.0.3" + }, + "engines": { + "node": "^22.22.3 || ^24.15.0 || >=26.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/gensync": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/gensync/-/gensync-1.0.5.tgz", + "integrity": "sha512-MbsRCT7mTikHwKZ0X+LVUTLRrZZRLipTuXEO9qOYO+zmjMVk81axyClMROf6uoPD9MRVu46bx8zoR0Ad9q3NAg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/jsesc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@types/jsesc/-/jsesc-2.5.1.tgz", + "integrity": "sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitejs/plugin-basic-ssl": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-2.3.0.tgz", + "integrity": "sha512-bdyo8rB3NnQbikdMpHaML9Z1OZPBu6fFOBo+OtxsBlvMJtysWskmBcnbIDhUqgC8tcxNv/a+BcV5U+2nQMm1OQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "peerDependencies": { + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", + "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", + "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.10", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/mocker/node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", + "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz", + "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.10", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz", + "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.10", + "@vitest/utils": "4.1.10", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot/node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", + "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", + "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.10", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/agent-base": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-9.0.0.tgz", + "integrity": "sha512-TQf59BsZnytt8GdJKLPfUZ54g/iaUL2OWDSFCCvMOhsHduDQxO8xC4PNeyIkVcA5KwL2phPSv0douC0fgWzmnA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.12", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.12.tgz", + "integrity": "sha512-r7WnVImvVCeFpf2DOXfy41aPWzeNg3H/A2X4dKmy1QL0MSyyk/e7z8ihJ3N6Nn2PsdhkVlqnEfnUE4a05P2aTA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/beasties": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/beasties/-/beasties-0.4.3.tgz", + "integrity": "sha512-fIIeLOcbAB/K1kb1HBVJoiq1alHL4RCYBSo5e7HzrNkkgMggXR1Vqt/Z9JWnkfe/qdCo66Ux3QRwZioAIBdWRA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "css-select": "^6.0.0", + "css-what": "^7.0.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "htmlparser2": "^10.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.49", + "postcss-media-query-parser": "^0.2.3", + "postcss-safe-parser": "^7.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "node_modules/body-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", + "on-finished": "^2.4.1", + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "node_modules/browserslist": { + "version": "4.28.7", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.7.tgz", + "integrity": "sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.44", + "caniuse-lite": "^1.0.30001806", + "electron-to-chromium": "^1.5.393", + "node-releases": "^2.0.51", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001806", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz", + "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chardet": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.2.0.tgz", + "integrity": "sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==", + "dev": true, + "license": "MIT" + }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-3.4.0.tgz", + "integrity": "sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz", + "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^8.0.0", + "string-width": "^8.2.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/cliui": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-select": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-6.0.0.tgz", + "integrity": "sha512-rZZVSLle8v0+EY8QAkDWrKhpgt6SA5OtHsgBnsj6ZaLb5dmDVOWUDtQitd9ydxxvEjhewNudS6eTVU7uOyzvXw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^7.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "nth-check": "^2.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css-what": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-7.0.0.tgz", + "integrity": "sha512-wD5oz5xibMOPHzy13CyGmogB3phdvcDaB5t0W/Nr5Z2O/agcB8YwOz6e2Lsp10pNDzBoDO9nVa3RGs/2BttpHQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssstyle": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-6.2.0.tgz", + "integrity": "sha512-Fm5NvhYathRnXNVndkUsCCuR63DCLVVwGOOwQw782coXFi5HhkXdu289l59HlXZBawsyNccXfWRYvLzcDCdDig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^5.0.1", + "@csstools/css-syntax-patches-for-csstree": "^1.0.28", + "css-tree": "^3.1.0", + "lru-cache": "^11.2.6" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.399", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.399.tgz", + "integrity": "sha512-lEcqhErbHjXRvd41rnWLpzbyU/IXfIYo7QwaFWmxGeLiLyY2TBCdHnWY88vB+p3ubnihRypDm66panXl7TylLA==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/empathic": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.1.tgz", + "integrity": "sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz", + "integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.0.tgz", + "integrity": "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.6.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.6.1.tgz", + "integrity": "sha512-0D493aP61w0TJ2A0wy27riRsO7FMQ7FK+KUHOKCSfPvYo0R55aiC6emCVgFUeShH0fq0ICPVzNcgoS+BsbXQCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "ip-address": "^10.2.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-string-truncated-width": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", + "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-string-width": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-3.0.2.tgz", + "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-string-truncated-width": "^3.0.2" + } + }, + "node_modules/fast-uri": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", + "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fast-wrap-ansi": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.2.tgz", + "integrity": "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-string-width": "^3.0.2" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hono": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.0.tgz", + "integrity": "sha512-jhunvfHWxd7J5EFfSgH4xsYJzSe/lfqbUCxiyyeaQasUsXeEHXtzVid+7EOGByc5JnFa23SSFL3Y2RV/z1T+eQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/hosted-git-info": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-10.1.1.tgz", + "integrity": "sha512-DeOnSPAvOndYKfw075gt8yZzQ7S2hNztw34zBTfhIzLhmBTswIBg5/y+pqu/VD5cYWm5goAFTusDmUEmKZ0PEQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^11.1.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/htmlparser2": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "entities": "^7.0.1" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-agent/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-9.1.0.tgz", + "integrity": "sha512-ag87y7cJJ9/3+GxFr8Oy4O5faDsGRGnBGsJj/YjOSsSx/5eadKLYTMPlzuR6obgoCDDm0abAAZitXXQkMOPSpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "9.0.0", + "debug": "^4.3.4", + "proxy-agent-negotiate": "1.1.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/immutable": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.9.tgz", + "integrity": "sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg==", + "dev": true, + "license": "MIT" + }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ip-address": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.4.0.tgz", + "integrity": "sha512-oSK96Grm3aP6OrS263xVxbNDGVL7rzBtYdpGqlDG8iQdoenDoTs/nkki+DflYbAEE8Xl6o5YxhxlrKvI3nqKXQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jose": { + "version": "6.2.8", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.8.tgz", + "integrity": "sha512-Bsdjwm3Qsd/P0jR+BHDe3LytDfY7WBq2HmCCLIwuVRHMuEC9ae7/R474GIUdF1NgCyZjzVo/A9DOiOBtXq8ZoQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsdom": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-28.1.0.tgz", + "integrity": "sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@acemir/cssom": "^0.9.31", + "@asamuzakjp/dom-selector": "^6.8.1", + "@bramus/specificity": "^2.4.2", + "@exodus/bytes": "^1.11.0", + "cssstyle": "^6.0.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "parse5": "^8.0.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.0", + "undici": "^7.21.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/jsdom/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/listr2": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-10.2.1.tgz", + "integrity": "sha512-7I5knELsJKTUjXG+A6BkKAiGkW1i25fNa/xlUl9hFtk15WbE9jndA89xu5FzQKrY5llajE1hfZZFMILXkDHk/Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "cli-truncate": "^5.2.0", + "eventemitter3": "^5.0.4", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^10.0.0" + }, + "engines": { + "node": ">=22.13.0" + } + }, + "node_modules/lmdb": { + "version": "3.5.6", + "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-3.5.6.tgz", + "integrity": "sha512-j3uE8ReKNyUWDjhfEFSJqE/1DLtfTR5Z8yFzVHvBjAk37wNg7HdScjcv8ttPHRvrdgPQMPWxFFI0SsdBzI5lBw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@harperfast/extended-iterable": "^1.0.3", + "msgpackr": "^1.11.2", + "node-addon-api": "^6.1.0", + "node-gyp-build-optional-packages": "5.2.2", + "ordered-binary": "^1.5.3", + "weak-lru-cache": "^1.2.2" + }, + "bin": { + "download-lmdb-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@lmdb/lmdb-darwin-arm64": "3.5.6", + "@lmdb/lmdb-darwin-x64": "3.5.6", + "@lmdb/lmdb-linux-arm": "3.5.6", + "@lmdb/lmdb-linux-arm64": "3.5.6", + "@lmdb/lmdb-linux-x64": "3.5.6", + "@lmdb/lmdb-win32-arm64": "3.5.6", + "@lmdb/lmdb-win32-x64": "3.5.6" + } + }, + "node_modules/log-symbols": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-7.0.1.tgz", + "integrity": "sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/magic-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.0.0.tgz", + "integrity": "sha512-CGvjzMN08iv6w1mm4/x3Gh1hLb4VnyRUA15FFpl6CsCIGGoe36k7kY5KNz9QDbSBN5I/fWHM6ZlIkUTa5xdUEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/material-icons": { + "version": "1.13.14", + "resolved": "https://registry.npmjs.org/material-icons/-/material-icons-1.13.14.tgz", + "integrity": "sha512-kZOfc7xCC0rAT8Q3DQixYAeT+tBqZnxkseQtp2bxBxz7q5pMAC+wmit7vJn1g/l7wRU+HEPq23gER4iPjGs5Cg==", + "license": "Apache-2.0" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/media-typer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/msgpackr": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.12.1.tgz", + "integrity": "sha512-4EUH9tQHnMmEgzW/MdAP0KIfa1T9AF+htl0ffe2n5vb2EKn9y2co8ccpgWko6S52Jy1PQZKwRnx5/KkYjtd9MQ==", + "dev": true, + "license": "MIT", + "optional": true, + "optionalDependencies": { + "msgpackr-extract": "^3.0.2" + } + }, + "node_modules/msgpackr-extract": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.4.tgz", + "integrity": "sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-gyp-build-optional-packages": "5.2.2" + }, + "bin": { + "download-msgpackr-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.4", + "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.4", + "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.4", + "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.4", + "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.4", + "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.4" + } + }, + "node_modules/mute-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-3.0.0.tgz", + "integrity": "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.17", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.17.tgz", + "integrity": "sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-addon-api": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/node-gyp-build-optional-packages": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", + "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.1" + }, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, + "node_modules/node-releases": { + "version": "2.0.51", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/npm-package-arg": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-14.0.0.tgz", + "integrity": "sha512-69XQh3k+dtGa1p+7RaR57IuG3rCko96xr/nUfN4yDYBXbTYICiWcOpsFKLN2GtGE9cyIljE+f1exnaYt9MvM+Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^10.1.0", + "proc-log": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^8.0.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obug": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", + "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-9.4.1.tgz", + "integrity": "sha512-6VlU9MLXbjVQD04AZCMX28hVtA5bUoadvUqO76MUCVA0ilwJbMiHsITRPfyVm6p/BC0Av/BXMujx39WCe1LEqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.6.2", + "cli-cursor": "^5.0.0", + "cli-spinners": "^3.2.0", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.1.0", + "log-symbols": "^7.0.1", + "stdin-discarder": "^0.3.2", + "string-width": "^8.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ordered-binary": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.6.1.tgz", + "integrity": "sha512-QkCdPooczexPLiXIrbVOPYkR3VO3T6v2OyKRkR1Xbhpy7/LAVXwahnRCgRp78Oe/Ehf0C/HATAxfSr6eA1oX+w==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/oxc-parser": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.142.0.tgz", + "integrity": "sha512-kKR+jPiRJYJDexVoziIg/FVGvr1fT1FZSSJOk6tVoMKKSlsf1Cso+cgGCJkOEDWOP174vRntCPFKg+AS7InWvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "^0.142.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxc-parser/binding-android-arm-eabi": "0.142.0", + "@oxc-parser/binding-android-arm64": "0.142.0", + "@oxc-parser/binding-darwin-arm64": "0.142.0", + "@oxc-parser/binding-darwin-x64": "0.142.0", + "@oxc-parser/binding-freebsd-x64": "0.142.0", + "@oxc-parser/binding-linux-arm-gnueabihf": "0.142.0", + "@oxc-parser/binding-linux-arm-musleabihf": "0.142.0", + "@oxc-parser/binding-linux-arm64-gnu": "0.142.0", + "@oxc-parser/binding-linux-arm64-musl": "0.142.0", + "@oxc-parser/binding-linux-ppc64-gnu": "0.142.0", + "@oxc-parser/binding-linux-riscv64-gnu": "0.142.0", + "@oxc-parser/binding-linux-riscv64-musl": "0.142.0", + "@oxc-parser/binding-linux-s390x-gnu": "0.142.0", + "@oxc-parser/binding-linux-x64-gnu": "0.142.0", + "@oxc-parser/binding-linux-x64-musl": "0.142.0", + "@oxc-parser/binding-openharmony-arm64": "0.142.0", + "@oxc-parser/binding-wasm32-wasi": "0.142.0", + "@oxc-parser/binding-win32-arm64-msvc": "0.142.0", + "@oxc-parser/binding-win32-ia32-msvc": "0.142.0", + "@oxc-parser/binding-win32-x64-msvc": "0.142.0" + } + }, + "node_modules/parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", + "license": "MIT", + "dependencies": { + "entities": "^8.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-html-rewriting-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-8.0.1.tgz", + "integrity": "sha512-NaRku2aMpUN1Sh1Gyk1KWUh2A7EJx2c6qYzvwsPtqhoHoaURshdrceYK3LunVCm3WHhm6FS7Vcczbvdh3/UIVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^8.0.0", + "parse5": "^8.0.0", + "parse5-sax-parser": "^8.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-html-rewriting-stream/node_modules/entities": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/parse5-sax-parser": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-8.0.0.tgz", + "integrity": "sha512-/dQ8UzHZwnrzs3EvDj6IkKrD/jIZyTlB+8XrHJvcjNgRdmWruNdN9i9RK/JtxakmlUdPwKubKPTCqvbTgzGhrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse5": "^8.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/piscina": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-5.2.0.tgz", + "integrity": "sha512-DszUCKeVN/5G5QKo6jAVHL8fmKnkJvQ0ACiVgY7YGCq3TUB2oznAOayvZPIAdEThvhczkXR+qm3IHsNXpFCYfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.x" + }, + "optionalDependencies": { + "@napi-rs/nice": "^1.0.4" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/postcss": { + "version": "8.5.25", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz", + "integrity": "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.16", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-media-query-parser": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", + "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", + "dev": true, + "license": "MIT" + }, + "node_modules/postcss-safe-parser": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz", + "integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-safe-parser" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/prettier": { + "version": "3.9.6", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz", + "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/proc-log": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-7.0.0.tgz", + "integrity": "sha512-FYgfaA69XZ93zaXLoMNQ+ViDXGGBgR8aLh03txzcFhV+9xOXx7+8DLCULrKKpR9+GsH9ZfHm82aSUPpozX0Ztg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-agent-negotiate": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-agent-negotiate/-/proxy-agent-negotiate-1.1.0.tgz", + "integrity": "sha512-N8IBcM3UgCVzz2L2Lqv8DVntDnnC8/hiV4nEDUPkqq72TPUgYWjQc+bdZlBPZK9LzPAvOY//gAt0S0DApoOXWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "kerberos": "^2.0.0" + }, + "peerDependenciesMeta": { + "kerberos": { + "optional": true + } + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.15.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/rolldown": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.0.tgz", + "integrity": "sha512-u7tgm5l4Yw1iTqUL4EcYOAt7fFvCgQMLeidrnD4GALlC6aOznCjezYajgxeyKw27u0Q5N7fwgCzjVyPIWzwuBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.140.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.2.0", + "@rolldown/binding-darwin-arm64": "1.2.0", + "@rolldown/binding-darwin-x64": "1.2.0", + "@rolldown/binding-freebsd-x64": "1.2.0", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.0", + "@rolldown/binding-linux-arm64-gnu": "1.2.0", + "@rolldown/binding-linux-arm64-musl": "1.2.0", + "@rolldown/binding-linux-ppc64-gnu": "1.2.0", + "@rolldown/binding-linux-s390x-gnu": "1.2.0", + "@rolldown/binding-linux-x64-gnu": "1.2.0", + "@rolldown/binding-linux-x64-musl": "1.2.0", + "@rolldown/binding-openharmony-arm64": "1.2.0", + "@rolldown/binding-wasm32-wasi": "1.2.0", + "@rolldown/binding-win32-arm64-msvc": "1.2.0", + "@rolldown/binding-win32-x64-msvc": "1.2.0" + } + }, + "node_modules/rolldown/node_modules/@oxc-project/types": { + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.140.0.tgz", + "integrity": "sha512-h5LUOzGArYemnW1NMz/DuuQhBi96J6JL2Bk8zE4kvqxB5Sg3jxmCiH4uyOWHDkiKSt5vWlG4FIwCR/DbstcNRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/sass": { + "version": "1.101.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.101.0.tgz", + "integrity": "sha512-OL3GoQyoUdDt843DpVmDO6y2k1sc5IhUDSpu8XucEI+35neq5QivZ1iuegnpraEVTJXlQGK1gl27zKcTLEPbQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^5.0.0", + "immutable": "^5.1.5", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=20.19.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slice-ansi": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz", + "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.3", + "is-fullwidth-code-point": "^5.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/std-env": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", + "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", + "dev": true, + "license": "MIT" + }, + "node_modules/stdin-discarder": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.3.2.tgz", + "integrity": "sha512-eCPu1qRxPVkl5605OTWF8Wz40b4Mf45NY5LQmVPQ599knfs5QhASUm9GbJ5BDMDOXgrnh0wyEdvzmL//YMlw0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz", + "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", + "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.1.tgz", + "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "7.4.10", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.10.tgz", + "integrity": "sha512-GgouD1B+sWwvkaEq8vXC15DjQitxbvs12oIXELpconwm+Tg3zfcEv4jgzq3vtKverDXsg3VI8aRgNL2Nra0Iog==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.4.10" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.4.10", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.10.tgz", + "integrity": "sha512-KnQjp53ZekKgm/r3l+u8kJGGzYgrWdP8+Mql7a4vijh2WE0IrZWspQj/TpTxDho/YxO+AnOZnIjQcCD+q6iJsw==", + "dev": true, + "license": "MIT" + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tough-cookie": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.2.tgz", + "integrity": "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "dev": true, + "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz", + "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/validate-npm-package-name": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-8.0.0.tgz", + "integrity": "sha512-SCv6OOV6Xj2/3cXy3dGmADluJTNcL3o7hZAglNPTe+WYuEuvxgJzxPrSDLZhF+CwyQOubqgecjMmTJGMVLWjYQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.5.tgz", + "integrity": "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.17", + "rolldown": "~1.1.5", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.3.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/vite/node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/vite/node_modules/@oxc-project/types": { + "version": "0.139.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz", + "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/vite/node_modules/@rolldown/binding-android-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz", + "integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/vite/node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz", + "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/vite/node_modules/@rolldown/binding-darwin-x64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz", + "integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/vite/node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz", + "integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/vite/node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz", + "integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/vite/node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz", + "integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/vite/node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz", + "integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/vite/node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz", + "integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/vite/node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz", + "integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/vite/node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz", + "integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/vite/node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz", + "integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/vite/node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz", + "integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/vite/node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz", + "integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.6" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/vite/node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz", + "integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/vite/node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz", + "integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/vite/node_modules/rolldown": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz", + "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.139.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.1.5", + "@rolldown/binding-darwin-arm64": "1.1.5", + "@rolldown/binding-darwin-x64": "1.1.5", + "@rolldown/binding-freebsd-x64": "1.1.5", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", + "@rolldown/binding-linux-arm64-gnu": "1.1.5", + "@rolldown/binding-linux-arm64-musl": "1.1.5", + "@rolldown/binding-linux-ppc64-gnu": "1.1.5", + "@rolldown/binding-linux-s390x-gnu": "1.1.5", + "@rolldown/binding-linux-x64-gnu": "1.1.5", + "@rolldown/binding-linux-x64-musl": "1.1.5", + "@rolldown/binding-openharmony-arm64": "1.1.5", + "@rolldown/binding-wasm32-wasi": "1.1.5", + "@rolldown/binding-win32-arm64-msvc": "1.1.5", + "@rolldown/binding-win32-x64-msvc": "1.1.5" + } + }, + "node_modules/vitest": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz", + "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.10", + "@vitest/mocker": "4.1.10", + "@vitest/pretty-format": "4.1.10", + "@vitest/runner": "4.1.10", + "@vitest/snapshot": "4.1.10", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.10", + "@vitest/browser-preview": "4.1.10", + "@vitest/browser-webdriverio": "4.1.10", + "@vitest/coverage-istanbul": "4.1.10", + "@vitest/coverage-v8": "4.1.10", + "@vitest/ui": "4.1.10", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/vitest/node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/watchpack": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.2.tgz", + "integrity": "sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/weak-lru-cache": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz", + "integrity": "sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-10.0.0.tgz", + "integrity": "sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.3", + "string-width": "^8.2.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", + "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^9.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "string-width": "^7.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^22.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.2.0.tgz", + "integrity": "sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..238c352 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,42 @@ +{ + "name": "ludos-web", + "version": "0.0.0", + "scripts": { + "ng": "ng", + "start": "ng serve", + "build": "ng build", + "watch": "ng build --watch --configuration development", + "test": "ng test", + "build:prod": "ng build --configuration production", + "lint": "ng lint", + "format": "prettier --write \"src/**/*.{ts,html,scss}\"" + }, + "private": true, + "packageManager": "npm@11.16.0", + "dependencies": { + "@angular/cdk": "^22.1.0", + "@angular/common": "^22.1.0", + "@angular/compiler": "^22.1.0", + "@angular/core": "^22.1.0", + "@angular/forms": "^22.1.0", + "@angular/material": "^22.1.0", + "@angular/platform-browser": "^22.1.0", + "@angular/router": "^22.1.0", + "@fontsource/roboto": "^5.3.0", + "material-icons": "^1.13.14", + "rxjs": "~7.8.0", + "tslib": "^2.3.0" + }, + "devDependencies": { + "@angular/build": "^22.1.2", + "@angular/cli": "^22.1.2", + "@angular/compiler-cli": "^22.1.0", + "jsdom": "^28.0.0", + "prettier": "^3.8.1", + "typescript": "~6.0.2", + "vitest": "^4.0.8" + }, + "engines": { + "node": ">=24 <25" + } +} diff --git a/frontend/proxy.conf.json b/frontend/proxy.conf.json new file mode 100644 index 0000000..08cc3a5 --- /dev/null +++ b/frontend/proxy.conf.json @@ -0,0 +1,12 @@ +{ + "/api": { + "target": "http://localhost:5099", + "secure": false, + "changeOrigin": true + }, + "/uploads": { + "target": "http://localhost:5099", + "secure": false, + "changeOrigin": true + } +} diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico new file mode 100644 index 0000000..57614f9 Binary files /dev/null and b/frontend/public/favicon.ico differ diff --git a/frontend/security-headers.conf b/frontend/security-headers.conf new file mode 100644 index 0000000..53861fd --- /dev/null +++ b/frontend/security-headers.conf @@ -0,0 +1,15 @@ +# Included into every location that sets its own add_header. +# +# nginx only inherits add_header directives into a nested block when that block +# declares none of its own. A location that sets Cache-Control therefore silently +# drops everything defined at server level, so these are included explicitly +# rather than relying on inheritance. + +add_header X-Content-Type-Options nosniff always; +add_header X-Frame-Options DENY always; +add_header Referrer-Policy strict-origin-when-cross-origin always; + +# The app loads no third-party scripts, styles, fonts or images: Angular bundles +# everything and the API is same-origin. 'unsafe-inline' for styles is required +# by Angular Material's runtime style injection. +add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'" always; diff --git a/frontend/src/app/app.config.ts b/frontend/src/app/app.config.ts new file mode 100644 index 0000000..e327d0f --- /dev/null +++ b/frontend/src/app/app.config.ts @@ -0,0 +1,23 @@ +import { provideHttpClient, withInterceptors } from '@angular/common/http'; +import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core'; +import { provideRouter, withComponentInputBinding, withInMemoryScrolling } from '@angular/router'; + +import { authInterceptor } from './core/auth.interceptor'; +import { routes } from './app.routes'; + +export const appConfig: ApplicationConfig = { + providers: [ + provideBrowserGlobalErrorListeners(), + provideRouter( + routes, + // Lets a component read route params via input() instead of injecting + // ActivatedRoute and reading a snapshot. + withComponentInputBinding(), + withInMemoryScrolling({ scrollPositionRestoration: 'top' }), + ), + provideHttpClient(withInterceptors([authInterceptor])), + // No provideAnimations here: @angular/animations is deprecated as of v22 and + // Angular Material 22 no longer depends on it. Component motion now comes + // from CSS, with animate.enter / animate.leave for element transitions. + ], +}; diff --git a/frontend/src/app/app.routes.ts b/frontend/src/app/app.routes.ts new file mode 100644 index 0000000..70be428 --- /dev/null +++ b/frontend/src/app/app.routes.ts @@ -0,0 +1,50 @@ +import { Routes } from '@angular/router'; + +import { authGuard, guestGuard } from './core/auth.guard'; + +/** + * Every feature is lazily loaded, so the login page does not ship the editor's + * code. In 2018 this was a single eager NgModule containing everything. + */ +export const routes: Routes = [ + { path: '', pathMatch: 'full', redirectTo: 'games' }, + + { + path: 'login', + canActivate: [guestGuard], + title: 'Sign in · LudosData', + loadComponent: () => import('./features/login/login').then((m) => m.Login), + }, + { + path: 'register', + canActivate: [guestGuard], + title: 'Create account · LudosData', + loadComponent: () => import('./features/register/register').then((m) => m.Register), + }, + { + path: 'games', + canActivate: [authGuard], + title: 'Library · LudosData', + loadComponent: () => import('./features/game-grid/game-grid').then((m) => m.GameGrid), + }, + { + path: 'games/new', + canActivate: [authGuard], + title: 'New game · LudosData', + loadComponent: () => import('./features/game-edit/game-edit').then((m) => m.GameEdit), + }, + { + path: 'games/:id', + canActivate: [authGuard], + title: 'Edit game · LudosData', + loadComponent: () => import('./features/game-edit/game-edit').then((m) => m.GameEdit), + }, + { + path: 'account', + canActivate: [authGuard], + title: 'Account · LudosData', + loadComponent: () => import('./features/account/account').then((m) => m.Account), + }, + + { path: '**', redirectTo: 'games' }, +]; diff --git a/frontend/src/app/app.ts b/frontend/src/app/app.ts new file mode 100644 index 0000000..2a070fc --- /dev/null +++ b/frontend/src/app/app.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; +import { RouterOutlet } from '@angular/router'; + +@Component({ + selector: 'app-root', + imports: [RouterOutlet], + template: '', +}) +export class App {} diff --git a/frontend/src/app/core/auth.guard.ts b/frontend/src/app/core/auth.guard.ts new file mode 100644 index 0000000..4588780 --- /dev/null +++ b/frontend/src/app/core/auth.guard.ts @@ -0,0 +1,29 @@ +import { inject } from '@angular/core'; +import { CanActivateFn, Router } from '@angular/router'; + +import { AuthService } from './auth.service'; + +/** + * Functional route guard — the modern replacement for the class-based + * `AuthGuard implements CanActivate` this app used in 2018. + */ +export const authGuard: CanActivateFn = (_route, state) => { + const auth = inject(AuthService); + const router = inject(Router); + + if (auth.isLoggedIn()) { + return true; + } + + return router.createUrlTree(['/login'], { + queryParams: { returnUrl: state.url }, + }); +}; + +/** Keeps an already-signed-in user off the login and register pages. */ +export const guestGuard: CanActivateFn = () => { + const auth = inject(AuthService); + const router = inject(Router); + + return auth.isLoggedIn() ? router.createUrlTree(['/games']) : true; +}; diff --git a/frontend/src/app/core/auth.interceptor.spec.ts b/frontend/src/app/core/auth.interceptor.spec.ts new file mode 100644 index 0000000..b6ef568 --- /dev/null +++ b/frontend/src/app/core/auth.interceptor.spec.ts @@ -0,0 +1,99 @@ +import { HttpClient, provideHttpClient, withInterceptors } from '@angular/common/http'; +import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; +import { TestBed } from '@angular/core/testing'; +import { provideRouter } from '@angular/router'; +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; + +import { AuthService } from './auth.service'; +import { authInterceptor } from './auth.interceptor'; + +describe('authInterceptor', () => { + let http: HttpClient; + let httpMock: HttpTestingController; + let auth: AuthService; + + beforeEach(() => { + localStorage.clear(); + + TestBed.configureTestingModule({ + providers: [ + provideRouter([{ path: 'login', children: [] }]), + provideHttpClient(withInterceptors([authInterceptor])), + provideHttpClientTesting(), + ], + }); + + http = TestBed.inject(HttpClient); + httpMock = TestBed.inject(HttpTestingController); + auth = TestBed.inject(AuthService); + }); + + afterEach(() => { + httpMock.verify(); + localStorage.clear(); + }); + + /** Signs in through AuthService, which is what actually stores the session. */ + function signIn(): void { + auth.login('ckoch', 'pw').subscribe(); + httpMock.expectOne('/api/auth/login').flush({ + token: 'test-token', + expiresAt: new Date(Date.now() + 3_600_000).toISOString(), + user: { id: 'u1', userName: 'ckoch', email: null, firstName: null, lastName: null, art: null }, + }); + } + + it('sends the token as a bearer header, never in the query string', () => { + signIn(); + + http.get('/api/games').subscribe(); + const req = httpMock.expectOne((r) => r.url === '/api/games'); + + expect(req.request.headers.get('Authorization')).toBe('Bearer test-token'); + // The 2018 client appended ?token=... which leaks into logs and history. + expect(req.request.urlWithParams).not.toContain('token'); + req.flush({ items: [], page: 1, pageSize: 20, total: 0, totalPages: 0 }); + }); + + it('does not attach the token to non-API requests', () => { + signIn(); + + http.get('/assets/config.json').subscribe(); + const req = httpMock.expectOne('/assets/config.json'); + + expect(req.request.headers.has('Authorization')).toBe(false); + req.flush({}); + }); + + it('clears the stored session when the API rejects the token', () => { + signIn(); + expect(auth.isLoggedIn()).toBe(true); + + http.get('/api/games').subscribe({ error: () => undefined }); + httpMock + .expectOne('/api/games') + .flush({ title: 'Unauthorized' }, { status: 401, statusText: 'Unauthorized' }); + + expect(auth.isLoggedIn()).toBe(false); + expect(auth.token()).toBeNull(); + }); + + it('treats an expired stored session as signed out', () => { + localStorage.setItem( + 'ludos.session', + JSON.stringify({ + token: 'stale', + expiresAt: new Date(Date.now() - 1000).toISOString(), + user: { id: 'u1', userName: 'ckoch' }, + }), + ); + + // A fresh injector picks the value up from storage on construction. + TestBed.resetTestingModule(); + TestBed.configureTestingModule({ + providers: [provideRouter([{ path: 'login', children: [] }]), provideHttpClient(), provideHttpClientTesting()], + }); + + expect(TestBed.inject(AuthService).isLoggedIn()).toBe(false); + }); +}); diff --git a/frontend/src/app/core/auth.interceptor.ts b/frontend/src/app/core/auth.interceptor.ts new file mode 100644 index 0000000..f60b1c2 --- /dev/null +++ b/frontend/src/app/core/auth.interceptor.ts @@ -0,0 +1,41 @@ +import { HttpErrorResponse, HttpInterceptorFn } from '@angular/common/http'; +import { inject } from '@angular/core'; +import { Router } from '@angular/router'; +import { catchError, throwError } from 'rxjs'; + +import { AuthService } from './auth.service'; + +/** + * Attaches the bearer token to same-origin API calls and turns a 401 into a + * redirect back to the login form. + * + * The old client put the token in the query string (`?token=...`), which leaks + * it into server logs, browser history and Referer headers. It belongs in the + * Authorization header. + */ +export const authInterceptor: HttpInterceptorFn = (req, next) => { + const auth = inject(AuthService); + const router = inject(Router); + + const token = auth.token(); + const isApiCall = req.url.startsWith('/api/'); + + const request = + token && isApiCall + ? req.clone({ setHeaders: { Authorization: `Bearer ${token}` } }) + : req; + + return next(request).pipe( + catchError((error: unknown) => { + if (error instanceof HttpErrorResponse && error.status === 401 && isApiCall) { + // Expired or rejected token: drop it and bounce to login, remembering + // where the user was trying to go. + auth.logout(); + void router.navigate(['/login'], { + queryParams: { returnUrl: router.url }, + }); + } + return throwError(() => error); + }), + ); +}; diff --git a/frontend/src/app/core/auth.service.ts b/frontend/src/app/core/auth.service.ts new file mode 100644 index 0000000..e68abf0 --- /dev/null +++ b/frontend/src/app/core/auth.service.ts @@ -0,0 +1,92 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable, computed, inject, signal } from '@angular/core'; +import { Observable, tap } from 'rxjs'; + +import { AuthResponse, User } from './models'; + +interface StoredSession { + token: string; + expiresAt: string; + user: User; +} + +const STORAGE_KEY = 'ludos.session'; + +@Injectable({ providedIn: 'root' }) +export class AuthService { + private readonly http = inject(HttpClient); + + private readonly session = signal(readStoredSession()); + + readonly user = computed(() => this.session()?.user ?? null); + readonly token = computed(() => this.session()?.token ?? null); + + /** + * True only while a stored token is present and unexpired. Checking expiry + * here means a stale token sends the user to the login form rather than + * producing a wall of 401s. + */ + readonly isLoggedIn = computed(() => { + const current = this.session(); + if (!current) { + return false; + } + return new Date(current.expiresAt).getTime() > Date.now(); + }); + + login(userName: string, password: string): Observable { + return this.http + .post('/api/auth/login', { userName, password }) + .pipe(tap((response) => this.store(response))); + } + + register(payload: { + userName: string; + email: string; + password: string; + firstName?: string; + lastName?: string; + }): Observable { + return this.http + .post('/api/auth/register', payload) + .pipe(tap((response) => this.store(response))); + } + + /** Availability check for the registration form; returns true when free. */ + isAvailable(field: 'userName' | 'email', value: string): Observable<{ available: boolean }> { + return this.http.get<{ available: boolean }>('/api/auth/available', { + params: { [field]: value }, + }); + } + + logout(): void { + localStorage.removeItem(STORAGE_KEY); + this.session.set(null); + } + + private store(response: AuthResponse): void { + const stored: StoredSession = { + token: response.token, + expiresAt: response.expiresAt, + user: response.user, + }; + localStorage.setItem(STORAGE_KEY, JSON.stringify(stored)); + this.session.set(stored); + } +} + +function readStoredSession(): StoredSession | null { + const raw = localStorage.getItem(STORAGE_KEY); + if (!raw) { + return null; + } + + try { + const parsed = JSON.parse(raw) as StoredSession; + return parsed?.token && parsed?.user ? parsed : null; + } catch { + // Corrupt or hand-edited storage should not wedge startup. + localStorage.removeItem(STORAGE_KEY); + return null; + } +} diff --git a/frontend/src/app/core/games.service.ts b/frontend/src/app/core/games.service.ts new file mode 100644 index 0000000..0575d9d --- /dev/null +++ b/frontend/src/app/core/games.service.ts @@ -0,0 +1,53 @@ +import { HttpClient, HttpParams } from '@angular/common/http'; +import { Injectable, inject } from '@angular/core'; +import { Observable } from 'rxjs'; + +import { Facets, Game, GameQuery, GameRequest, PagedResult, UploadResponse } from './models'; + +@Injectable({ providedIn: 'root' }) +export class GamesService { + private readonly http = inject(HttpClient); + + /** + * Note what is absent: the caller never passes a user id. Ownership is taken + * from the bearer token server-side, so there is no filter for a client to + * tamper with the way there was in the old php-crud-api query string. + */ + list(query: GameQuery): Observable> { + let params = new HttpParams(); + + for (const [key, value] of Object.entries(query)) { + if (value !== undefined && value !== null && value !== '') { + params = params.set(key, String(value)); + } + } + + return this.http.get>('/api/games', { params }); + } + + get(id: number): Observable { + return this.http.get(`/api/games/${id}`); + } + + facets(): Observable { + return this.http.get('/api/games/facets'); + } + + create(game: GameRequest): Observable { + return this.http.post('/api/games', game); + } + + update(id: number, game: GameRequest): Observable { + return this.http.put(`/api/games/${id}`, game); + } + + remove(id: number): Observable { + return this.http.delete(`/api/games/${id}`); + } + + uploadArt(file: File): Observable { + const form = new FormData(); + form.append('file', file, file.name); + return this.http.post('/api/images', form); + } +} diff --git a/frontend/src/app/core/models.ts b/frontend/src/app/core/models.ts new file mode 100644 index 0000000..c10f83f --- /dev/null +++ b/frontend/src/app/core/models.ts @@ -0,0 +1,108 @@ +/** Mirrors the API contracts in backend/src/LudosData.Api/Contracts. */ + +export interface Game { + id: number; + title: string; + system: string | null; + genre: string | null; + year: string | null; + developer: string | null; + publisher: string | null; + /** Stored filename. */ + art: string | null; + /** Ready-to-use URL, built by the API. Null when no art was uploaded. */ + artUrl: string | null; + description: string | null; + own: boolean; + dumped: boolean; + played: boolean; + finished: boolean; + createdAt: string; + updatedAt: string; +} + +/** Create/update payload. No id and no owner — the API derives both. */ +export interface GameRequest { + title: string; + system: string | null; + genre: string | null; + year: string | null; + developer: string | null; + publisher: string | null; + art: string | null; + description: string | null; + own: boolean; + dumped: boolean; + played: boolean; + finished: boolean; +} + +export interface PagedResult { + items: T[]; + page: number; + pageSize: number; + total: number; + totalPages: number; +} + +export interface GameQuery { + search?: string; + system?: string; + genre?: string; + own?: boolean; + dumped?: boolean; + played?: boolean; + finished?: boolean; + page?: number; + pageSize?: number; + sort?: string; + dir?: 'asc' | 'desc'; +} + +export interface Facets { + systems: string[]; + genres: string[]; +} + +export interface User { + id: string; + userName: string; + email: string | null; + firstName: string | null; + lastName: string | null; + art: string | null; +} + +export interface AuthResponse { + token: string; + expiresAt: string; + user: User; +} + +export interface UploadResponse { + fileName: string; + url: string; +} + +/** RFC 7807 body returned by the API for validation failures. */ +export interface ProblemDetails { + title?: string; + detail?: string; + status?: number; + errors?: Record; +} + +export const EMPTY_GAME: GameRequest = { + title: '', + system: null, + genre: null, + year: null, + developer: null, + publisher: null, + art: null, + description: null, + own: true, + dumped: false, + played: false, + finished: false, +}; diff --git a/frontend/src/app/features/account/account.ts b/frontend/src/app/features/account/account.ts new file mode 100644 index 0000000..61b247b --- /dev/null +++ b/frontend/src/app/features/account/account.ts @@ -0,0 +1,78 @@ +import { Component, inject } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatCardModule } from '@angular/material/card'; +import { MatIconModule } from '@angular/material/icon'; +import { MatListModule } from '@angular/material/list'; +import { Router, RouterLink } from '@angular/router'; + +import { AuthService } from '../../core/auth.service'; +import { Toolbar } from '../../shared/toolbar'; + +@Component({ + selector: 'app-account', + imports: [Toolbar, RouterLink, MatCardModule, MatIconModule, MatButtonModule, MatListModule], + template: ` + + +
+ + + Account + + + + @if (user(); as currentUser) { + + + person +
{{ currentUser.userName }}
+
Username
+
+ + @if (currentUser.email) { + + mail +
{{ currentUser.email }}
+
Email
+
+ } + + @if (currentUser.firstName || currentUser.lastName) { + + badge +
+ {{ currentUser.firstName }} {{ currentUser.lastName }} +
+
Name
+
+ } +
+ } +
+ + + Back to library + + +
+
+ `, + styles: ` + .page { + max-width: 40rem; + margin-inline: auto; + padding: 1.5rem; + } + `, +}) +export class Account { + private readonly auth = inject(AuthService); + private readonly router = inject(Router); + + readonly user = this.auth.user; + + logout(): void { + this.auth.logout(); + void this.router.navigate(['/login']); + } +} diff --git a/frontend/src/app/features/game-edit/game-edit.html b/frontend/src/app/features/game-edit/game-edit.html new file mode 100644 index 0000000..380784d --- /dev/null +++ b/frontend/src/app/features/game-edit/game-edit.html @@ -0,0 +1,137 @@ + + +@if (loading() || saving()) { + +} + +@if (loadFailed()) { +
+ error_outline +

Game not found

+

It may have been removed, or it belongs to another account.

+ Back to library +
+} @else { +
+ + +
+ @if (artPreview(); as preview) { + Box art preview + } @else { +
+ image + No box art +
+ } + + @if (uploading()) { +
+ } +
+ + + +
+ + + @if (artPreview()) { + + } +
+
+ + + +

{{ isEdit() ? 'Edit game' : 'New game' }}

+ + + Title + + @if (form.controls.title.touched && form.controls.title.invalid) { + Title is required + } + + +
+ + System + + — + @for (option of systems; track option) { + {{ option }} + } + + + + + Genre + + — + @for (option of genres; track option) { + {{ option }} + } + + + + + Year + + +
+ +
+ + Developer + + + + + Publisher + + +
+ + + Description + + + +
+ Collection status + Own + Dumped + Played + Finished +
+ +
+ Cancel + + @if (isEdit()) { + + } + + + + +
+
+
+} diff --git a/frontend/src/app/features/game-edit/game-edit.scss b/frontend/src/app/features/game-edit/game-edit.scss new file mode 100644 index 0000000..4b2646a --- /dev/null +++ b/frontend/src/app/features/game-edit/game-edit.scss @@ -0,0 +1,151 @@ +.editor { + display: grid; + grid-template-columns: minmax(14rem, 20rem) minmax(0, 1fr); + gap: 1.5rem; + align-items: start; + padding: 1.5rem; + max-width: 68rem; + margin-inline: auto; + + @media (max-width: 899px) { + grid-template-columns: 1fr; + padding: 1rem; + gap: 1rem; + } +} + +.art-panel { + padding: 1rem; + position: sticky; + top: 5rem; + + @media (max-width: 899px) { + position: static; + } +} + +.art-frame { + position: relative; + aspect-ratio: 3 / 4; + border-radius: 0.5rem; + overflow: hidden; + background: var(--mat-sys-surface-container-high); + + img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; + } +} + +.art-placeholder { + display: grid; + place-items: center; + align-content: center; + gap: 0.5rem; + height: 100%; + color: var(--mat-sys-outline); + + mat-icon { + font-size: 3rem; + width: 3rem; + height: 3rem; + } +} + +.art-overlay { + position: absolute; + inset: auto 0 0 0; +} + +.art-actions { + display: flex; + align-items: center; + gap: 0.5rem; + margin-top: 0.75rem; +} + +.fields-panel { + display: flex; + flex-direction: column; + gap: 0.25rem; + padding: 1.5rem; + + @media (max-width: 599px) { + padding: 1rem; + } +} + +.editor-title { + margin: 0 0 1rem; + font-size: 1.25rem; + font-weight: 500; +} + +.field-row { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; + + mat-form-field { + flex: 1 1 10rem; + } +} + +.flags { + display: flex; + flex-wrap: wrap; + gap: 1rem; + border: 1px solid var(--mat-sys-outline-variant); + border-radius: 0.5rem; + padding: 0.75rem 1rem 1rem; + margin: 0.5rem 0 1rem; + + legend { + padding-inline: 0.375rem; + font-size: 0.8125rem; + color: var(--mat-sys-on-surface-variant); + } +} + +.editor-actions { + display: flex; + align-items: center; + gap: 0.5rem; + flex-wrap: wrap; + + .spacer { + flex: 1 1 auto; + } + + .danger { + color: var(--mat-sys-error); + } +} + +.state-panel { + display: grid; + place-items: center; + gap: 0.75rem; + padding: 4rem 1.5rem; + text-align: center; + color: var(--mat-sys-on-surface-variant); + + mat-icon { + font-size: 3rem; + width: 3rem; + height: 3rem; + opacity: 0.6; + } + + h2 { + margin: 0; + font-size: 1.125rem; + font-weight: 500; + } + + p { + margin: 0 0 0.5rem; + } +} diff --git a/frontend/src/app/features/game-edit/game-edit.spec.ts b/frontend/src/app/features/game-edit/game-edit.spec.ts new file mode 100644 index 0000000..b0b92ce --- /dev/null +++ b/frontend/src/app/features/game-edit/game-edit.spec.ts @@ -0,0 +1,82 @@ +import { provideHttpClient } from '@angular/common/http'; +import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideRouter } from '@angular/router'; +import { beforeEach, describe, expect, it } from 'vitest'; + +import { GameEdit } from './game-edit'; + +/** + * These exist mainly to prove the Material components in this screen construct + * and render without an animations provider. @angular/animations is deprecated + * in v22 and Material no longer depends on it, but MatSelect, MatDialog and + * MatCheckbox historically did — so this is worth pinning down in a test rather + * than assuming. + */ +describe('GameEdit', () => { + let fixture: ComponentFixture; + let httpMock: HttpTestingController; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [GameEdit], + providers: [provideRouter([]), provideHttpClient(), provideHttpClientTesting()], + }).compileComponents(); + + fixture = TestBed.createComponent(GameEdit); + httpMock = TestBed.inject(HttpTestingController); + }); + + it('renders the new-game form without an animations provider', () => { + fixture.detectChanges(); + + const host: HTMLElement = fixture.nativeElement; + expect(host.querySelector('.editor')).toBeTruthy(); + expect(host.textContent).toContain('New game'); + }); + + it('treats a missing route id as create mode and issues no fetch', () => { + fixture.detectChanges(); + + const component = fixture.componentInstance; + expect(component['isEdit']()).toBe(false); + + // Nothing to load when creating, so no outbound request should exist. + httpMock.verify(); + }); + + it('builds a request payload that turns blank fields into null', () => { + fixture.detectChanges(); + const component = fixture.componentInstance; + + component['form'].patchValue({ + title: ' Chrono Trigger ', + system: 'SNES', + genre: '', + year: '1995', + developer: ' ', + finished: true, + }); + + const payload = component['toRequest'](); + + expect(payload.title).toBe('Chrono Trigger'); + expect(payload.system).toBe('SNES'); + expect(payload.genre).toBeNull(); + expect(payload.developer).toBeNull(); + expect(payload.year).toBe('1995'); + expect(payload.finished).toBe(true); + // The payload must never carry an owner: the API takes it from the token. + expect('ownerId' in payload).toBe(false); + expect('userId' in payload).toBe(false); + }); + + it('requires a title', () => { + fixture.detectChanges(); + const form = fixture.componentInstance['form']; + + expect(form.valid).toBe(false); + form.patchValue({ title: 'Metroid' }); + expect(form.valid).toBe(true); + }); +}); diff --git a/frontend/src/app/features/game-edit/game-edit.ts b/frontend/src/app/features/game-edit/game-edit.ts new file mode 100644 index 0000000..07e9e93 --- /dev/null +++ b/frontend/src/app/features/game-edit/game-edit.ts @@ -0,0 +1,248 @@ +import { HttpErrorResponse } from '@angular/common/http'; +import { Component, computed, inject, input, signal } from '@angular/core'; +import { NonNullableFormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatCardModule } from '@angular/material/card'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatDialog, MatDialogModule } from '@angular/material/dialog'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; +import { MatSelectModule } from '@angular/material/select'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { Router, RouterLink } from '@angular/router'; + +import { ConfirmDialog, ConfirmDialogData } from '../../shared/confirm-dialog'; +import { GamesService } from '../../core/games.service'; +import { GameRequest } from '../../core/models'; +import { Toolbar } from '../../shared/toolbar'; + +/** Kept in sync with the values already present in the 2018 data. */ +const SYSTEMS = [ + 'NES', 'SNES', 'N64', 'GC', 'WII', 'GB', 'GBA', 'DS', + 'PS1', 'PS2', 'PSP', '360', 'PC', +]; + +const GENRES = [ + 'action', 'adventure', 'card', 'fighter', 'fps', 'lightgun', + 'platformer', 'racing', 'rpg', 'simulation', 'sports', 'strategy', +]; + +@Component({ + selector: 'app-game-edit', + imports: [ + ReactiveFormsModule, + RouterLink, + Toolbar, + MatCardModule, + MatFormFieldModule, + MatInputModule, + MatSelectModule, + MatCheckboxModule, + MatButtonModule, + MatIconModule, + MatProgressBarModule, + MatDialogModule, + ], + templateUrl: './game-edit.html', + styleUrl: './game-edit.scss', +}) +export class GameEdit { + private readonly games = inject(GamesService); + private readonly router = inject(Router); + private readonly fb = inject(NonNullableFormBuilder); + private readonly snackBar = inject(MatSnackBar); + private readonly dialog = inject(MatDialog); + + /** Bound from the `:id` route param by withComponentInputBinding(). */ + readonly id = input(); + + protected readonly systems = SYSTEMS; + protected readonly genres = GENRES; + + protected readonly isEdit = computed(() => !!this.id()); + protected readonly loading = signal(false); + protected readonly saving = signal(false); + protected readonly uploading = signal(false); + protected readonly loadFailed = signal(false); + + /** Preview URL: a freshly-picked local file, or the stored art from the API. */ + protected readonly artPreview = signal(null); + + protected readonly form = this.fb.group({ + title: ['', [Validators.required, Validators.maxLength(200)]], + system: [''], + genre: [''], + year: ['', Validators.maxLength(50)], + developer: ['', Validators.maxLength(100)], + publisher: ['', Validators.maxLength(100)], + description: [''], + art: [''], + own: [true], + dumped: [false], + played: [false], + finished: [false], + }); + + constructor() { + // input() is a signal, so this reacts if the route id ever changes without + // the component being torn down. + queueMicrotask(() => this.load()); + } + + private load(): void { + const gameId = this.id(); + if (!gameId) { + return; + } + + this.loading.set(true); + this.games.get(Number(gameId)).subscribe({ + next: (game) => { + this.form.patchValue({ + title: game.title, + system: game.system ?? '', + genre: game.genre ?? '', + year: game.year ?? '', + developer: game.developer ?? '', + publisher: game.publisher ?? '', + description: game.description ?? '', + art: game.art ?? '', + own: game.own, + dumped: game.dumped, + played: game.played, + finished: game.finished, + }); + this.artPreview.set(game.artUrl); + this.loading.set(false); + }, + error: () => { + this.loading.set(false); + this.loadFailed.set(true); + }, + }); + } + + protected onFileSelected(event: Event): void { + const input = event.target as HTMLInputElement; + const file = input.files?.[0]; + if (!file) { + return; + } + + // Show the local file straight away rather than waiting for the round trip. + const reader = new FileReader(); + reader.onload = () => this.artPreview.set(String(reader.result)); + reader.readAsDataURL(file); + + this.uploading.set(true); + this.games.uploadArt(file).subscribe({ + next: (result) => { + this.form.controls.art.setValue(result.fileName); + this.artPreview.set(result.url); + this.uploading.set(false); + // Clear the input so picking the same file again still fires a change. + input.value = ''; + }, + error: (err: HttpErrorResponse) => { + this.uploading.set(false); + this.artPreview.set(null); + input.value = ''; + this.snackBar.open( + err.status === 400 ? 'That file could not be read as an image.' : 'Upload failed.', + 'Dismiss', + { duration: 5000 }, + ); + }, + }); + } + + protected removeArt(): void { + this.form.controls.art.setValue(''); + this.artPreview.set(null); + } + + protected submit(): void { + if (this.form.invalid || this.saving()) { + this.form.markAllAsTouched(); + return; + } + + this.saving.set(true); + const payload = this.toRequest(); + + const request = this.isEdit() + ? this.games.update(Number(this.id()), payload) + : this.games.create(payload); + + request.subscribe({ + next: () => { + this.snackBar.open(this.isEdit() ? 'Game updated' : 'Game added', undefined, { + duration: 3000, + }); + void this.router.navigate(['/games']); + }, + error: () => { + this.saving.set(false); + this.snackBar.open('Could not save the game.', 'Dismiss', { duration: 5000 }); + }, + }); + } + + protected confirmRemove(): void { + const data: ConfirmDialogData = { + title: 'Remove this game?', + message: `"${this.form.controls.title.value}" will be deleted from your library. This cannot be undone.`, + confirmLabel: 'Remove', + destructive: true, + }; + + this.dialog + .open(ConfirmDialog, { data, width: '24rem' }) + .afterClosed() + .subscribe((confirmed) => { + if (confirmed) { + this.remove(); + } + }); + } + + private remove(): void { + this.saving.set(true); + this.games.remove(Number(this.id())).subscribe({ + next: () => { + this.snackBar.open('Game removed', undefined, { duration: 3000 }); + void this.router.navigate(['/games']); + }, + error: () => { + this.saving.set(false); + this.snackBar.open('Could not remove the game.', 'Dismiss', { duration: 5000 }); + }, + }); + } + + /** Empty strings become null, matching the API's nullable columns. */ + private toRequest(): GameRequest { + const value = this.form.getRawValue(); + const blankToNull = (input: string) => { + const trimmed = input.trim(); + return trimmed.length ? trimmed : null; + }; + + return { + title: value.title.trim(), + system: blankToNull(value.system), + genre: blankToNull(value.genre), + year: blankToNull(value.year), + developer: blankToNull(value.developer), + publisher: blankToNull(value.publisher), + description: blankToNull(value.description), + art: blankToNull(value.art), + own: value.own, + dumped: value.dumped, + played: value.played, + finished: value.finished, + }; + } +} diff --git a/frontend/src/app/features/game-grid/game-grid.html b/frontend/src/app/features/game-grid/game-grid.html new file mode 100644 index 0000000..203a106 --- /dev/null +++ b/frontend/src/app/features/game-grid/game-grid.html @@ -0,0 +1,144 @@ + + + search + + + + +@if (loading()) { + +} + +
+ + System + + All systems + @for (option of facets().systems; track option) { + {{ option }} + } + + + + + Genre + + All genres + @for (option of facets().genres; track option) { + {{ option }} + } + + + + + Status + + Any status + Owned + Dumped + Played + Finished + + + + + Sort by + + Title + System + Genre + Year + Date added + Last updated + + + + + + @if (hasFilters()) { + + } +
+ +@if (failed()) { +
+ cloud_off +

Could not load your library

+

The server did not respond. Check that the API is running, then try again.

+
+} @else if (!loading() && items().length === 0) { +
+ videogame_asset_off + @if (hasFilters()) { +

No games match those filters

+ + } @else { +

Your library is empty

+ Add your first game + } +
+} @else { +
+ @for (game of items(); track game.id) { + +
+ @if (game.artUrl) { + + } @else { + + } +
+ +
+

{{ game.title }}

+

+ @if (game.system) { + {{ game.system }} + } + @if (game.year) { + {{ game.year }} + } +

+ + @if (badges(game).length) { +
+ @for (badge of badges(game); track badge) { + {{ badge }} + } +
+ } +
+
+ } +
+ + +} diff --git a/frontend/src/app/features/game-grid/game-grid.scss b/frontend/src/app/features/game-grid/game-grid.scss new file mode 100644 index 0000000..aac1ada --- /dev/null +++ b/frontend/src/app/features/game-grid/game-grid.scss @@ -0,0 +1,181 @@ +.search-field { + width: min(28rem, 40vw); + + @media (max-width: 899px) { + width: 12rem; + } + @media (max-width: 599px) { + width: 100%; + } +} + +.filters { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.75rem; + padding: 1rem 1.5rem 0.5rem; + + mat-form-field { + min-width: 10rem; + } + + @media (max-width: 599px) { + padding-inline: 1rem; + mat-form-field { + flex: 1 1 8rem; + min-width: 0; + } + } +} + +.grid { + display: grid; + /* Cards size themselves; no masonry library required for this. */ + grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr)); + gap: 1rem; + padding: 1rem 1.5rem; + + @media (max-width: 599px) { + grid-template-columns: repeat(auto-fill, minmax(8.5rem, 1fr)); + gap: 0.75rem; + padding: 0.75rem 1rem; + } +} + +.game-card { + display: flex; + flex-direction: column; + padding: 0; + overflow: hidden; + cursor: pointer; + transition: + transform 120ms ease, + box-shadow 120ms ease; + + &:hover, + &:focus-visible { + transform: translateY(-2px); + box-shadow: var(--mat-sys-level3); + } + + &:focus-visible { + outline: 2px solid var(--mat-sys-primary); + outline-offset: 2px; + } + + .art { + aspect-ratio: 3 / 4; + background: var(--mat-sys-surface-container-high); + } + + img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; + } + + .art-placeholder { + display: grid; + place-items: center; + height: 100%; + color: var(--mat-sys-outline); + + mat-icon { + font-size: 2.5rem; + width: 2.5rem; + height: 2.5rem; + } + } + + .body { + display: flex; + flex-direction: column; + gap: 0.25rem; + padding: 0.625rem 0.75rem 0.75rem; + } + + .title { + margin: 0; + font-size: 0.9rem; + font-weight: 600; + line-height: 1.3; + /* Two lines, then ellipsis — keeps every card the same height. */ + display: -webkit-box; + -webkit-line-clamp: 2; + line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + } + + .meta { + display: flex; + gap: 0.5rem; + margin: 0; + font-size: 0.75rem; + color: var(--mat-sys-on-surface-variant); + } + + .system { + font-weight: 600; + } +} + +.badges { + display: flex; + flex-wrap: wrap; + gap: 0.25rem; + margin-top: 0.125rem; +} + +.badge { + font-size: 0.625rem; + font-weight: 600; + letter-spacing: 0.02em; + text-transform: uppercase; + padding: 0.125rem 0.375rem; + border-radius: 0.25rem; + background: var(--mat-sys-surface-container-highest); + color: var(--mat-sys-on-surface-variant); + + &--finished { + background: var(--mat-sys-primary); + color: var(--mat-sys-on-primary); + } + &--played { + background: var(--mat-sys-tertiary-container); + color: var(--mat-sys-on-tertiary-container); + } +} + +.state-panel { + display: grid; + place-items: center; + gap: 0.75rem; + padding: 4rem 1.5rem; + text-align: center; + color: var(--mat-sys-on-surface-variant); + + mat-icon { + font-size: 3rem; + width: 3rem; + height: 3rem; + opacity: 0.6; + } + + h2 { + margin: 0; + font-size: 1.125rem; + font-weight: 500; + } + + p { + margin: 0; + max-width: 28rem; + } +} + +mat-paginator { + background: transparent; +} diff --git a/frontend/src/app/features/game-grid/game-grid.ts b/frontend/src/app/features/game-grid/game-grid.ts new file mode 100644 index 0000000..d6d1073 --- /dev/null +++ b/frontend/src/app/features/game-grid/game-grid.ts @@ -0,0 +1,160 @@ +import { Component, computed, effect, inject, signal } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatCardModule } from '@angular/material/card'; +import { MatChipsModule } from '@angular/material/chips'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatPaginatorModule, PageEvent } from '@angular/material/paginator'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; +import { MatSelectModule } from '@angular/material/select'; +import { RouterLink } from '@angular/router'; +import { Subject, debounceTime, switchMap } from 'rxjs'; +import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop'; + +import { GamesService } from '../../core/games.service'; +import { Facets, Game } from '../../core/models'; +import { Toolbar } from '../../shared/toolbar'; + +type SortKey = 'title' | 'system' | 'genre' | 'year' | 'created' | 'updated'; + +@Component({ + selector: 'app-game-grid', + imports: [ + FormsModule, + RouterLink, + Toolbar, + MatCardModule, + MatIconModule, + MatButtonModule, + MatFormFieldModule, + MatInputModule, + MatSelectModule, + MatPaginatorModule, + MatProgressBarModule, + MatChipsModule, + ], + templateUrl: './game-grid.html', + styleUrl: './game-grid.scss', +}) +export class GameGrid { + private readonly games = inject(GamesService); + + protected readonly search = signal(''); + protected readonly system = signal(''); + protected readonly genre = signal(''); + protected readonly status = signal<'' | 'own' | 'played' | 'finished' | 'dumped'>(''); + protected readonly sort = signal('title'); + protected readonly dir = signal<'asc' | 'desc'>('asc'); + + protected readonly page = signal(1); + protected readonly pageSize = signal(24); + protected readonly pageSizeOptions = [12, 24, 48, 96]; + + protected readonly items = signal([]); + protected readonly total = signal(0); + protected readonly loading = signal(false); + protected readonly failed = signal(false); + + protected readonly facets = toSignal(this.games.facets(), { + initialValue: { systems: [], genres: [] } as Facets, + }); + + protected readonly hasFilters = computed( + () => !!this.search() || !!this.system() || !!this.genre() || !!this.status(), + ); + + /** Debounced trigger, so typing in the search box does not fire a request per keystroke. */ + private readonly reload$ = new Subject(); + + constructor() { + this.reload$ + .pipe( + // Collapses bursts of signal changes (typing, or a filter that also + // resets the page) into one request. switchMap then cancels any + // in-flight response that a newer query has superseded. + debounceTime(250), + switchMap(() => { + this.loading.set(true); + this.failed.set(false); + return this.games.list({ + search: this.search().trim() || undefined, + system: this.system() || undefined, + genre: this.genre() || undefined, + ...this.statusFilter(), + page: this.page(), + pageSize: this.pageSize(), + sort: this.sort(), + dir: this.dir(), + }); + }), + takeUntilDestroyed(), + ) + .subscribe({ + next: (result) => { + this.items.set(result.items); + this.total.set(result.total); + this.loading.set(false); + }, + error: () => { + this.loading.set(false); + this.failed.set(true); + }, + }); + + // Any filter change resets to page 1 and refetches. + effect(() => { + this.search(); + this.system(); + this.genre(); + this.status(); + this.sort(); + this.dir(); + this.pageSize(); + this.page(); + this.reload$.next(); + }); + } + + private statusFilter(): Record { + const value = this.status(); + return value ? { [value]: true } : {}; + } + + protected onSearch(value: string): void { + this.page.set(1); + this.search.set(value); + } + + protected onFilterChange(): void { + this.page.set(1); + } + + protected onPage(event: PageEvent): void { + this.pageSize.set(event.pageSize); + this.page.set(event.pageIndex + 1); + } + + protected clearFilters(): void { + this.page.set(1); + this.search.set(''); + this.system.set(''); + this.genre.set(''); + this.status.set(''); + } + + protected toggleDirection(): void { + this.dir.set(this.dir() === 'asc' ? 'desc' : 'asc'); + } + + /** Badges shown on each card for the four collection flags. */ + protected badges(game: Game): string[] { + const flags: string[] = []; + if (game.own) flags.push('Own'); + if (game.dumped) flags.push('Dumped'); + if (game.played) flags.push('Played'); + if (game.finished) flags.push('Finished'); + return flags; + } +} diff --git a/frontend/src/app/features/login/login.html b/frontend/src/app/features/login/login.html new file mode 100644 index 0000000..f37e60f --- /dev/null +++ b/frontend/src/app/features/login/login.html @@ -0,0 +1,73 @@ +
+ + @if (loading()) { + + } + + + + videogame_asset + LudosData + + Sign in to your game library + + + +
+ + Username + + @if (form.controls.userName.touched && form.controls.userName.invalid) { + Username is required + } + + + + Password + + + @if (form.controls.password.touched && form.controls.password.invalid) { + Password is required + } + + + @if (error(); as message) { + + } + + +
+
+ + + No account yet? + Create one + +
+
diff --git a/frontend/src/app/features/login/login.scss b/frontend/src/app/features/login/login.scss new file mode 100644 index 0000000..7589dad --- /dev/null +++ b/frontend/src/app/features/login/login.scss @@ -0,0 +1,54 @@ +.auth-shell { + display: grid; + place-items: center; + min-height: 100dvh; + padding: 1.5rem; +} + +.auth-card { + width: min(100%, 26rem); + overflow: hidden; + + mat-card-title { + display: flex; + align-items: center; + gap: 0.5rem; + } + + mat-card-content { + padding-top: 1.25rem; + } + + form { + display: flex; + flex-direction: column; + gap: 0.25rem; + } + + button[type='submit'] { + margin-top: 0.75rem; + height: 2.75rem; + } + + mat-card-actions { + display: flex; + align-items: center; + gap: 0.25rem; + padding: 0.5rem 1rem 1rem; + } +} + +.form-error { + display: flex; + align-items: center; + gap: 0.5rem; + margin: 0 0 0.5rem; + color: var(--mat-sys-error); + font-size: 0.875rem; + + mat-icon { + font-size: 1.125rem; + width: 1.125rem; + height: 1.125rem; + } +} diff --git a/frontend/src/app/features/login/login.ts b/frontend/src/app/features/login/login.ts new file mode 100644 index 0000000..6e21def --- /dev/null +++ b/frontend/src/app/features/login/login.ts @@ -0,0 +1,78 @@ +import { HttpErrorResponse } from '@angular/common/http'; +import { Component, inject, signal } from '@angular/core'; +import { NonNullableFormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatCardModule } from '@angular/material/card'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; +import { Router, RouterLink } from '@angular/router'; + +import { AuthService } from '../../core/auth.service'; + +@Component({ + selector: 'app-login', + imports: [ + ReactiveFormsModule, + RouterLink, + MatCardModule, + MatFormFieldModule, + MatInputModule, + MatButtonModule, + MatIconModule, + MatProgressBarModule, + ], + templateUrl: './login.html', + styleUrl: './login.scss', +}) +export class Login { + private readonly auth = inject(AuthService); + private readonly router = inject(Router); + private readonly fb = inject(NonNullableFormBuilder); + + /** Set by the router when the guard bounced an unauthenticated deep link. */ + readonly returnUrl = signal('/games'); + + protected readonly loading = signal(false); + protected readonly error = signal(null); + protected readonly showPassword = signal(false); + + protected readonly form = this.fb.group({ + userName: ['', Validators.required], + password: ['', Validators.required], + }); + + constructor() { + const url = new URLSearchParams(window.location.search).get('returnUrl'); + if (url?.startsWith('/')) { + // Only same-origin paths, so a crafted link cannot bounce through login + // to an external site. + this.returnUrl.set(url); + } + } + + submit(): void { + if (this.form.invalid || this.loading()) { + this.form.markAllAsTouched(); + return; + } + + this.loading.set(true); + this.error.set(null); + + const { userName, password } = this.form.getRawValue(); + + this.auth.login(userName, password).subscribe({ + next: () => void this.router.navigateByUrl(this.returnUrl()), + error: (err: HttpErrorResponse) => { + this.loading.set(false); + this.error.set( + err.status === 423 + ? 'Too many failed attempts. Try again in 15 minutes.' + : 'Incorrect username or password.', + ); + }, + }); + } +} diff --git a/frontend/src/app/features/register/register.html b/frontend/src/app/features/register/register.html new file mode 100644 index 0000000..7cbb85d --- /dev/null +++ b/frontend/src/app/features/register/register.html @@ -0,0 +1,113 @@ +
+ + @if (loading()) { + + } + + + + videogame_asset + Create account + + Start cataloguing your collection + + + +
+
+ + First name + + + + + Last name + + +
+ + + Username + + @if (form.controls.userName.pending) { + Checking availability… + } + @if (form.controls.userName.touched) { + @if (form.controls.userName.hasError('required')) { + Username is required + } @else if (form.controls.userName.hasError('minlength')) { + At least 3 characters + } @else if (form.controls.userName.hasError('taken')) { + That username is already taken + } + } + + + + Email + + @if (form.controls.email.pending) { + Checking availability… + } + @if (form.controls.email.touched) { + @if (form.controls.email.hasError('required')) { + Email is required + } @else if (form.controls.email.hasError('email')) { + Enter a valid email address + } @else if (form.controls.email.hasError('taken')) { + That email is already registered + } + } + + + + Password + + + @if (form.controls.password.touched && form.controls.password.invalid) { + At least 12 characters, with upper, lower and a number + } @else { + At least 12 characters, with upper, lower and a number + } + + + @if (error(); as message) { + + } + + +
+
+ + + Already registered? + Sign in + +
+
diff --git a/frontend/src/app/features/register/register.ts b/frontend/src/app/features/register/register.ts new file mode 100644 index 0000000..01a790e --- /dev/null +++ b/frontend/src/app/features/register/register.ts @@ -0,0 +1,143 @@ +import { HttpErrorResponse } from '@angular/common/http'; +import { Component, inject, signal } from '@angular/core'; +import { + AbstractControl, + AsyncValidatorFn, + NonNullableFormBuilder, + ReactiveFormsModule, + ValidationErrors, + Validators, +} from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatCardModule } from '@angular/material/card'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; +import { Router, RouterLink } from '@angular/router'; +import { Observable, catchError, debounceTime, first, map, of, switchMap } from 'rxjs'; + +import { AuthService } from '../../core/auth.service'; +import { ProblemDetails } from '../../core/models'; + +/** + * Async "is this taken?" validator. + * + * The 2018 version asked the generic CRUD endpoint for the whole users row to + * answer this, which exposed every user column to anonymous callers. The API + * now has a dedicated endpoint that returns only a boolean. + */ +function availabilityValidator( + auth: AuthService, + field: 'userName' | 'email', +): AsyncValidatorFn { + return (control: AbstractControl): Observable => { + const value = String(control.value ?? '').trim(); + if (!value) { + return of(null); + } + + return of(value).pipe( + debounceTime(400), + switchMap((v) => auth.isAvailable(field, v)), + map((result) => (result.available ? null : { taken: true })), + // A failed availability check should not block submission; the server + // enforces uniqueness regardless. + catchError(() => of(null)), + first(), + ); + }; +} + +@Component({ + selector: 'app-register', + imports: [ + ReactiveFormsModule, + RouterLink, + MatCardModule, + MatFormFieldModule, + MatInputModule, + MatButtonModule, + MatIconModule, + MatProgressBarModule, + ], + templateUrl: './register.html', + styleUrl: '../login/login.scss', +}) +export class Register { + private readonly auth = inject(AuthService); + private readonly router = inject(Router); + private readonly fb = inject(NonNullableFormBuilder); + + protected readonly loading = signal(false); + protected readonly error = signal(null); + protected readonly showPassword = signal(false); + + protected readonly form = this.fb.group({ + firstName: [''], + lastName: [''], + userName: [ + '', + [Validators.required, Validators.minLength(3), Validators.maxLength(50)], + [availabilityValidator(this.auth, 'userName')], + ], + email: [ + '', + [Validators.required, Validators.email, Validators.maxLength(256)], + [availabilityValidator(this.auth, 'email')], + ], + // Mirrors the server's Identity policy, so the rules are visible before + // submitting rather than coming back as an error. + password: [ + '', + [ + Validators.required, + Validators.minLength(12), + Validators.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$/), + ], + ], + }); + + submit(): void { + if (this.form.invalid || this.loading()) { + this.form.markAllAsTouched(); + return; + } + + this.loading.set(true); + this.error.set(null); + + const value = this.form.getRawValue(); + + this.auth + .register({ + userName: value.userName.trim(), + email: value.email.trim(), + password: value.password, + firstName: value.firstName.trim() || undefined, + lastName: value.lastName.trim() || undefined, + }) + .subscribe({ + // Registration returns a token, so the new account lands straight in + // the library instead of being bounced back to the login form. + next: () => void this.router.navigate(['/games']), + error: (err: HttpErrorResponse) => { + this.loading.set(false); + this.error.set(describeProblem(err)); + }, + }); + } +} + +function describeProblem(err: HttpErrorResponse): string { + const problem = err.error as ProblemDetails | undefined; + + if (problem?.errors) { + const messages = Object.values(problem.errors).flat(); + if (messages.length) { + return messages.join(' '); + } + } + + return problem?.title ?? 'Could not create the account. Please try again.'; +} diff --git a/frontend/src/app/shared/confirm-dialog.ts b/frontend/src/app/shared/confirm-dialog.ts new file mode 100644 index 0000000..6fbba6a --- /dev/null +++ b/frontend/src/app/shared/confirm-dialog.ts @@ -0,0 +1,35 @@ +import { Component, inject } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; + +export interface ConfirmDialogData { + title: string; + message: string; + confirmLabel?: string; + destructive?: boolean; +} + +/** Replaces the old pattern of deleting immediately on click with no confirmation. */ +@Component({ + selector: 'app-confirm-dialog', + imports: [MatDialogModule, MatButtonModule], + template: ` +

{{ data.title }}

+ {{ data.message }} + + + + + `, +}) +export class ConfirmDialog { + readonly dialogRef = inject(MatDialogRef); + readonly data = inject(MAT_DIALOG_DATA); +} diff --git a/frontend/src/app/shared/toolbar.ts b/frontend/src/app/shared/toolbar.ts new file mode 100644 index 0000000..b71fa4a --- /dev/null +++ b/frontend/src/app/shared/toolbar.ts @@ -0,0 +1,100 @@ +import { Component, inject } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatToolbarModule } from '@angular/material/toolbar'; +import { Router, RouterLink } from '@angular/router'; + +import { AuthService } from '../core/auth.service'; + +/** App bar shared by the library and editor screens. */ +@Component({ + selector: 'app-toolbar', + imports: [MatToolbarModule, MatButtonModule, MatIconModule, MatMenuModule, RouterLink], + template: ` + + + videogame_asset + LudosData + + + + + + + + add + New game + + + + + + @if (user(); as currentUser) { + + } + + person + Account + + + + + `, + styles: ` + .toolbar { + position: sticky; + top: 0; + z-index: 10; + gap: 0.5rem; + } + .brand { + display: inline-flex; + align-items: center; + gap: 0.5rem; + color: inherit; + text-decoration: none; + font-weight: 600; + } + .spacer { + flex: 1 1 auto; + } + .menu-header { + display: flex; + flex-direction: column; + padding: 0.5rem 1rem; + line-height: 1.3; + } + .menu-header small { + opacity: 0.7; + } + /* Keep the bar usable on a phone: icons stay, text labels drop out. */ + @media (max-width: 599px) { + .brand-text, + .label-md { + display: none; + } + } + `, +}) +export class Toolbar { + private readonly auth = inject(AuthService); + private readonly router = inject(Router); + + readonly user = this.auth.user; + + logout(): void { + this.auth.logout(); + void this.router.navigate(['/login']); + } +} diff --git a/frontend/src/index.html b/frontend/src/index.html new file mode 100644 index 0000000..ceece2b --- /dev/null +++ b/frontend/src/index.html @@ -0,0 +1,15 @@ + + + + + LudosData + + + + + + + + + + diff --git a/frontend/src/main.ts b/frontend/src/main.ts new file mode 100644 index 0000000..5df75f9 --- /dev/null +++ b/frontend/src/main.ts @@ -0,0 +1,6 @@ +import { bootstrapApplication } from '@angular/platform-browser'; +import { appConfig } from './app/app.config'; +import { App } from './app/app'; + +bootstrapApplication(App, appConfig) + .catch((err) => console.error(err)); diff --git a/frontend/src/styles.scss b/frontend/src/styles.scss new file mode 100644 index 0000000..296ffb5 --- /dev/null +++ b/frontend/src/styles.scss @@ -0,0 +1,52 @@ +@use '@angular/material' as mat; + + +// Material 3 theming. `color-scheme: light dark` lets a single theme definition +// follow the OS preference, so there is no separate dark stylesheet to maintain. +html { + color-scheme: light dark; + + @include mat.theme( + ( + color: ( + primary: mat.$violet-palette, + tertiary: mat.$magenta-palette, + ), + typography: Roboto, + density: 0, + ) + ); +} + +* { + box-sizing: border-box; +} + +html, +body { + height: 100%; +} + +body { + margin: 0; + background: var(--mat-sys-surface); + color: var(--mat-sys-on-surface); + font-family: var(--mat-sys-body-medium-font, Roboto, sans-serif); + -webkit-font-smoothing: antialiased; +} + +a { + color: var(--mat-sys-primary); +} + +// Respect users who have asked for less motion. +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } +} diff --git a/frontend/tsconfig.app.json b/frontend/tsconfig.app.json new file mode 100644 index 0000000..cb151e1 --- /dev/null +++ b/frontend/tsconfig.app.json @@ -0,0 +1,14 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "types": [] + }, + "include": [ + "src/**/*.ts" + ], + "exclude": [ + "src/**/*.spec.ts" + ] +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..d2fbb9c --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,31 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "compileOnSave": false, + "compilerOptions": { + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "isolatedModules": true, + "experimentalDecorators": true, + "importHelpers": true, + "target": "ES2022", + "module": "preserve" + }, + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true + }, + "files": [], + "references": [ + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/frontend/tsconfig.spec.json b/frontend/tsconfig.spec.json new file mode 100644 index 0000000..9c8efb9 --- /dev/null +++ b/frontend/tsconfig.spec.json @@ -0,0 +1,14 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "types": [ + "vitest/globals" + ] + }, + "include": [ + "src/**/*.d.ts", + "src/**/*.spec.ts" + ] +} diff --git a/interfaceServices/api.php b/interfaceServices/api.php deleted file mode 100644 index b107108..0000000 --- a/interfaceServices/api.php +++ /dev/null @@ -1,2808 +0,0 @@ -parse((string) $providedToken); -$token->getHeaders(); -$token->getClaims(); - -$signer = new Sha256(); -$tokenCheck = (new Builder()) - ->setIssuer( $token->getClaim('iss') ) - ->setIssuedAt( $token->getClaim('iat') ) - ->set("userName", $token->getClaim('userName') ) - ->sign($signer, "testing") - ->getToken(); - -if( ( (string)$tokenCheck != (string)$providedToken ) ){ - http_response_code(400); - die(); -} - -unset($_GET['token']); - - -interface DatabaseInterface { - public function getSql($name); - public function connect($hostname,$username,$password,$database,$port,$socket,$charset); - public function query($sql,$params=array()); - public function fetchAssoc($result); - public function fetchRow($result); - public function insertId($result); - public function affectedRows($result); - public function close($result); - public function fetchFields($table); - public function addLimitToSql($sql,$limit,$offset); - public function likeEscape($string); - public function isNumericType($field); - public function isBinaryType($field); - public function isGeometryType($field); - public function isJsonType($field); - public function getDefaultCharset(); - public function beginTransaction(); - public function commitTransaction(); - public function rollbackTransaction(); - public function jsonEncode($object); - public function jsonDecode($string); -} - -class MySQL implements DatabaseInterface { - - protected $db; - protected $queries; - - public function __construct() { - $this->queries = array( - 'list_tables'=>'SELECT - "TABLE_NAME","TABLE_COMMENT" - FROM - "INFORMATION_SCHEMA"."TABLES" - WHERE - "TABLE_SCHEMA" = ?', - 'reflect_table'=>'SELECT - "TABLE_NAME" - FROM - "INFORMATION_SCHEMA"."TABLES" - WHERE - "TABLE_NAME" COLLATE \'utf8_bin\' = ? AND - "TABLE_SCHEMA" = ?', - 'reflect_pk'=>'SELECT - "COLUMN_NAME" - FROM - "INFORMATION_SCHEMA"."COLUMNS" - WHERE - "COLUMN_KEY" = \'PRI\' AND - "TABLE_NAME" = ? AND - "TABLE_SCHEMA" = ?', - 'reflect_belongs_to'=>'SELECT - "TABLE_NAME","COLUMN_NAME", - "REFERENCED_TABLE_NAME","REFERENCED_COLUMN_NAME" - FROM - "INFORMATION_SCHEMA"."KEY_COLUMN_USAGE" - WHERE - "TABLE_NAME" COLLATE \'utf8_bin\' = ? AND - "REFERENCED_TABLE_NAME" COLLATE \'utf8_bin\' IN ? AND - "TABLE_SCHEMA" = ? AND - "REFERENCED_TABLE_SCHEMA" = ?', - 'reflect_has_many'=>'SELECT - "TABLE_NAME","COLUMN_NAME", - "REFERENCED_TABLE_NAME","REFERENCED_COLUMN_NAME" - FROM - "INFORMATION_SCHEMA"."KEY_COLUMN_USAGE" - WHERE - "TABLE_NAME" COLLATE \'utf8_bin\' IN ? AND - "REFERENCED_TABLE_NAME" COLLATE \'utf8_bin\' = ? AND - "TABLE_SCHEMA" = ? AND - "REFERENCED_TABLE_SCHEMA" = ?', - 'reflect_habtm'=>'SELECT - k1."TABLE_NAME", k1."COLUMN_NAME", - k1."REFERENCED_TABLE_NAME", k1."REFERENCED_COLUMN_NAME", - k2."TABLE_NAME", k2."COLUMN_NAME", - k2."REFERENCED_TABLE_NAME", k2."REFERENCED_COLUMN_NAME" - FROM - "INFORMATION_SCHEMA"."KEY_COLUMN_USAGE" k1, - "INFORMATION_SCHEMA"."KEY_COLUMN_USAGE" k2 - WHERE - k1."TABLE_SCHEMA" = ? AND - k2."TABLE_SCHEMA" = ? AND - k1."REFERENCED_TABLE_SCHEMA" = ? AND - k2."REFERENCED_TABLE_SCHEMA" = ? AND - k1."TABLE_NAME" COLLATE \'utf8_bin\' = k2."TABLE_NAME" COLLATE \'utf8_bin\' AND - k1."REFERENCED_TABLE_NAME" COLLATE \'utf8_bin\' = ? AND - k2."REFERENCED_TABLE_NAME" COLLATE \'utf8_bin\' IN ?', - 'reflect_columns'=> 'SELECT - "COLUMN_NAME", "COLUMN_DEFAULT", "IS_NULLABLE", "DATA_TYPE", "CHARACTER_MAXIMUM_LENGTH" - FROM - "INFORMATION_SCHEMA"."COLUMNS" - WHERE - "TABLE_NAME" = ? AND - "TABLE_SCHEMA" = ? - ORDER BY - "ORDINAL_POSITION"' - ); - } - - public function getSql($name) { - return isset($this->queries[$name])?$this->queries[$name]:false; - } - - public function connect($hostname,$username,$password,$database,$port,$socket,$charset) { - - - $db = mysqli_init(); - if (defined('MYSQLI_OPT_INT_AND_FLOAT_NATIVE')) { - mysqli_options($db,MYSQLI_OPT_INT_AND_FLOAT_NATIVE,true); - } - $success = mysqli_real_connect($db,$hostname,$username,$password,$database,$port,$socket,MYSQLI_CLIENT_FOUND_ROWS); - if (!$success) { - throw new \Exception('Connect failed. '.mysqli_connect_error()); - } - if (!mysqli_set_charset($db,$charset)) { - throw new \Exception('Error setting charset. '.mysqli_error($db)); - } - if (!mysqli_query($db,'SET SESSION sql_mode = \'ANSI_QUOTES\';')) { - throw new \Exception('Error setting ANSI quotes. '.mysqli_error($db)); - } - $this->db = $db; - } - - public function query($sql,$params=array()) { - $db = $this->db; - $sql = preg_replace_callback('/\!|\?/', function ($matches) use (&$db,&$params) { - $param = array_shift($params); - if ($matches[0]=='!') { - $key = preg_replace('/[^a-zA-Z0-9\-_=<> ]/','',is_object($param)?$param->key:$param); - if (is_object($param) && $param->type=='hex') { - return "HEX(\"$key\") as \"$key\""; - } - if (is_object($param) && $param->type=='wkt') { - return "ST_AsText(\"$key\") as \"$key\""; - } - return '"'.$key.'"'; - } else { - if (is_array($param)) return '('.implode(',',array_map(function($v) use (&$db) { - return "'".mysqli_real_escape_string($db,$v)."'"; - },$param)).')'; - if (is_object($param) && $param->type=='hex') { - return "x'".$param->value."'"; - } - if (is_object($param) && $param->type=='wkt') { - return "ST_GeomFromText('".mysqli_real_escape_string($db,$param->value)."')"; - } - if ($param===null) return 'NULL'; - return "'".mysqli_real_escape_string($db,$param)."'"; - } - }, $sql); - //if (!strpos($sql,'INFORMATION_SCHEMA')) echo "\n$sql\n"; - //if (!strpos($sql,'INFORMATION_SCHEMA')) file_put_contents('log.txt',"\n$sql\n",FILE_APPEND); - return mysqli_query($db,$sql); - } - - public function fetchAssoc($result) { - return mysqli_fetch_assoc($result); - } - - public function fetchRow($result) { - return mysqli_fetch_row($result); - } - - public function insertId($result) { - return mysqli_insert_id($this->db); - } - - public function affectedRows($result) { - return mysqli_affected_rows($this->db); - } - - public function close($result) { - return mysqli_free_result($result); - } - - public function fetchFields($table) { - $result = $this->query('SELECT * FROM ! WHERE 1=2;',array($table)); - return mysqli_fetch_fields($result); - } - - public function addLimitToSql($sql,$limit,$offset) { - return "$sql LIMIT $limit OFFSET $offset"; - } - - public function likeEscape($string) { - return addcslashes($string,'%_'); - } - - public function convertFilter($field, $comparator, $value) { - return false; - } - - public function isNumericType($field) { - return in_array($field->type,array(1,2,3,4,5,6,8,9)); - } - - public function isBinaryType($field) { - //echo "$field->name: $field->type ($field->flags)\n"; - return (($field->flags & 128) && (($field->type>=249 && $field->type<=252) || ($field->type>=253 && $field->type<=254 && $field->charsetnr==63))); - } - - public function isGeometryType($field) { - return ($field->type==255); - } - - public function isJsonType($field) { - return ($field->type==245); - } - - public function getDefaultCharset() { - return 'utf8'; - } - - public function beginTransaction() { - mysqli_query($this->db,'BEGIN'); - //return mysqli_begin_transaction($this->db); - } - - public function commitTransaction() { - mysqli_query($this->db,'COMMIT'); - //return mysqli_commit($this->db); - } - - public function rollbackTransaction() { - mysqli_query($this->db,'ROLLBACK'); - //return mysqli_rollback($this->db); - } - - public function jsonEncode($object) { - return json_encode($object); - } - - public function jsonDecode($string) { - return json_decode($string); - } -} - -class PostgreSQL implements DatabaseInterface { - - protected $db; - protected $queries; - - public function __construct() { - $this->queries = array( - 'list_tables'=>'select - "table_name",\'\' as "table_comment" - from - "information_schema"."tables" - where - "table_schema" = \'public\' and - "table_catalog" = ?', - 'reflect_table'=>'select - "table_name" - from - "information_schema"."tables" - where - "table_name" = ? and - "table_schema" = \'public\' and - "table_catalog" = ?', - 'reflect_pk'=>'select - "column_name" - from - "information_schema"."table_constraints" tc, - "information_schema"."key_column_usage" ku - where - tc."constraint_type" = \'PRIMARY KEY\' and - tc."constraint_name" = ku."constraint_name" and - ku."table_name" = ? and - ku."table_schema" = \'public\' and - ku."table_catalog" = ?', - 'reflect_belongs_to'=>'select - cu1."table_name",cu1."column_name", - cu2."table_name",cu2."column_name" - from - "information_schema".referential_constraints rc, - "information_schema".key_column_usage cu1, - "information_schema".key_column_usage cu2 - where - cu1."constraint_name" = rc."constraint_name" and - cu2."constraint_name" = rc."unique_constraint_name" and - cu1."table_name" = ? and - cu2."table_name" in ? and - cu1."table_schema" = \'public\' and - cu2."table_schema" = \'public\' and - cu1."table_catalog" = ? and - cu2."table_catalog" = ?', - 'reflect_has_many'=>'select - cu1."table_name",cu1."column_name", - cu2."table_name",cu2."column_name" - from - "information_schema".referential_constraints rc, - "information_schema".key_column_usage cu1, - "information_schema".key_column_usage cu2 - where - cu1."constraint_name" = rc."constraint_name" and - cu2."constraint_name" = rc."unique_constraint_name" and - cu1."table_name" in ? and - cu2."table_name" = ? and - cu1."table_schema" = \'public\' and - cu2."table_schema" = \'public\' and - cu1."table_catalog" = ? and - cu2."table_catalog" = ?', - 'reflect_habtm'=>'select - cua1."table_name",cua1."column_name", - cua2."table_name",cua2."column_name", - cub1."table_name",cub1."column_name", - cub2."table_name",cub2."column_name" - from - "information_schema".referential_constraints rca, - "information_schema".referential_constraints rcb, - "information_schema".key_column_usage cua1, - "information_schema".key_column_usage cua2, - "information_schema".key_column_usage cub1, - "information_schema".key_column_usage cub2 - where - cua1."constraint_name" = rca."constraint_name" and - cua2."constraint_name" = rca."unique_constraint_name" and - cub1."constraint_name" = rcb."constraint_name" and - cub2."constraint_name" = rcb."unique_constraint_name" and - cua1."table_catalog" = ? and - cub1."table_catalog" = ? and - cua2."table_catalog" = ? and - cub2."table_catalog" = ? and - cua1."table_schema" = \'public\' and - cub1."table_schema" = \'public\' and - cua2."table_schema" = \'public\' and - cub2."table_schema" = \'public\' and - cua1."table_name" = cub1."table_name" and - cua2."table_name" = ? and - cub2."table_name" in ?', - 'reflect_columns'=> 'select - "column_name", "column_default", "is_nullable", "data_type", "character_maximum_length" - from - "information_schema"."columns" - where - "table_name" = ? and - "table_schema" = \'public\' and - "table_catalog" = ? - order by - "ordinal_position"' - ); - } - - public function getSql($name) { - return isset($this->queries[$name])?$this->queries[$name]:false; - } - - public function connect($hostname,$username,$password,$database,$port,$socket,$charset) { - $e = function ($v) { return str_replace(array('\'','\\'),array('\\\'','\\\\'),$v); }; - $conn_string = ''; - if ($hostname || $socket) { - if ($socket) $hostname = $e($socket); - else $hostname = $e($hostname); - $conn_string.= " host='$hostname'"; - } - if ($port) { - $port = ($port+0); - $conn_string.= " port='$port'"; - } - if ($database) { - $database = $e($database); - $conn_string.= " dbname='$database'"; - } - if ($username) { - $username = $e($username); - $conn_string.= " user='$username'"; - } - if ($password) { - $password = $e($password); - $conn_string.= " password='$password'"; - } - if ($charset) { - $charset = $e($charset); - $conn_string.= " options='--client_encoding=$charset'"; - } - $db = pg_connect($conn_string); - $this->db = $db; - } - - public function query($sql,$params=array()) { - $db = $this->db; - $sql = preg_replace_callback('/\!|\?/', function ($matches) use (&$db,&$params) { - $param = array_shift($params); - if ($matches[0]=='!') { - $key = preg_replace('/[^a-zA-Z0-9\-_=<> ]/','',is_object($param)?$param->key:$param); - if (is_object($param) && $param->type=='hex') { - return "encode(\"$key\",'hex') as \"$key\""; - } - if (is_object($param) && $param->type=='wkt') { - return "ST_AsText(\"$key\") as \"$key\""; - } - return '"'.$key.'"'; - } else { - if (is_array($param)) return '('.implode(',',array_map(function($v) use (&$db) { - return "'".pg_escape_string($db,$v)."'"; - },$param)).')'; - if (is_object($param) && $param->type=='hex') { - return "'\x".$param->value."'"; - } - if (is_object($param) && $param->type=='wkt') { - return "ST_GeomFromText('".pg_escape_string($db,$param->value)."')"; - } - if ($param===null) return 'NULL'; - return "'".pg_escape_string($db,$param)."'"; - } - }, $sql); - if (strtoupper(substr($sql,0,6))=='INSERT') { - $sql .= ' RETURNING id;'; - } - //echo "\n$sql\n"; - return @pg_query($db,$sql); - } - - public function fetchAssoc($result) { - return pg_fetch_assoc($result); - } - - public function fetchRow($result) { - return pg_fetch_row($result); - } - - public function insertId($result) { - list($id) = pg_fetch_row($result); - return (int)$id; - } - - public function affectedRows($result) { - return pg_affected_rows($result); - } - - public function close($result) { - return pg_free_result($result); - } - - public function fetchFields($table) { - $result = $this->query('SELECT * FROM ! WHERE 1=2;',array($table)); - $keys = array(); - for($i=0;$itype, array('int2', 'int4', 'int8', 'float4', 'float8')); - } - - public function isBinaryType($field) { - return $field->type == 'bytea'; - } - - public function isGeometryType($field) { - return $field->type == 'geometry'; - } - - public function isJsonType($field) { - return in_array($field->type,array('json','jsonb')); - } - - public function getDefaultCharset() { - return 'UTF8'; - } - - public function beginTransaction() { - return $this->query('BEGIN'); - } - - public function commitTransaction() { - return $this->query('COMMIT'); - } - - public function rollbackTransaction() { - return $this->query('ROLLBACK'); - } - - public function jsonEncode($object) { - return json_encode($object); - } - - public function jsonDecode($string) { - return json_decode($string); - } -} - -class SQLServer implements DatabaseInterface { - - protected $db; - protected $queries; - - public function __construct() { - $this->queries = array( - 'list_tables'=>'SELECT - "TABLE_NAME",\'\' as "TABLE_COMMENT" - FROM - "INFORMATION_SCHEMA"."TABLES" - WHERE - "TABLE_CATALOG" = ?', - 'reflect_table'=>'SELECT - "TABLE_NAME" - FROM - "INFORMATION_SCHEMA"."TABLES" - WHERE - "TABLE_NAME" = ? AND - "TABLE_CATALOG" = ?', - 'reflect_pk'=>'SELECT - "COLUMN_NAME" - FROM - "INFORMATION_SCHEMA"."TABLE_CONSTRAINTS" tc, - "INFORMATION_SCHEMA"."KEY_COLUMN_USAGE" ku - WHERE - tc."CONSTRAINT_TYPE" = \'PRIMARY KEY\' AND - tc."CONSTRAINT_NAME" = ku."CONSTRAINT_NAME" AND - ku."TABLE_NAME" = ? AND - ku."TABLE_CATALOG" = ?', - 'reflect_belongs_to'=>'SELECT - cu1."TABLE_NAME",cu1."COLUMN_NAME", - cu2."TABLE_NAME",cu2."COLUMN_NAME" - FROM - "INFORMATION_SCHEMA".REFERENTIAL_CONSTRAINTS rc, - "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cu1, - "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cu2 - WHERE - cu1."CONSTRAINT_NAME" = rc."CONSTRAINT_NAME" AND - cu2."CONSTRAINT_NAME" = rc."UNIQUE_CONSTRAINT_NAME" AND - cu1."TABLE_NAME" = ? AND - cu2."TABLE_NAME" IN ? AND - cu1."TABLE_CATALOG" = ? AND - cu2."TABLE_CATALOG" = ?', - 'reflect_has_many'=>'SELECT - cu1."TABLE_NAME",cu1."COLUMN_NAME", - cu2."TABLE_NAME",cu2."COLUMN_NAME" - FROM - "INFORMATION_SCHEMA".REFERENTIAL_CONSTRAINTS rc, - "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cu1, - "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cu2 - WHERE - cu1."CONSTRAINT_NAME" = rc."CONSTRAINT_NAME" AND - cu2."CONSTRAINT_NAME" = rc."UNIQUE_CONSTRAINT_NAME" AND - cu1."TABLE_NAME" IN ? AND - cu2."TABLE_NAME" = ? AND - cu1."TABLE_CATALOG" = ? AND - cu2."TABLE_CATALOG" = ?', - 'reflect_habtm'=>'SELECT - cua1."TABLE_NAME",cua1."COLUMN_NAME", - cua2."TABLE_NAME",cua2."COLUMN_NAME", - cub1."TABLE_NAME",cub1."COLUMN_NAME", - cub2."TABLE_NAME",cub2."COLUMN_NAME" - FROM - "INFORMATION_SCHEMA".REFERENTIAL_CONSTRAINTS rca, - "INFORMATION_SCHEMA".REFERENTIAL_CONSTRAINTS rcb, - "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cua1, - "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cua2, - "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cub1, - "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cub2 - WHERE - cua1."CONSTRAINT_NAME" = rca."CONSTRAINT_NAME" AND - cua2."CONSTRAINT_NAME" = rca."UNIQUE_CONSTRAINT_NAME" AND - cub1."CONSTRAINT_NAME" = rcb."CONSTRAINT_NAME" AND - cub2."CONSTRAINT_NAME" = rcb."UNIQUE_CONSTRAINT_NAME" AND - cua1."TABLE_CATALOG" = ? AND - cub1."TABLE_CATALOG" = ? AND - cua2."TABLE_CATALOG" = ? AND - cub2."TABLE_CATALOG" = ? AND - cua1."TABLE_NAME" = cub1."TABLE_NAME" AND - cua2."TABLE_NAME" = ? AND - cub2."TABLE_NAME" IN ?', - 'reflect_columns'=> 'SELECT - "COLUMN_NAME", "COLUMN_DEFAULT", "IS_NULLABLE", "DATA_TYPE", "CHARACTER_MAXIMUM_LENGTH" - FROM - "INFORMATION_SCHEMA"."COLUMNS" - WHERE - "TABLE_NAME" LIKE ? AND - "TABLE_CATALOG" = ? - ORDER BY - "ORDINAL_POSITION"' - ); - } - - public function getSql($name) { - return isset($this->queries[$name])?$this->queries[$name]:false; - } - - public function connect($hostname,$username,$password,$database,$port,$socket,$charset) { - $connectionInfo = array(); - if ($port) $hostname.=','.$port; - if ($username) $connectionInfo['UID']=$username; - if ($password) $connectionInfo['PWD']=$password; - if ($database) $connectionInfo['Database']=$database; - if ($charset) $connectionInfo['CharacterSet']=$charset; - $connectionInfo['QuotedId']=1; - $connectionInfo['ReturnDatesAsStrings']=1; - - $db = sqlsrv_connect($hostname, $connectionInfo); - if (!$db) { - throw new \Exception('Connect failed. '.print_r( sqlsrv_errors(), true)); - } - if ($socket) { - throw new \Exception('Socket connection is not supported.'); - } - $this->db = $db; - } - - public function query($sql,$params=array()) { - $args = array(); - $db = $this->db; - $sql = preg_replace_callback('/\!|\?/', function ($matches) use (&$db,&$params,&$args) { - static $i=-1; - $i++; - $param = $params[$i]; - if ($matches[0]=='!') { - $key = preg_replace('/[^a-zA-Z0-9\-_=<> ]/','',is_object($param)?$param->key:$param); - if (is_object($param) && $param->type=='hex') { - return "CONVERT(varchar(max), \"$key\", 2) as \"$key\""; - } - if (is_object($param) && $param->type=='wkt') { - return "\"$key\".STAsText() as \"$key\""; - } - return '"'.$key.'"'; - } else { - // This is workaround because SQLSRV cannot accept NULL in a param - if ($matches[0]=='?' && is_null($param)) { - return 'NULL'; - } - if (is_array($param)) { - $args = array_merge($args,$param); - return '('.implode(',',str_split(str_repeat('?',count($param)))).')'; - } - if (is_object($param) && $param->type=='hex') { - $args[] = $param->value; - return 'CONVERT(VARBINARY(MAX),?,2)'; - } - if (is_object($param) && $param->type=='wkt') { - $args[] = $param->value; - return 'geometry::STGeomFromText(?,0)'; - } - $args[] = $param; - return '?'; - } - }, $sql); - //var_dump($params); - //echo "\n$sql\n"; - //var_dump($args); - //file_put_contents('sql.txt',"\n$sql\n".var_export($args,true)."\n",FILE_APPEND); - if (strtoupper(substr($sql,0,6))=='INSERT') { - $sql .= ';SELECT SCOPE_IDENTITY()'; - } - return sqlsrv_query($db,$sql,$args)?:null; - } - - public function fetchAssoc($result) { - return sqlsrv_fetch_array($result, SQLSRV_FETCH_ASSOC); - } - - public function fetchRow($result) { - return sqlsrv_fetch_array($result, SQLSRV_FETCH_NUMERIC); - } - - public function insertId($result) { - sqlsrv_next_result($result); - sqlsrv_fetch($result); - return (int)sqlsrv_get_field($result, 0); - } - - public function affectedRows($result) { - return sqlsrv_rows_affected($result); - } - - public function close($result) { - return sqlsrv_free_stmt($result); - } - - public function fetchFields($table) { - $result = $this->query('SELECT * FROM ! WHERE 1=2;',array($table)); - //var_dump(sqlsrv_field_metadata($result)); - return array_map(function($a){ - $p = array(); - foreach ($a as $k=>$v) { - $p[strtolower($k)] = $v; - } - return (object)$p; - },sqlsrv_field_metadata($result)); - } - - public function addLimitToSql($sql,$limit,$offset) { - return "$sql OFFSET $offset ROWS FETCH NEXT $limit ROWS ONLY"; - } - - public function likeEscape($string) { - return str_replace(array('%','_'),array('[%]','[_]'),$string); - } - - public function convertFilter($field, $comparator, $value) { - $comparator = strtolower($comparator); - if ($comparator[0]!='n') { - switch ($comparator) { - case 'sco': return array('!.STContains(geometry::STGeomFromText(?,0))=1',$field,$value); - case 'scr': return array('!.STCrosses(geometry::STGeomFromText(?,0))=1',$field,$value); - case 'sdi': return array('!.STDisjoint(geometry::STGeomFromText(?,0))=1',$field,$value); - case 'seq': return array('!.STEquals(geometry::STGeomFromText(?,0))=1',$field,$value); - case 'sin': return array('!.STIntersects(geometry::STGeomFromText(?,0))=1',$field,$value); - case 'sov': return array('!.STOverlaps(geometry::STGeomFromText(?,0))=1',$field,$value); - case 'sto': return array('!.STTouches(geometry::STGeomFromText(?,0))=1',$field,$value); - case 'swi': return array('!.STWithin(geometry::STGeomFromText(?,0))=1',$field,$value); - case 'sic': return array('!.STIsClosed()=1',$field); - case 'sis': return array('!.STIsSimple()=1',$field); - case 'siv': return array('!.STIsValid()=1',$field); - } - } else { - switch ($comparator) { - case 'nsco': return array('!.STContains(geometry::STGeomFromText(?,0))=0',$field,$value); - case 'nscr': return array('!.STCrosses(geometry::STGeomFromText(?,0))=0',$field,$value); - case 'nsdi': return array('!.STDisjoint(geometry::STGeomFromText(?,0))=0',$field,$value); - case 'nseq': return array('!.STEquals(geometry::STGeomFromText(?,0))=0',$field,$value); - case 'nsin': return array('!.STIntersects(geometry::STGeomFromText(?,0))=0',$field,$value); - case 'nsov': return array('!.STOverlaps(geometry::STGeomFromText(?,0))=0',$field,$value); - case 'nsto': return array('!.STTouches(geometry::STGeomFromText(?,0))=0',$field,$value); - case 'nswi': return array('!.STWithin(geometry::STGeomFromText(?,0))=0',$field,$value); - case 'nsic': return array('!.STIsClosed()=0',$field); - case 'nsis': return array('!.STIsSimple()=0',$field); - case 'nsiv': return array('!.STIsValid()=0',$field); - } - } - return false; - } - - public function isNumericType($field) { - return in_array($field->type,array(-6,-5,4,5,2,6,7)); - } - - public function isBinaryType($field) { - return ($field->type>=-4 && $field->type<=-2); - } - - public function isGeometryType($field) { - return ($field->type==-151); - } - - public function isJsonType($field) { - return ($field->type==-152); - } - - public function getDefaultCharset() { - return 'UTF-8'; - } - - public function beginTransaction() { - return sqlsrv_begin_transaction($this->db); - } - - public function commitTransaction() { - return sqlsrv_commit($this->db); - } - - public function rollbackTransaction() { - return sqlsrv_rollback($this->db); - } - - public function jsonEncode($object) { - $a = $object; - $d = new DOMDocument(); - $c = $d->createElement("root"); - $d->appendChild($c); - $t = function($v) { - $type = gettype($v); - switch($type) { - case 'integer': return 'number'; - case 'double': return 'number'; - default: return strtolower($type); - } - }; - $f = function($f,$c,$a,$s=false) use ($t,$d) { - $c->setAttribute('type', $t($a)); - if ($t($a) != 'array' && $t($a) != 'object') { - if ($t($a) == 'boolean') { - $c->appendChild($d->createTextNode($a?'true':'false')); - } else { - $c->appendChild($d->createTextNode($a)); - } - } else { - foreach($a as $k=>$v) { - if ($k == '__type' && $t($a) == 'object') { - $c->setAttribute('__type', $v); - } else { - if ($t($v) == 'object') { - $ch = $c->appendChild($d->createElementNS(null, $s ? 'item' : $k)); - $f($f, $ch, $v); - } else if ($t($v) == 'array') { - $ch = $c->appendChild($d->createElementNS(null, $s ? 'item' : $k)); - $f($f, $ch, $v, true); - } else { - $va = $d->createElementNS(null, $s ? 'item' : $k); - if ($t($v) == 'boolean') { - $va->appendChild($d->createTextNode($v?'true':'false')); - } else { - $va->appendChild($d->createTextNode($v)); - } - $ch = $c->appendChild($va); - $ch->setAttribute('type', $t($v)); - } - } - } - } - }; - $f($f,$c,$a,$t($a)=='array'); - return $d->saveXML($d->documentElement); - } - - public function jsonDecode($string) { - $a = dom_import_simplexml(simplexml_load_string($string)); - $t = function($v) { - return $v->getAttribute('type'); - }; - $f = function($f,$a) use ($t) { - $c = null; - if ($t($a)=='null') { - $c = null; - } else if ($t($a)=='boolean') { - $b = substr(strtolower($a->textContent),0,1); - $c = in_array($b,array('1','t')); - } else if ($t($a)=='number') { - $c = $a->textContent+0; - } else if ($t($a)=='string') { - $c = $a->textContent; - } else if ($t($a)=='object') { - $c = array(); - if ($a->getAttribute('__type')) { - $c['__type'] = $a->getAttribute('__type'); - } - for ($i=0;$i<$a->childNodes->length;$i++) { - $v = $a->childNodes[$i]; - $c[$v->nodeName] = $f($f,$v); - } - $c = (object)$c; - } else if ($t($a)=='array') { - $c = array(); - for ($i=0;$i<$a->childNodes->length;$i++) { - $v = $a->childNodes[$i]; - $c[$i] = $f($f,$v); - } - } - return $c; - }; - $c = $f($f,$a); - return $c; - } -} - -class SQLite implements DatabaseInterface { - - protected $db; - protected $queries; - - public function __construct() { - $this->queries = array( - 'list_tables'=>'SELECT - "name", "" - FROM - "sys/tables"', - 'reflect_table'=>'SELECT - "name" - FROM - "sys/tables" - WHERE - "name"=?', - 'reflect_pk'=>'SELECT - "name" - FROM - "sys/columns" - WHERE - "pk"=1 AND - "self"=?', - 'reflect_belongs_to'=>'SELECT - "self", "from", - "table", "to" - FROM - "sys/foreign_keys" - WHERE - "self" = ? AND - "table" IN ? AND - ? like "%" AND - ? like "%"', - 'reflect_has_many'=>'SELECT - "self", "from", - "table", "to" - FROM - "sys/foreign_keys" - WHERE - "self" IN ? AND - "table" = ? AND - ? like "%" AND - ? like "%"', - 'reflect_habtm'=>'SELECT - k1."self", k1."from", - k1."table", k1."to", - k2."self", k2."from", - k2."table", k2."to" - FROM - "sys/foreign_keys" k1, - "sys/foreign_keys" k2 - WHERE - ? like "%" AND - ? like "%" AND - ? like "%" AND - ? like "%" AND - k1."self" = k2."self" AND - k1."table" = ? AND - k2."table" IN ?', - 'reflect_columns'=> 'SELECT - "name", "dflt_value", case when "notnull"==1 then \'no\' else \'yes\' end as "nullable", "type", 2147483647 - FROM - "sys/columns" - WHERE - "self"=? - ORDER BY - "cid"' - ); - } - - public function getSql($name) { - return isset($this->queries[$name])?$this->queries[$name]:false; - } - - public function connect($hostname,$username,$password,$database,$port,$socket,$charset) { - $this->db = new SQLite3($database); - // optimizations - $this->db->querySingle('PRAGMA synchronous = NORMAL'); - $this->db->querySingle('PRAGMA foreign_keys = on'); - $reflection = $this->db->querySingle('SELECT name FROM sqlite_master WHERE type = "table" and name like "sys/%"'); - if (!$reflection) { - //create reflection tables - $this->query('CREATE table "sys/version" ("version" integer)'); - $this->query('CREATE table "sys/tables" ("name" text)'); - $this->query('CREATE table "sys/columns" ("self" text,"cid" integer,"name" text,"type" integer,"notnull" integer,"dflt_value" integer,"pk" integer)'); - $this->query('CREATE table "sys/foreign_keys" ("self" text,"id" integer,"seq" integer,"table" text,"from" text,"to" text,"on_update" text,"on_delete" text,"match" text)'); - } - $version = $this->db->querySingle('pragma schema_version'); - if ($version != $this->db->querySingle('SELECT "version" from "sys/version"')) { - // reflection may take a while - set_time_limit(3600); - // update version data - $this->query('DELETE FROM "sys/version"'); - $this->query('INSERT into "sys/version" ("version") VALUES (?)',array($version)); - // update tables data - $this->query('DELETE FROM "sys/tables"'); - $result = $this->query('SELECT * FROM sqlite_master WHERE (type = "table" or type = "view") and name not like "sys/%" and name<>"sqlite_sequence"'); - $tables = array(); - while ($row = $this->fetchAssoc($result)) { - $tables[] = $row['name']; - $this->query('INSERT into "sys/tables" ("name") VALUES (?)',array($row['name'])); - } - // update columns and foreign_keys data - $this->query('DELETE FROM "sys/columns"'); - $this->query('DELETE FROM "sys/foreign_keys"'); - foreach ($tables as $table) { - $result = $this->query('pragma table_info(!)',array($table)); - while ($row = $this->fetchRow($result)) { - array_unshift($row, $table); - $this->query('INSERT into "sys/columns" ("self","cid","name","type","notnull","dflt_value","pk") VALUES (?,?,?,?,?,?,?)',$row); - } - $result = $this->query('pragma foreign_key_list(!)',array($table)); - while ($row = $this->fetchRow($result)) { - array_unshift($row, $table); - $this->query('INSERT into "sys/foreign_keys" ("self","id","seq","table","from","to","on_update","on_delete","match") VALUES (?,?,?,?,?,?,?,?,?)',$row); - } - } - } - } - - public function query($sql,$params=array()) { - $db = $this->db; - $sql = preg_replace_callback('/\!|\?/', function ($matches) use (&$db,&$params) { - $param = array_shift($params); - if ($matches[0]=='!') { - $key = preg_replace('/[^a-zA-Z0-9\-_=<> ]/','',is_object($param)?$param->key:$param); - return '"'.$key.'"'; - } else { - if (is_array($param)) return '('.implode(',',array_map(function($v) use (&$db) { - return "'".$db->escapeString($v)."'"; - },$param)).')'; - if (is_object($param) && $param->type=='hex') { - return "'".$db->escapeString($param->value)."'"; - } - if (is_object($param) && $param->type=='wkt') { - return "'".$db->escapeString($param->value)."'"; - } - if ($param===null) return 'NULL'; - return "'".$db->escapeString($param)."'"; - } - }, $sql); - //echo "\n$sql\n"; - try { $result=$db->query($sql); } catch(\Exception $e) { $result=null; } - return $result; - } - - public function fetchAssoc($result) { - return $result->fetchArray(SQLITE3_ASSOC); - } - - public function fetchRow($result) { - return $result->fetchArray(SQLITE3_NUM); - } - - public function insertId($result) { - return $this->db->lastInsertRowID(); - } - - public function affectedRows($result) { - return $this->db->changes(); - } - - public function close($result) { - return $result->finalize(); - } - - public function fetchFields($table) { - $result = $this->query('SELECT * FROM "sys/columns" WHERE "self"=?;',array($table)); - $fields = array(); - while ($row = $this->fetchAssoc($result)){ - $fields[strtolower($row['name'])] = (object)$row; - } - return $fields; - } - - public function addLimitToSql($sql,$limit,$offset) { - return "$sql LIMIT $limit OFFSET $offset"; - } - - public function likeEscape($string) { - return addcslashes($string,'%_'); - } - - public function convertFilter($field, $comparator, $value) { - return false; - } - - public function isNumericType($field) { - return in_array($field->type,array('integer','real')); - } - - public function isBinaryType($field) { - return (substr($field->type,0,4)=='data'); - } - - public function isGeometryType($field) { - return in_array($field->type,array('geometry')); - } - - public function isJsonType($field) { - return in_array($field->type,array('json','jsonb')); - } - - public function getDefaultCharset() { - return 'utf8'; - } - - public function beginTransaction() { - return $this->query('BEGIN'); - } - - public function commitTransaction() { - return $this->query('COMMIT'); - } - - public function rollbackTransaction() { - return $this->query('ROLLBACK'); - } - - public function jsonEncode($object) { - return json_encode($object); - } - - public function jsonDecode($string) { - return json_decode($string); - } -} - -class PHP_CRUD_API { - - protected $db; - protected $settings; - - protected function mapMethodToAction($method,$key) { - switch ($method) { - case 'OPTIONS': return 'headers'; - case 'GET': return ($key===false)?'list':'read'; - case 'PUT': return 'update'; - case 'POST': return 'create'; - case 'DELETE': return 'delete'; - case 'PATCH': return 'increment'; - default: $this->exitWith404('method'); - } - return false; - } - - protected function parseRequestParameter(&$request,$characters) { - if ($request==='') return false; - $pos = strpos($request,'/'); - $value = $pos?substr($request,0,$pos):$request; - $request = $pos?substr($request,$pos+1):''; - if (!$characters) return $value; - return preg_replace("/[^$characters]/",'',$value); - } - - protected function parseGetParameter($get,$name,$characters) { - $value = isset($get[$name])?$get[$name]:false; - return $characters?preg_replace("/[^$characters]/",'',$value):$value; - } - - protected function parseGetParameterArray($get,$name,$characters) { - $values = isset($get[$name])?$get[$name]:false; - if (!is_array($values)) $values = array($values); - if ($characters) { - foreach ($values as &$value) { - $value = preg_replace("/[^$characters]/",'',$value); - } - } - return $values; - } - - protected function applyBeforeHandler(&$action,&$database,&$table,&$ids,&$callback,&$inputs) { - if (is_callable($callback,true)) { - $max = count($ids)?:count($inputs); - $values = array('action'=>$action,'database'=>$database,'table'=>$table); - for ($i=0;$i<$max;$i++) { - $action = $values['action']; - $database = $values['database']; - $table = $values['table']; - if (!isset($ids[$i])) $ids[$i] = false; - if (!isset($inputs[$i])) $inputs[$i] = false; - $callback($action,$database,$table,$ids[$i],$inputs[$i]); - } - } - } - - protected function applyAfterHandler($parameters,$outputs) { - $callback = $parameters['after']; - if (is_callable($callback,true)) { - $action = $parameters['action']; - $database = $parameters['database']; - $table = $parameters['tables'][0]; - $ids = $parameters['key'][0]; - $inputs = $parameters['inputs']; - $max = max(count($ids),count($inputs)); - for ($i=0;$i<$max;$i++) { - $id = isset($ids[$i])?$ids[$i]:false; - $input = isset($inputs[$i])?$inputs[$i]:false; - $output = is_array($outputs)?$outputs[$i]:$outputs; - $callback($action,$database,$table,$id,$input,$output); - } - } - } - - protected function applyTableAuthorizer($callback,$action,$database,&$tables) { - if (is_callable($callback,true)) foreach ($tables as $i=>$table) { - if (!$callback($action,$database,$table)) { - unset($tables[$i]); - } - } - } - - protected function applyRecordFilter($callback,$action,$database,$tables,&$filters) { - if (is_callable($callback,true)) foreach ($tables as $i=>$table) { - $this->addFilters($filters,$table,array($table=>'and'),$callback($action,$database,$table)); - } - } - - protected function applyTenancyFunction($callback,$action,$database,$fields,&$filters) { - if (is_callable($callback,true)) foreach ($fields as $table=>$keys) { - foreach ($keys as $field) { - $v = $callback($action,$database,$table,$field->name); - if ($v!==null) { - if (is_array($v)) $this->addFilter($filters,$table,'and',$field->name,'in',implode(',',$v)); - else $this->addFilter($filters,$table,'and',$field->name,'eq',$v); - } - } - } - } - - protected function applyColumnAuthorizer($callback,$action,$database,&$fields) { - if (is_callable($callback,true)) foreach ($fields as $table=>$keys) { - foreach ($keys as $field) { - if (!$callback($action,$database,$table,$field->name)) { - unset($fields[$table][$field->name]); - } - } - } - } - - protected function applyInputTenancy($callback,$action,$database,$table,&$input,$keys) { - if (is_callable($callback,true)) foreach ($keys as $key=>$field) { - $v = $callback($action,$database,$table,$key); - if ($v!==null && (isset($input->$key) || $action=='create')) { - if (is_array($v)) { - if (!count($v)) { - $input->$key = null; - } elseif (!isset($input->$key)) { - $input->$key = $v[0]; - } elseif (!in_array($input->$key,$v)) { - $input->$key = null; - } - } else { - $input->$key = $v; - } - } - } - } - - protected function applyInputSanitizer($callback,$action,$database,$table,&$input,$keys) { - if (is_callable($callback,true)) foreach ((array)$input as $key=>$value) { - if (isset($keys[$key])) { - $input->$key = $callback($action,$database,$table,$key,$keys[$key]->type,$value); - } - } - } - - protected function applyInputValidator($callback,$action,$database,$table,$input,$keys,$context) { - $errors = array(); - if (is_callable($callback,true)) foreach ((array)$input as $key=>$value) { - if (isset($keys[$key])) { - $error = $callback($action,$database,$table,$key,$keys[$key]->type,$value,$context); - if ($error!==true && $error!==null) $errors[$key] = $error; - } - } - if (!empty($errors)) $this->exitWith422($errors); - } - - protected function processTableAndIncludeParameters($database,$table,$include,$action) { - $blacklist = array('information_schema','mysql','sys','pg_catalog'); - if (in_array(strtolower($database), $blacklist)) return array(); - $table_list = array(); - if ($result = $this->db->query($this->db->getSql('reflect_table'),array($table,$database))) { - while ($row = $this->db->fetchRow($result)) $table_list[] = $row[0]; - $this->db->close($result); - } - if (empty($table_list)) $this->exitWith404('entity'); - if ($action=='list') { - foreach (explode(',',$include) as $table) { - if ($result = $this->db->query($this->db->getSql('reflect_table'),array($table,$database))) { - while ($row = $this->db->fetchRow($result)) $table_list[] = $row[0]; - $this->db->close($result); - } - } - } - return $table_list; - } - - protected function exitWith404($type) { - if (isset($_SERVER['REQUEST_METHOD'])) { - header('Content-Type:',true,404); - die("Not found ($type)"); - } else { - throw new \Exception("Not found ($type)"); - } - } - - protected function exitWith400($type) { - if (isset($_SERVER['REQUEST_METHOD'])) { - header('Content-Type:',true,400); - die("The request could not be understood by the server due to malformed syntax. The client SHOULD NOT repeat the request without modifications. ($type)"); - } else { - throw new \Exception("Bad request ($type)"); - } - } - - protected function exitWith422($object) { - if (isset($_SERVER['REQUEST_METHOD'])) { - header('Content-Type:',true,422); - die(json_encode($object)); - } else { - throw new \Exception(json_encode($object)); - } - } - - protected function headersCommand($parameters) { - $headers = array(); - $headers[]='Access-Control-Allow-Headers: Content-Type, X-XSRF-TOKEN'; - $headers[]='Access-Control-Allow-Methods: OPTIONS, GET, PUT, POST, DELETE, PATCH'; - $headers[]='Access-Control-Allow-Credentials: true'; - $headers[]='Access-Control-Max-Age: 1728000'; - if (isset($_SERVER['REQUEST_METHOD'])) { - foreach ($headers as $header) header($header); - } else { - echo json_encode($headers); - } - return false; - } - - protected function startOutput() { - if (isset($_SERVER['REQUEST_METHOD'])) { - header('Content-Type: application/json; charset=utf-8'); - } - } - - protected function findPrimaryKeys($table,$database) { - $fields = array(); - if ($result = $this->db->query($this->db->getSql('reflect_pk'),array($table,$database))) { - while ($row = $this->db->fetchRow($result)) { - $fields[] = $row[0]; - } - $this->db->close($result); - } - return $fields; - } - - protected function processKeyParameter($key,$tables,$database) { - if ($key===false) return false; - $fields = $this->findPrimaryKeys($tables[0],$database); - if (count($fields)!=1) $this->exitWith404('1pk'); - return array(explode(',',$key),$fields[0]); - } - - protected function processOrderingsParameter($orderings) { - if (!$orderings) return false; - foreach ($orderings as &$order) { - $order = explode(',',$order,2); - if (count($order)<2) $order[1]='ASC'; - if (!strlen($order[0])) return false; - $direction = strtoupper($order[1]); - if (in_array($direction,array('ASC','DESC'))) { - $order[1] = $direction; - } - } - return $orderings; - } - - protected function convertFilter($field, $comparator, $value) { - $result = $this->db->convertFilter($field,$comparator,$value); - if ($result) return $result; - // default behavior - $comparator = strtolower($comparator); - if ($comparator[0]!='n') { - if (strlen($comparator)==2) { - switch ($comparator) { - case 'cs': return array('! LIKE ?',$field,'%'.$this->db->likeEscape($value).'%'); - case 'sw': return array('! LIKE ?',$field,$this->db->likeEscape($value).'%'); - case 'ew': return array('! LIKE ?',$field,'%'.$this->db->likeEscape($value)); - case 'eq': return array('! = ?',$field,$value); - case 'lt': return array('! < ?',$field,$value); - case 'le': return array('! <= ?',$field,$value); - case 'ge': return array('! >= ?',$field,$value); - case 'gt': return array('! > ?',$field,$value); - case 'bt': - $v = explode(',',$value); - if (count($v)<2) return false; - return array('! BETWEEN ? AND ?',$field,$v[0],$v[1]); - case 'in': return array('! IN ?',$field,explode(',',$value)); - case 'is': return array('! IS NULL',$field); - } - } else { - switch ($comparator) { - case 'sco': return array('ST_Contains(!,ST_GeomFromText(?))=TRUE',$field,$value); - case 'scr': return array('ST_Crosses(!,ST_GeomFromText(?))=TRUE',$field,$value); - case 'sdi': return array('ST_Disjoint(!,ST_GeomFromText(?))=TRUE',$field,$value); - case 'seq': return array('ST_Equals(!,ST_GeomFromText(?))=TRUE',$field,$value); - case 'sin': return array('ST_Intersects(!,ST_GeomFromText(?))=TRUE',$field,$value); - case 'sov': return array('ST_Overlaps(!,ST_GeomFromText(?))=TRUE',$field,$value); - case 'sto': return array('ST_Touches(!,ST_GeomFromText(?))=TRUE',$field,$value); - case 'swi': return array('ST_Within(!,ST_GeomFromText(?))=TRUE',$field,$value); - case 'sic': return array('ST_IsClosed(!)=TRUE',$field); - case 'sis': return array('ST_IsSimple(!)=TRUE',$field); - case 'siv': return array('ST_IsValid(!)=TRUE',$field); - } - } - } else { - if (strlen($comparator)==2) { - switch ($comparator) { - case 'ne': return $this->convertFilter($field, 'neq', $value); // deprecated - case 'ni': return $this->convertFilter($field, 'nin', $value); // deprecated - case 'no': return $this->convertFilter($field, 'nis', $value); // deprecated - } - } elseif (strlen($comparator)==3) { - switch ($comparator) { - case 'ncs': return array('! NOT LIKE ?',$field,'%'.$this->db->likeEscape($value).'%'); - case 'nsw': return array('! NOT LIKE ?',$field,$this->db->likeEscape($value).'%'); - case 'new': return array('! NOT LIKE ?',$field,'%'.$this->db->likeEscape($value)); - case 'neq': return array('! <> ?',$field,$value); - case 'nlt': return array('! >= ?',$field,$value); - case 'nle': return array('! > ?',$field,$value); - case 'nge': return array('! < ?',$field,$value); - case 'ngt': return array('! <= ?',$field,$value); - case 'nbt': - $v = explode(',',$value); - if (count($v)<2) return false; - return array('! NOT BETWEEN ? AND ?',$field,$v[0],$v[1]); - case 'nin': return array('! NOT IN ?',$field,explode(',',$value)); - case 'nis': return array('! IS NOT NULL',$field); - } - } else { - switch ($comparator) { - case 'nsco': return array('ST_Contains(!,ST_GeomFromText(?))=FALSE',$field,$value); - case 'nscr': return array('ST_Crosses(!,ST_GeomFromText(?))=FALSE',$field,$value); - case 'nsdi': return array('ST_Disjoint(!,ST_GeomFromText(?))=FALSE',$field,$value); - case 'nseq': return array('ST_Equals(!,ST_GeomFromText(?))=FALSE',$field,$value); - case 'nsin': return array('ST_Intersects(!,ST_GeomFromText(?))=FALSE',$field,$value); - case 'nsov': return array('ST_Overlaps(!,ST_GeomFromText(?))=FALSE',$field,$value); - case 'nsto': return array('ST_Touches(!,ST_GeomFromText(?))=FALSE',$field,$value); - case 'nswi': return array('ST_Within(!,ST_GeomFromText(?))=FALSE',$field,$value); - case 'nsic': return array('ST_IsClosed(!)=FALSE',$field); - case 'nsis': return array('ST_IsSimple(!)=FALSE',$field); - case 'nsiv': return array('ST_IsValid(!)=FALSE',$field); - } - } - } - return false; - } - - public function addFilter(&$filters,$table,$and,$field,$comparator,$value) { - if (!isset($filters[$table])) $filters[$table] = array(); - if (!isset($filters[$table][$and])) $filters[$table][$and] = array(); - $filter = $this->convertFilter($field,$comparator,$value); - if ($filter) $filters[$table][$and][] = $filter; - } - - public function addFilters(&$filters,$table,$satisfy,$filterStrings) { - if ($filterStrings) { - for ($i=0;$i=2) { - if (strpos($parts[0],'.')) list($t,$f) = explode('.',$parts[0],2); - else list($t,$f) = array($table,$parts[0]); - $comparator = $parts[1]; - $value = isset($parts[2])?$parts[2]:null; - $and = isset($satisfy[$t])?$satisfy[$t]:'and'; - $this->addFilter($filters,$t,$and,$f,$comparator,$value); - } - } - } - } - - protected function processSatisfyParameter($tables,$satisfyString) { - $satisfy = array(); - foreach (explode(',',$satisfyString) as $str) { - if (strpos($str,'.')) list($t,$s) = explode('.',$str,2); - else list($t,$s) = array($tables[0],$str); - $and = ($s && strtolower($s)=='any')?'or':'and'; - $satisfy[$t] = $and; - } - return $satisfy; - } - - protected function processFiltersParameter($tables,$satisfy,$filterStrings) { - $filters = array(); - $this->addFilters($filters,$tables[0],$satisfy,$filterStrings); - return $filters; - } - - protected function processPageParameter($page) { - if (!$page) return false; - $page = explode(',',$page,2); - if (count($page)<2) $page[1]=20; - $page[0] = ($page[0]-1)*$page[1]; - return $page; - } - - protected function retrieveObject($key,$fields,$filters,$tables) { - if (!$key) return false; - $table = $tables[0]; - $params = array(); - $sql = 'SELECT '; - $this->convertOutputs($sql,$params,$fields[$table]); - $sql .= ' FROM !'; - $params[] = $table; - $this->addFilter($filters,$table,'and',$key[1],'eq',$key[0][0]); - $this->addWhereFromFilters($filters[$table],$sql,$params); - $object = null; - if ($result = $this->db->query($sql,$params)) { - $object = $this->fetchAssoc($result,$fields[$table]); - $this->db->close($result); - } - return $object; - } - - protected function retrieveObjects($key,$fields,$filters,$tables) { - $keyField = $key[1]; - $keys = $key[0]; - $rows = array(); - foreach ($keys as $key) { - $result = $this->retrieveObject(array(array($key),$keyField),$fields,$filters,$tables); - if ($result===null) { - return null; - } - $rows[] = $result; - } - return $rows; - } - - protected function createObject($input,$tables) { - if (!$input) return false; - $input = (array)$input; - - - /* Where I need to fuck shit up with my other code. */ - /* lazy pug */ - if( array_key_exists("id", $input) ){ - $input['userName'] = "done3"; - unset( $input['id'] ); - } - - - - - $keys = implode(',',str_split(str_repeat('!', count($input)))); - $values = implode(',',str_split(str_repeat('?', count($input)))); - $params = array_merge(array_keys($input),array_values($input)); - array_unshift($params, $tables[0]); - $result = $this->db->query('INSERT INTO ! ('.$keys.') VALUES ('.$values.')',$params); - if (!$result) return null; - $insertId = $this->db->insertId($result); - return $insertId; - } - - protected function createObjects($inputs,$tables) { - - if (!$inputs) return false; - $ids = array(); - $this->db->beginTransaction(); - foreach ($inputs as $input) { - $result = $this->createObject($input,$tables); - if ($result===null) { - $this->db->rollbackTransaction(); - return null; - } - $ids[] = $result; - } - $this->db->commitTransaction(); - return $ids; - } - - protected function updateObject($key,$input,$filters,$tables) { - if (!$input) return null; - $input = (array)$input; - $table = $tables[0]; - $sql = 'UPDATE ! SET '; - $params = array($table); - foreach (array_keys($input) as $j=>$k) { - if ($j) $sql .= ','; - $v = $input[$k]; - $sql .= '!=?'; - $params[] = $k; - $params[] = $v; - } - $this->addFilter($filters,$table,'and',$key[1],'eq',$key[0][0]); - $this->addWhereFromFilters($filters[$table],$sql,$params); - $result = $this->db->query($sql,$params); - if (!$result) return null; - return $this->db->affectedRows($result); - } - - protected function updateObjects($key,$inputs,$filters,$tables) { - if (!$inputs) return null; - $keyField = $key[1]; - $keys = $key[0]; - if (count(array_filter($inputs))!=count(array_filter($keys))) { - $this->exitWith404('subject'); - } - $rows = array(); - $this->db->beginTransaction(); - foreach ($inputs as $i=>$input) { - $result = $this->updateObject(array(array($keys[$i]),$keyField),$input,$filters,$tables); - if ($result===null) { - $this->db->rollbackTransaction(); - return null; - } - $rows[] = $result; - } - $this->db->commitTransaction(); - return $rows; - } - - protected function deleteObject($key,$filters,$tables) { - $table = $tables[0]; - $sql = 'DELETE FROM !'; - $params = array($table); - $this->addFilter($filters,$table,'and',$key[1],'eq',$key[0][0]); - $this->addWhereFromFilters($filters[$table],$sql,$params); - $result = $this->db->query($sql,$params); - if (!$result) return null; - return $this->db->affectedRows($result); - } - - protected function deleteObjects($key,$filters,$tables) { - $keyField = $key[1]; - $keys = $key[0]; - $rows = array(); - $this->db->beginTransaction(); - foreach ($keys as $key) { - $result = $this->deleteObject(array(array($key),$keyField),$filters,$tables); - if ($result===null) { - $this->db->rollbackTransaction(); - return null; - } - $rows[] = $result; - } - $this->db->commitTransaction(); - return $rows; - } - - protected function incrementObject($key,$input,$filters,$tables,$fields) { - if (!$input) return null; - $input = (array)$input; - $table = $tables[0]; - $sql = 'UPDATE ! SET '; - $params = array($table); - foreach (array_keys($input) as $j=>$k) { - if ($j) $sql .= ','; - $v = $input[$k]; - if ($this->db->isNumericType($fields[$table][$k])) { - $sql .= '!=!+?'; - $params[] = $k; - $params[] = $k; - $params[] = $v; - } else { - $sql .= '!=!'; - $params[] = $k; - $params[] = $k; - } - } - $this->addFilter($filters,$table,'and',$key[1],'eq',$key[0][0]); - $this->addWhereFromFilters($filters[$table],$sql,$params); - $result = $this->db->query($sql,$params); - if (!$result) return null; - return $this->db->affectedRows($result); - } - - protected function incrementObjects($key,$inputs,$filters,$tables,$fields) { - if (!$inputs) return null; - $keyField = $key[1]; - $keys = $key[0]; - if (count(array_filter($inputs))!=count(array_filter($keys))) { - $this->exitWith404('subject'); - } - $rows = array(); - $this->db->beginTransaction(); - foreach ($inputs as $i=>$input) { - $result = $this->incrementObject(array(array($keys[$i]),$keyField),$input,$filters,$tables,$fields); - if ($result===null) { - $this->db->rollbackTransaction(); - return null; - } - $rows[] = $result; - } - $this->db->commitTransaction(); - return $rows; - } - - protected function findRelations($tables,$database,$auto_include) { - $tableset = array(); - $collect = array(); - $select = array(); - - while (count($tables)>1) { - $table0 = array_shift($tables); - $tableset[] = $table0; - - $result = $this->db->query($this->db->getSql('reflect_belongs_to'),array($table0,$tables,$database,$database)); - while ($row = $this->db->fetchRow($result)) { - if (!$auto_include && !in_array($row[0],array_merge($tables,$tableset))) continue; - $collect[$row[0]][$row[1]]=array(); - $select[$row[2]][$row[3]]=array($row[0],$row[1]); - if (!in_array($row[0],$tableset)) $tableset[] = $row[0]; - } - $result = $this->db->query($this->db->getSql('reflect_has_many'),array($tables,$table0,$database,$database)); - while ($row = $this->db->fetchRow($result)) { - if (!$auto_include && !in_array($row[2],array_merge($tables,$tableset))) continue; - $collect[$row[2]][$row[3]]=array(); - $select[$row[0]][$row[1]]=array($row[2],$row[3]); - if (!in_array($row[2],$tableset)) $tableset[] = $row[2]; - } - $result = $this->db->query($this->db->getSql('reflect_habtm'),array($database,$database,$database,$database,$table0,$tables)); - while ($row = $this->db->fetchRow($result)) { - if (!$auto_include && !in_array($row[2],array_merge($tables,$tableset))) continue; - if (!$auto_include && !in_array($row[4],array_merge($tables,$tableset))) continue; - $collect[$row[2]][$row[3]]=array(); - $select[$row[0]][$row[1]]=array($row[2],$row[3]); - $collect[$row[4]][$row[5]]=array(); - $select[$row[6]][$row[7]]=array($row[4],$row[5]); - if (!in_array($row[2],$tableset)) $tableset[] = $row[2]; - if (!in_array($row[4],$tableset)) $tableset[] = $row[4]; - } - } - $tableset[] = array_shift($tables); - $tableset = array_unique($tableset); - return array($tableset,$collect,$select); - } - - protected function retrieveInputs($data) { - $data = trim($data, " \t\n\r"); - if (strlen($data)==0) { - $input = false; - } else if ($data[0]=='{' || $data[0]=='[') { - $input = json_decode($data); - $causeCode = json_last_error(); - if ($causeCode !== JSON_ERROR_NONE) { - $errorString = "Error decoding input JSON. json_last_error code: " . $causeCode; - $this->exitWith400($errorString); - } - } else { - parse_str($data, $input); - foreach ($input as $key => $value) { - if (substr($key,-9)=='__is_null') { - $input[substr($key,0,-9)] = null; - unset($input[$key]); - } - } - $input = (object)$input; - } - return is_array($input)?$input:array($input); - } - - protected function getRelationShipColumns($select) { - $keep = array(); - foreach ($select as $table=>$keys) { - foreach ($keys as $key=>$other) { - if (!isset($keep[$table])) $keep[$table] = array(); - $keep[$table][$key]=true; - list($table2,$key2) = $other; - if (!isset($keep[$table2])) $keep[$table2] = array(); - $keep[$table2][$key2]=true; - } - } - return $keep; - } - - protected function findFields($tables,$columns,$exclude,$select,$database) { - $fields = array(); - if ($select && ($columns || $exclude)) { - $keep = $this->getRelationShipColumns($select); - } else { - $keep = false; - } - foreach ($tables as $i=>$table) { - $fields[$table] = $this->findTableFields($table,$database); - $fields[$table] = $this->filterFieldsByColumns($fields[$table],$columns,$keep,$i==0,$table); - $fields[$table] = $this->filterFieldsByExclude($fields[$table],$exclude,$keep,$i==0,$table); - } - return $fields; - } - - protected function filterFieldsByColumns($fields,$columns,$keep,$first,$table) { - if ($columns) { - $columns = explode(',',$columns); - foreach (array_keys($fields) as $key) { - $delete = true; - foreach ($columns as $column) { - if (strpos($column,'.')) { - if ($column=="$table.$key" || $column=="$table.*") { - $delete = false; - } - } elseif ($first) { - if ($column==$key || $column=="*") { - $delete = false; - } - } - } - if ($delete && !isset($keep[$table][$key])) { - unset($fields[$key]); - } - } - } - return $fields; - } - - protected function filterFieldsByExclude($fields,$exclude,$keep,$first,$table) { - if ($exclude) { - $columns = explode(',',$exclude); - foreach (array_keys($fields) as $key) { - $delete = false; - foreach ($columns as $column) { - if (strpos($column,'.')) { - if ($column=="$table.$key" || $column=="$table.*") { - $delete = true; - } - } elseif ($first) { - if ($column==$key || $column=="*") { - $delete = true; - } - } - } - if ($delete && !isset($keep[$table][$key])) { - unset($fields[$key]); - } - } - } - return $fields; - } - - protected function findTableFields($table,$database) { - $fields = array(); - foreach ($this->db->fetchFields($table) as $field) { - $fields[$field->name] = $field; - } - return $fields; - } - - protected function filterInputByFields($input,$fields) { - if ($fields) foreach (array_keys((array)$input) as $key) { - if (!isset($fields[$key])) { - unset($input->$key); - } - } - return $input; - } - - protected function convertInputs(&$input,$fields) { - foreach ($fields as $key=>$field) { - if (isset($input->$key) && $input->$key && $this->db->isBinaryType($field)) { - $value = $input->$key; - $value = str_pad(strtr($value, '-_', '+/'), ceil(strlen($value) / 4) * 4, '=', STR_PAD_RIGHT); - $input->$key = (object)array('type'=>'hex','value'=>bin2hex(base64_decode($value))); - } - if (isset($input->$key) && $input->$key && $this->db->isGeometryType($field)) { - $input->$key = (object)array('type'=>'wkt','value'=>$input->$key); - } - if (isset($input->$key) && $input->$key && $this->db->isJsonType($field)) { - $input->$key = $this->db->jsonEncode($input->$key); - } - } - } - - protected function convertOutputs(&$sql, &$params, $fields) { - $sql .= implode(',',str_split(str_repeat('!',count($fields)))); - foreach ($fields as $key=>$field) { - if ($this->db->isBinaryType($field)) { - $params[] = (object)array('type'=>'hex','key'=>$key); - } - else if ($this->db->isGeometryType($field)) { - $params[] = (object)array('type'=>'wkt','key'=>$key); - } - else { - $params[] = $key; - } - } - } - - protected function convertTypes($result,&$values,&$fields) { - foreach ($values as $i=>$v) { - if (is_string($v)) { - if ($this->db->isNumericType($fields[$i])) { - $values[$i] = $v + 0; - } - else if ($this->db->isBinaryType($fields[$i])) { - $values[$i] = base64_encode(pack("H*",$v)); - } - else if ($this->db->isJsonType($fields[$i])) { - $values[$i] = $this->db->jsonDecode($v); - } - } - } - } - - protected function fetchAssoc($result,$fields=false) { - $values = $this->db->fetchAssoc($result); - if ($values && $fields) { - $this->convertTypes($result,$values,$fields); - } - return $values; - } - - protected function fetchRow($result,$fields=false) { - $values = $this->db->fetchRow($result,$fields); - if ($values && $fields) { - $fields = array_values($fields); - $this->convertTypes($result,$values,$fields); - } - return $values; - } - - protected function getParameters($settings) { - extract($settings); - - $table = $this->parseRequestParameter($request, 'a-zA-Z0-9\-_'); - $key = $this->parseRequestParameter($request, 'a-zA-Z0-9\-_,'); // auto-increment or uuid - $action = $this->mapMethodToAction($method,$key); - $include = $this->parseGetParameter($get, 'include', 'a-zA-Z0-9\-_,'); - $page = $this->parseGetParameter($get, 'page', '0-9,'); - $filters = $this->parseGetParameterArray($get, 'filter', false); - $satisfy = $this->parseGetParameter($get, 'satisfy', 'a-zA-Z0-9\-_,.'); - $columns = $this->parseGetParameter($get, 'columns', 'a-zA-Z0-9\-_,.*'); - $exclude = $this->parseGetParameter($get, 'exclude', 'a-zA-Z0-9\-_,.*'); - $orderings = $this->parseGetParameterArray($get, 'order', 'a-zA-Z0-9\-_,'); - $transform = $this->parseGetParameter($get, 'transform', 't1'); - - $tables = $this->processTableAndIncludeParameters($database,$table,$include,$action); - $key = $this->processKeyParameter($key,$tables,$database); - $satisfy = $this->processSatisfyParameter($tables,$satisfy); - $filters = $this->processFiltersParameter($tables,$satisfy,$filters); - $page = $this->processPageParameter($page); - $orderings = $this->processOrderingsParameter($orderings); - - // reflection - list($tables,$collect,$select) = $this->findRelations($tables,$database,$auto_include); - $fields = $this->findFields($tables,$columns,$exclude,$select,$database); - - // permissions - if ($table_authorizer) $this->applyTableAuthorizer($table_authorizer,$action,$database,$tables); - if (!isset($tables[0])) $this->exitWith404('entity'); - if ($record_filter) $this->applyRecordFilter($record_filter,$action,$database,$tables,$filters); - if ($tenancy_function) $this->applyTenancyFunction($tenancy_function,$action,$database,$fields,$filters); - if ($column_authorizer) $this->applyColumnAuthorizer($column_authorizer,$action,$database,$fields); - - // input - $inputs = $this->retrieveInputs($post); - foreach ($inputs as $k=>$context) { - $input = $this->filterInputByFields($context,$fields[$tables[0]]); - - if ($tenancy_function) $this->applyInputTenancy($tenancy_function,$action,$database,$tables[0],$input,$fields[$tables[0]]); - if ($input_sanitizer) $this->applyInputSanitizer($input_sanitizer,$action,$database,$tables[0],$input,$fields[$tables[0]]); - if ($input_validator) $this->applyInputValidator($input_validator,$action,$database,$tables[0],$input,$fields[$tables[0]],$context); - - $this->convertInputs($input,$fields[$tables[0]]); - $inputs[$k] = $input; - } - - if ($before) { - $this->applyBeforeHandler($action,$database,$tables[0],$key[0],$before,$inputs); - } - - return compact('action','database','tables','key','page','filters','fields','orderings','transform','inputs','collect','select','before','after'); - } - - protected function addWhereFromFilters($filters,&$sql,&$params) { - $first = true; - if (isset($filters['or'])) { - $first = false; - $sql .= ' WHERE ('; - foreach ($filters['or'] as $i=>$filter) { - $sql .= $i==0?'':' OR '; - $sql .= $filter[0]; - for ($i=1;$i$filter) { - $sql .= $first?' WHERE ':' AND '; - $sql .= $filter[0]; - for ($i=1;$i$ordering) { - $sql .= $i==0?' ORDER BY ':', '; - $sql .= '! '.$ordering[1]; - $params[] = $ordering[0]; - } - } - - protected function listCommandInternal($parameters) { - extract($parameters); - echo '{'; - $table = array_shift($tables); - // first table - $count = false; - echo '"'.$table.'":{'; - if (is_array($orderings) && is_array($page)) { - $params = array(); - $sql = 'SELECT COUNT(*) FROM !'; - $params[] = $table; - if (isset($filters[$table])) { - $this->addWhereFromFilters($filters[$table],$sql,$params); - } - if ($result = $this->db->query($sql,$params)) { - while ($pages = $this->db->fetchRow($result)) { - $count = (int)$pages[0]; - } - } - } - $params = array(); - $sql = 'SELECT '; - $this->convertOutputs($sql,$params,$fields[$table]); - $sql .= ' FROM !'; - $params[] = $table; - if (isset($filters[$table])) { - $this->addWhereFromFilters($filters[$table],$sql,$params); - } - if (is_array($orderings)) { - $this->addOrderByFromOrderings($orderings,$sql,$params); - } - if (is_array($orderings) && is_array($page)) { - $sql = $this->db->addLimitToSql($sql,$page[1],$page[0]); - } - if ($result = $this->db->query($sql,$params)) { - echo '"columns":'; - $keys = array_keys($fields[$table]); - echo json_encode($keys); - $keys = array_flip($keys); - echo ',"records":['; - $first_row = true; - while ($row = $this->fetchRow($result,$fields[$table])) { - if ($first_row) $first_row = false; - else echo ','; - if (isset($collect[$table])) { - foreach (array_keys($collect[$table]) as $field) { - $collect[$table][$field][] = $row[$keys[$field]]; - } - } - echo json_encode($row); - } - $this->db->close($result); - echo ']'; - if ($count) echo ','; - } - if ($count) echo '"results":'.$count; - echo '}'; - // other tables - foreach ($tables as $t=>$table) { - echo ','; - echo '"'.$table.'":{'; - $params = array(); - $sql = 'SELECT '; - $this->convertOutputs($sql,$params,$fields[$table]); - $sql .= ' FROM !'; - $params[] = $table; - if (isset($select[$table])) { - echo '"relations":{'; - $first_row = true; - foreach ($select[$table] as $field => $path) { - $values = $collect[$path[0]][$path[1]]; - if ($values) { - $this->addFilter($filters,$table,'and',$field,'in',implode(',',$values)); - } - if ($first_row) $first_row = false; - else echo ','; - echo '"'.$field.'":"'.implode('.',$path).'"'; - } - echo '}'; - } - if (isset($filters[$table])) { - $this->addWhereFromFilters($filters[$table],$sql,$params); - } - if ($result = $this->db->query($sql,$params)) { - if (isset($select[$table])) echo ','; - echo '"columns":'; - $keys = array_keys($fields[$table]); - echo json_encode($keys); - $keys = array_flip($keys); - echo ',"records":['; - $first_row = true; - while ($row = $this->fetchRow($result,$fields[$table])) { - if ($first_row) $first_row = false; - else echo ','; - if (isset($collect[$table])) { - foreach (array_keys($collect[$table]) as $field) { - $collect[$table][$field][]=$row[$keys[$field]]; - } - } - echo json_encode($row); - } - $this->db->close($result); - echo ']'; - } - echo '}'; - } - echo '}'; - } - - protected function readCommand($parameters) { - extract($parameters); - if (count($key[0])>1) $object = $this->retrieveObjects($key,$fields,$filters,$tables); - else $object = $this->retrieveObject($key,$fields,$filters,$tables); - if (!$object) $this->exitWith404('object'); - $this->startOutput(); - echo json_encode($object); - return false; - } - - protected function createCommand($parameters) { - extract($parameters); - if (!$inputs || !$inputs[0]) $this->exitWith404('input'); - if (count($inputs)>1) return $this->createObjects($inputs,$tables); - return $this->createObject($inputs[0],$tables); - - } - - protected function updateCommand($parameters) { - extract($parameters); - if (!$inputs || !$inputs[0]) $this->exitWith404('subject'); - if (count($inputs)>1) return $this->updateObjects($key,$inputs,$filters,$tables); - return $this->updateObject($key,$inputs[0],$filters,$tables); - } - - protected function deleteCommand($parameters) { - extract($parameters); - if (count($key[0])>1) return $this->deleteObjects($key,$filters,$tables); - return $this->deleteObject($key,$filters,$tables); - } - - protected function incrementCommand($parameters) { - extract($parameters); - if (!$inputs || !$inputs[0]) $this->exitWith404('subject'); - if (count($inputs)>1) return $this->incrementObjects($key,$inputs,$filters,$tables,$fields); - return $this->incrementObject($key,$inputs[0],$filters,$tables,$fields); - } - - protected function listCommand($parameters) { - extract($parameters); - $this->startOutput(); - if ($transform) { - ob_start(); - } - $this->listCommandInternal($parameters); - if ($transform) { - $content = ob_get_contents(); - ob_end_clean(); - $data = json_decode($content,true); - echo json_encode(self::php_crud_api_transform($data)); - } - return false; - } - - protected function retrievePostData() { - if ($_FILES) { - $files = array(); - foreach ($_FILES as $name => $file) { - foreach ($file as $key => $value) { - switch ($key) { - case 'tmp_name': $files[$name] = $value?base64_encode(file_get_contents($value)):''; break; - default: $files[$name.'_'.$key] = $value; - } - } - } - return http_build_query(array_merge($files,$_POST)); - } - return file_get_contents('php://input'); - } - - public function __construct($config) { - extract($config); - - - // initialize - $dbengine = isset($dbengine)?$dbengine:null; - $hostname = isset($hostname)?$hostname:null; - $username = isset($username)?$username:null; - $password = isset($password)?$password:null; - $database = isset($database)?$database:null; - $port = isset($port)?$port:null; - $socket = isset($socket)?$socket:null; - $charset = isset($charset)?$charset:null; - - $table_authorizer = isset($table_authorizer)?$table_authorizer:null; - $record_filter = isset($record_filter)?$record_filter:null; - $column_authorizer = isset($column_authorizer)?$column_authorizer:null; - $tenancy_function = isset($tenancy_function)?$tenancy_function:null; - $input_sanitizer = isset($input_sanitizer)?$input_sanitizer:null; - $input_validator = isset($input_validator)?$input_validator:null; - $auto_include = isset($auto_include)?$auto_include:null; - $allow_origin = isset($allow_origin)?$allow_origin:null; - $before = isset($before)?$before:null; - $after = isset($after)?$after:null; - - $db = isset($db)?$db:null; - $method = isset($method)?$method:null; - $request = isset($request)?$request:null; - $get = isset($get)?$get:null; - $post = isset($post)?$post:null; - $origin = isset($origin)?$origin:null; - - // defaults - if (!$dbengine) { - $dbengine = 'MySQL'; - } - if (!$method) { - $method = $_SERVER['REQUEST_METHOD']; - } - if (!$request) { - $request = isset($_SERVER['PATH_INFO'])?$_SERVER['PATH_INFO']:''; - if (!$request) { - $request = isset($_SERVER['ORIG_PATH_INFO'])?$_SERVER['ORIG_PATH_INFO']:''; - $request = $request!=$_SERVER['SCRIPT_NAME']?$request:''; - } - } - if (!$get) { - $get = $_GET; - } - if (!$post) { - $post = $this->retrievePostData(); - } - if (!$origin) { - $origin = isset($_SERVER['HTTP_ORIGIN'])?$_SERVER['HTTP_ORIGIN']:''; - } - - // connect - $request = trim($request,'/'); - if (!$database) { - $database = $this->parseRequestParameter($request, 'a-zA-Z0-9\-_'); - } - if (!$db) { - $db = new $dbengine(); - if (!$charset) { - $charset = $db->getDefaultCharset(); - } - $db->connect($hostname,$username,$password,$database,$port,$socket,$charset); - } - if ($auto_include===null) { - $auto_include = true; - } - if ($allow_origin===null) { - $allow_origin = '*'; - } - - $this->db = $db; - $this->settings = compact('method', 'request', 'get', 'post', 'origin', 'database', 'table_authorizer', 'record_filter', 'column_authorizer', 'tenancy_function', 'input_sanitizer', 'input_validator', 'before', 'after', 'auto_include', 'allow_origin'); - } - - public static function php_crud_api_transform(&$tables) { - $get_objects = function (&$tables,$table_name,$where_index=false,$match_value=false) use (&$get_objects) { - $objects = array(); - if (isset($tables[$table_name]['records'])) { - foreach ($tables[$table_name]['records'] as $record) { - if ($where_index===false || $record[$where_index]==$match_value) { - $object = array(); - foreach ($tables[$table_name]['columns'] as $index=>$column) { - $object[$column] = $record[$index]; - foreach ($tables as $relation=>$reltable) { - if (isset($reltable['relations'])) { - foreach ($reltable['relations'] as $key=>$target) { - if ($target == "$table_name.$column") { - $column_indices = array_flip($reltable['columns']); - $object[$relation] = $get_objects($tables,$relation,$column_indices[$key],$record[$index]); - } - } - } - } - } - $objects[] = $object; - } - } - } - return $objects; - }; - $tree = array(); - foreach ($tables as $name=>$table) { - if (!isset($table['relations'])) { - $tree[$name] = $get_objects($tables,$name); - if (isset($table['results'])) { - $tree['_results'] = $table['results']; - } - } - } - return $tree; - } - - protected function swagger($settings) { - extract($settings); - - $tables = array(); - if ($result = $this->db->query($this->db->getSql('list_tables'),array($database))) { - while ($row = $this->db->fetchRow($result)) { - $table = array( - 'name'=>$row[0], - 'comments'=>$row[1], - 'root_actions'=>array( - array('name'=>'list','method'=>'get'), - array('name'=>'create','method'=>'post'), - ), - 'id_actions'=>array( - array('name'=>'read','method'=>'get'), - array('name'=>'update','method'=>'put'), - array('name'=>'delete','method'=>'delete'), - array('name'=>'increment','method'=>'patch'), - ), - ); - $tables[] = $table; - } - $this->db->close($result); - } - - $table_names = array_map(function($v){ return $v['name'];},$tables); - foreach ($tables as $t=>$table) { - $table_list = array($table['name']); - $table_fields = $this->findFields($table_list,false,false,false,$database); - - // extensions - $result = $this->db->query($this->db->getSql('reflect_belongs_to'),array($table_list[0],$table_names,$database,$database)); - while ($row = $this->db->fetchRow($result)) { - $table_fields[$table['name']][$row[1]]->references=array($row[2],$row[3]); - } - $result = $this->db->query($this->db->getSql('reflect_has_many'),array($table_names,$table_list[0],$database,$database)); - while ($row = $this->db->fetchRow($result)) { - $table_fields[$table['name']][$row[3]]->referenced[]=array($row[0],$row[1]); - } - $primaryKeys = $this->findPrimaryKeys($table_list[0],$database); - foreach ($primaryKeys as $primaryKey) { - $table_fields[$table['name']][$primaryKey]->primaryKey = true; - } - $result = $this->db->query($this->db->getSql('reflect_columns'),array($table_list[0],$database)); - while ($row = $this->db->fetchRow($result)) { - $table_fields[$table['name']][$row[0]]->required = strtolower($row[2])=='no' && $row[1]===null; - $table_fields[$table['name']][$row[0]]->{'x-nullable'} = strtolower($row[2])=='yes'; - $table_fields[$table['name']][$row[0]]->{'x-dbtype'} = $row[3]; - if ($this->db->isNumericType($table_fields[$table['name']][$row[0]])) { - if (strpos(strtolower($table_fields[$table['name']][$row[0]]->{'x-dbtype'}),'int')!==false) { - $table_fields[$table['name']][$row[0]]->type = 'integer'; - if ($row[1]!==null) $table_fields[$table['name']][$row[0]]->default = (int)$row[1]; - } else { - $table_fields[$table['name']][$row[0]]->type = 'number'; - if ($row[1]!==null) $table_fields[$table['name']][$row[0]]->default = (float)$row[1]; - } - } else { - if ($this->db->isBinaryType($table_fields[$table['name']][$row[0]])) { - $table_fields[$table['name']][$row[0]]->format = 'byte'; - } else if ($this->db->isGeometryType($table_fields[$table['name']][$row[0]])) { - $table_fields[$table['name']][$row[0]]->format = 'wkt'; - } else if ($this->db->isJsonType($table_fields[$table['name']][$row[0]])) { - $table_fields[$table['name']][$row[0]]->format = 'json'; - } - $table_fields[$table['name']][$row[0]]->type = 'string'; - if ($row[1]!==null) $table_fields[$table['name']][$row[0]]->default = $row[1]; - if ($row[4]!==null) $table_fields[$table['name']][$row[0]]->maxLength = (int)$row[4]; - } - } - - foreach (array('root_actions','id_actions') as $path) { - foreach ($table[$path] as $i=>$action) { - $table_list = array($table['name']); - $fields = $table_fields; - if ($table_authorizer) $this->applyTableAuthorizer($table_authorizer,$action['name'],$database,$table_list); - if ($column_authorizer) $this->applyColumnAuthorizer($column_authorizer,$action['name'],$database,$fields); - if (!$table_list || !$fields[$table['name']]) $tables[$t][$path][$i] = false; - else $tables[$t][$path][$i]['fields'] = $fields[$table['name']]; - } - // remove unauthorized tables and tables without fields - $tables[$t][$path] = array_values(array_filter($tables[$t][$path])); - } - if (!$tables[$t]['root_actions']&&!$tables[$t]['id_actions']) $tables[$t] = false; - } - $tables = array_merge(array_filter($tables)); - //var_dump($tables);die(); - - header('Content-Type: application/json; charset=utf-8'); - echo '{"swagger":"2.0",'; - echo '"info":{'; - echo '"title":"'.$database.'",'; - echo '"description":"API generated with [PHP-CRUD-API](https://github.com/mevdschee/php-crud-api)",'; - echo '"version":"1.0.0"'; - echo '},'; - echo '"host":"'.$_SERVER['HTTP_HOST'].'",'; - echo '"basePath":"'.$_SERVER['SCRIPT_NAME'].'",'; - echo '"schemes":["http'.((!empty($_SERVER['HTTPS'])&&$_SERVER['HTTPS']!=='off')?'s':'').'"],'; - echo '"consumes":["application/json"],'; - echo '"produces":["application/json"],'; - echo '"tags":['; - foreach ($tables as $i=>$table) { - if ($i>0) echo ','; - echo '{'; - echo '"name":"'.$table['name'].'",'; - echo '"description":"'.$table['comments'].'"'; - echo '}'; - } - echo '],'; - echo '"paths":{'; - foreach ($tables as $i=>$table) { - if ($table['root_actions']) { - if ($i>0) echo ','; - echo '"/'.$table['name'].'":{'; - foreach ($table['root_actions'] as $j=>$action) { - if ($j>0) echo ','; - echo '"'.$action['method'].'":{'; - echo '"tags":["'.$table['name'].'"],'; - echo '"summary":"'.ucfirst($action['name']).'",'; - if ($action['name']=='list') { - echo '"parameters":['; - echo '{'; - echo '"name":"exclude",'; - echo '"in":"query",'; - echo '"description":"One or more related entities (comma separated).",'; - echo '"required":false,'; - echo '"type":"string"'; - echo '},'; - echo '{'; - echo '"name":"include",'; - echo '"in":"query",'; - echo '"description":"One or more related entities (comma separated).",'; - echo '"required":false,'; - echo '"type":"string"'; - echo '},'; - echo '{'; - echo '"name":"order",'; - echo '"in":"query",'; - echo '"description":"Column you want to sort on and the sort direction (comma separated). Example: id,desc",'; - echo '"required":false,'; - echo '"type":"string"'; - echo '},'; - echo '{'; - echo '"name":"page",'; - echo '"in":"query",'; - echo '"description":"Page number and page size (comma separated). NB: You cannot use \"page\" without \"order\"! Example: 1,10",'; - echo '"required":false,'; - echo '"type":"string"'; - echo '},'; - echo '{'; - echo '"name":"transform",'; - echo '"in":"query",'; - echo '"description":"Transform the records to object format. NB: This can also be done client-side in JavaScript!",'; - echo '"required":false,'; - echo '"type":"boolean"'; - echo '},'; - echo '{'; - echo '"name":"columns",'; - echo '"in":"query",'; - echo '"description":"The table columns you want to retrieve (comma separated). Example: posts.*,categories.name",'; - echo '"required":false,'; - echo '"type":"string"'; - echo '},'; - echo '{'; - echo '"name":"filter[]",'; - echo '"in":"query",'; - echo '"description":"Filters to be applied. Each filter consists of a column, an operator and a value (comma separated). Example: id,eq,1",'; - echo '"required":false,'; - echo '"type":"array",'; - echo '"collectionFormat":"multi",'; - echo '"items":{"type":"string"}'; - echo '},'; - echo '{'; - echo '"name":"satisfy",'; - echo '"in":"query",'; - echo '"description":"Should all filters match (default)? Or any?",'; - echo '"required":false,'; - echo '"type":"string",'; - echo '"enum":["any"]'; - echo '}'; - echo '],'; - echo '"responses":{'; - echo '"200":{'; - echo '"description":"An array of '.$table['name'].'",'; - echo '"schema":{'; - echo '"type": "object",'; - echo '"properties": {'; - echo '"'.$table['name'].'": {'; - echo '"type":"array",'; - echo '"items":{'; - echo '"type": "object",'; - echo '"properties": {'; - foreach (array_keys($action['fields']) as $k=>$field) { - if ($k>0) echo ','; - echo '"'.$field.'": {'; - echo '"type": '.json_encode($action['fields'][$field]->type); - if (isset($action['fields'][$field]->format)) { - echo ',"format": '.json_encode($action['fields'][$field]->format); - } - echo ',"x-dbtype": '.json_encode($action['fields'][$field]->{'x-dbtype'}); - echo ',"x-nullable": '.json_encode($action['fields'][$field]->{'x-nullable'}); - if (isset($action['fields'][$field]->maxLength) && $action['fields'][$field]->maxLength>0) { - echo ',"maxLength": '.json_encode($action['fields'][$field]->maxLength); - } - if (isset($action['fields'][$field]->default)) { - echo ',"default": '.json_encode($action['fields'][$field]->default); - } - if (isset($action['fields'][$field]->referenced)) { - echo ',"x-referenced": '.json_encode($action['fields'][$field]->referenced); - } - if (isset($action['fields'][$field]->references)) { - echo ',"x-references": '.json_encode($action['fields'][$field]->references); - } - if (isset($action['fields'][$field]->primaryKey)) { - echo ',"x-primary-key": true'; - } - echo '}'; - } - echo '}'; //properties - echo '}'; //items - echo '}'; //table - echo '}'; //properties - echo '}'; //schema - echo '}'; //200 - echo '}'; //responses - } - if ($action['name']=='create') { - echo '"parameters":[{'; - echo '"name":"item",'; - echo '"in":"body",'; - echo '"description":"Item to create.",'; - echo '"required":true,'; - echo '"schema":{'; - echo '"type": "object",'; - $required_fields = array_keys(array_filter($action['fields'],function($f){ return $f->required; })); - if (count($required_fields) > 0) { - echo '"required":'.json_encode($required_fields).','; - } - echo '"properties": {'; - foreach (array_keys($action['fields']) as $k=>$field) { - if ($k>0) echo ','; - echo '"'.$field.'": {'; - echo '"type": '.json_encode($action['fields'][$field]->type); - if (isset($action['fields'][$field]->format)) { - echo ',"format": '.json_encode($action['fields'][$field]->format); - } - echo ',"x-dbtype": '.json_encode($action['fields'][$field]->{'x-dbtype'}); - echo ',"x-nullable": '.json_encode($action['fields'][$field]->{'x-nullable'}); - if (isset($action['fields'][$field]->maxLength)) { - echo ',"maxLength": '.json_encode($action['fields'][$field]->maxLength); - } - if (isset($action['fields'][$field]->default)) { - echo ',"default": '.json_encode($action['fields'][$field]->default); - } - if (isset($action['fields'][$field]->referenced)) { - echo ',"x-referenced": '.json_encode($action['fields'][$field]->referenced); - } - if (isset($action['fields'][$field]->references)) { - echo ',"x-references": '.json_encode($action['fields'][$field]->references); - } - if (isset($action['fields'][$field]->primaryKey)) { - echo ',"x-primary-key": true'; - } - echo '}'; - } - echo '}'; //properties - echo '}'; //schema - echo '}],'; - echo '"responses":{'; - echo '"200":{'; - echo '"description":"Identifier of created item.",'; - echo '"schema":{'; - echo '"type":"integer"'; - echo '}';//schema - echo '}';//200 - echo '}';//responses - } - echo '}';//method - } - echo '}'; - } - if ($table['id_actions']) { - if ($i>0 || $table['root_actions']) echo ','; - echo '"/'.$table['name'].'/{id}":{'; - foreach ($table['id_actions'] as $j=>$action) { - if ($j>0) echo ','; - echo '"'.$action['method'].'":{'; - echo '"tags":["'.$table['name'].'"],'; - echo '"summary":"'.ucfirst($action['name']).'",'; - echo '"parameters":['; - echo '{'; - echo '"name":"id",'; - echo '"in":"path",'; - echo '"description":"Identifier for item.",'; - echo '"required":true,'; - echo '"type":"string"'; - echo '}'; - if ($action['name']=='update' || $action['name']=='increment') { - echo ',{'; - echo '"name":"item",'; - echo '"in":"body",'; - echo '"description":"Properties of item to update.",'; - echo '"required":true,'; - echo '"schema":{'; - echo '"type": "object",'; - $required_fields = array_keys(array_filter($action['fields'],function($f){ return $f->required; })); - if (count($required_fields) > 0) { - echo '"required":'.json_encode($required_fields).','; - } - echo '"properties": {'; - foreach (array_keys($action['fields']) as $k=>$field) { - if ($k>0) echo ','; - echo '"'.$field.'": {'; - echo '"type": '.json_encode($action['fields'][$field]->type); - if (isset($action['fields'][$field]->format)) { - echo ',"format": '.json_encode($action['fields'][$field]->format); - } - echo ',"x-dbtype": '.json_encode($action['fields'][$field]->{'x-dbtype'}); - echo ',"x-nullable": '.json_encode($action['fields'][$field]->{'x-nullable'}); - if (isset($action['fields'][$field]->maxLength)) { - echo ',"maxLength": '.json_encode($action['fields'][$field]->maxLength); - } - if (isset($action['fields'][$field]->default)) { - echo ',"default": '.json_encode($action['fields'][$field]->default); - } - if (isset($action['fields'][$field]->referenced)) { - echo ',"x-referenced": '.json_encode($action['fields'][$field]->referenced); - } - if (isset($action['fields'][$field]->references)) { - echo ',"x-references": '.json_encode($action['fields'][$field]->references); - } - if (isset($action['fields'][$field]->primaryKey)) { - echo ',"x-primary-key": true'; - } - echo '}'; - } - echo '}'; //properties - echo '}'; //schema - echo '}'; - } - echo '],'; - if ($action['name']=='read') { - echo '"responses":{'; - echo '"200":{'; - echo '"description":"The requested item.",'; - echo '"schema":{'; - echo '"type": "object",'; - echo '"properties": {'; - foreach (array_keys($action['fields']) as $k=>$field) { - if ($k>0) echo ','; - echo '"'.$field.'": {'; - echo '"type": '.json_encode($action['fields'][$field]->type); - if (isset($action['fields'][$field]->format)) { - echo ',"format": '.json_encode($action['fields'][$field]->format); - } - echo ',"x-dbtype": '.json_encode($action['fields'][$field]->{'x-dbtype'}); - echo ',"x-nullable": '.json_encode($action['fields'][$field]->{'x-nullable'}); - if (isset($action['fields'][$field]->maxLength)) { - echo ',"maxLength": '.json_encode($action['fields'][$field]->maxLength); - } - if (isset($action['fields'][$field]->default)) { - echo ',"default": '.json_encode($action['fields'][$field]->default); - } - if (isset($action['fields'][$field]->referenced)) { - echo ',"x-referenced": '.json_encode($action['fields'][$field]->referenced); - } - if (isset($action['fields'][$field]->references)) { - echo ',"x-references": '.json_encode($action['fields'][$field]->references); - } - if (isset($action['fields'][$field]->primaryKey)) { - echo ',"x-primary-key": true'; - } - echo '}'; - } - echo '}'; //properties - echo '}'; //schema - echo '}'; - echo '}'; - } else { - echo '"responses":{'; - echo '"200":{'; - echo '"description":"Number of affected rows.",'; - echo '"schema":{'; - echo '"type":"integer"'; - echo '}'; - echo '}'; - echo '}'; - } - echo '}'; - } - echo '}'; - } - } - echo '}'; - echo '}'; - } - - protected function allowOrigin($origin,$allowOrigins) { - if (isset($_SERVER['REQUEST_METHOD'])) { - header('Access-Control-Allow-Credentials: true'); - foreach (explode(',',$allowOrigins) as $o) { - if (preg_match('/^'.str_replace('\*','.*',preg_quote(strtolower(trim($o)))).'$/',$origin)) { - - //header('Access-Control-Allow-Origin: *'); - header('Access-Control-Allow-Origin: '.$origin); - break; - } - } - } - } - - public function executeCommand() { - if ($this->settings['origin']) { - $this->allowOrigin($this->settings['origin'],$this->settings['allow_origin']); - } - if (!$this->settings['request']) { - $this->swagger($this->settings); - } else { - $parameters = $this->getParameters($this->settings); - switch($parameters['action']){ - case 'list': $output = $this->listCommand($parameters); break; - case 'read': $output = $this->readCommand($parameters); break; - case 'create': $output = $this->createCommand($parameters); break; - case 'update': $output = $this->updateCommand($parameters); break; - case 'delete': $output = $this->deleteCommand($parameters); break; - case 'increment': $output = $this->incrementCommand($parameters); break; - case 'headers': $output = $this->headersCommand($parameters); break; - default: $output = false; - } - if ($output!==false) { - $this->startOutput(); - echo json_encode($output); - } - if ($parameters['after']) { - $this->applyAfterHandler($parameters,$output); - } - } - } -} - -// require 'auth.php'; // from the PHP-API-AUTH project, see: https://github.com/mevdschee/php-api-auth - -// uncomment the lines below for token+session based authentication (see "login_token.html" + "login_token.php"): - -// $auth = new PHP_API_AUTH(array( -// 'secret'=>'someVeryLongPassPhraseChangeMe', -// )); -// if ($auth->executeCommand()) exit(0); -// if (empty($_SESSION['user']) || !$auth->hasValidCsrfToken()) { -// header('HTTP/1.0 401 Unauthorized'); -// exit(0); -// } - -// uncomment the lines below for form+session based authentication (see "login.html"): - -// $auth = new PHP_API_AUTH(array( -// 'authenticator'=>function($user,$pass){ $_SESSION['user']=($user=='admin' && $pass=='admin'); } -// )); -// if ($auth->executeCommand()) exit(0); -// if (empty($_SESSION['user']) || !$auth->hasValidCsrfToken()) { -// header('HTTP/1.0 401 Unauthorized'); -// exit(0); -// } - -// uncomment the lines below when running in stand-alone mode: - - $api = new PHP_CRUD_API(array( - 'dbengine'=>'MySQL', - 'hostname'=>'localhost', - 'username'=>'lazyp_workadmin', - 'password'=>'GH5fZF0iCtLnHLrz', - 'database'=>'LudosData', - 'charset'=>'utf8mb4' - )); - $api->executeCommand(); - -// For Microsoft SQL Server 2012 use: - -// $api = new PHP_CRUD_API(array( -// 'dbengine'=>'SQLServer', -// 'hostname'=>'(local)', -// 'username'=>'', -// 'password'=>'', -// 'database'=>'xxx', -// 'charset'=>'UTF-8' -// )); -// $api->executeCommand(); - -// For PostgreSQL 9 use: - -// $api = new PHP_CRUD_API(array( -// 'dbengine'=>'PostgreSQL', -// 'hostname'=>'localhost', -// 'username'=>'xxx', -// 'password'=>'xxx', -// 'database'=>'xxx', -// 'charset'=>'UTF8' -// )); -// $api->executeCommand(); - -// For SQLite 3 use: - -// $api = new PHP_CRUD_API(array( -// 'dbengine'=>'SQLite', -// 'database'=>'data/blog.db', -// )); -// $api->executeCommand(); diff --git a/interfaceServices/class.upload.php b/interfaceServices/class.upload.php deleted file mode 100644 index 81e643e..0000000 --- a/interfaceServices/class.upload.php +++ /dev/null @@ -1,5062 +0,0 @@ - - * @license http://opensource.org/licenses/gpl-license.php GNU Public License - * @copyright Colin Verot - */ -class upload { - - - /** - * Class version - * - * @access public - * @var string - */ - var $version; - - /** - * Uploaded file name - * - * @access public - * @var string - */ - var $file_src_name; - - /** - * Uploaded file name body (i.e. without extension) - * - * @access public - * @var string - */ - var $file_src_name_body; - - /** - * Uploaded file name extension - * - * @access public - * @var string - */ - var $file_src_name_ext; - - /** - * Uploaded file MIME type - * - * @access public - * @var string - */ - var $file_src_mime; - - /** - * Uploaded file size, in bytes - * - * @access public - * @var double - */ - var $file_src_size; - - /** - * Holds eventual PHP error code from $_FILES - * - * @access public - * @var string - */ - var $file_src_error; - - /** - * Uloaded file name, including server path - * - * @access public - * @var string - */ - var $file_src_pathname; - - /** - * Uloaded file name temporary copy - * - * @access private - * @var string - */ - var $file_src_temp; - - /** - * Destination file name - * - * @access public - * @var string - */ - var $file_dst_path; - - /** - * Destination file name - * - * @access public - * @var string - */ - var $file_dst_name; - - /** - * Destination file name body (i.e. without extension) - * - * @access public - * @var string - */ - var $file_dst_name_body; - - /** - * Destination file extension - * - * @access public - * @var string - */ - var $file_dst_name_ext; - - /** - * Destination file name, including path - * - * @access public - * @var string - */ - var $file_dst_pathname; - - /** - * Source image width - * - * @access public - * @var integer - */ - var $image_src_x; - - /** - * Source image height - * - * @access public - * @var integer - */ - var $image_src_y; - - /** - * Source image color depth - * - * @access public - * @var integer - */ - var $image_src_bits; - - /** - * Number of pixels - * - * @access public - * @var long - */ - var $image_src_pixels; - - /** - * Type of image (png, gif, jpg or bmp) - * - * @access public - * @var string - */ - var $image_src_type; - - /** - * Destination image width - * - * @access public - * @var integer - */ - var $image_dst_x; - - /** - * Destination image height - * - * @access public - * @var integer - */ - var $image_dst_y; - - /** - * Destination image type (png, gif, jpg or bmp) - * - * @access public - * @var integer - */ - var $image_dst_type; - - /** - * Supported image formats - * - * @access private - * @var array - */ - var $image_supported; - - /** - * Flag to determine if the source file is an image - * - * @access public - * @var boolean - */ - var $file_is_image; - - /** - * Flag set after instanciating the class - * - * Indicates if the file has been uploaded properly - * - * @access public - * @var bool - */ - var $uploaded; - - /** - * Flag stopping PHP upload checks - * - * Indicates whether we instanciated the class with a filename, in which case - * we will not check on the validity of the PHP *upload* - * - * This flag is automatically set to true when working on a local file - * - * Warning: for uploads, this flag MUST be set to false for security reason - * - * @access public - * @var bool - */ - var $no_upload_check; - - /** - * Flag set after calling a process - * - * Indicates if the processing, and copy of the resulting file went OK - * - * @access public - * @var bool - */ - var $processed; - - /** - * Holds eventual error message in plain english - * - * @access public - * @var string - */ - var $error; - - /** - * Holds an HTML formatted log - * - * @access public - * @var string - */ - var $log; - - - // overiddable processing variables - - - /** - * Set this variable to replace the name body (i.e. without extension) - * - * @access public - * @var string - */ - var $file_new_name_body; - - /** - * Set this variable to append a string to the file name body - * - * @access public - * @var string - */ - var $file_name_body_add; - - /** - * Set this variable to prepend a string to the file name body - * - * @access public - * @var string - */ - var $file_name_body_pre; - - /** - * Set this variable to change the file extension - * - * @access public - * @var string - */ - var $file_new_name_ext; - - /** - * Set this variable to format the filename (spaces changed to _) - * - * @access public - * @var boolean - */ - var $file_safe_name; - - /** - * Forces an extension if the source file doesn't have one - * - * If the file is an image, then the correct extension will be added - * Otherwise, a .txt extension will be chosen - * - * @access public - * @var boolean - */ - var $file_force_extension; - - /** - * Set this variable to false if you don't want to check the MIME against the allowed list - * - * This variable is set to true by default for security reason - * - * @access public - * @var boolean - */ - var $mime_check; - - /** - * Set this variable to false in the init() function if you don't want to check the MIME - * with Fileinfo PECL extension. On some systems, Fileinfo is known to be buggy, and you - * may want to deactivate it in the class code directly. - * - * You can also set it with the path of the magic database file. - * If set to true, the class will try to read the MAGIC environment variable - * and if it is empty, will default to the system's default - * If set to an empty string, it will call finfo_open without the path argument - * - * This variable is set to true by default for security reason - * - * @access public - * @var boolean - */ - var $mime_fileinfo; - - /** - * Set this variable to false in the init() function if you don't want to check the MIME - * with UNIX file() command - * - * This variable is set to true by default for security reason - * - * @access public - * @var boolean - */ - var $mime_file; - - /** - * Set this variable to false in the init() function if you don't want to check the MIME - * with the magic.mime file - * - * The function mime_content_type() will be deprecated, - * and this variable will be set to false in a future release - * - * This variable is set to true by default for security reason - * - * @access public - * @var boolean - */ - var $mime_magic; - - /** - * Set this variable to false in the init() function if you don't want to check the MIME - * with getimagesize() - * - * The class tries to get a MIME type from getimagesize() - * If no MIME is returned, it tries to guess the MIME type from the file type - * - * This variable is set to true by default for security reason - * - * @access public - * @var boolean - */ - var $mime_getimagesize; - - /** - * Set this variable to false if you don't want to turn dangerous scripts into simple text files - * - * @access public - * @var boolean - */ - var $no_script; - - /** - * Set this variable to true to allow automatic renaming of the file - * if the file already exists - * - * Default value is true - * - * For instance, on uploading foo.ext,
- * if foo.ext already exists, upload will be renamed foo_1.ext
- * and if foo_1.ext already exists, upload will be renamed foo_2.ext
- * - * Note that this option doesn't have any effect if {@link file_overwrite} is true - * - * @access public - * @var bool - */ - var $file_auto_rename; - - /** - * Set this variable to true to allow automatic creation of the destination - * directory if it is missing (works recursively) - * - * Default value is true - * - * @access public - * @var bool - */ - var $dir_auto_create; - - /** - * Set this variable to true to allow automatic chmod of the destination - * directory if it is not writeable - * - * Default value is true - * - * @access public - * @var bool - */ - var $dir_auto_chmod; - - /** - * Set this variable to the default chmod you want the class to use - * when creating directories, or attempting to write in a directory - * - * Default value is 0777 (without quotes) - * - * @access public - * @var bool - */ - var $dir_chmod; - - /** - * Set this variable tu true to allow overwriting of an existing file - * - * Default value is false, so no files will be overwritten - * - * @access public - * @var bool - */ - var $file_overwrite; - - /** - * Set this variable to change the maximum size in bytes for an uploaded file - * - * Default value is the value upload_max_filesize from php.ini - * - * Value in bytes (integer) or shorthand byte values (string) is allowed. - * The available options are K (for Kilobytes), M (for Megabytes) and G (for Gigabytes) - * - * @access public - * @var double - */ - var $file_max_size; - - /** - * Set this variable to true to resize the file if it is an image - * - * You will probably want to set {@link image_x} and {@link image_y}, and maybe one of the ratio variables - * - * Default value is false (no resizing) - * - * @access public - * @var bool - */ - var $image_resize; - - /** - * Set this variable to convert the file if it is an image - * - * Possibles values are : ''; 'png'; 'jpeg'; 'gif'; 'bmp' - * - * Default value is '' (no conversion)
- * If {@link resize} is true, {@link convert} will be set to the source file extension - * - * @access public - * @var string - */ - var $image_convert; - - /** - * Set this variable to the wanted (or maximum/minimum) width for the processed image, in pixels - * - * Default value is 150 - * - * @access public - * @var integer - */ - var $image_x; - - /** - * Set this variable to the wanted (or maximum/minimum) height for the processed image, in pixels - * - * Default value is 150 - * - * @access public - * @var integer - */ - var $image_y; - - /** - * Set this variable to keep the original size ratio to fit within {@link image_x} x {@link image_y} - * - * Default value is false - * - * @access public - * @var bool - */ - var $image_ratio; - - /** - * Set this variable to keep the original size ratio to fit within {@link image_x} x {@link image_y} - * - * The image will be resized as to fill the whole space, and excedent will be cropped - * - * Value can also be a string, one or more character from 'TBLR' (top, bottom, left and right) - * If set as a string, it determines which side of the image is kept while cropping. - * By default, the part of the image kept is in the center, i.e. it crops equally on both sides - * - * Default value is false - * - * @access public - * @var mixed - */ - var $image_ratio_crop; - - /** - * Set this variable to keep the original size ratio to fit within {@link image_x} x {@link image_y} - * - * The image will be resized to fit entirely in the space, and the rest will be colored. - * The default color is white, but can be set with {@link image_default_color} - * - * Value can also be a string, one or more character from 'TBLR' (top, bottom, left and right) - * If set as a string, it determines in which side of the space the image is displayed. - * By default, the image is displayed in the center, i.e. it fills the remaining space equally on both sides - * - * Default value is false - * - * @access public - * @var mixed - */ - var $image_ratio_fill; - - /** - * Set this variable to a number of pixels so that {@link image_x} and {@link image_y} are the best match possible - * - * The image will be resized to have approximatively the number of pixels - * The aspect ratio wil be conserved - * - * Default value is false - * - * @access public - * @var mixed - */ - var $image_ratio_pixels; - - /** - * Set this variable to calculate {@link image_x} automatically , using {@link image_y} and conserving ratio - * - * Default value is false - * - * @access public - * @var bool - */ - var $image_ratio_x; - - /** - * Set this variable to calculate {@link image_y} automatically , using {@link image_x} and conserving ratio - * - * Default value is false - * - * @access public - * @var bool - */ - var $image_ratio_y; - - /** - * (deprecated) Set this variable to keep the original size ratio to fit within {@link image_x} x {@link image_y}, - * but only if original image is bigger - * - * This setting is soon to be deprecated. Instead, use {@link image_ratio} and {@link image_no_enlarging} - * - * Default value is false - * - * @access public - * @var bool - */ - var $image_ratio_no_zoom_in; - - /** - * (deprecated) Set this variable to keep the original size ratio to fit within {@link image_x} x {@link image_y}, - * but only if original image is smaller - * - * Default value is false - * - * This setting is soon to be deprecated. Instead, use {@link image_ratio} and {@link image_no_shrinking} - * - * @access public - * @var bool - */ - var $image_ratio_no_zoom_out; - - /** - * Cancel resizing if the resized image is bigger than the original image, to prevent enlarging - * - * Default value is false - * - * @access public - * @var bool - */ - var $image_no_enlarging; - - /** - * Cancel resizing if the resized image is smaller than the original image, to prevent shrinking - * - * Default value is false - * - * @access public - * @var bool - */ - var $image_no_shrinking; - - /** - * Set this variable to set a maximum image width, above which the upload will be invalid - * - * Default value is null - * - * @access public - * @var integer - */ - var $image_max_width; - - /** - * Set this variable to set a maximum image height, above which the upload will be invalid - * - * Default value is null - * - * @access public - * @var integer - */ - var $image_max_height; - - /** - * Set this variable to set a maximum number of pixels for an image, above which the upload will be invalid - * - * Default value is null - * - * @access public - * @var long - */ - var $image_max_pixels; - - /** - * Set this variable to set a maximum image aspect ratio, above which the upload will be invalid - * - * Note that ratio = width / height - * - * Default value is null - * - * @access public - * @var float - */ - var $image_max_ratio; - - /** - * Set this variable to set a minimum image width, below which the upload will be invalid - * - * Default value is null - * - * @access public - * @var integer - */ - var $image_min_width; - - /** - * Set this variable to set a minimum image height, below which the upload will be invalid - * - * Default value is null - * - * @access public - * @var integer - */ - var $image_min_height; - - /** - * Set this variable to set a minimum number of pixels for an image, below which the upload will be invalid - * - * Default value is null - * - * @access public - * @var long - */ - var $image_min_pixels; - - /** - * Set this variable to set a minimum image aspect ratio, below which the upload will be invalid - * - * Note that ratio = width / height - * - * Default value is null - * - * @access public - * @var float - */ - var $image_min_ratio; - - /** - * Compression level for PNG images - * - * Between 1 (fast but large files) and 9 (slow but smaller files) - * - * Default value is null (Zlib default) - * - * @access public - * @var integer - */ - var $png_compression; - - /** - * Quality of JPEG created/converted destination image - * - * Default value is 85 - * - * @access public - * @var integer - */ - var $jpeg_quality; - - /** - * Determines the quality of the JPG image to fit a desired file size - * - * The JPG quality will be set between 1 and 100% - * The calculations are approximations. - * - * Value in bytes (integer) or shorthand byte values (string) is allowed. - * The available options are K (for Kilobytes), M (for Megabytes) and G (for Gigabytes) - * - * Default value is null (no calculations) - * - * @access public - * @var integer - */ - var $jpeg_size; - - /** - * Turns the interlace bit on - * - * This is actually used only for JPEG images, and defaults to false - * - * @access public - * @var boolean - */ - var $image_interlace; - - /** - * Flag set to true when the image is transparent - * - * This is actually used only for transparent GIFs - * - * @access public - * @var boolean - */ - var $image_is_transparent; - - /** - * Transparent color in a palette - * - * This is actually used only for transparent GIFs - * - * @access public - * @var boolean - */ - var $image_transparent_color; - - /** - * Background color, used to paint transparent areas with - * - * If set, it will forcibly remove transparency by painting transparent areas with the color - * This setting will fill in all transparent areas in PNG and GIF, as opposed to {@link image_default_color} - * which will do so only in BMP, JPEG, and alpha transparent areas in transparent GIFs - * This setting overrides {@link image_default_color} - * - * Default value is null - * - * @access public - * @var string - */ - var $image_background_color; - - /** - * Default color for non alpha-transparent images - * - * This setting is to be used to define a background color for semi transparent areas - * of an alpha transparent when the output format doesn't support alpha transparency - * This is useful when, from an alpha transparent PNG image, or an image with alpha transparent features - * if you want to output it as a transparent GIFs for instance, you can set a blending color for transparent areas - * If you output in JPEG or BMP, this color will be used to fill in the previously transparent areas - * - * The default color white - * - * @access public - * @var boolean - */ - var $image_default_color; - - /** - * Flag set to true when the image is not true color - * - * @access public - * @var boolean - */ - var $image_is_palette; - - /** - * Corrects the image brightness - * - * Value can range between -127 and 127 - * - * Default value is null - * - * @access public - * @var integer - */ - var $image_brightness; - - /** - * Corrects the image contrast - * - * Value can range between -127 and 127 - * - * Default value is null - * - * @access public - * @var integer - */ - var $image_contrast; - - /** - * Changes the image opacity - * - * Value can range between 0 and 100 - * - * Default value is null - * - * @access public - * @var integer - */ - var $image_opacity; - - /** - * Applies threshold filter - * - * Value can range between -127 and 127 - * - * Default value is null - * - * @access public - * @var integer - */ - var $image_threshold; - - /** - * Applies a tint on the image - * - * Value is an hexadecimal color, such as #FFFFFF - * - * Default value is null - * - * @access public - * @var string; - */ - var $image_tint_color; - - /** - * Applies a colored overlay on the image - * - * Value is an hexadecimal color, such as #FFFFFF - * - * To use with {@link image_overlay_opacity} - * - * Default value is null - * - * @access public - * @var string; - */ - var $image_overlay_color; - - /** - * Sets the opacity for the colored overlay - * - * Value is a percentage, as an integer between 0 (transparent) and 100 (opaque) - * - * Unless used with {@link image_overlay_color}, this setting has no effect - * - * Default value is 50 - * - * @access public - * @var integer - */ - var $image_overlay_opacity; - - /** - * Inverts the color of an image - * - * Default value is FALSE - * - * @access public - * @var boolean; - */ - var $image_negative; - - /** - * Turns the image into greyscale - * - * Default value is FALSE - * - * @access public - * @var boolean; - */ - var $image_greyscale; - - /** - * Pixelate an image - * - * Value is integer, represents the block size - * - * Default value is null - * - * @access public - * @var integer; - */ - var $image_pixelate; - - /** - * Applies an unsharp mask, with alpha transparency support - * - * Beware that this unsharp mask is quite resource-intensive - * - * Default value is FALSE - * - * @access public - * @var boolean; - */ - var $image_unsharp; - - /** - * Sets the unsharp mask amount - * - * Value is an integer between 0 and 500, typically between 50 and 200 - * - * Unless used with {@link image_unsharp}, this setting has no effect - * - * Default value is 80 - * - * @access public - * @var integer - */ - var $image_unsharp_amount; - - /** - * Sets the unsharp mask radius - * - * Value is an integer between 0 and 50, typically between 0.5 and 1 - * It is not recommended to change it, the default works best - * - * Unless used with {@link image_unsharp}, this setting has no effect - * - * From PHP 5.1, imageconvolution is used, and this setting has no effect - * - * Default value is 0.5 - * - * @access public - * @var integer - */ - var $image_unsharp_radius; - - /** - * Sets the unsharp mask threshold - * - * Value is an integer between 0 and 255, typically between 0 and 5 - * - * Unless used with {@link image_unsharp}, this setting has no effect - * - * Default value is 1 - * - * @access public - * @var integer - */ - var $image_unsharp_threshold; - - /** - * Adds a text label on the image - * - * Value is a string, any text. Text will not word-wrap, although you can use breaklines in your text "\n" - * - * If set, this setting allow the use of all other settings starting with image_text_ - * - * Replacement tokens can be used in the string: - *
-     * gd_version    src_name       src_name_body src_name_ext
-     * src_pathname  src_mime       src_x         src_y
-     * src_type      src_bits       src_pixels
-     * src_size      src_size_kb    src_size_mb   src_size_human
-     * dst_path      dst_name_body  dst_pathname
-     * dst_name      dst_name_ext   dst_x         dst_y
-     * date          time           host          server        ip
-     * 
- * The tokens must be enclosed in square brackets: [dst_x] will be replaced by the width of the picture - * - * Default value is null - * - * @access public - * @var string; - */ - var $image_text; - - /** - * Sets the text direction for the text label - * - * Value is either 'h' or 'v', as in horizontal and vertical - * - * Note that if you use a TrueType font, you can use {@link image_text_angle} instead - * - * Default value is h (horizontal) - * - * @access public - * @var string; - */ - var $image_text_direction; - - /** - * Sets the text color for the text label - * - * Value is an hexadecimal color, such as #FFFFFF - * - * Default value is #FFFFFF (white) - * - * @access public - * @var string; - */ - var $image_text_color; - - /** - * Sets the text opacity in the text label - * - * Value is a percentage, as an integer between 0 (transparent) and 100 (opaque) - * - * Default value is 100 - * - * @access public - * @var integer - */ - var $image_text_opacity; - - /** - * Sets the text background color for the text label - * - * Value is an hexadecimal color, such as #FFFFFF - * - * Default value is null (no background) - * - * @access public - * @var string; - */ - var $image_text_background; - - /** - * Sets the text background opacity in the text label - * - * Value is a percentage, as an integer between 0 (transparent) and 100 (opaque) - * - * Default value is 100 - * - * @access public - * @var integer - */ - var $image_text_background_opacity; - - /** - * Sets the text font in the text label - * - * Value is a an integer between 1 and 5 for GD built-in fonts. 1 is the smallest font, 5 the biggest - * Value can also be a string, which represents the path to a GDF or TTF font (TrueType). - * - * Default value is 5 - * - * @access public - * @var mixed; - */ - var $image_text_font; - - /** - * Sets the text font size for TrueType fonts - * - * Value is a an integer, and represents the font size in pixels (GD1) or points (GD1) - * - * Note that this setting is only applicable to TrueType fonts, and has no effects with GD fonts - * - * Default value is 16 - * - * @access public - * @var integer; - */ - var $image_text_size; - - /** - * Sets the text angle for TrueType fonts - * - * Value is a an integer between 0 and 360, in degrees, with 0 degrees being left-to-right reading text. - * - * Note that this setting is only applicable to TrueType fonts, and has no effects with GD fonts - * For GD fonts, you can use {@link image_text_direction} instead - * - * Default value is null (so it is determined by the value of {@link image_text_direction}) - * - * @access public - * @var integer; - */ - var $image_text_angle; - - /** - * Sets the text label position within the image - * - * Value is one or two out of 'TBLR' (top, bottom, left, right) - * - * The positions are as following: - *
-     *                        TL  T  TR
-     *                        L       R
-     *                        BL  B  BR
-     * 
- * - * Default value is null (centered, horizontal and vertical) - * - * Note that is {@link image_text_x} and {@link image_text_y} are used, this setting has no effect - * - * @access public - * @var string; - */ - var $image_text_position; - - /** - * Sets the text label absolute X position within the image - * - * Value is in pixels, representing the distance between the left of the image and the label - * If a negative value is used, it will represent the distance between the right of the image and the label - * - * Default value is null (so {@link image_text_position} is used) - * - * @access public - * @var integer - */ - var $image_text_x; - - /** - * Sets the text label absolute Y position within the image - * - * Value is in pixels, representing the distance between the top of the image and the label - * If a negative value is used, it will represent the distance between the bottom of the image and the label - * - * Default value is null (so {@link image_text_position} is used) - * - * @access public - * @var integer - */ - var $image_text_y; - - /** - * Sets the text label padding - * - * Value is in pixels, representing the distance between the text and the label background border - * - * Default value is 0 - * - * This setting can be overriden by {@link image_text_padding_x} and {@link image_text_padding_y} - * - * @access public - * @var integer - */ - var $image_text_padding; - - /** - * Sets the text label horizontal padding - * - * Value is in pixels, representing the distance between the text and the left and right label background borders - * - * Default value is null - * - * If set, this setting overrides the horizontal part of {@link image_text_padding} - * - * @access public - * @var integer - */ - var $image_text_padding_x; - - /** - * Sets the text label vertical padding - * - * Value is in pixels, representing the distance between the text and the top and bottom label background borders - * - * Default value is null - * - * If set, his setting overrides the vertical part of {@link image_text_padding} - * - * @access public - * @var integer - */ - var $image_text_padding_y; - - /** - * Sets the text alignment - * - * Value is a string, which can be either 'L', 'C' or 'R' - * - * Default value is 'C' - * - * This setting is relevant only if the text has several lines. - * - * Note that this setting is only applicable to GD fonts, and has no effects with TrueType fonts - * - * @access public - * @var string; - */ - var $image_text_alignment; - - /** - * Sets the text line spacing - * - * Value is an integer, in pixels - * - * Default value is 0 - * - * This setting is relevant only if the text has several lines. - * - * Note that this setting is only applicable to GD fonts, and has no effects with TrueType fonts - * - * @access public - * @var integer - */ - var $image_text_line_spacing; - - /** - * Sets the height of the reflection - * - * Value is an integer in pixels, or a string which format can be in pixels or percentage. - * For instance, values can be : 40, '40', '40px' or '40%' - * - * Default value is null, no reflection - * - * @access public - * @var mixed; - */ - var $image_reflection_height; - - /** - * Sets the space between the source image and its relection - * - * Value is an integer in pixels, which can be negative - * - * Default value is 2 - * - * This setting is relevant only if {@link image_reflection_height} is set - * - * @access public - * @var integer - */ - var $image_reflection_space; - - /** - * Sets the initial opacity of the reflection - * - * Value is an integer between 0 (no opacity) and 100 (full opacity). - * The reflection will start from {@link image_reflection_opacity} and end up at 0 - * - * Default value is 60 - * - * This setting is relevant only if {@link image_reflection_height} is set - * - * @access public - * @var integer - */ - var $image_reflection_opacity; - - /** - * Automatically rotates the image according to EXIF data (JPEG only) - * - * Default value is true - * - * @access public - * @var boolean; - */ - var $image_auto_rotate; - - /** - * Flips the image vertically or horizontally - * - * Value is either 'h' or 'v', as in horizontal and vertical - * - * Default value is null (no flip) - * - * @access public - * @var string; - */ - var $image_flip; - - /** - * Rotates the image by increments of 45 degrees - * - * Value is either 90, 180 or 270 - * - * Default value is null (no rotation) - * - * @access public - * @var string; - */ - var $image_rotate; - - /** - * Crops an image - * - * Values are four dimensions, or two, or one (CSS style) - * They represent the amount cropped top, right, bottom and left. - * These values can either be in an array, or a space separated string. - * Each value can be in pixels (with or without 'px'), or percentage (of the source image) - * - * For instance, are valid: - *
-     * $foo->image_crop = 20                  OR array(20);
-     * $foo->image_crop = '20px'              OR array('20px');
-     * $foo->image_crop = '20 40'             OR array('20', 40);
-     * $foo->image_crop = '-20 25%'           OR array(-20, '25%');
-     * $foo->image_crop = '20px 25%'          OR array('20px', '25%');
-     * $foo->image_crop = '20% 25%'           OR array('20%', '25%');
-     * $foo->image_crop = '20% 25% 10% 30%'   OR array('20%', '25%', '10%', '30%');
-     * $foo->image_crop = '20px 25px 2px 2px' OR array('20px', '25%px', '2px', '2px');
-     * $foo->image_crop = '20 25% 40px 10%'   OR array(20, '25%', '40px', '10%');
-     * 
- * - * If a value is negative, the image will be expanded, and the extra parts will be filled with black - * - * Default value is null (no cropping) - * - * @access public - * @var string OR array; - */ - var $image_crop; - - /** - * Crops an image, before an eventual resizing - * - * See {@link image_crop} for valid formats - * - * Default value is null (no cropping) - * - * @access public - * @var string OR array; - */ - var $image_precrop; - - /** - * Adds a bevel border on the image - * - * Value is a positive integer, representing the thickness of the bevel - * - * If the bevel colors are the same as the background, it makes a fade out effect - * - * Default value is null (no bevel) - * - * @access public - * @var integer - */ - var $image_bevel; - - /** - * Top and left bevel color - * - * Value is a color, in hexadecimal format - * This setting is used only if {@link image_bevel} is set - * - * Default value is #FFFFFF - * - * @access public - * @var string; - */ - var $image_bevel_color1; - - /** - * Right and bottom bevel color - * - * Value is a color, in hexadecimal format - * This setting is used only if {@link image_bevel} is set - * - * Default value is #000000 - * - * @access public - * @var string; - */ - var $image_bevel_color2; - - /** - * Adds a single-color border on the outer of the image - * - * Values are four dimensions, or two, or one (CSS style) - * They represent the border thickness top, right, bottom and left. - * These values can either be in an array, or a space separated string. - * Each value can be in pixels (with or without 'px'), or percentage (of the source image) - * - * See {@link image_crop} for valid formats - * - * If a value is negative, the image will be cropped. - * Note that the dimensions of the picture will be increased by the borders' thickness - * - * Default value is null (no border) - * - * @access public - * @var integer - */ - var $image_border; - - /** - * Border color - * - * Value is a color, in hexadecimal format. - * This setting is used only if {@link image_border} is set - * - * Default value is #FFFFFF - * - * @access public - * @var string; - */ - var $image_border_color; - - /** - * Sets the opacity for the borders - * - * Value is a percentage, as an integer between 0 (transparent) and 100 (opaque) - * - * Unless used with {@link image_border}, this setting has no effect - * - * Default value is 100 - * - * @access public - * @var integer - */ - var $image_border_opacity; - - /** - * Adds a fading-to-transparent border on the image - * - * Values are four dimensions, or two, or one (CSS style) - * They represent the border thickness top, right, bottom and left. - * These values can either be in an array, or a space separated string. - * Each value can be in pixels (with or without 'px'), or percentage (of the source image) - * - * See {@link image_crop} for valid formats - * - * Note that the dimensions of the picture will not be increased by the borders' thickness - * - * Default value is null (no border) - * - * @access public - * @var integer - */ - var $image_border_transparent; - - /** - * Adds a multi-color frame on the outer of the image - * - * Value is an integer. Two values are possible for now: - * 1 for flat border, meaning that the frame is mirrored horizontally and vertically - * 2 for crossed border, meaning that the frame will be inversed, as in a bevel effect - * - * The frame will be composed of colored lines set in {@link image_frame_colors} - * - * Note that the dimensions of the picture will be increased by the borders' thickness - * - * Default value is null (no frame) - * - * @access public - * @var integer - */ - var $image_frame; - - /** - * Sets the colors used to draw a frame - * - * Values is a list of n colors in hexadecimal format. - * These values can either be in an array, or a space separated string. - * - * The colors are listed in the following order: from the outset of the image to its center - * - * For instance, are valid: - *
-     * $foo->image_frame_colors = '#FFFFFF #999999 #666666 #000000';
-     * $foo->image_frame_colors = array('#FFFFFF', '#999999', '#666666', '#000000');
-     * 
- * - * This setting is used only if {@link image_frame} is set - * - * Default value is '#FFFFFF #999999 #666666 #000000' - * - * @access public - * @var string OR array; - */ - var $image_frame_colors; - - /** - * Sets the opacity for the frame - * - * Value is a percentage, as an integer between 0 (transparent) and 100 (opaque) - * - * Unless used with {@link image_frame}, this setting has no effect - * - * Default value is 100 - * - * @access public - * @var integer - */ - var $image_frame_opacity; - - /** - * Adds a watermark on the image - * - * Value is a local image filename, relative or absolute. GIF, JPG, BMP and PNG are supported, as well as PNG alpha. - * - * If set, this setting allow the use of all other settings starting with image_watermark_ - * - * Default value is null - * - * @access public - * @var string; - */ - var $image_watermark; - - /** - * Sets the watermarkposition within the image - * - * Value is one or two out of 'TBLR' (top, bottom, left, right) - * - * The positions are as following: TL T TR - * L R - * BL B BR - * - * Default value is null (centered, horizontal and vertical) - * - * Note that is {@link image_watermark_x} and {@link image_watermark_y} are used, this setting has no effect - * - * @access public - * @var string; - */ - var $image_watermark_position; - - /** - * Sets the watermark absolute X position within the image - * - * Value is in pixels, representing the distance between the top of the image and the watermark - * If a negative value is used, it will represent the distance between the bottom of the image and the watermark - * - * Default value is null (so {@link image_watermark_position} is used) - * - * @access public - * @var integer - */ - var $image_watermark_x; - - /** - * Sets the twatermark absolute Y position within the image - * - * Value is in pixels, representing the distance between the left of the image and the watermark - * If a negative value is used, it will represent the distance between the right of the image and the watermark - * - * Default value is null (so {@link image_watermark_position} is used) - * - * @access public - * @var integer - */ - var $image_watermark_y; - - /** - * Prevents the watermark to be resized up if it is smaller than the image - * - * If the watermark if smaller than the destination image, taking in account the desired watermark position - * then it will be resized up to fill in the image (minus the {@link image_watermark_x} or {@link image_watermark_y} values) - * - * If you don't want your watermark to be resized in any way, then - * set {@link image_watermark_no_zoom_in} and {@link image_watermark_no_zoom_out} to true - * If you want your watermark to be resized up or doan to fill in the image better, then - * set {@link image_watermark_no_zoom_in} and {@link image_watermark_no_zoom_out} to false - * - * Default value is true (so the watermark will not be resized up, which is the behaviour most people expect) - * - * @access public - * @var integer - */ - var $image_watermark_no_zoom_in; - - /** - * Prevents the watermark to be resized down if it is bigger than the image - * - * If the watermark if bigger than the destination image, taking in account the desired watermark position - * then it will be resized down to fit in the image (minus the {@link image_watermark_x} or {@link image_watermark_y} values) - * - * If you don't want your watermark to be resized in any way, then - * set {@link image_watermark_no_zoom_in} and {@link image_watermark_no_zoom_out} to true - * If you want your watermark to be resized up or doan to fill in the image better, then - * set {@link image_watermark_no_zoom_in} and {@link image_watermark_no_zoom_out} to false - * - * Default value is false (so the watermark may be shrinked to fit in the image) - * - * @access public - * @var integer - */ - var $image_watermark_no_zoom_out; - - /** - * List of MIME types per extension - * - * @access private - * @var array - */ - var $mime_types; - - /** - * Allowed MIME types - * - * Default is a selection of safe mime-types, but you might want to change it - * - * Simple wildcards are allowed, such as image/* or application/* - * If there is only one MIME type allowed, then it can be a string instead of an array - * - * @access public - * @var array OR string - */ - var $allowed; - - /** - * Forbidden MIME types - * - * Default is a selection of safe mime-types, but you might want to change it - * To only check for forbidden MIME types, and allow everything else, set {@link allowed} to array('* / *') without the spaces - * - * Simple wildcards are allowed, such as image/* or application/* - * If there is only one MIME type forbidden, then it can be a string instead of an array - * - * @access public - * @var array OR string - */ - var $forbidden; - - /** - * Array of translated error messages - * - * By default, the language is english (en_GB) - * Translations can be in separate files, in a lang/ subdirectory - * - * @access public - * @var array - */ - var $translation; - - /** - * Language selected for the translations - * - * By default, the language is english ("en_GB") - * - * @access public - * @var array - */ - var $lang; - - /** - * Init or re-init all the processing variables to their default values - * - * This function is called in the constructor, and after each call of {@link process} - * - * @access private - */ - function init() { - - // overiddable variables - $this->file_new_name_body = null; // replace the name body - $this->file_name_body_add = null; // append to the name body - $this->file_name_body_pre = null; // prepend to the name body - $this->file_new_name_ext = null; // replace the file extension - $this->file_safe_name = true; // format safely the filename - $this->file_force_extension = true; // forces extension if there isn't one - $this->file_overwrite = false; // allows overwritting if the file already exists - $this->file_auto_rename = true; // auto-rename if the file already exists - $this->dir_auto_create = true; // auto-creates directory if missing - $this->dir_auto_chmod = true; // auto-chmod directory if not writeable - $this->dir_chmod = 0777; // default chmod to use - - $this->no_script = true; // turns scripts into test files - $this->mime_check = true; // checks the mime type against the allowed list - - // these are the different MIME detection methods. if one of these method doesn't work on your - // system, you can deactivate it here; just set it to false - $this->mime_fileinfo = true; // MIME detection with Fileinfo PECL extension - $this->mime_file = true; // MIME detection with UNIX file() command - $this->mime_magic = true; // MIME detection with mime_magic (mime_content_type()) - $this->mime_getimagesize = true; // MIME detection with getimagesize() - - // get the default max size from php.ini - $this->file_max_size_raw = trim(ini_get('upload_max_filesize')); - $this->file_max_size = $this->getsize($this->file_max_size_raw); - - $this->image_resize = false; // resize the image - $this->image_convert = ''; // convert. values :''; 'png'; 'jpeg'; 'gif'; 'bmp' - - $this->image_x = 150; - $this->image_y = 150; - $this->image_ratio = false; // keeps aspect ratio within x and y dimensions - $this->image_ratio_crop = false; // keeps aspect ratio within x and y dimensions, filling the space - $this->image_ratio_fill = false; // keeps aspect ratio within x and y dimensions, fitting the image in the space - $this->image_ratio_pixels = false; // keeps aspect ratio, calculating x and y to reach the number of pixels - $this->image_ratio_x = false; // calculate the $image_x if true - $this->image_ratio_y = false; // calculate the $image_y if true - $this->image_ratio_no_zoom_in = false; - $this->image_ratio_no_zoom_out = false; - $this->image_no_enlarging = false; - $this->image_no_shrinking = false; - - $this->png_compression = null; - $this->jpeg_quality = 85; - $this->jpeg_size = null; - $this->image_interlace = false; - $this->image_is_transparent = false; - $this->image_transparent_color = null; - $this->image_background_color = null; - $this->image_default_color = '#ffffff'; - $this->image_is_palette = false; - - $this->image_max_width = null; - $this->image_max_height = null; - $this->image_max_pixels = null; - $this->image_max_ratio = null; - $this->image_min_width = null; - $this->image_min_height = null; - $this->image_min_pixels = null; - $this->image_min_ratio = null; - - $this->image_brightness = null; - $this->image_contrast = null; - $this->image_opacity = null; - $this->image_threshold = null; - $this->image_tint_color = null; - $this->image_overlay_color = null; - $this->image_overlay_opacity = null; - $this->image_negative = false; - $this->image_greyscale = false; - $this->image_pixelate = null; - $this->image_unsharp = false; - $this->image_unsharp_amount = 80; - $this->image_unsharp_radius = 0.5; - $this->image_unsharp_threshold = 1; - - $this->image_text = null; - $this->image_text_direction = null; - $this->image_text_color = '#FFFFFF'; - $this->image_text_opacity = 100; - $this->image_text_background = null; - $this->image_text_background_opacity = 100; - $this->image_text_font = 5; - $this->image_text_size = 16; - $this->image_text_angle = null; - $this->image_text_x = null; - $this->image_text_y = null; - $this->image_text_position = null; - $this->image_text_padding = 0; - $this->image_text_padding_x = null; - $this->image_text_padding_y = null; - $this->image_text_alignment = 'C'; - $this->image_text_line_spacing = 0; - - $this->image_reflection_height = null; - $this->image_reflection_space = 2; - $this->image_reflection_opacity = 60; - - $this->image_watermark = null; - $this->image_watermark_x = null; - $this->image_watermark_y = null; - $this->image_watermark_position = null; - $this->image_watermark_no_zoom_in = true; - $this->image_watermark_no_zoom_out = false; - - $this->image_flip = null; - $this->image_auto_rotate = true; - $this->image_rotate = null; - $this->image_crop = null; - $this->image_precrop = null; - - $this->image_bevel = null; - $this->image_bevel_color1 = '#FFFFFF'; - $this->image_bevel_color2 = '#000000'; - $this->image_border = null; - $this->image_border_color = '#FFFFFF'; - $this->image_border_opacity = 100; - $this->image_border_transparent = null; - $this->image_frame = null; - $this->image_frame_colors = '#FFFFFF #999999 #666666 #000000'; - $this->image_frame_opacity = 100; - - $this->forbidden = array(); - $this->allowed = array( - 'application/arj', - 'application/excel', - 'application/gnutar', - 'application/mspowerpoint', - 'application/msword', - 'application/octet-stream', - 'application/onenote', - 'application/pdf', - 'application/plain', - 'application/postscript', - 'application/powerpoint', - 'application/rar', - 'application/rtf', - 'application/vnd.ms-excel', - 'application/vnd.ms-excel.addin.macroEnabled.12', - 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', - 'application/vnd.ms-excel.sheet.macroEnabled.12', - 'application/vnd.ms-excel.template.macroEnabled.12', - 'application/vnd.ms-office', - 'application/vnd.ms-officetheme', - 'application/vnd.ms-powerpoint', - 'application/vnd.ms-powerpoint.addin.macroEnabled.12', - 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', - 'application/vnd.ms-powerpoint.slide.macroEnabled.12', - 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12', - 'application/vnd.ms-powerpoint.template.macroEnabled.12', - 'application/vnd.ms-word', - 'application/vnd.ms-word.document.macroEnabled.12', - 'application/vnd.ms-word.template.macroEnabled.12', - 'application/vnd.oasis.opendocument.chart', - 'application/vnd.oasis.opendocument.database', - 'application/vnd.oasis.opendocument.formula', - 'application/vnd.oasis.opendocument.graphics', - 'application/vnd.oasis.opendocument.graphics-template', - 'application/vnd.oasis.opendocument.image', - 'application/vnd.oasis.opendocument.presentation', - 'application/vnd.oasis.opendocument.presentation-template', - 'application/vnd.oasis.opendocument.spreadsheet', - 'application/vnd.oasis.opendocument.spreadsheet-template', - 'application/vnd.oasis.opendocument.text', - 'application/vnd.oasis.opendocument.text-master', - 'application/vnd.oasis.opendocument.text-template', - 'application/vnd.oasis.opendocument.text-web', - 'application/vnd.openofficeorg.extension', - 'application/vnd.openxmlformats-officedocument.presentationml.presentation', - 'application/vnd.openxmlformats-officedocument.presentationml.slide', - 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', - 'application/vnd.openxmlformats-officedocument.presentationml.template', - 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', - 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', - 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', - 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', - 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', - 'application/vocaltec-media-file', - 'application/wordperfect', - 'application/x-bittorrent', - 'application/x-bzip', - 'application/x-bzip2', - 'application/x-compressed', - 'application/x-excel', - 'application/x-gzip', - 'application/x-latex', - 'application/x-midi', - 'application/xml', - 'application/x-msexcel', - 'application/x-rar', - 'application/x-rar-compressed', - 'application/x-rtf', - 'application/x-shockwave-flash', - 'application/x-sit', - 'application/x-stuffit', - 'application/x-troff-msvideo', - 'application/x-zip', - 'application/x-zip-compressed', - 'application/zip', - 'audio/*', - 'image/*', - 'multipart/x-gzip', - 'multipart/x-zip', - 'text/plain', - 'text/rtf', - 'text/richtext', - 'text/xml', - 'video/*', - 'text/csv' - ); - - $this->mime_types = array( - 'jpg' => 'image/jpeg', - 'jpeg' => 'image/jpeg', - 'jpe' => 'image/jpeg', - 'gif' => 'image/gif', - 'png' => 'image/png', - 'bmp' => 'image/bmp', - 'flif' => 'image/flif', - 'flv' => 'video/x-flv', - 'js' => 'application/x-javascript', - 'json' => 'application/json', - 'tiff' => 'image/tiff', - 'css' => 'text/css', - 'xml' => 'application/xml', - 'doc' => 'application/msword', - 'xls' => 'application/vnd.ms-excel', - 'xlt' => 'application/vnd.ms-excel', - 'xlm' => 'application/vnd.ms-excel', - 'xld' => 'application/vnd.ms-excel', - 'xla' => 'application/vnd.ms-excel', - 'xlc' => 'application/vnd.ms-excel', - 'xlw' => 'application/vnd.ms-excel', - 'xll' => 'application/vnd.ms-excel', - 'ppt' => 'application/vnd.ms-powerpoint', - 'pps' => 'application/vnd.ms-powerpoint', - 'rtf' => 'application/rtf', - 'pdf' => 'application/pdf', - 'html' => 'text/html', - 'htm' => 'text/html', - 'php' => 'text/html', - 'txt' => 'text/plain', - 'mpeg' => 'video/mpeg', - 'mpg' => 'video/mpeg', - 'mpe' => 'video/mpeg', - 'mp3' => 'audio/mpeg3', - 'wav' => 'audio/wav', - 'aiff' => 'audio/aiff', - 'aif' => 'audio/aiff', - 'avi' => 'video/msvideo', - 'wmv' => 'video/x-ms-wmv', - 'mov' => 'video/quicktime', - 'zip' => 'application/zip', - 'tar' => 'application/x-tar', - 'swf' => 'application/x-shockwave-flash', - 'odt' => 'application/vnd.oasis.opendocument.text', - 'ott' => 'application/vnd.oasis.opendocument.text-template', - 'oth' => 'application/vnd.oasis.opendocument.text-web', - 'odm' => 'application/vnd.oasis.opendocument.text-master', - 'odg' => 'application/vnd.oasis.opendocument.graphics', - 'otg' => 'application/vnd.oasis.opendocument.graphics-template', - 'odp' => 'application/vnd.oasis.opendocument.presentation', - 'otp' => 'application/vnd.oasis.opendocument.presentation-template', - 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', - 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template', - 'odc' => 'application/vnd.oasis.opendocument.chart', - 'odf' => 'application/vnd.oasis.opendocument.formula', - 'odb' => 'application/vnd.oasis.opendocument.database', - 'odi' => 'application/vnd.oasis.opendocument.image', - 'oxt' => 'application/vnd.openofficeorg.extension', - 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', - 'docm' => 'application/vnd.ms-word.document.macroEnabled.12', - 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', - 'dotm' => 'application/vnd.ms-word.template.macroEnabled.12', - 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', - 'xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12', - 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', - 'xltm' => 'application/vnd.ms-excel.template.macroEnabled.12', - 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', - 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12', - 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', - 'pptm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', - 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', - 'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12', - 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', - 'potm' => 'application/vnd.ms-powerpoint.template.macroEnabled.12', - 'ppam' => 'application/vnd.ms-powerpoint.addin.macroEnabled.12', - 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide', - 'sldm' => 'application/vnd.ms-powerpoint.slide.macroEnabled.12', - 'thmx' => 'application/vnd.ms-officetheme', - 'onetoc' => 'application/onenote', - 'onetoc2' => 'application/onenote', - 'onetmp' => 'application/onenote', - 'onepkg' => 'application/onenote', - 'csv' => 'text/csv', - ); - - } - - /** - * Constructor, for PHP5+ - */ - function __construct($file, $lang = 'en_GB') { - $this->upload($file, $lang); - } - - /** - * Constructor, for PHP4. Checks if the file has been uploaded - * - * The constructor takes $_FILES['form_field'] array as argument - * where form_field is the form field name - * - * The constructor will check if the file has been uploaded in its temporary location, and - * accordingly will set {@link uploaded} (and {@link error} is an error occurred) - * - * If the file has been uploaded, the constructor will populate all the variables holding the upload - * information (none of the processing class variables are used here). - * You can have access to information about the file (name, size, MIME type...). - * - * - * Alternatively, you can set the first argument to be a local filename (string) - * This allows processing of a local file, as if the file was uploaded - * - * The optional second argument allows you to set the language for the error messages - * - * @access private - * @param array $file $_FILES['form_field'] - * or string $file Local filename - * @param string $lang Optional language code - */ - function upload($file, $lang = 'en_GB') { - - $this->version = '0.35dev'; - - $this->file_src_name = ''; - $this->file_src_name_body = ''; - $this->file_src_name_ext = ''; - $this->file_src_mime = ''; - $this->file_src_size = ''; - $this->file_src_error = ''; - $this->file_src_pathname = ''; - $this->file_src_temp = ''; - - $this->file_dst_path = ''; - $this->file_dst_name = ''; - $this->file_dst_name_body = ''; - $this->file_dst_name_ext = ''; - $this->file_dst_pathname = ''; - - $this->image_src_x = null; - $this->image_src_y = null; - $this->image_src_bits = null; - $this->image_src_type = null; - $this->image_src_pixels = null; - $this->image_dst_x = 0; - $this->image_dst_y = 0; - $this->image_dst_type = ''; - - $this->uploaded = true; - $this->no_upload_check = false; - $this->processed = false; - $this->error = ''; - $this->log = ''; - $this->allowed = array(); - $this->forbidden = array(); - $this->file_is_image = false; - $this->init(); - $info = null; - $mime_from_browser = null; - - // sets default language - $this->translation = array(); - $this->translation['file_error'] = 'File error. Please try again.'; - $this->translation['local_file_missing'] = 'Local file doesn\'t exist.'; - $this->translation['local_file_not_readable'] = 'Local file is not readable.'; - $this->translation['uploaded_too_big_ini'] = 'File upload error (the uploaded file exceeds the upload_max_filesize directive in php.ini).'; - $this->translation['uploaded_too_big_html'] = 'File upload error (the uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the html form).'; - $this->translation['uploaded_partial'] = 'File upload error (the uploaded file was only partially uploaded).'; - $this->translation['uploaded_missing'] = 'File upload error (no file was uploaded).'; - $this->translation['uploaded_no_tmp_dir'] = 'File upload error (missing a temporary folder).'; - $this->translation['uploaded_cant_write'] = 'File upload error (failed to write file to disk).'; - $this->translation['uploaded_err_extension'] = 'File upload error (file upload stopped by extension).'; - $this->translation['uploaded_unknown'] = 'File upload error (unknown error code).'; - $this->translation['try_again'] = 'File upload error. Please try again.'; - $this->translation['file_too_big'] = 'File too big.'; - $this->translation['no_mime'] = 'MIME type can\'t be detected.'; - $this->translation['incorrect_file'] = 'Incorrect type of file.'; - $this->translation['image_too_wide'] = 'Image too wide.'; - $this->translation['image_too_narrow'] = 'Image too narrow.'; - $this->translation['image_too_high'] = 'Image too tall.'; - $this->translation['image_too_short'] = 'Image too short.'; - $this->translation['ratio_too_high'] = 'Image ratio too high (image too wide).'; - $this->translation['ratio_too_low'] = 'Image ratio too low (image too high).'; - $this->translation['too_many_pixels'] = 'Image has too many pixels.'; - $this->translation['not_enough_pixels'] = 'Image has not enough pixels.'; - $this->translation['file_not_uploaded'] = 'File not uploaded. Can\'t carry on a process.'; - $this->translation['already_exists'] = '%s already exists. Please change the file name.'; - $this->translation['temp_file_missing'] = 'No correct temp source file. Can\'t carry on a process.'; - $this->translation['source_missing'] = 'No correct uploaded source file. Can\'t carry on a process.'; - $this->translation['destination_dir'] = 'Destination directory can\'t be created. Can\'t carry on a process.'; - $this->translation['destination_dir_missing'] = 'Destination directory doesn\'t exist. Can\'t carry on a process.'; - $this->translation['destination_path_not_dir'] = 'Destination path is not a directory. Can\'t carry on a process.'; - $this->translation['destination_dir_write'] = 'Destination directory can\'t be made writeable. Can\'t carry on a process.'; - $this->translation['destination_path_write'] = 'Destination path is not a writeable. Can\'t carry on a process.'; - $this->translation['temp_file'] = 'Can\'t create the temporary file. Can\'t carry on a process.'; - $this->translation['source_not_readable'] = 'Source file is not readable. Can\'t carry on a process.'; - $this->translation['no_create_support'] = 'No create from %s support.'; - $this->translation['create_error'] = 'Error in creating %s image from source.'; - $this->translation['source_invalid'] = 'Can\'t read image source. Not an image?.'; - $this->translation['gd_missing'] = 'GD doesn\'t seem to be present.'; - $this->translation['watermark_no_create_support'] = 'No create from %s support, can\'t read watermark.'; - $this->translation['watermark_create_error'] = 'No %s read support, can\'t create watermark.'; - $this->translation['watermark_invalid'] = 'Unknown image format, can\'t read watermark.'; - $this->translation['file_create'] = 'No %s create support.'; - $this->translation['no_conversion_type'] = 'No conversion type defined.'; - $this->translation['copy_failed'] = 'Error copying file on the server. copy() failed.'; - $this->translation['reading_failed'] = 'Error reading the file.'; - - // determines the language - $this->lang = $lang; - if ($this->lang != 'en_GB' && file_exists(dirname(__FILE__).'/lang') && file_exists(dirname(__FILE__).'/lang/class.upload.' . $lang . '.php')) { - $translation = null; - include(dirname(__FILE__).'/lang/class.upload.' . $lang . '.php'); - if (is_array($translation)) { - $this->translation = array_merge($this->translation, $translation); - } else { - $this->lang = 'en_GB'; - } - } - - - // determines the supported MIME types, and matching image format - $this->image_supported = array(); - if ($this->gdversion()) { - if (imagetypes() & IMG_GIF) { - $this->image_supported['image/gif'] = 'gif'; - } - if (imagetypes() & IMG_JPG) { - $this->image_supported['image/jpg'] = 'jpg'; - $this->image_supported['image/jpeg'] = 'jpg'; - $this->image_supported['image/pjpeg'] = 'jpg'; - } - if (imagetypes() & IMG_PNG) { - $this->image_supported['image/png'] = 'png'; - $this->image_supported['image/x-png'] = 'png'; - } - if (imagetypes() & IMG_WBMP) { - $this->image_supported['image/bmp'] = 'bmp'; - $this->image_supported['image/x-ms-bmp'] = 'bmp'; - $this->image_supported['image/x-windows-bmp'] = 'bmp'; - } - } - - // display some system information - if (empty($this->log)) { - $this->log .= 'system information
'; - if ($this->function_enabled('ini_get_all')) { - $inis = ini_get_all(); - $open_basedir = (array_key_exists('open_basedir', $inis) && array_key_exists('local_value', $inis['open_basedir']) && !empty($inis['open_basedir']['local_value'])) ? $inis['open_basedir']['local_value'] : false; - } else { - $open_basedir = false; - } - $gd = $this->gdversion() ? $this->gdversion(true) : 'GD not present'; - $supported = trim((in_array('png', $this->image_supported) ? 'png' : '') . ' ' . - (in_array('jpg', $this->image_supported) ? 'jpg' : '') . ' ' . - (in_array('gif', $this->image_supported) ? 'gif' : '') . ' ' . - (in_array('bmp', $this->image_supported) ? 'bmp' : '')); - $this->log .= '- class version : ' . $this->version . '
'; - $this->log .= '- operating system : ' . PHP_OS . '
'; - $this->log .= '- PHP version : ' . PHP_VERSION . '
'; - $this->log .= '- GD version : ' . $gd . '
'; - $this->log .= '- supported image types : ' . (!empty($supported) ? $supported : 'none') . '
'; - $this->log .= '- open_basedir : ' . (!empty($open_basedir) ? $open_basedir : 'no restriction') . '
'; - $this->log .= '- upload_max_filesize : ' . $this->file_max_size_raw . ' (' . $this->file_max_size . ' bytes)
'; - $this->log .= '- language : ' . $this->lang . '
'; - } - - if (!$file) { - $this->uploaded = false; - $this->error = $this->translate('file_error'); - } - - // check if we sent a local filename or a PHP stream rather than a $_FILE element - if (!is_array($file)) { - if (empty($file)) { - $this->uploaded = false; - $this->error = $this->translate('file_error'); - } else { - if (substr($file, 0, 4) == 'php:' || substr($file, 0, 5) == 'data:' || substr($file, 0, 7) == 'base64:') { - $data = null; - - // this is a PHP stream, i.e.not uploaded - if (substr($file, 0, 4) == 'php:') { - $file = preg_replace('/^php:(.*)/i', '$1', $file); - if (!$file) $file = $_SERVER['HTTP_X_FILE_NAME']; - if (!$file) $file = 'unknown'; - $data = file_get_contents('php://input'); - $this->log .= 'source is a PHP stream ' . $file . ' of length ' . strlen($data) . '
'; - - // this is the raw file data, base64-encoded, i.e.not uploaded - } else if (substr($file, 0, 7) == 'base64:') { - $data = base64_decode(preg_replace('/^base64:(.*)/i', '$1', $file)); - $file = 'base64'; - $this->log .= 'source is a base64 string of length ' . strlen($data) . '
'; - - // this is the raw file data, base64-encoded, i.e.not uploaded - } else if (substr($file, 0, 5) == 'data:' && strpos($file, 'base64,') !== false) { - $data = base64_decode(preg_replace('/^data:.*base64,(.*)/i', '$1', $file)); - $file = 'base64'; - $this->log .= 'source is a base64 data string of length ' . strlen($data) . '
'; - - // this is the raw file data, i.e.not uploaded - } else if (substr($file, 0, 5) == 'data:') { - $data = preg_replace('/^data:(.*)/i', '$1', $file); - $file = 'data'; - $this->log .= 'source is a data string of length ' . strlen($data) . '
'; - } - - if (!$data) { - $this->log .= '- source is empty!
'; - $this->uploaded = false; - $this->error = $this->translate('source_invalid'); - } - - $this->no_upload_check = TRUE; - - if ($this->uploaded) { - $this->log .= '- requires a temp file ... '; - $hash = $this->temp_dir() . md5($file . rand(1, 1000)); - if ($data && file_put_contents($hash, $data)) { - $this->file_src_pathname = $hash; - $this->log .= ' file created
'; - $this->log .= '    temp file is: ' . $this->file_src_pathname . '
'; - } else { - $this->log .= ' failed
'; - $this->uploaded = false; - $this->error = $this->translate('temp_file'); - } - } - - if ($this->uploaded) { - $this->file_src_name = $file; - $this->log .= '- local file OK
'; - preg_match('/\.([^\.]*$)/', $this->file_src_name, $extension); - if (is_array($extension) && sizeof($extension) > 0) { - $this->file_src_name_ext = strtolower($extension[1]); - $this->file_src_name_body = substr($this->file_src_name, 0, ((strlen($this->file_src_name) - strlen($this->file_src_name_ext)))-1); - } else { - $this->file_src_name_ext = ''; - $this->file_src_name_body = $this->file_src_name; - } - $this->file_src_size = (file_exists($this->file_src_pathname) ? filesize($this->file_src_pathname) : 0); - } - $this->file_src_error = 0; - - } else { - // this is a local filename, i.e.not uploaded - $this->log .= 'source is a local file ' . $file . '
'; - $this->no_upload_check = TRUE; - - if ($this->uploaded && !file_exists($file)) { - $this->uploaded = false; - $this->error = $this->translate('local_file_missing'); - } - - if ($this->uploaded && !is_readable($file)) { - $this->uploaded = false; - $this->error = $this->translate('local_file_not_readable'); - } - - if ($this->uploaded) { - $this->file_src_pathname = $file; - $this->file_src_name = basename($file); - $this->log .= '- local file OK
'; - preg_match('/\.([^\.]*$)/', $this->file_src_name, $extension); - if (is_array($extension) && sizeof($extension) > 0) { - $this->file_src_name_ext = strtolower($extension[1]); - $this->file_src_name_body = substr($this->file_src_name, 0, ((strlen($this->file_src_name) - strlen($this->file_src_name_ext)))-1); - } else { - $this->file_src_name_ext = ''; - $this->file_src_name_body = $this->file_src_name; - } - $this->file_src_size = (file_exists($this->file_src_pathname) ? filesize($this->file_src_pathname) : 0); - } - $this->file_src_error = 0; - } - } - } else { - // this is an element from $_FILE, i.e. an uploaded file - $this->log .= 'source is an uploaded file
'; - if ($this->uploaded) { - $this->file_src_error = trim($file['error']); - switch($this->file_src_error) { - case UPLOAD_ERR_OK: - // all is OK - $this->log .= '- upload OK
'; - break; - case UPLOAD_ERR_INI_SIZE: - $this->uploaded = false; - $this->error = $this->translate('uploaded_too_big_ini'); - break; - case UPLOAD_ERR_FORM_SIZE: - $this->uploaded = false; - $this->error = $this->translate('uploaded_too_big_html'); - break; - case UPLOAD_ERR_PARTIAL: - $this->uploaded = false; - $this->error = $this->translate('uploaded_partial'); - break; - case UPLOAD_ERR_NO_FILE: - $this->uploaded = false; - $this->error = $this->translate('uploaded_missing'); - break; - case @UPLOAD_ERR_NO_TMP_DIR: - $this->uploaded = false; - $this->error = $this->translate('uploaded_no_tmp_dir'); - break; - case @UPLOAD_ERR_CANT_WRITE: - $this->uploaded = false; - $this->error = $this->translate('uploaded_cant_write'); - break; - case @UPLOAD_ERR_EXTENSION: - $this->uploaded = false; - $this->error = $this->translate('uploaded_err_extension'); - break; - default: - $this->uploaded = false; - $this->error = $this->translate('uploaded_unknown') . ' ('.$this->file_src_error.')'; - } - } - - if ($this->uploaded) { - $this->file_src_pathname = $file['tmp_name']; - $this->file_src_name = $file['name']; - if ($this->file_src_name == '') { - $this->uploaded = false; - $this->error = $this->translate('try_again'); - } - } - - if ($this->uploaded) { - $this->log .= '- file name OK
'; - preg_match('/\.([^\.]*$)/', $this->file_src_name, $extension); - if (is_array($extension) && sizeof($extension) > 0) { - $this->file_src_name_ext = strtolower($extension[1]); - $this->file_src_name_body = substr($this->file_src_name, 0, ((strlen($this->file_src_name) - strlen($this->file_src_name_ext)))-1); - } else { - $this->file_src_name_ext = ''; - $this->file_src_name_body = $this->file_src_name; - } - $this->file_src_size = $file['size']; - $mime_from_browser = $file['type']; - } - } - - if ($this->uploaded) { - $this->log .= 'determining MIME type
'; - $this->file_src_mime = null; - - // checks MIME type with Fileinfo PECL extension - if (!$this->file_src_mime || !is_string($this->file_src_mime) || empty($this->file_src_mime) || strpos($this->file_src_mime, '/') === FALSE) { - if ($this->mime_fileinfo) { - $this->log .= '- Checking MIME type with Fileinfo PECL extension
'; - if ($this->function_enabled('finfo_open')) { - $path = null; - if ($this->mime_fileinfo !== '') { - if ($this->mime_fileinfo === true) { - if (getenv('MAGIC') === FALSE) { - if (substr(PHP_OS, 0, 3) == 'WIN') { - $path = realpath(ini_get('extension_dir') . '/../') . '/extras/magic'; - $this->log .= '    MAGIC path defaults to ' . $path . '
'; - } - } else { - $path = getenv('MAGIC'); - $this->log .= '    MAGIC path is set to ' . $path . ' from MAGIC variable
'; - } - } else { - $path = $this->mime_fileinfo; - $this->log .= '    MAGIC path is set to ' . $path . '
'; - } - } - if ($path) { - $f = @finfo_open(FILEINFO_MIME, $path); - } else { - $this->log .= '    MAGIC path will not be used
'; - $f = @finfo_open(FILEINFO_MIME); - } - if (is_resource($f)) { - $mime = finfo_file($f, realpath($this->file_src_pathname)); - finfo_close($f); - $this->file_src_mime = $mime; - $this->log .= '    MIME type detected as ' . $this->file_src_mime . ' by Fileinfo PECL extension
'; - if (preg_match("/^([\.\w-]+)\/([\.\w-]+)(.*)$/i", $this->file_src_mime)) { - $this->file_src_mime = preg_replace("/^([\.\w-]+)\/([\.\w-]+)(.*)$/i", '$1/$2', $this->file_src_mime); - $this->log .= '- MIME validated as ' . $this->file_src_mime . '
'; - } else { - $this->file_src_mime = null; - } - } else { - $this->log .= '    Fileinfo PECL extension failed (finfo_open)
'; - } - } elseif (@class_exists('finfo', false)) { - $f = new finfo( FILEINFO_MIME ); - if ($f) { - $this->file_src_mime = $f->file(realpath($this->file_src_pathname)); - $this->log .= '- MIME type detected as ' . $this->file_src_mime . ' by Fileinfo PECL extension
'; - if (preg_match("/^([\.\w-]+)\/([\.\w-]+)(.*)$/i", $this->file_src_mime)) { - $this->file_src_mime = preg_replace("/^([\.\w-]+)\/([\.\w-]+)(.*)$/i", '$1/$2', $this->file_src_mime); - $this->log .= '- MIME validated as ' . $this->file_src_mime . '
'; - } else { - $this->file_src_mime = null; - } - } else { - $this->log .= '    Fileinfo PECL extension failed (finfo)
'; - } - } else { - $this->log .= '    Fileinfo PECL extension not available
'; - } - } else { - $this->log .= '- Fileinfo PECL extension deactivated
'; - } - } - - // checks MIME type with shell if unix access is authorized - if (!$this->file_src_mime || !is_string($this->file_src_mime) || empty($this->file_src_mime) || strpos($this->file_src_mime, '/') === FALSE) { - if ($this->mime_file) { - $this->log .= '- Checking MIME type with UNIX file() command
'; - if (substr(PHP_OS, 0, 3) != 'WIN') { - if ($this->function_enabled('exec') && $this->function_enabled('escapeshellarg')) { - if (strlen($mime = @exec("file -bi ".escapeshellarg($this->file_src_pathname))) != 0) { - $this->file_src_mime = trim($mime); - $this->log .= '    MIME type detected as ' . $this->file_src_mime . ' by UNIX file() command
'; - if (preg_match("/^([\.\w-]+)\/([\.\w-]+)(.*)$/i", $this->file_src_mime)) { - $this->file_src_mime = preg_replace("/^([\.\w-]+)\/([\.\w-]+)(.*)$/i", '$1/$2', $this->file_src_mime); - $this->log .= '- MIME validated as ' . $this->file_src_mime . '
'; - } else { - $this->file_src_mime = null; - } - } else { - $this->log .= '    UNIX file() command failed
'; - } - } else { - $this->log .= '    PHP exec() function is disabled
'; - } - } else { - $this->log .= '    UNIX file() command not availabled
'; - } - } else { - $this->log .= '- UNIX file() command is deactivated
'; - } - } - - // checks MIME type with mime_magic - if (!$this->file_src_mime || !is_string($this->file_src_mime) || empty($this->file_src_mime) || strpos($this->file_src_mime, '/') === FALSE) { - if ($this->mime_magic) { - $this->log .= '- Checking MIME type with mime.magic file (mime_content_type())
'; - if ($this->function_enabled('mime_content_type')) { - $this->file_src_mime = mime_content_type($this->file_src_pathname); - $this->log .= '    MIME type detected as ' . $this->file_src_mime . ' by mime_content_type()
'; - if (preg_match("/^([\.\w-]+)\/([\.\w-]+)(.*)$/i", $this->file_src_mime)) { - $this->file_src_mime = preg_replace("/^([\.\w-]+)\/([\.\w-]+)(.*)$/i", '$1/$2', $this->file_src_mime); - $this->log .= '- MIME validated as ' . $this->file_src_mime . '
'; - } else { - $this->file_src_mime = null; - } - } else { - $this->log .= '    mime_content_type() is not available
'; - } - } else { - $this->log .= '- mime.magic file (mime_content_type()) is deactivated
'; - } - } - - // checks MIME type with getimagesize() - if (!$this->file_src_mime || !is_string($this->file_src_mime) || empty($this->file_src_mime) || strpos($this->file_src_mime, '/') === FALSE) { - if ($this->mime_getimagesize) { - $this->log .= '- Checking MIME type with getimagesize()
'; - $info = getimagesize($this->file_src_pathname); - if (is_array($info) && array_key_exists('mime', $info)) { - $this->file_src_mime = trim($info['mime']); - if (empty($this->file_src_mime)) { - $this->log .= '    MIME empty, guessing from type
'; - $mime = (is_array($info) && array_key_exists(2, $info) ? $info[2] : null); // 1 = GIF, 2 = JPG, 3 = PNG - $this->file_src_mime = ($mime==IMAGETYPE_GIF ? 'image/gif' : - ($mime==IMAGETYPE_JPEG ? 'image/jpeg' : - ($mime==IMAGETYPE_PNG ? 'image/png' : - ($mime==IMAGETYPE_BMP ? 'image/bmp' : null)))); - } - $this->log .= '    MIME type detected as ' . $this->file_src_mime . ' by PHP getimagesize() function
'; - if (preg_match("/^([\.\w-]+)\/([\.\w-]+)(.*)$/i", $this->file_src_mime)) { - $this->file_src_mime = preg_replace("/^([\.\w-]+)\/([\.\w-]+)(.*)$/i", '$1/$2', $this->file_src_mime); - $this->log .= '- MIME validated as ' . $this->file_src_mime . '
'; - } else { - $this->file_src_mime = null; - } - } else { - $this->log .= '    getimagesize() failed
'; - } - } else { - $this->log .= '- getimagesize() is deactivated
'; - } - } - - // default to MIME from browser (or Flash) - if (!empty($mime_from_browser) && !$this->file_src_mime || !is_string($this->file_src_mime) || empty($this->file_src_mime)) { - $this->file_src_mime =$mime_from_browser; - $this->log .= '- MIME type detected as ' . $this->file_src_mime . ' by browser
'; - if (preg_match("/^([\.\w-]+)\/([\.\w-]+)(.*)$/i", $this->file_src_mime)) { - $this->file_src_mime = preg_replace("/^([\.\w-]+)\/([\.\w-]+)(.*)$/i", '$1/$2', $this->file_src_mime); - $this->log .= '- MIME validated as ' . $this->file_src_mime . '
'; - } else { - $this->file_src_mime = null; - } - } - - // we need to work some magic if we upload via Flash - if ($this->file_src_mime == 'application/octet-stream' || !$this->file_src_mime || !is_string($this->file_src_mime) || empty($this->file_src_mime) || strpos($this->file_src_mime, '/') === FALSE) { - if ($this->file_src_mime == 'application/octet-stream') $this->log .= '- Flash may be rewriting MIME as application/octet-stream
'; - $this->log .= '- Try to guess MIME type from file extension (' . $this->file_src_name_ext . '): '; - if (array_key_exists($this->file_src_name_ext, $this->mime_types)) $this->file_src_mime = $this->mime_types[$this->file_src_name_ext]; - if ($this->file_src_mime == 'application/octet-stream') { - $this->log .= 'doesn\'t look like anything known
'; - } else { - $this->log .= 'MIME type set to ' . $this->file_src_mime . '
'; - } - } - - if (!$this->file_src_mime || !is_string($this->file_src_mime) || empty($this->file_src_mime) || strpos($this->file_src_mime, '/') === FALSE) { - $this->log .= '- MIME type couldn\'t be detected! (' . (string) $this->file_src_mime . ')
'; - } - - // determine whether the file is an image - if ($this->file_src_mime && is_string($this->file_src_mime) && !empty($this->file_src_mime) && array_key_exists($this->file_src_mime, $this->image_supported)) { - $this->file_is_image = true; - $this->image_src_type = $this->image_supported[$this->file_src_mime]; - } - - // if the file is an image, we gather some useful data - if ($this->file_is_image) { - if ($h = fopen($this->file_src_pathname, 'r')) { - fclose($h); - $info = getimagesize($this->file_src_pathname); - if (is_array($info)) { - $this->image_src_x = $info[0]; - $this->image_src_y = $info[1]; - $this->image_dst_x = $this->image_src_x; - $this->image_dst_y = $this->image_src_y; - $this->image_src_pixels = $this->image_src_x * $this->image_src_y; - $this->image_src_bits = array_key_exists('bits', $info) ? $info['bits'] : null; - } else { - $this->file_is_image = false; - $this->uploaded = false; - $this->log .= '- can\'t retrieve image information, image may have been tampered with
'; - $this->error = $this->translate('source_invalid'); - } - } else { - $this->log .= '- can\'t read source file directly. open_basedir restriction in place?
'; - } - } - - $this->log .= 'source variables
'; - $this->log .= '- You can use all these before calling process()
'; - $this->log .= '    file_src_name : ' . $this->file_src_name . '
'; - $this->log .= '    file_src_name_body : ' . $this->file_src_name_body . '
'; - $this->log .= '    file_src_name_ext : ' . $this->file_src_name_ext . '
'; - $this->log .= '    file_src_pathname : ' . $this->file_src_pathname . '
'; - $this->log .= '    file_src_mime : ' . $this->file_src_mime . '
'; - $this->log .= '    file_src_size : ' . $this->file_src_size . ' (max= ' . $this->file_max_size . ')
'; - $this->log .= '    file_src_error : ' . $this->file_src_error . '
'; - - if ($this->file_is_image) { - $this->log .= '- source file is an image
'; - $this->log .= '    image_src_x : ' . $this->image_src_x . '
'; - $this->log .= '    image_src_y : ' . $this->image_src_y . '
'; - $this->log .= '    image_src_pixels : ' . $this->image_src_pixels . '
'; - $this->log .= '    image_src_type : ' . $this->image_src_type . '
'; - $this->log .= '    image_src_bits : ' . $this->image_src_bits . '
'; - } - } - - } - - /** - * Returns the version of GD - * - * @access public - * @param boolean $full Optional flag to get precise version - * @return float GD version - */ - function gdversion($full = false) { - static $gd_version = null; - static $gd_full_version = null; - if ($gd_version === null) { - if ($this->function_enabled('gd_info')) { - $gd = gd_info(); - $gd = $gd["GD Version"]; - $regex = "/([\d\.]+)/i"; - } else { - ob_start(); - phpinfo(8); - $gd = ob_get_contents(); - ob_end_clean(); - $regex = "/\bgd\s+version\b[^\d\n\r]+?([\d\.]+)/i"; - } - if (preg_match($regex, $gd, $m)) { - $gd_full_version = (string) $m[1]; - $gd_version = (float) $m[1]; - } else { - $gd_full_version = 'none'; - $gd_version = 0; - } - } - if ($full) { - return $gd_full_version; - } else { - return $gd_version; - } - } - - /** - * Checks if a function is available - * - * @access private - * @param string $func Function name - * @return boolean Success - */ - function function_enabled($func) { - // cache the list of disabled functions - static $disabled = null; - if ($disabled === null) $disabled = array_map('trim', array_map('strtolower', explode(',', ini_get('disable_functions')))); - // cache the list of functions blacklisted by suhosin - static $blacklist = null; - if ($blacklist === null) $blacklist = extension_loaded('suhosin') ? array_map('trim', array_map('strtolower', explode(',', ini_get(' suhosin.executor.func.blacklist')))) : array(); - // checks if the function is really enabled - return (function_exists($func) && !in_array($func, $disabled) && !in_array($func, $blacklist)); - } - - /** - * Creates directories recursively - * - * @access private - * @param string $path Path to create - * @param integer $mode Optional permissions - * @return boolean Success - */ - function rmkdir($path, $mode = 0777) { - return is_dir($path) || ( $this->rmkdir(dirname($path), $mode) && $this->_mkdir($path, $mode) ); - } - - /** - * Creates directory - * - * @access private - * @param string $path Path to create - * @param integer $mode Optional permissions - * @return boolean Success - */ - function _mkdir($path, $mode = 0777) { - $old = umask(0); - $res = @mkdir($path, $mode); - umask($old); - return $res; - } - - /** - * Translate error messages - * - * @access private - * @param string $str Message to translate - * @param array $tokens Optional token values - * @return string Translated string - */ - function translate($str, $tokens = array()) { - if (array_key_exists($str, $this->translation)) $str = $this->translation[$str]; - if (is_array($tokens) && sizeof($tokens) > 0) $str = vsprintf($str, $tokens); - return $str; - } - - /** - * Returns the temp directory - * - * @access private - * @return string Temp directory string - */ - function temp_dir() { - $dir = ''; - if ($this->function_enabled('sys_get_temp_dir')) $dir = sys_get_temp_dir(); - if (!$dir && $tmp=getenv('TMP')) $dir = $tmp; - if (!$dir && $tmp=getenv('TEMP')) $dir = $tmp; - if (!$dir && $tmp=getenv('TMPDIR')) $dir = $tmp; - if (!$dir) { - $tmp = tempnam(__FILE__,''); - if (file_exists($tmp)) { - unlink($tmp); - $dir = dirname($tmp); - } - } - if (!$dir) return ''; - $slash = (strtolower(substr(PHP_OS, 0, 3)) === 'win' ? '\\' : '/'); - if (substr($dir, -1) != $slash) $dir = $dir . $slash; - return $dir; - } - - /** - * Sanitize a file name - * - * @access private - * @param string $filename File name - * @return string Sanitized file name - */ - function sanitize($filename) { - // remove HTML tags - $filename = strip_tags($filename); - // remove non-breaking spaces - $filename = preg_replace("#\x{00a0}#siu", ' ', $filename); - // remove illegal file system characters - $filename = str_replace(array_map('chr', range(0, 31)), '', $filename); - // remove dangerous characters for file names - $chars = array("?", "[", "]", "/", "\\", "=", "<", ">", ":", ";", ",", "'", "\"", "&", "’", "%20", - "+", "$", "#", "*", "(", ")", "|", "~", "`", "!", "{", "}", "%", "+", "^", chr(0)); - $filename = str_replace($chars, '-', $filename); - // remove break/tabs/return carriage - $filename = preg_replace('/[\r\n\t -]+/', '-', $filename); - // convert some special letters - $convert = array('Þ' => 'TH', 'þ' => 'th', 'Ð' => 'DH', 'ð' => 'dh', 'ß' => 'ss', - 'Œ' => 'OE', 'œ' => 'oe', 'Æ' => 'AE', 'æ' => 'ae', 'µ' => 'u'); - $filename = strtr($filename, $convert); - // remove foreign accents by converting to HTML entities, and then remove the code - $filename = html_entity_decode( $filename, ENT_QUOTES, "utf-8" ); - $filename = htmlentities($filename, ENT_QUOTES, "utf-8"); - $filename = preg_replace("/(&)([a-z])([a-z]+;)/i", '$2', $filename); - // clean up, and remove repetitions - $filename = preg_replace('/_+/', '_', $filename); - $filename = preg_replace(array('/ +/', '/-+/'), '-', $filename); - $filename = preg_replace(array('/-*\.-*/', '/\.{2,}/'), '.', $filename); - // cut to 255 characters - $length = 255 - strlen($this->file_dst_name_ext) + 1; - $filename = extension_loaded('mbstring') ? mb_strcut($filename, 0, $length, mb_detect_encoding($filename)) : substr($filename, 0, $length); - // remove bad characters at start and end - $filename = trim($filename, '.-_'); - return $filename; - } - - /** - * Decodes colors - * - * @access private - * @param string $color Color string - * @return array RGB colors - */ - function getcolors($color) { - $color = str_replace('#', '', $color); - if (strlen($color) == 3) $color = str_repeat(substr($color, 0, 1), 2) . str_repeat(substr($color, 1, 1), 2) . str_repeat(substr($color, 2, 1), 2); - $r = sscanf($color, "%2x%2x%2x"); - $red = (is_array($r) && array_key_exists(0, $r) && is_numeric($r[0]) ? $r[0] : 0); - $green = (is_array($r) && array_key_exists(1, $r) && is_numeric($r[1]) ? $r[1] : 0); - $blue = (is_array($r) && array_key_exists(2, $r) && is_numeric($r[2]) ? $r[2] : 0); - return array($red, $green, $blue); - } - - /** - * Decodes sizes - * - * @access private - * @param string $size Size in bytes, or shorthand byte options - * @return integer Size in bytes - */ - function getsize($size) { - if ($size === null) return null; - $last = strtolower($size{strlen($size)-1}); - $size = (int) $size; - switch($last) { - case 'g': - $size *= 1024; - case 'm': - $size *= 1024; - case 'k': - $size *= 1024; - } - return $size; - } - - /** - * Decodes offsets - * - * @access private - * @param misc $offsets Offsets, as an integer, a string or an array - * @param integer $x Reference picture width - * @param integer $y Reference picture height - * @param boolean $round Round offsets before returning them - * @param boolean $negative Allow negative offsets to be returned - * @return array Array of four offsets (TRBL) - */ - function getoffsets($offsets, $x, $y, $round = true, $negative = true) { - if (!is_array($offsets)) $offsets = explode(' ', $offsets); - if (sizeof($offsets) == 4) { - $ct = $offsets[0]; $cr = $offsets[1]; $cb = $offsets[2]; $cl = $offsets[3]; - } else if (sizeof($offsets) == 2) { - $ct = $offsets[0]; $cr = $offsets[1]; $cb = $offsets[0]; $cl = $offsets[1]; - } else { - $ct = $offsets[0]; $cr = $offsets[0]; $cb = $offsets[0]; $cl = $offsets[0]; - } - if (strpos($ct, '%')>0) $ct = $y * (str_replace('%','',$ct) / 100); - if (strpos($cr, '%')>0) $cr = $x * (str_replace('%','',$cr) / 100); - if (strpos($cb, '%')>0) $cb = $y * (str_replace('%','',$cb) / 100); - if (strpos($cl, '%')>0) $cl = $x * (str_replace('%','',$cl) / 100); - if (strpos($ct, 'px')>0) $ct = str_replace('px','',$ct); - if (strpos($cr, 'px')>0) $cr = str_replace('px','',$cr); - if (strpos($cb, 'px')>0) $cb = str_replace('px','',$cb); - if (strpos($cl, 'px')>0) $cl = str_replace('px','',$cl); - $ct = (int) $ct; $cr = (int) $cr; $cb = (int) $cb; $cl = (int) $cl; - if ($round) { - $ct = round($ct); - $cr = round($cr); - $cb = round($cb); - $cl = round($cl); - } - if (!$negative) { - if ($ct < 0) $ct = 0; - if ($cr < 0) $cr = 0; - if ($cb < 0) $cb = 0; - if ($cl < 0) $cl = 0; - } - return array($ct, $cr, $cb, $cl); - } - - /** - * Creates a container image - * - * @access private - * @param integer $x Width - * @param integer $y Height - * @param boolean $fill Optional flag to draw the background color or not - * @param boolean $trsp Optional flag to set the background to be transparent - * @return resource Container image - */ - function imagecreatenew($x, $y, $fill = true, $trsp = false) { - if ($x < 1) $x = 1; if ($y < 1) $y = 1; - if ($this->gdversion() >= 2 && !$this->image_is_palette) { - // create a true color image - $dst_im = imagecreatetruecolor($x, $y); - // this preserves transparency in PNGs, in true color - if (empty($this->image_background_color) || $trsp) { - imagealphablending($dst_im, false ); - imagefilledrectangle($dst_im, 0, 0, $x, $y, imagecolorallocatealpha($dst_im, 0, 0, 0, 127)); - } - } else { - // creates a palette image - $dst_im = imagecreate($x, $y); - // preserves transparency for palette images, if the original image has transparency - if (($fill && $this->image_is_transparent && empty($this->image_background_color)) || $trsp) { - imagefilledrectangle($dst_im, 0, 0, $x, $y, $this->image_transparent_color); - imagecolortransparent($dst_im, $this->image_transparent_color); - } - } - // fills with background color if any is set - if ($fill && !empty($this->image_background_color) && !$trsp) { - list($red, $green, $blue) = $this->getcolors($this->image_background_color); - $background_color = imagecolorallocate($dst_im, $red, $green, $blue); - imagefilledrectangle($dst_im, 0, 0, $x, $y, $background_color); - } - return $dst_im; - } - - - /** - * Transfers an image from the container to the destination image - * - * @access private - * @param resource $src_im Container image - * @param resource $dst_im Destination image - * @return resource Destination image - */ - function imagetransfer($src_im, $dst_im) { - if (is_resource($dst_im)) imagedestroy($dst_im); - $dst_im = & $src_im; - return $dst_im; - } - - /** - * Merges two images - * - * If the output format is PNG, then we do it pixel per pixel to retain the alpha channel - * - * @access private - * @param resource $dst_img Destination image - * @param resource $src_img Overlay image - * @param int $dst_x x-coordinate of destination point - * @param int $dst_y y-coordinate of destination point - * @param int $src_x x-coordinate of source point - * @param int $src_y y-coordinate of source point - * @param int $src_w Source width - * @param int $src_h Source height - * @param int $pct Optional percentage of the overlay, between 0 and 100 (default: 100) - * @return resource Destination image - */ - function imagecopymergealpha(&$dst_im, &$src_im, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h, $pct = 0) { - $dst_x = (int) $dst_x; - $dst_y = (int) $dst_y; - $src_x = (int) $src_x; - $src_y = (int) $src_y; - $src_w = (int) $src_w; - $src_h = (int) $src_h; - $pct = (int) $pct; - $dst_w = imagesx($dst_im); - $dst_h = imagesy($dst_im); - - for ($y = $src_y; $y < $src_h; $y++) { - for ($x = $src_x; $x < $src_w; $x++) { - - if ($x + $dst_x >= 0 && $x + $dst_x < $dst_w && $x + $src_x >= 0 && $x + $src_x < $src_w - && $y + $dst_y >= 0 && $y + $dst_y < $dst_h && $y + $src_y >= 0 && $y + $src_y < $src_h) { - - $dst_pixel = imagecolorsforindex($dst_im, imagecolorat($dst_im, $x + $dst_x, $y + $dst_y)); - $src_pixel = imagecolorsforindex($src_im, imagecolorat($src_im, $x + $src_x, $y + $src_y)); - - $src_alpha = 1 - ($src_pixel['alpha'] / 127); - $dst_alpha = 1 - ($dst_pixel['alpha'] / 127); - $opacity = $src_alpha * $pct / 100; - if ($dst_alpha >= $opacity) $alpha = $dst_alpha; - if ($dst_alpha < $opacity) $alpha = $opacity; - if ($alpha > 1) $alpha = 1; - - if ($opacity > 0) { - $dst_red = round(( ($dst_pixel['red'] * $dst_alpha * (1 - $opacity)) ) ); - $dst_green = round(( ($dst_pixel['green'] * $dst_alpha * (1 - $opacity)) ) ); - $dst_blue = round(( ($dst_pixel['blue'] * $dst_alpha * (1 - $opacity)) ) ); - $src_red = round((($src_pixel['red'] * $opacity)) ); - $src_green = round((($src_pixel['green'] * $opacity)) ); - $src_blue = round((($src_pixel['blue'] * $opacity)) ); - $red = round(($dst_red + $src_red ) / ($dst_alpha * (1 - $opacity) + $opacity)); - $green = round(($dst_green + $src_green) / ($dst_alpha * (1 - $opacity) + $opacity)); - $blue = round(($dst_blue + $src_blue ) / ($dst_alpha * (1 - $opacity) + $opacity)); - if ($red > 255) $red = 255; - if ($green > 255) $green = 255; - if ($blue > 255) $blue = 255; - $alpha = round((1 - $alpha) * 127); - $color = imagecolorallocatealpha($dst_im, $red, $green, $blue, $alpha); - imagesetpixel($dst_im, $x + $dst_x, $y + $dst_y, $color); - } - } - } - } - return true; - } - - - - /** - * Actually uploads the file, and act on it according to the set processing class variables - * - * This function copies the uploaded file to the given location, eventually performing actions on it. - * Typically, you can call {@link process} several times for the same file, - * for instance to create a resized image and a thumbnail of the same file. - * The original uploaded file remains intact in its temporary location, so you can use {@link process} several times. - * You will be able to delete the uploaded file with {@link clean} when you have finished all your {@link process} calls. - * - * According to the processing class variables set in the calling file, the file can be renamed, - * and if it is an image, can be resized or converted. - * - * When the processing is completed, and the file copied to its new location, the - * processing class variables will be reset to their default value. - * This allows you to set new properties, and perform another {@link process} on the same uploaded file - * - * If the function is called with a null or empty argument, then it will return the content of the picture - * - * It will set {@link processed} (and {@link error} is an error occurred) - * - * @access public - * @param string $server_path Optional path location of the uploaded file, with an ending slash - * @return string Optional content of the image - */ - function process($server_path = null) { - $this->error = ''; - $this->processed = true; - $return_mode = false; - $return_content = null; - - // clean up dst variables - $this->file_dst_path = ''; - $this->file_dst_pathname = ''; - $this->file_dst_name = ''; - $this->file_dst_name_body = ''; - $this->file_dst_name_ext = ''; - - // clean up some parameters - $this->file_max_size = $this->getsize($this->file_max_size); - $this->jpeg_size = $this->getsize($this->jpeg_size); - - // copy some variables as we need to keep them clean - $file_src_name = $this->file_src_name; - $file_src_name_body = $this->file_src_name_body; - $file_src_name_ext = $this->file_src_name_ext; - - if (!$this->uploaded) { - $this->error = $this->translate('file_not_uploaded'); - $this->processed = false; - } - - if ($this->processed) { - if (empty($server_path) || is_null($server_path)) { - $this->log .= 'process file and return the content
'; - $return_mode = true; - } else { - if(strtolower(substr(PHP_OS, 0, 3)) === 'win') { - if (substr($server_path, -1, 1) != '\\') $server_path = $server_path . '\\'; - } else { - if (substr($server_path, -1, 1) != '/') $server_path = $server_path . '/'; - } - $this->log .= 'process file to ' . $server_path . '
'; - } - } - - if ($this->processed) { - // checks file max size - if ($this->file_src_size > $this->file_max_size) { - $this->processed = false; - $this->error = $this->translate('file_too_big') . ' : ' . $this->file_src_size . ' > ' . $this->file_max_size; - } else { - $this->log .= '- file size OK
'; - } - } - - if ($this->processed) { - // if we have an image without extension, set it - if ($this->file_force_extension && $this->file_is_image && !$this->file_src_name_ext) $file_src_name_ext = $this->image_src_type; - // turn dangerous scripts into text files - if ($this->no_script) { - // if the file has no extension, we try to guess it from the MIME type - if ($this->file_force_extension && empty($file_src_name_ext)) { - if ($key = array_search($this->file_src_mime, $this->mime_types)) { - $file_src_name_ext = $key; - $file_src_name = $file_src_name_body . '.' . $file_src_name_ext; - $this->log .= '- file renamed as ' . $file_src_name_body . '.' . $file_src_name_ext . '!
'; - } - } - // if the file is text based, or has a dangerous extension, we rename it as .txt - if ((((substr($this->file_src_mime, 0, 5) == 'text/' && $this->file_src_mime != 'text/rtf') || strpos($this->file_src_mime, 'javascript') !== false) && (substr($file_src_name, -4) != '.txt')) - || preg_match('/\.(php|php5|php4|php3|phtml|pl|py|cgi|asp|js)$/i', $this->file_src_name) - || $this->file_force_extension && empty($file_src_name_ext)) { - $this->file_src_mime = 'text/plain'; - if ($this->file_src_name_ext) $file_src_name_body = $file_src_name_body . '.' . $this->file_src_name_ext; - $file_src_name_ext = 'txt'; - $file_src_name = $file_src_name_body . '.' . $file_src_name_ext; - $this->log .= '- script renamed as ' . $file_src_name_body . '.' . $file_src_name_ext . '!
'; - } - } - - if ($this->mime_check && empty($this->file_src_mime)) { - $this->processed = false; - $this->error = $this->translate('no_mime'); - } else if ($this->mime_check && !empty($this->file_src_mime) && strpos($this->file_src_mime, '/') !== false) { - list($m1, $m2) = explode('/', $this->file_src_mime); - $allowed = false; - // check wether the mime type is allowed - if (!is_array($this->allowed)) $this->allowed = array($this->allowed); - foreach($this->allowed as $k => $v) { - list($v1, $v2) = explode('/', $v); - if (($v1 == '*' && $v2 == '*') || ($v1 == $m1 && ($v2 == $m2 || $v2 == '*'))) { - $allowed = true; - break; - } - } - // check wether the mime type is forbidden - if (!is_array($this->forbidden)) $this->forbidden = array($this->forbidden); - foreach($this->forbidden as $k => $v) { - list($v1, $v2) = explode('/', $v); - if (($v1 == '*' && $v2 == '*') || ($v1 == $m1 && ($v2 == $m2 || $v2 == '*'))) { - $allowed = false; - break; - } - } - if (!$allowed) { - $this->processed = false; - $this->error = $this->translate('incorrect_file'); - } else { - $this->log .= '- file mime OK : ' . $this->file_src_mime . '
'; - } - } else { - $this->log .= '- file mime (not checked) : ' . $this->file_src_mime . '
'; - } - - // if the file is an image, we can check on its dimensions - // these checks are not available if open_basedir restrictions are in place - if ($this->file_is_image) { - if (is_numeric($this->image_src_x) && is_numeric($this->image_src_y)) { - $ratio = $this->image_src_x / $this->image_src_y; - if (!is_null($this->image_max_width) && $this->image_src_x > $this->image_max_width) { - $this->processed = false; - $this->error = $this->translate('image_too_wide'); - } - if (!is_null($this->image_min_width) && $this->image_src_x < $this->image_min_width) { - $this->processed = false; - $this->error = $this->translate('image_too_narrow'); - } - if (!is_null($this->image_max_height) && $this->image_src_y > $this->image_max_height) { - $this->processed = false; - $this->error = $this->translate('image_too_high'); - } - if (!is_null($this->image_min_height) && $this->image_src_y < $this->image_min_height) { - $this->processed = false; - $this->error = $this->translate('image_too_short'); - } - if (!is_null($this->image_max_ratio) && $ratio > $this->image_max_ratio) { - $this->processed = false; - $this->error = $this->translate('ratio_too_high'); - } - if (!is_null($this->image_min_ratio) && $ratio < $this->image_min_ratio) { - $this->processed = false; - $this->error = $this->translate('ratio_too_low'); - } - if (!is_null($this->image_max_pixels) && $this->image_src_pixels > $this->image_max_pixels) { - $this->processed = false; - $this->error = $this->translate('too_many_pixels'); - } - if (!is_null($this->image_min_pixels) && $this->image_src_pixels < $this->image_min_pixels) { - $this->processed = false; - $this->error = $this->translate('not_enough_pixels'); - } - } else { - $this->log .= '- no image properties available, can\'t enforce dimension checks : ' . $this->file_src_mime . '
'; - } - } - } - - if ($this->processed) { - $this->file_dst_path = $server_path; - - // repopulate dst variables from src - $this->file_dst_name = $file_src_name; - $this->file_dst_name_body = $file_src_name_body; - $this->file_dst_name_ext = $file_src_name_ext; - if ($this->file_overwrite) $this->file_auto_rename = false; - - if ($this->image_convert && $this->file_is_image) { // if we convert as an image - $this->file_dst_name_ext = $this->image_convert; - $this->log .= '- new file name ext : ' . $this->file_dst_name_ext . '
'; - } - if (!is_null($this->file_new_name_body)) { // rename file body - $this->file_dst_name_body = $this->file_new_name_body; - $this->log .= '- new file name body : ' . $this->file_new_name_body . '
'; - } - if (!is_null($this->file_new_name_ext)) { // rename file ext - $this->file_dst_name_ext = $this->file_new_name_ext; - $this->log .= '- new file name ext : ' . $this->file_new_name_ext . '
'; - } - if (!is_null($this->file_name_body_add)) { // append a string to the name - $this->file_dst_name_body = $this->file_dst_name_body . $this->file_name_body_add; - $this->log .= '- file name body append : ' . $this->file_name_body_add . '
'; - } - if (!is_null($this->file_name_body_pre)) { // prepend a string to the name - $this->file_dst_name_body = $this->file_name_body_pre . $this->file_dst_name_body; - $this->log .= '- file name body prepend : ' . $this->file_name_body_pre . '
'; - } - if ($this->file_safe_name) { // sanitize the name - $this->file_dst_name_body = $this->sanitize($this->file_dst_name_body); - $this->log .= '- file name safe format
'; - } - - $this->log .= '- destination variables
'; - if (empty($this->file_dst_path) || is_null($this->file_dst_path)) { - $this->log .= '    file_dst_path : n/a
'; - } else { - $this->log .= '    file_dst_path : ' . $this->file_dst_path . '
'; - } - $this->log .= '    file_dst_name_body : ' . $this->file_dst_name_body . '
'; - $this->log .= '    file_dst_name_ext : ' . $this->file_dst_name_ext . '
'; - - // set the destination file name - $this->file_dst_name = $this->file_dst_name_body . (!empty($this->file_dst_name_ext) ? '.' . $this->file_dst_name_ext : ''); - - if (!$return_mode) { - if (!$this->file_auto_rename) { - $this->log .= '- no auto_rename if same filename exists
'; - $this->file_dst_pathname = $this->file_dst_path . $this->file_dst_name; - } else { - $this->log .= '- checking for auto_rename
'; - $this->file_dst_pathname = $this->file_dst_path . $this->file_dst_name; - $body = $this->file_dst_name_body; - $ext = ''; - // if we have changed the extension, then we add our increment before - if ($file_src_name_ext != $this->file_src_name_ext) { - if (substr($this->file_dst_name_body, -1 - strlen($this->file_src_name_ext)) == '.' . $this->file_src_name_ext) { - $body = substr($this->file_dst_name_body, 0, strlen($this->file_dst_name_body) - 1 - strlen($this->file_src_name_ext)); - $ext = '.' . $this->file_src_name_ext; - } - } - $cpt = 1; - while (@file_exists($this->file_dst_pathname)) { - $this->file_dst_name_body = $body . '_' . $cpt . $ext; - $this->file_dst_name = $this->file_dst_name_body . (!empty($this->file_dst_name_ext) ? '.' . $this->file_dst_name_ext : ''); - $cpt++; - $this->file_dst_pathname = $this->file_dst_path . $this->file_dst_name; - } - if ($cpt>1) $this->log .= '    auto_rename to ' . $this->file_dst_name . '
'; - } - - $this->log .= '- destination file details
'; - $this->log .= '    file_dst_name : ' . $this->file_dst_name . '
'; - $this->log .= '    file_dst_pathname : ' . $this->file_dst_pathname . '
'; - - if ($this->file_overwrite) { - $this->log .= '- no overwrite checking
'; - } else { - if (@file_exists($this->file_dst_pathname)) { - $this->processed = false; - $this->error = $this->translate('already_exists', array($this->file_dst_name)); - } else { - $this->log .= '- ' . $this->file_dst_name . ' doesn\'t exist already
'; - } - } - } - } - - if ($this->processed) { - // if we have already moved the uploaded file, we use the temporary copy as source file, and check if it exists - if (!empty($this->file_src_temp)) { - $this->log .= '- use the temp file instead of the original file since it is a second process
'; - $this->file_src_pathname = $this->file_src_temp; - if (!file_exists($this->file_src_pathname)) { - $this->processed = false; - $this->error = $this->translate('temp_file_missing'); - } - // if we haven't a temp file, and that we do check on uploads, we use is_uploaded_file() - } else if (!$this->no_upload_check) { - if (!is_uploaded_file($this->file_src_pathname)) { - $this->processed = false; - $this->error = $this->translate('source_missing'); - } - // otherwise, if we don't check on uploaded files (local file for instance), we use file_exists() - } else { - if (!file_exists($this->file_src_pathname)) { - $this->processed = false; - $this->error = $this->translate('source_missing'); - } - } - - // checks if the destination directory exists, and attempt to create it - if (!$return_mode) { - if ($this->processed && !file_exists($this->file_dst_path)) { - if ($this->dir_auto_create) { - $this->log .= '- ' . $this->file_dst_path . ' doesn\'t exist. Attempting creation:'; - if (!$this->rmkdir($this->file_dst_path, $this->dir_chmod)) { - $this->log .= ' failed
'; - $this->processed = false; - $this->error = $this->translate('destination_dir'); - } else { - $this->log .= ' success
'; - } - } else { - $this->error = $this->translate('destination_dir_missing'); - } - } - - if ($this->processed && !is_dir($this->file_dst_path)) { - $this->processed = false; - $this->error = $this->translate('destination_path_not_dir'); - } - - // checks if the destination directory is writeable, and attempt to make it writeable - $hash = md5($this->file_dst_name_body . rand(1, 1000)); - if ($this->processed && !($f = @fopen($this->file_dst_path . $hash . (!empty($this->file_dst_name_ext) ? '.' . $this->file_dst_name_ext : ''), 'a+'))) { - if ($this->dir_auto_chmod) { - $this->log .= '- ' . $this->file_dst_path . ' is not writeable. Attempting chmod:'; - if (!@chmod($this->file_dst_path, $this->dir_chmod)) { - $this->log .= ' failed
'; - $this->processed = false; - $this->error = $this->translate('destination_dir_write'); - } else { - $this->log .= ' success
'; - if (!($f = @fopen($this->file_dst_path . $hash . (!empty($this->file_dst_name_ext) ? '.' . $this->file_dst_name_ext : ''), 'a+'))) { // we re-check - $this->processed = false; - $this->error = $this->translate('destination_dir_write'); - } else { - @fclose($f); - } - } - } else { - $this->processed = false; - $this->error = $this->translate('destination_path_write'); - } - } else { - if ($this->processed) @fclose($f); - @unlink($this->file_dst_path . $hash . (!empty($this->file_dst_name_ext) ? '.' . $this->file_dst_name_ext : '')); - } - - - // if we have an uploaded file, and if it is the first process, and if we can't access the file directly (open_basedir restriction) - // then we create a temp file that will be used as the source file in subsequent processes - // the third condition is there to check if the file is not accessible *directly* (it already has positively gone through is_uploaded_file(), so it exists) - if (!$this->no_upload_check && empty($this->file_src_temp) && !@file_exists($this->file_src_pathname)) { - $this->log .= '- attempting to use a temp file:'; - $hash = md5($this->file_dst_name_body . rand(1, 1000)); - if (move_uploaded_file($this->file_src_pathname, $this->file_dst_path . $hash . (!empty($this->file_dst_name_ext) ? '.' . $this->file_dst_name_ext : ''))) { - $this->file_src_pathname = $this->file_dst_path . $hash . (!empty($this->file_dst_name_ext) ? '.' . $this->file_dst_name_ext : ''); - $this->file_src_temp = $this->file_src_pathname; - $this->log .= ' file created
'; - $this->log .= '    temp file is: ' . $this->file_src_temp . '
'; - } else { - $this->log .= ' failed
'; - $this->processed = false; - $this->error = $this->translate('temp_file'); - } - } - } - } - - if ($this->processed) { - - // check if we need to autorotate, to automatically pre-rotates the image according to EXIF data (JPEG only) - $auto_flip = false; - $auto_rotate = 0; - if ($this->file_is_image && $this->image_auto_rotate && $this->image_src_type == 'jpg' && $this->function_enabled('exif_read_data')) { - $exif = @exif_read_data($this->file_src_pathname); - if (is_array($exif) && isset($exif['Orientation'])) { - $orientation = $exif['Orientation']; - switch($orientation) { - case 1: - $this->log .= '- EXIF orientation = 1 : default
'; - break; - case 2: - $auto_flip = 'v'; - $this->log .= '- EXIF orientation = 2 : vertical flip
'; - break; - case 3: - $auto_rotate = 180; - $this->log .= '- EXIF orientation = 3 : 180 rotate left
'; - break; - case 4: - $auto_flip = 'h'; - $this->log .= '- EXIF orientation = 4 : horizontal flip
'; - break; - case 5: - $auto_flip = 'h'; - $auto_rotate = 90; - $this->log .= '- EXIF orientation = 5 : horizontal flip + 90 rotate right
'; - break; - case 6: - $auto_rotate = 90; - $this->log .= '- EXIF orientation = 6 : 90 rotate right
'; - break; - case 7: - $auto_flip = 'v'; - $auto_rotate = 90; - $this->log .= '- EXIF orientation = 7 : vertical flip + 90 rotate right
'; - break; - case 8: - $auto_rotate = 270; - $this->log .= '- EXIF orientation = 8 : 90 rotate left
'; - break; - default: - $this->log .= '- EXIF orientation = '.$orientation.' : unknown
'; - break; - } - } else { - $this->log .= '- EXIF data is invalid or missing
'; - } - } else { - if (!$this->image_auto_rotate) { - $this->log .= '- auto-rotate deactivated
'; - } else if (!$this->image_src_type == 'jpg') { - $this->log .= '- auto-rotate applies only to JPEG images
'; - } else if (!$this->function_enabled('exif_read_data')) { - $this->log .= '- auto-rotate requires function exif_read_data to be enabled
'; - } - } - - // do we do some image manipulation? - $image_manipulation = ($this->file_is_image && ( - $this->image_resize - || $this->image_convert != '' - || is_numeric($this->image_brightness) - || is_numeric($this->image_contrast) - || is_numeric($this->image_opacity) - || is_numeric($this->image_threshold) - || !empty($this->image_tint_color) - || !empty($this->image_overlay_color) - || $this->image_pixelate - || $this->image_unsharp - || !empty($this->image_text) - || $this->image_greyscale - || $this->image_negative - || !empty($this->image_watermark) - || $auto_rotate || $auto_flip - || is_numeric($this->image_rotate) - || is_numeric($this->jpeg_size) - || !empty($this->image_flip) - || !empty($this->image_crop) - || !empty($this->image_precrop) - || !empty($this->image_border) - || !empty($this->image_border_transparent) - || $this->image_frame > 0 - || $this->image_bevel > 0 - || $this->image_reflection_height)); - - // we do a quick check to ensure the file is really an image - // we can do this only now, as it would have failed before in case of open_basedir - if ($image_manipulation && !@getimagesize($this->file_src_pathname)) { - $this->log .= '- the file is not an image!
'; - $image_manipulation = false; - } - - if ($image_manipulation) { - - // make sure GD doesn't complain too much - @ini_set("gd.jpeg_ignore_warning", 1); - - // checks if the source file is readable - if ($this->processed && !($f = @fopen($this->file_src_pathname, 'r'))) { - $this->processed = false; - $this->error = $this->translate('source_not_readable'); - } else { - @fclose($f); - } - - // we now do all the image manipulations - $this->log .= '- image resizing or conversion wanted
'; - if ($this->gdversion()) { - switch($this->image_src_type) { - case 'jpg': - if (!$this->function_enabled('imagecreatefromjpeg')) { - $this->processed = false; - $this->error = $this->translate('no_create_support', array('JPEG')); - } else { - $image_src = @imagecreatefromjpeg($this->file_src_pathname); - if (!$image_src) { - $this->processed = false; - $this->error = $this->translate('create_error', array('JPEG')); - } else { - $this->log .= '- source image is JPEG
'; - } - } - break; - case 'png': - if (!$this->function_enabled('imagecreatefrompng')) { - $this->processed = false; - $this->error = $this->translate('no_create_support', array('PNG')); - } else { - $image_src = @imagecreatefrompng($this->file_src_pathname); - if (!$image_src) { - $this->processed = false; - $this->error = $this->translate('create_error', array('PNG')); - } else { - $this->log .= '- source image is PNG
'; - } - } - break; - case 'gif': - if (!$this->function_enabled('imagecreatefromgif')) { - $this->processed = false; - $this->error = $this->translate('no_create_support', array('GIF')); - } else { - $image_src = @imagecreatefromgif($this->file_src_pathname); - if (!$image_src) { - $this->processed = false; - $this->error = $this->translate('create_error', array('GIF')); - } else { - $this->log .= '- source image is GIF
'; - } - } - break; - case 'bmp': - if (!method_exists($this, 'imagecreatefrombmp')) { - $this->processed = false; - $this->error = $this->translate('no_create_support', array('BMP')); - } else { - $image_src = @$this->imagecreatefrombmp($this->file_src_pathname); - if (!$image_src) { - $this->processed = false; - $this->error = $this->translate('create_error', array('BMP')); - } else { - $this->log .= '- source image is BMP
'; - } - } - break; - default: - $this->processed = false; - $this->error = $this->translate('source_invalid'); - } - } else { - $this->processed = false; - $this->error = $this->translate('gd_missing'); - } - - if ($this->processed && $image_src) { - - // we have to set image_convert if it is not already - if (empty($this->image_convert)) { - $this->log .= '- setting destination file type to ' . $this->image_src_type . '
'; - $this->image_convert = $this->image_src_type; - } - - if (!in_array($this->image_convert, $this->image_supported)) { - $this->image_convert = 'jpg'; - } - - // we set the default color to be the background color if we don't output in a transparent format - if ($this->image_convert != 'png' && $this->image_convert != 'gif' && !empty($this->image_default_color) && empty($this->image_background_color)) $this->image_background_color = $this->image_default_color; - if (!empty($this->image_background_color)) $this->image_default_color = $this->image_background_color; - if (empty($this->image_default_color)) $this->image_default_color = '#FFFFFF'; - - $this->image_src_x = imagesx($image_src); - $this->image_src_y = imagesy($image_src); - $gd_version = $this->gdversion(); - $ratio_crop = null; - - if (!imageistruecolor($image_src)) { // $this->image_src_type == 'gif' - $this->log .= '- image is detected as having a palette
'; - $this->image_is_palette = true; - $this->image_transparent_color = imagecolortransparent($image_src); - if ($this->image_transparent_color >= 0 && imagecolorstotal($image_src) > $this->image_transparent_color) { - $this->image_is_transparent = true; - $this->log .= '    palette image is detected as transparent
'; - } - // if the image has a palette (GIF), we convert it to true color, preserving transparency - $this->log .= '    convert palette image to true color
'; - $true_color = imagecreatetruecolor($this->image_src_x, $this->image_src_y); - imagealphablending($true_color, false); - imagesavealpha($true_color, true); - for ($x = 0; $x < $this->image_src_x; $x++) { - for ($y = 0; $y < $this->image_src_y; $y++) { - if ($this->image_transparent_color >= 0 && imagecolorat($image_src, $x, $y) == $this->image_transparent_color) { - imagesetpixel($true_color, $x, $y, 127 << 24); - } else { - $rgb = imagecolorsforindex($image_src, imagecolorat($image_src, $x, $y)); - imagesetpixel($true_color, $x, $y, ($rgb['alpha'] << 24) | ($rgb['red'] << 16) | ($rgb['green'] << 8) | $rgb['blue']); - } - } - } - $image_src = $this->imagetransfer($true_color, $image_src); - imagealphablending($image_src, false); - imagesavealpha($image_src, true); - $this->image_is_palette = false; - } - - $image_dst = & $image_src; - - // auto-flip image, according to EXIF data (JPEG only) - if ($gd_version >= 2 && !empty($auto_flip)) { - $this->log .= '- auto-flip image : ' . ($auto_flip == 'v' ? 'vertical' : 'horizontal') . '
'; - $tmp = $this->imagecreatenew($this->image_src_x, $this->image_src_y); - for ($x = 0; $x < $this->image_src_x; $x++) { - for ($y = 0; $y < $this->image_src_y; $y++){ - if (strpos($auto_flip, 'v') !== false) { - imagecopy($tmp, $image_dst, $this->image_src_x - $x - 1, $y, $x, $y, 1, 1); - } else { - imagecopy($tmp, $image_dst, $x, $this->image_src_y - $y - 1, $x, $y, 1, 1); - } - } - } - // we transfert tmp into image_dst - $image_dst = $this->imagetransfer($tmp, $image_dst); - } - - // auto-rotate image, according to EXIF data (JPEG only) - if ($gd_version >= 2 && is_numeric($auto_rotate)) { - if (!in_array($auto_rotate, array(0, 90, 180, 270))) $auto_rotate = 0; - if ($auto_rotate != 0) { - if ($auto_rotate == 90 || $auto_rotate == 270) { - $tmp = $this->imagecreatenew($this->image_src_y, $this->image_src_x); - } else { - $tmp = $this->imagecreatenew($this->image_src_x, $this->image_src_y); - } - $this->log .= '- auto-rotate image : ' . $auto_rotate . '
'; - for ($x = 0; $x < $this->image_src_x; $x++) { - for ($y = 0; $y < $this->image_src_y; $y++){ - if ($auto_rotate == 90) { - imagecopy($tmp, $image_dst, $y, $x, $x, $this->image_src_y - $y - 1, 1, 1); - } else if ($auto_rotate == 180) { - imagecopy($tmp, $image_dst, $x, $y, $this->image_src_x - $x - 1, $this->image_src_y - $y - 1, 1, 1); - } else if ($auto_rotate == 270) { - imagecopy($tmp, $image_dst, $y, $x, $this->image_src_x - $x - 1, $y, 1, 1); - } else { - imagecopy($tmp, $image_dst, $x, $y, $x, $y, 1, 1); - } - } - } - if ($auto_rotate == 90 || $auto_rotate == 270) { - $t = $this->image_src_y; - $this->image_src_y = $this->image_src_x; - $this->image_src_x = $t; - } - // we transfert tmp into image_dst - $image_dst = $this->imagetransfer($tmp, $image_dst); - } - } - - // pre-crop image, before resizing - if ((!empty($this->image_precrop))) { - list($ct, $cr, $cb, $cl) = $this->getoffsets($this->image_precrop, $this->image_src_x, $this->image_src_y, true, true); - $this->log .= '- pre-crop image : ' . $ct . ' ' . $cr . ' ' . $cb . ' ' . $cl . '
'; - $this->image_src_x = $this->image_src_x - $cl - $cr; - $this->image_src_y = $this->image_src_y - $ct - $cb; - if ($this->image_src_x < 1) $this->image_src_x = 1; - if ($this->image_src_y < 1) $this->image_src_y = 1; - $tmp = $this->imagecreatenew($this->image_src_x, $this->image_src_y); - - // we copy the image into the recieving image - imagecopy($tmp, $image_dst, 0, 0, $cl, $ct, $this->image_src_x, $this->image_src_y); - - // if we crop with negative margins, we have to make sure the extra bits are the right color, or transparent - if ($ct < 0 || $cr < 0 || $cb < 0 || $cl < 0 ) { - // use the background color if present - if (!empty($this->image_background_color)) { - list($red, $green, $blue) = $this->getcolors($this->image_background_color); - $fill = imagecolorallocate($tmp, $red, $green, $blue); - } else { - $fill = imagecolorallocatealpha($tmp, 0, 0, 0, 127); - } - // fills eventual negative margins - if ($ct < 0) imagefilledrectangle($tmp, 0, 0, $this->image_src_x, -$ct, $fill); - if ($cr < 0) imagefilledrectangle($tmp, $this->image_src_x + $cr, 0, $this->image_src_x, $this->image_src_y, $fill); - if ($cb < 0) imagefilledrectangle($tmp, 0, $this->image_src_y + $cb, $this->image_src_x, $this->image_src_y, $fill); - if ($cl < 0) imagefilledrectangle($tmp, 0, 0, -$cl, $this->image_src_y, $fill); - } - - // we transfert tmp into image_dst - $image_dst = $this->imagetransfer($tmp, $image_dst); - } - - // resize image (and move image_src_x, image_src_y dimensions into image_dst_x, image_dst_y) - if ($this->image_resize) { - $this->log .= '- resizing...
'; - $this->image_dst_x = $this->image_x; - $this->image_dst_y = $this->image_y; - - // backward compatibility for soon to be deprecated settings - if ($this->image_ratio_no_zoom_in) { - $this->image_ratio = true; - $this->image_no_enlarging = true; - } else if ($this->image_ratio_no_zoom_out) { - $this->image_ratio = true; - $this->image_no_shrinking = true; - } - - // keeps aspect ratio with x calculated from y - if ($this->image_ratio_x) { - $this->log .= '    calculate x size
'; - $this->image_dst_x = round(($this->image_src_x * $this->image_y) / $this->image_src_y); - $this->image_dst_y = $this->image_y; - - // keeps aspect ratio with y calculated from x - } else if ($this->image_ratio_y) { - $this->log .= '    calculate y size
'; - $this->image_dst_x = $this->image_x; - $this->image_dst_y = round(($this->image_src_y * $this->image_x) / $this->image_src_x); - - // keeps aspect ratio, calculating x and y so that the image is approx the set number of pixels - } else if (is_numeric($this->image_ratio_pixels)) { - $this->log .= '    calculate x/y size to match a number of pixels
'; - $pixels = $this->image_src_y * $this->image_src_x; - $diff = sqrt($this->image_ratio_pixels / $pixels); - $this->image_dst_x = round($this->image_src_x * $diff); - $this->image_dst_y = round($this->image_src_y * $diff); - - // keeps aspect ratio with x and y dimensions, filling the space - } else if ($this->image_ratio_crop) { - if (!is_string($this->image_ratio_crop)) $this->image_ratio_crop = ''; - $this->image_ratio_crop = strtolower($this->image_ratio_crop); - if (($this->image_src_x/$this->image_x) > ($this->image_src_y/$this->image_y)) { - $this->image_dst_y = $this->image_y; - $this->image_dst_x = intval($this->image_src_x*($this->image_y / $this->image_src_y)); - $ratio_crop = array(); - $ratio_crop['x'] = $this->image_dst_x - $this->image_x; - if (strpos($this->image_ratio_crop, 'l') !== false) { - $ratio_crop['l'] = 0; - $ratio_crop['r'] = $ratio_crop['x']; - } else if (strpos($this->image_ratio_crop, 'r') !== false) { - $ratio_crop['l'] = $ratio_crop['x']; - $ratio_crop['r'] = 0; - } else { - $ratio_crop['l'] = round($ratio_crop['x']/2); - $ratio_crop['r'] = $ratio_crop['x'] - $ratio_crop['l']; - } - $this->log .= '    ratio_crop_x : ' . $ratio_crop['x'] . ' (' . $ratio_crop['l'] . ';' . $ratio_crop['r'] . ')
'; - if (is_null($this->image_crop)) $this->image_crop = array(0, 0, 0, 0); - } else { - $this->image_dst_x = $this->image_x; - $this->image_dst_y = intval($this->image_src_y*($this->image_x / $this->image_src_x)); - $ratio_crop = array(); - $ratio_crop['y'] = $this->image_dst_y - $this->image_y; - if (strpos($this->image_ratio_crop, 't') !== false) { - $ratio_crop['t'] = 0; - $ratio_crop['b'] = $ratio_crop['y']; - } else if (strpos($this->image_ratio_crop, 'b') !== false) { - $ratio_crop['t'] = $ratio_crop['y']; - $ratio_crop['b'] = 0; - } else { - $ratio_crop['t'] = round($ratio_crop['y']/2); - $ratio_crop['b'] = $ratio_crop['y'] - $ratio_crop['t']; - } - $this->log .= '    ratio_crop_y : ' . $ratio_crop['y'] . ' (' . $ratio_crop['t'] . ';' . $ratio_crop['b'] . ')
'; - if (is_null($this->image_crop)) $this->image_crop = array(0, 0, 0, 0); - } - - // keeps aspect ratio with x and y dimensions, fitting the image in the space, and coloring the rest - } else if ($this->image_ratio_fill) { - if (!is_string($this->image_ratio_fill)) $this->image_ratio_fill = ''; - $this->image_ratio_fill = strtolower($this->image_ratio_fill); - if (($this->image_src_x/$this->image_x) < ($this->image_src_y/$this->image_y)) { - $this->image_dst_y = $this->image_y; - $this->image_dst_x = intval($this->image_src_x*($this->image_y / $this->image_src_y)); - $ratio_crop = array(); - $ratio_crop['x'] = $this->image_dst_x - $this->image_x; - if (strpos($this->image_ratio_fill, 'l') !== false) { - $ratio_crop['l'] = 0; - $ratio_crop['r'] = $ratio_crop['x']; - } else if (strpos($this->image_ratio_fill, 'r') !== false) { - $ratio_crop['l'] = $ratio_crop['x']; - $ratio_crop['r'] = 0; - } else { - $ratio_crop['l'] = round($ratio_crop['x']/2); - $ratio_crop['r'] = $ratio_crop['x'] - $ratio_crop['l']; - } - $this->log .= '    ratio_fill_x : ' . $ratio_crop['x'] . ' (' . $ratio_crop['l'] . ';' . $ratio_crop['r'] . ')
'; - if (is_null($this->image_crop)) $this->image_crop = array(0, 0, 0, 0); - } else { - $this->image_dst_x = $this->image_x; - $this->image_dst_y = intval($this->image_src_y*($this->image_x / $this->image_src_x)); - $ratio_crop = array(); - $ratio_crop['y'] = $this->image_dst_y - $this->image_y; - if (strpos($this->image_ratio_fill, 't') !== false) { - $ratio_crop['t'] = 0; - $ratio_crop['b'] = $ratio_crop['y']; - } else if (strpos($this->image_ratio_fill, 'b') !== false) { - $ratio_crop['t'] = $ratio_crop['y']; - $ratio_crop['b'] = 0; - } else { - $ratio_crop['t'] = round($ratio_crop['y']/2); - $ratio_crop['b'] = $ratio_crop['y'] - $ratio_crop['t']; - } - $this->log .= '    ratio_fill_y : ' . $ratio_crop['y'] . ' (' . $ratio_crop['t'] . ';' . $ratio_crop['b'] . ')
'; - if (is_null($this->image_crop)) $this->image_crop = array(0, 0, 0, 0); - } - - // keeps aspect ratio with x and y dimensions - } else if ($this->image_ratio) { - if (($this->image_src_x/$this->image_x) > ($this->image_src_y/$this->image_y)) { - $this->image_dst_x = $this->image_x; - $this->image_dst_y = intval($this->image_src_y*($this->image_x / $this->image_src_x)); - } else { - $this->image_dst_y = $this->image_y; - $this->image_dst_x = intval($this->image_src_x*($this->image_y / $this->image_src_y)); - } - - // resize to provided exact dimensions - } else { - $this->log .= '    use plain sizes
'; - $this->image_dst_x = $this->image_x; - $this->image_dst_y = $this->image_y; - } - - if ($this->image_dst_x < 1) $this->image_dst_x = 1; - if ($this->image_dst_y < 1) $this->image_dst_y = 1; - $this->log .= '    image_src_x y : ' . $this->image_src_x . ' x ' . $this->image_src_y . '
'; - $this->log .= '    image_dst_x y : ' . $this->image_dst_x . ' x ' . $this->image_dst_y . '
'; - - // make sure we don't enlarge the image if we don't want to - if ($this->image_no_enlarging && ($this->image_src_x < $this->image_dst_x || $this->image_src_y < $this->image_dst_y)) { - $this->log .= '    cancel resizing, as it would enlarge the image!
'; - $this->image_dst_x = $this->image_src_x; - $this->image_dst_y = $this->image_src_y; - $ratio_crop = null; - } - - // make sure we don't shrink the image if we don't want to - if ($this->image_no_shrinking && ($this->image_src_x > $this->image_dst_x || $this->image_src_y > $this->image_dst_y)) { - $this->log .= '    cancel resizing, as it would shrink the image!
'; - $this->image_dst_x = $this->image_src_x; - $this->image_dst_y = $this->image_src_y; - $ratio_crop = null; - } - - // resize the image - if ($this->image_dst_x != $this->image_src_x && $this->image_dst_y != $this->image_src_y) { - $tmp = $this->imagecreatenew($this->image_dst_x, $this->image_dst_y); - - if ($gd_version >= 2) { - $res = imagecopyresampled($tmp, $image_src, 0, 0, 0, 0, $this->image_dst_x, $this->image_dst_y, $this->image_src_x, $this->image_src_y); - } else { - $res = imagecopyresized($tmp, $image_src, 0, 0, 0, 0, $this->image_dst_x, $this->image_dst_y, $this->image_src_x, $this->image_src_y); - } - - $this->log .= '    resized image object created
'; - // we transfert tmp into image_dst - $image_dst = $this->imagetransfer($tmp, $image_dst); - } - - } else { - $this->image_dst_x = $this->image_src_x; - $this->image_dst_y = $this->image_src_y; - } - - // crop image (and also crops if image_ratio_crop is used) - if ((!empty($this->image_crop) || !is_null($ratio_crop))) { - list($ct, $cr, $cb, $cl) = $this->getoffsets($this->image_crop, $this->image_dst_x, $this->image_dst_y, true, true); - // we adjust the cropping if we use image_ratio_crop - if (!is_null($ratio_crop)) { - if (array_key_exists('t', $ratio_crop)) $ct += $ratio_crop['t']; - if (array_key_exists('r', $ratio_crop)) $cr += $ratio_crop['r']; - if (array_key_exists('b', $ratio_crop)) $cb += $ratio_crop['b']; - if (array_key_exists('l', $ratio_crop)) $cl += $ratio_crop['l']; - } - if ($ct != 0 || $cr != 0 || $cb != 0 || $cl != 0) { - $this->log .= '- crop image : ' . $ct . ' ' . $cr . ' ' . $cb . ' ' . $cl . '
'; - $this->image_dst_x = $this->image_dst_x - $cl - $cr; - $this->image_dst_y = $this->image_dst_y - $ct - $cb; - if ($this->image_dst_x < 1) $this->image_dst_x = 1; - if ($this->image_dst_y < 1) $this->image_dst_y = 1; - $tmp = $this->imagecreatenew($this->image_dst_x, $this->image_dst_y); - - // we copy the image into the recieving image - imagecopy($tmp, $image_dst, 0, 0, $cl, $ct, $this->image_dst_x, $this->image_dst_y); - - // if we crop with negative margins, we have to make sure the extra bits are the right color, or transparent - if ($ct < 0 || $cr < 0 || $cb < 0 || $cl < 0 ) { - // use the background color if present - if (!empty($this->image_background_color)) { - list($red, $green, $blue) = $this->getcolors($this->image_background_color); - $fill = imagecolorallocate($tmp, $red, $green, $blue); - } else { - $fill = imagecolorallocatealpha($tmp, 0, 0, 0, 127); - } - // fills eventual negative margins - if ($ct < 0) imagefilledrectangle($tmp, 0, 0, $this->image_dst_x, -$ct-1, $fill); - if ($cr < 0) imagefilledrectangle($tmp, $this->image_dst_x + $cr, 0, $this->image_dst_x, $this->image_dst_y, $fill); - if ($cb < 0) imagefilledrectangle($tmp, 0, $this->image_dst_y + $cb, $this->image_dst_x, $this->image_dst_y, $fill); - if ($cl < 0) imagefilledrectangle($tmp, 0, 0, -$cl-1, $this->image_dst_y, $fill); - } - - // we transfert tmp into image_dst - $image_dst = $this->imagetransfer($tmp, $image_dst); - } - } - - // flip image - if ($gd_version >= 2 && !empty($this->image_flip)) { - $this->image_flip = strtolower($this->image_flip); - $this->log .= '- flip image : ' . $this->image_flip . '
'; - $tmp = $this->imagecreatenew($this->image_dst_x, $this->image_dst_y); - for ($x = 0; $x < $this->image_dst_x; $x++) { - for ($y = 0; $y < $this->image_dst_y; $y++){ - if (strpos($this->image_flip, 'v') !== false) { - imagecopy($tmp, $image_dst, $this->image_dst_x - $x - 1, $y, $x, $y, 1, 1); - } else { - imagecopy($tmp, $image_dst, $x, $this->image_dst_y - $y - 1, $x, $y, 1, 1); - } - } - } - // we transfert tmp into image_dst - $image_dst = $this->imagetransfer($tmp, $image_dst); - } - - // rotate image - if ($gd_version >= 2 && is_numeric($this->image_rotate)) { - if (!in_array($this->image_rotate, array(0, 90, 180, 270))) $this->image_rotate = 0; - if ($this->image_rotate != 0) { - if ($this->image_rotate == 90 || $this->image_rotate == 270) { - $tmp = $this->imagecreatenew($this->image_dst_y, $this->image_dst_x); - } else { - $tmp = $this->imagecreatenew($this->image_dst_x, $this->image_dst_y); - } - $this->log .= '- rotate image : ' . $this->image_rotate . '
'; - for ($x = 0; $x < $this->image_dst_x; $x++) { - for ($y = 0; $y < $this->image_dst_y; $y++){ - if ($this->image_rotate == 90) { - imagecopy($tmp, $image_dst, $y, $x, $x, $this->image_dst_y - $y - 1, 1, 1); - } else if ($this->image_rotate == 180) { - imagecopy($tmp, $image_dst, $x, $y, $this->image_dst_x - $x - 1, $this->image_dst_y - $y - 1, 1, 1); - } else if ($this->image_rotate == 270) { - imagecopy($tmp, $image_dst, $y, $x, $this->image_dst_x - $x - 1, $y, 1, 1); - } else { - imagecopy($tmp, $image_dst, $x, $y, $x, $y, 1, 1); - } - } - } - if ($this->image_rotate == 90 || $this->image_rotate == 270) { - $t = $this->image_dst_y; - $this->image_dst_y = $this->image_dst_x; - $this->image_dst_x = $t; - } - // we transfert tmp into image_dst - $image_dst = $this->imagetransfer($tmp, $image_dst); - } - } - - // pixelate image - if ((is_numeric($this->image_pixelate) && $this->image_pixelate > 0)) { - $this->log .= '- pixelate image (' . $this->image_pixelate . 'px)
'; - $filter = $this->imagecreatenew($this->image_dst_x, $this->image_dst_y); - if ($gd_version >= 2) { - imagecopyresampled($filter, $image_dst, 0, 0, 0, 0, round($this->image_dst_x / $this->image_pixelate), round($this->image_dst_y / $this->image_pixelate), $this->image_dst_x, $this->image_dst_y); - imagecopyresampled($image_dst, $filter, 0, 0, 0, 0, $this->image_dst_x, $this->image_dst_y, round($this->image_dst_x / $this->image_pixelate), round($this->image_dst_y / $this->image_pixelate)); - } else { - imagecopyresized($filter, $image_dst, 0, 0, 0, 0, round($this->image_dst_x / $this->image_pixelate), round($this->image_dst_y / $this->image_pixelate), $this->image_dst_x, $this->image_dst_y); - imagecopyresized($image_dst, $filter, 0, 0, 0, 0, $this->image_dst_x, $this->image_dst_y, round($this->image_dst_x / $this->image_pixelate), round($this->image_dst_y / $this->image_pixelate)); - } - imagedestroy($filter); - } - - // unsharp mask - if ($gd_version >= 2 && $this->image_unsharp && is_numeric($this->image_unsharp_amount) && is_numeric($this->image_unsharp_radius) && is_numeric($this->image_unsharp_threshold)) { - // Unsharp Mask for PHP - version 2.1.1 - // Unsharp mask algorithm by Torstein Hønsi 2003-07. - // Used with permission - // Modified to support alpha transparency - if ($this->image_unsharp_amount > 500) $this->image_unsharp_amount = 500; - $this->image_unsharp_amount = $this->image_unsharp_amount * 0.016; - if ($this->image_unsharp_radius > 50) $this->image_unsharp_radius = 50; - $this->image_unsharp_radius = $this->image_unsharp_radius * 2; - if ($this->image_unsharp_threshold > 255) $this->image_unsharp_threshold = 255; - $this->image_unsharp_radius = abs(round($this->image_unsharp_radius)); - if ($this->image_unsharp_radius != 0) { - $this->image_dst_x = imagesx($image_dst); $this->image_dst_y = imagesy($image_dst); - $canvas = $this->imagecreatenew($this->image_dst_x, $this->image_dst_y, false, true); - $blur = $this->imagecreatenew($this->image_dst_x, $this->image_dst_y, false, true); - if ($this->function_enabled('imageconvolution')) { // PHP >= 5.1 - $matrix = array(array( 1, 2, 1 ), array( 2, 4, 2 ), array( 1, 2, 1 )); - imagecopy($blur, $image_dst, 0, 0, 0, 0, $this->image_dst_x, $this->image_dst_y); - imageconvolution($blur, $matrix, 16, 0); - } else { - for ($i = 0; $i < $this->image_unsharp_radius; $i++) { - imagecopy($blur, $image_dst, 0, 0, 1, 0, $this->image_dst_x - 1, $this->image_dst_y); // left - $this->imagecopymergealpha($blur, $image_dst, 1, 0, 0, 0, $this->image_dst_x, $this->image_dst_y, 50); // right - $this->imagecopymergealpha($blur, $image_dst, 0, 0, 0, 0, $this->image_dst_x, $this->image_dst_y, 50); // center - imagecopy($canvas, $blur, 0, 0, 0, 0, $this->image_dst_x, $this->image_dst_y); - $this->imagecopymergealpha($blur, $canvas, 0, 0, 0, 1, $this->image_dst_x, $this->image_dst_y - 1, 33.33333 ); // up - $this->imagecopymergealpha($blur, $canvas, 0, 1, 0, 0, $this->image_dst_x, $this->image_dst_y, 25); // down - } - } - $p_new = array(); - if($this->image_unsharp_threshold>0) { - for ($x = 0; $x < $this->image_dst_x-1; $x++) { - for ($y = 0; $y < $this->image_dst_y; $y++) { - $p_orig = imagecolorsforindex($image_dst, imagecolorat($image_dst, $x, $y)); - $p_blur = imagecolorsforindex($blur, imagecolorat($blur, $x, $y)); - $p_new['red'] = (abs($p_orig['red'] - $p_blur['red']) >= $this->image_unsharp_threshold) ? max(0, min(255, ($this->image_unsharp_amount * ($p_orig['red'] - $p_blur['red'])) + $p_orig['red'])) : $p_orig['red']; - $p_new['green'] = (abs($p_orig['green'] - $p_blur['green']) >= $this->image_unsharp_threshold) ? max(0, min(255, ($this->image_unsharp_amount * ($p_orig['green'] - $p_blur['green'])) + $p_orig['green'])) : $p_orig['green']; - $p_new['blue'] = (abs($p_orig['blue'] - $p_blur['blue']) >= $this->image_unsharp_threshold) ? max(0, min(255, ($this->image_unsharp_amount * ($p_orig['blue'] - $p_blur['blue'])) + $p_orig['blue'])) : $p_orig['blue']; - if (($p_orig['red'] != $p_new['red']) || ($p_orig['green'] != $p_new['green']) || ($p_orig['blue'] != $p_new['blue'])) { - $color = imagecolorallocatealpha($image_dst, $p_new['red'], $p_new['green'], $p_new['blue'], $p_orig['alpha']); - imagesetpixel($image_dst, $x, $y, $color); - } - } - } - } else { - for ($x = 0; $x < $this->image_dst_x; $x++) { - for ($y = 0; $y < $this->image_dst_y; $y++) { - $p_orig = imagecolorsforindex($image_dst, imagecolorat($image_dst, $x, $y)); - $p_blur = imagecolorsforindex($blur, imagecolorat($blur, $x, $y)); - $p_new['red'] = ($this->image_unsharp_amount * ($p_orig['red'] - $p_blur['red'])) + $p_orig['red']; - if ($p_new['red']>255) { $p_new['red']=255; } elseif ($p_new['red']<0) { $p_new['red']=0; } - $p_new['green'] = ($this->image_unsharp_amount * ($p_orig['green'] - $p_blur['green'])) + $p_orig['green']; - if ($p_new['green']>255) { $p_new['green']=255; } elseif ($p_new['green']<0) { $p_new['green']=0; } - $p_new['blue'] = ($this->image_unsharp_amount * ($p_orig['blue'] - $p_blur['blue'])) + $p_orig['blue']; - if ($p_new['blue']>255) { $p_new['blue']=255; } elseif ($p_new['blue']<0) { $p_new['blue']=0; } - $color = imagecolorallocatealpha($image_dst, $p_new['red'], $p_new['green'], $p_new['blue'], $p_orig['alpha']); - imagesetpixel($image_dst, $x, $y, $color); - } - } - } - imagedestroy($canvas); - imagedestroy($blur); - } - } - - // add color overlay - if ($gd_version >= 2 && (is_numeric($this->image_overlay_opacity) && $this->image_overlay_opacity > 0 && !empty($this->image_overlay_color))) { - $this->log .= '- apply color overlay
'; - list($red, $green, $blue) = $this->getcolors($this->image_overlay_color); - $filter = imagecreatetruecolor($this->image_dst_x, $this->image_dst_y); - $color = imagecolorallocate($filter, $red, $green, $blue); - imagefilledrectangle($filter, 0, 0, $this->image_dst_x, $this->image_dst_y, $color); - $this->imagecopymergealpha($image_dst, $filter, 0, 0, 0, 0, $this->image_dst_x, $this->image_dst_y, $this->image_overlay_opacity); - imagedestroy($filter); - } - - // add brightness, contrast and tint, turns to greyscale and inverts colors - if ($gd_version >= 2 && ($this->image_negative || $this->image_greyscale || is_numeric($this->image_threshold)|| is_numeric($this->image_brightness) || is_numeric($this->image_contrast) || !empty($this->image_tint_color))) { - $this->log .= '- apply tint, light, contrast correction, negative, greyscale and threshold
'; - if (!empty($this->image_tint_color)) list($tint_red, $tint_green, $tint_blue) = $this->getcolors($this->image_tint_color); - //imagealphablending($image_dst, true); - for($y=0; $y < $this->image_dst_y; $y++) { - for($x=0; $x < $this->image_dst_x; $x++) { - if ($this->image_greyscale) { - $pixel = imagecolorsforindex($image_dst, imagecolorat($image_dst, $x, $y)); - $r = $g = $b = round((0.2125 * $pixel['red']) + (0.7154 * $pixel['green']) + (0.0721 * $pixel['blue'])); - $color = imagecolorallocatealpha($image_dst, $r, $g, $b, $pixel['alpha']); - imagesetpixel($image_dst, $x, $y, $color); - unset($color); unset($pixel); - } - if (is_numeric($this->image_threshold)) { - $pixel = imagecolorsforindex($image_dst, imagecolorat($image_dst, $x, $y)); - $c = (round($pixel['red'] + $pixel['green'] + $pixel['blue']) / 3) - 127; - $r = $g = $b = ($c > $this->image_threshold ? 255 : 0); - $color = imagecolorallocatealpha($image_dst, $r, $g, $b, $pixel['alpha']); - imagesetpixel($image_dst, $x, $y, $color); - unset($color); unset($pixel); - } - if (is_numeric($this->image_brightness)) { - $pixel = imagecolorsforindex($image_dst, imagecolorat($image_dst, $x, $y)); - $r = max(min(round($pixel['red'] + (($this->image_brightness * 2))), 255), 0); - $g = max(min(round($pixel['green'] + (($this->image_brightness * 2))), 255), 0); - $b = max(min(round($pixel['blue'] + (($this->image_brightness * 2))), 255), 0); - $color = imagecolorallocatealpha($image_dst, $r, $g, $b, $pixel['alpha']); - imagesetpixel($image_dst, $x, $y, $color); - unset($color); unset($pixel); - } - if (is_numeric($this->image_contrast)) { - $pixel = imagecolorsforindex($image_dst, imagecolorat($image_dst, $x, $y)); - $r = max(min(round(($this->image_contrast + 128) * $pixel['red'] / 128), 255), 0); - $g = max(min(round(($this->image_contrast + 128) * $pixel['green'] / 128), 255), 0); - $b = max(min(round(($this->image_contrast + 128) * $pixel['blue'] / 128), 255), 0); - $color = imagecolorallocatealpha($image_dst, $r, $g, $b, $pixel['alpha']); - imagesetpixel($image_dst, $x, $y, $color); - unset($color); unset($pixel); - } - if (!empty($this->image_tint_color)) { - $pixel = imagecolorsforindex($image_dst, imagecolorat($image_dst, $x, $y)); - $r = min(round($tint_red * $pixel['red'] / 169), 255); - $g = min(round($tint_green * $pixel['green'] / 169), 255); - $b = min(round($tint_blue * $pixel['blue'] / 169), 255); - $color = imagecolorallocatealpha($image_dst, $r, $g, $b, $pixel['alpha']); - imagesetpixel($image_dst, $x, $y, $color); - unset($color); unset($pixel); - } - if (!empty($this->image_negative)) { - $pixel = imagecolorsforindex($image_dst, imagecolorat($image_dst, $x, $y)); - $r = round(255 - $pixel['red']); - $g = round(255 - $pixel['green']); - $b = round(255 - $pixel['blue']); - $color = imagecolorallocatealpha($image_dst, $r, $g, $b, $pixel['alpha']); - imagesetpixel($image_dst, $x, $y, $color); - unset($color); unset($pixel); - } - } - } - } - - // adds a border - if ($gd_version >= 2 && !empty($this->image_border)) { - list($ct, $cr, $cb, $cl) = $this->getoffsets($this->image_border, $this->image_dst_x, $this->image_dst_y, true, false); - $this->log .= '- add border : ' . $ct . ' ' . $cr . ' ' . $cb . ' ' . $cl . '
'; - $this->image_dst_x = $this->image_dst_x + $cl + $cr; - $this->image_dst_y = $this->image_dst_y + $ct + $cb; - if (!empty($this->image_border_color)) list($red, $green, $blue) = $this->getcolors($this->image_border_color); - $opacity = (is_numeric($this->image_border_opacity) ? (int) (127 - $this->image_border_opacity / 100 * 127): 0); - // we now create an image, that we fill with the border color - $tmp = $this->imagecreatenew($this->image_dst_x, $this->image_dst_y); - $background = imagecolorallocatealpha($tmp, $red, $green, $blue, $opacity); - imagefilledrectangle($tmp, 0, 0, $this->image_dst_x, $this->image_dst_y, $background); - // we then copy the source image into the new image, without merging so that only the border is actually kept - imagecopy($tmp, $image_dst, $cl, $ct, 0, 0, $this->image_dst_x - $cr - $cl, $this->image_dst_y - $cb - $ct); - // we transfert tmp into image_dst - $image_dst = $this->imagetransfer($tmp, $image_dst); - } - - // adds a fading-to-transparent border - if ($gd_version >= 2 && !empty($this->image_border_transparent)) { - list($ct, $cr, $cb, $cl) = $this->getoffsets($this->image_border_transparent, $this->image_dst_x, $this->image_dst_y, true, false); - $this->log .= '- add transparent border : ' . $ct . ' ' . $cr . ' ' . $cb . ' ' . $cl . '
'; - // we now create an image, that we fill with the border color - $tmp = $this->imagecreatenew($this->image_dst_x, $this->image_dst_y); - // we then copy the source image into the new image, without the borders - imagecopy($tmp, $image_dst, $cl, $ct, $cl, $ct, $this->image_dst_x - $cr - $cl, $this->image_dst_y - $cb - $ct); - // we now add the top border - $opacity = 100; - for ($y = $ct - 1; $y >= 0; $y--) { - $il = (int) ($ct > 0 ? ($cl * ($y / $ct)) : 0); - $ir = (int) ($ct > 0 ? ($cr * ($y / $ct)) : 0); - for ($x = $il; $x < $this->image_dst_x - $ir; $x++) { - $pixel = imagecolorsforindex($image_dst, imagecolorat($image_dst, $x, $y)); - $alpha = (1 - ($pixel['alpha'] / 127)) * $opacity / 100; - if ($alpha > 0) { - if ($alpha > 1) $alpha = 1; - $color = imagecolorallocatealpha($tmp, $pixel['red'] , $pixel['green'], $pixel['blue'], round((1 - $alpha) * 127)); - imagesetpixel($tmp, $x, $y, $color); - } - } - if ($opacity > 0) $opacity = $opacity - (100 / $ct); - } - // we now add the right border - $opacity = 100; - for ($x = $this->image_dst_x - $cr; $x < $this->image_dst_x; $x++) { - $it = (int) ($cr > 0 ? ($ct * (($this->image_dst_x - $x - 1) / $cr)) : 0); - $ib = (int) ($cr > 0 ? ($cb * (($this->image_dst_x - $x - 1) / $cr)) : 0); - for ($y = $it; $y < $this->image_dst_y - $ib; $y++) { - $pixel = imagecolorsforindex($image_dst, imagecolorat($image_dst, $x, $y)); - $alpha = (1 - ($pixel['alpha'] / 127)) * $opacity / 100; - if ($alpha > 0) { - if ($alpha > 1) $alpha = 1; - $color = imagecolorallocatealpha($tmp, $pixel['red'] , $pixel['green'], $pixel['blue'], round((1 - $alpha) * 127)); - imagesetpixel($tmp, $x, $y, $color); - } - } - if ($opacity > 0) $opacity = $opacity - (100 / $cr); - } - // we now add the bottom border - $opacity = 100; - for ($y = $this->image_dst_y - $cb; $y < $this->image_dst_y; $y++) { - $il = (int) ($cb > 0 ? ($cl * (($this->image_dst_y - $y - 1) / $cb)) : 0); - $ir = (int) ($cb > 0 ? ($cr * (($this->image_dst_y - $y - 1) / $cb)) : 0); - for ($x = $il; $x < $this->image_dst_x - $ir; $x++) { - $pixel = imagecolorsforindex($image_dst, imagecolorat($image_dst, $x, $y)); - $alpha = (1 - ($pixel['alpha'] / 127)) * $opacity / 100; - if ($alpha > 0) { - if ($alpha > 1) $alpha = 1; - $color = imagecolorallocatealpha($tmp, $pixel['red'] , $pixel['green'], $pixel['blue'], round((1 - $alpha) * 127)); - imagesetpixel($tmp, $x, $y, $color); - } - } - if ($opacity > 0) $opacity = $opacity - (100 / $cb); - } - // we now add the left border - $opacity = 100; - for ($x = $cl - 1; $x >= 0; $x--) { - $it = (int) ($cl > 0 ? ($ct * ($x / $cl)) : 0); - $ib = (int) ($cl > 0 ? ($cb * ($x / $cl)) : 0); - for ($y = $it; $y < $this->image_dst_y - $ib; $y++) { - $pixel = imagecolorsforindex($image_dst, imagecolorat($image_dst, $x, $y)); - $alpha = (1 - ($pixel['alpha'] / 127)) * $opacity / 100; - if ($alpha > 0) { - if ($alpha > 1) $alpha = 1; - $color = imagecolorallocatealpha($tmp, $pixel['red'] , $pixel['green'], $pixel['blue'], round((1 - $alpha) * 127)); - imagesetpixel($tmp, $x, $y, $color); - } - } - if ($opacity > 0) $opacity = $opacity - (100 / $cl); - } - // we transfert tmp into image_dst - $image_dst = $this->imagetransfer($tmp, $image_dst); - } - - // add frame border - if ($gd_version >= 2 && is_numeric($this->image_frame)) { - if (is_array($this->image_frame_colors)) { - $vars = $this->image_frame_colors; - $this->log .= '- add frame : ' . implode(' ', $this->image_frame_colors) . '
'; - } else { - $this->log .= '- add frame : ' . $this->image_frame_colors . '
'; - $vars = explode(' ', $this->image_frame_colors); - } - $nb = sizeof($vars); - $this->image_dst_x = $this->image_dst_x + ($nb * 2); - $this->image_dst_y = $this->image_dst_y + ($nb * 2); - $tmp = $this->imagecreatenew($this->image_dst_x, $this->image_dst_y); - imagecopy($tmp, $image_dst, $nb, $nb, 0, 0, $this->image_dst_x - ($nb * 2), $this->image_dst_y - ($nb * 2)); - $opacity = (is_numeric($this->image_frame_opacity) ? (int) (127 - $this->image_frame_opacity / 100 * 127): 0); - for ($i=0; $i<$nb; $i++) { - list($red, $green, $blue) = $this->getcolors($vars[$i]); - $c = imagecolorallocatealpha($tmp, $red, $green, $blue, $opacity); - if ($this->image_frame == 1) { - imageline($tmp, $i, $i, $this->image_dst_x - $i -1, $i, $c); - imageline($tmp, $this->image_dst_x - $i -1, $this->image_dst_y - $i -1, $this->image_dst_x - $i -1, $i, $c); - imageline($tmp, $this->image_dst_x - $i -1, $this->image_dst_y - $i -1, $i, $this->image_dst_y - $i -1, $c); - imageline($tmp, $i, $i, $i, $this->image_dst_y - $i -1, $c); - } else { - imageline($tmp, $i, $i, $this->image_dst_x - $i -1, $i, $c); - imageline($tmp, $this->image_dst_x - $nb + $i, $this->image_dst_y - $nb + $i, $this->image_dst_x - $nb + $i, $nb - $i, $c); - imageline($tmp, $this->image_dst_x - $nb + $i, $this->image_dst_y - $nb + $i, $nb - $i, $this->image_dst_y - $nb + $i, $c); - imageline($tmp, $i, $i, $i, $this->image_dst_y - $i -1, $c); - } - } - // we transfert tmp into image_dst - $image_dst = $this->imagetransfer($tmp, $image_dst); - } - - // add bevel border - if ($gd_version >= 2 && $this->image_bevel > 0) { - if (empty($this->image_bevel_color1)) $this->image_bevel_color1 = '#FFFFFF'; - if (empty($this->image_bevel_color2)) $this->image_bevel_color2 = '#000000'; - list($red1, $green1, $blue1) = $this->getcolors($this->image_bevel_color1); - list($red2, $green2, $blue2) = $this->getcolors($this->image_bevel_color2); - $tmp = $this->imagecreatenew($this->image_dst_x, $this->image_dst_y); - imagecopy($tmp, $image_dst, 0, 0, 0, 0, $this->image_dst_x, $this->image_dst_y); - imagealphablending($tmp, true); - for ($i=0; $i<$this->image_bevel; $i++) { - $alpha = round(($i / $this->image_bevel) * 127); - $c1 = imagecolorallocatealpha($tmp, $red1, $green1, $blue1, $alpha); - $c2 = imagecolorallocatealpha($tmp, $red2, $green2, $blue2, $alpha); - imageline($tmp, $i, $i, $this->image_dst_x - $i -1, $i, $c1); - imageline($tmp, $this->image_dst_x - $i -1, $this->image_dst_y - $i, $this->image_dst_x - $i -1, $i, $c2); - imageline($tmp, $this->image_dst_x - $i -1, $this->image_dst_y - $i -1, $i, $this->image_dst_y - $i -1, $c2); - imageline($tmp, $i, $i, $i, $this->image_dst_y - $i -1, $c1); - } - // we transfert tmp into image_dst - $image_dst = $this->imagetransfer($tmp, $image_dst); - } - - // add watermark image - if ($this->image_watermark!='' && file_exists($this->image_watermark)) { - $this->log .= '- add watermark
'; - $this->image_watermark_position = strtolower($this->image_watermark_position); - $watermark_info = getimagesize($this->image_watermark); - $watermark_type = (array_key_exists(2, $watermark_info) ? $watermark_info[2] : null); // 1 = GIF, 2 = JPG, 3 = PNG - $watermark_checked = false; - if ($watermark_type == IMAGETYPE_GIF) { - if (!$this->function_enabled('imagecreatefromgif')) { - $this->error = $this->translate('watermark_no_create_support', array('GIF')); - } else { - $filter = @imagecreatefromgif($this->image_watermark); - if (!$filter) { - $this->error = $this->translate('watermark_create_error', array('GIF')); - } else { - $this->log .= '    watermark source image is GIF
'; - $watermark_checked = true; - } - } - } else if ($watermark_type == IMAGETYPE_JPEG) { - if (!$this->function_enabled('imagecreatefromjpeg')) { - $this->error = $this->translate('watermark_no_create_support', array('JPEG')); - } else { - $filter = @imagecreatefromjpeg($this->image_watermark); - if (!$filter) { - $this->error = $this->translate('watermark_create_error', array('JPEG')); - } else { - $this->log .= '    watermark source image is JPEG
'; - $watermark_checked = true; - } - } - } else if ($watermark_type == IMAGETYPE_PNG) { - if (!$this->function_enabled('imagecreatefrompng')) { - $this->error = $this->translate('watermark_no_create_support', array('PNG')); - } else { - $filter = @imagecreatefrompng($this->image_watermark); - if (!$filter) { - $this->error = $this->translate('watermark_create_error', array('PNG')); - } else { - $this->log .= '    watermark source image is PNG
'; - $watermark_checked = true; - } - } - } else if ($watermark_type == IMAGETYPE_BMP) { - if (!method_exists($this, 'imagecreatefrombmp')) { - $this->error = $this->translate('watermark_no_create_support', array('BMP')); - } else { - $filter = @$this->imagecreatefrombmp($this->image_watermark); - if (!$filter) { - $this->error = $this->translate('watermark_create_error', array('BMP')); - } else { - $this->log .= '    watermark source image is BMP
'; - $watermark_checked = true; - } - } - } else { - $this->error = $this->translate('watermark_invalid'); - } - if ($watermark_checked) { - $watermark_dst_width = $watermark_src_width = imagesx($filter); - $watermark_dst_height = $watermark_src_height = imagesy($filter); - - // if watermark is too large/tall, resize it first - if ((!$this->image_watermark_no_zoom_out && ($watermark_dst_width > $this->image_dst_x || $watermark_dst_height > $this->image_dst_y)) - || (!$this->image_watermark_no_zoom_in && $watermark_dst_width < $this->image_dst_x && $watermark_dst_height < $this->image_dst_y)) { - $canvas_width = $this->image_dst_x - abs($this->image_watermark_x); - $canvas_height = $this->image_dst_y - abs($this->image_watermark_y); - if (($watermark_src_width/$canvas_width) > ($watermark_src_height/$canvas_height)) { - $watermark_dst_width = $canvas_width; - $watermark_dst_height = intval($watermark_src_height*($canvas_width / $watermark_src_width)); - } else { - $watermark_dst_height = $canvas_height; - $watermark_dst_width = intval($watermark_src_width*($canvas_height / $watermark_src_height)); - } - $this->log .= '    watermark resized from '.$watermark_src_width.'x'.$watermark_src_height.' to '.$watermark_dst_width.'x'.$watermark_dst_height.'
'; - - } - // determine watermark position - $watermark_x = 0; - $watermark_y = 0; - if (is_numeric($this->image_watermark_x)) { - if ($this->image_watermark_x < 0) { - $watermark_x = $this->image_dst_x - $watermark_dst_width + $this->image_watermark_x; - } else { - $watermark_x = $this->image_watermark_x; - } - } else { - if (strpos($this->image_watermark_position, 'r') !== false) { - $watermark_x = $this->image_dst_x - $watermark_dst_width; - } else if (strpos($this->image_watermark_position, 'l') !== false) { - $watermark_x = 0; - } else { - $watermark_x = ($this->image_dst_x - $watermark_dst_width) / 2; - } - } - if (is_numeric($this->image_watermark_y)) { - if ($this->image_watermark_y < 0) { - $watermark_y = $this->image_dst_y - $watermark_dst_height + $this->image_watermark_y; - } else { - $watermark_y = $this->image_watermark_y; - } - } else { - if (strpos($this->image_watermark_position, 'b') !== false) { - $watermark_y = $this->image_dst_y - $watermark_dst_height; - } else if (strpos($this->image_watermark_position, 't') !== false) { - $watermark_y = 0; - } else { - $watermark_y = ($this->image_dst_y - $watermark_dst_height) / 2; - } - } - imagealphablending($image_dst, true); - imagecopyresampled($image_dst, $filter, $watermark_x, $watermark_y, 0, 0, $watermark_dst_width, $watermark_dst_height, $watermark_src_width, $watermark_src_height); - } else { - $this->error = $this->translate('watermark_invalid'); - } - } - - // add text - if (!empty($this->image_text)) { - $this->log .= '- add text
'; - - // calculate sizes in human readable format - $src_size = $this->file_src_size / 1024; - $src_size_mb = number_format($src_size / 1024, 1, ".", " "); - $src_size_kb = number_format($src_size, 1, ".", " "); - $src_size_human = ($src_size > 1024 ? $src_size_mb . " MB" : $src_size_kb . " kb"); - - $this->image_text = str_replace( - array('[src_name]', - '[src_name_body]', - '[src_name_ext]', - '[src_pathname]', - '[src_mime]', - '[src_size]', - '[src_size_kb]', - '[src_size_mb]', - '[src_size_human]', - '[src_x]', - '[src_y]', - '[src_pixels]', - '[src_type]', - '[src_bits]', - '[dst_path]', - '[dst_name_body]', - '[dst_name_ext]', - '[dst_name]', - '[dst_pathname]', - '[dst_x]', - '[dst_y]', - '[date]', - '[time]', - '[host]', - '[server]', - '[ip]', - '[gd_version]'), - array($this->file_src_name, - $this->file_src_name_body, - $this->file_src_name_ext, - $this->file_src_pathname, - $this->file_src_mime, - $this->file_src_size, - $src_size_kb, - $src_size_mb, - $src_size_human, - $this->image_src_x, - $this->image_src_y, - $this->image_src_pixels, - $this->image_src_type, - $this->image_src_bits, - $this->file_dst_path, - $this->file_dst_name_body, - $this->file_dst_name_ext, - $this->file_dst_name, - $this->file_dst_pathname, - $this->image_dst_x, - $this->image_dst_y, - date('Y-m-d'), - date('H:i:s'), - (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'n/a'), - (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'n/a'), - (isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : 'n/a'), - $this->gdversion(true)), - $this->image_text); - - if (!is_numeric($this->image_text_padding)) $this->image_text_padding = 0; - if (!is_numeric($this->image_text_line_spacing)) $this->image_text_line_spacing = 0; - if (!is_numeric($this->image_text_padding_x)) $this->image_text_padding_x = $this->image_text_padding; - if (!is_numeric($this->image_text_padding_y)) $this->image_text_padding_y = $this->image_text_padding; - $this->image_text_position = strtolower($this->image_text_position); - $this->image_text_direction = strtolower($this->image_text_direction); - $this->image_text_alignment = strtolower($this->image_text_alignment); - - $font_type = 'gd'; - - // if the font is a string with a GDF font path, we assume that we might want to load a font - if (!is_numeric($this->image_text_font) && strlen($this->image_text_font) > 4 && substr(strtolower($this->image_text_font), -4) == '.gdf') { - if (strpos($this->image_text_font, '/') === false) $this->image_text_font = "./" . $this->image_text_font; - $this->log .= '    try to load font ' . $this->image_text_font . '... '; - if ($this->image_text_font = @imageloadfont($this->image_text_font)) { - $this->log .= 'success
'; - } else { - $this->log .= 'error
'; - $this->image_text_font = 5; - } - } - - // if the font is a string with a TTF font path, we check if we can access the font file - if (!is_numeric($this->image_text_font) && strlen($this->image_text_font) > 4 && substr(strtolower($this->image_text_font), -4) == '.ttf') { - $this->log .= '    try to load font ' . $this->image_text_font . '... '; - if (strpos($this->image_text_font, '/') === false) $this->image_text_font = "./" . $this->image_text_font; - if (file_exists($this->image_text_font) && is_readable($this->image_text_font)) { - $this->log .= 'success
'; - $font_type = 'tt'; - } else { - $this->log .= 'error
'; - $this->image_text_font = 5; - } - } - - // get the text bounding box (GD fonts) - if ($font_type == 'gd') { - $text = explode("\n", $this->image_text); - $char_width = imagefontwidth($this->image_text_font); - $char_height = imagefontheight($this->image_text_font); - $text_height = 0; - $text_width = 0; - $line_height = 0; - $line_width = 0; - foreach ($text as $k => $v) { - if ($this->image_text_direction == 'v') { - $h = ($char_width * strlen($v)); - if ($h > $text_height) $text_height = $h; - $line_width = $char_height; - $text_width += $line_width + ($k < (sizeof($text)-1) ? $this->image_text_line_spacing : 0); - } else { - $w = ($char_width * strlen($v)); - if ($w > $text_width) $text_width = $w; - $line_height = $char_height; - $text_height += $line_height + ($k < (sizeof($text)-1) ? $this->image_text_line_spacing : 0); - } - } - $text_width += (2 * $this->image_text_padding_x); - $text_height += (2 * $this->image_text_padding_y); - - // get the text bounding box (TrueType fonts) - } else if ($font_type == 'tt') { - $text = $this->image_text; - if (!$this->image_text_angle) $this->image_text_angle = $this->image_text_direction == 'v' ? 90 : 0; - $text_height = 0; - $text_width = 0; - $text_offset_x = 0; - $text_offset_y = 0; - $rect = imagettfbbox($this->image_text_size, $this->image_text_angle, $this->image_text_font, $text ); - if ($rect) { - $minX = min(array($rect[0],$rect[2],$rect[4],$rect[6])); - $maxX = max(array($rect[0],$rect[2],$rect[4],$rect[6])); - $minY = min(array($rect[1],$rect[3],$rect[5],$rect[7])); - $maxY = max(array($rect[1],$rect[3],$rect[5],$rect[7])); - $text_offset_x = abs($minX) - 1; - $text_offset_y = abs($minY) - 1; - $text_width = $maxX - $minX + (2 * $this->image_text_padding_x); - $text_height = $maxY - $minY + (2 * $this->image_text_padding_y); - } - } - - // position the text block - $text_x = 0; - $text_y = 0; - if (is_numeric($this->image_text_x)) { - if ($this->image_text_x < 0) { - $text_x = $this->image_dst_x - $text_width + $this->image_text_x; - } else { - $text_x = $this->image_text_x; - } - } else { - if (strpos($this->image_text_position, 'r') !== false) { - $text_x = $this->image_dst_x - $text_width; - } else if (strpos($this->image_text_position, 'l') !== false) { - $text_x = 0; - } else { - $text_x = ($this->image_dst_x - $text_width) / 2; - } - } - if (is_numeric($this->image_text_y)) { - if ($this->image_text_y < 0) { - $text_y = $this->image_dst_y - $text_height + $this->image_text_y; - } else { - $text_y = $this->image_text_y; - } - } else { - if (strpos($this->image_text_position, 'b') !== false) { - $text_y = $this->image_dst_y - $text_height; - } else if (strpos($this->image_text_position, 't') !== false) { - $text_y = 0; - } else { - $text_y = ($this->image_dst_y - $text_height) / 2; - } - } - - // add a background, maybe transparent - if (!empty($this->image_text_background)) { - list($red, $green, $blue) = $this->getcolors($this->image_text_background); - if ($gd_version >= 2 && (is_numeric($this->image_text_background_opacity)) && $this->image_text_background_opacity >= 0 && $this->image_text_background_opacity <= 100) { - $filter = imagecreatetruecolor($text_width, $text_height); - $background_color = imagecolorallocate($filter, $red, $green, $blue); - imagefilledrectangle($filter, 0, 0, $text_width, $text_height, $background_color); - $this->imagecopymergealpha($image_dst, $filter, $text_x, $text_y, 0, 0, $text_width, $text_height, $this->image_text_background_opacity); - imagedestroy($filter); - } else { - $background_color = imagecolorallocate($image_dst ,$red, $green, $blue); - imagefilledrectangle($image_dst, $text_x, $text_y, $text_x + $text_width, $text_y + $text_height, $background_color); - } - } - - $text_x += $this->image_text_padding_x; - $text_y += $this->image_text_padding_y; - $t_width = $text_width - (2 * $this->image_text_padding_x); - $t_height = $text_height - (2 * $this->image_text_padding_y); - list($red, $green, $blue) = $this->getcolors($this->image_text_color); - - // add the text, maybe transparent - if ($gd_version >= 2 && (is_numeric($this->image_text_opacity)) && $this->image_text_opacity >= 0 && $this->image_text_opacity <= 100) { - if ($t_width < 0) $t_width = 0; - if ($t_height < 0) $t_height = 0; - $filter = $this->imagecreatenew($t_width, $t_height, false, true); - $text_color = imagecolorallocate($filter ,$red, $green, $blue); - - if ($font_type == 'gd') { - foreach ($text as $k => $v) { - if ($this->image_text_direction == 'v') { - imagestringup($filter, - $this->image_text_font, - $k * ($line_width + ($k > 0 && $k < (sizeof($text)) ? $this->image_text_line_spacing : 0)), - $text_height - (2 * $this->image_text_padding_y) - ($this->image_text_alignment == 'l' ? 0 : (($t_height - strlen($v) * $char_width) / ($this->image_text_alignment == 'r' ? 1 : 2))) , - $v, - $text_color); - } else { - imagestring($filter, - $this->image_text_font, - ($this->image_text_alignment == 'l' ? 0 : (($t_width - strlen($v) * $char_width) / ($this->image_text_alignment == 'r' ? 1 : 2))), - $k * ($line_height + ($k > 0 && $k < (sizeof($text)) ? $this->image_text_line_spacing : 0)), - $v, - $text_color); - } - } - } else if ($font_type == 'tt') { - imagettftext($filter, - $this->image_text_size, - $this->image_text_angle, - $text_offset_x, - $text_offset_y, - $text_color, - $this->image_text_font, - $text); - } - $this->imagecopymergealpha($image_dst, $filter, $text_x, $text_y, 0, 0, $t_width, $t_height, $this->image_text_opacity); - imagedestroy($filter); - - } else { - $text_color = imagecolorallocate($image_dst ,$red, $green, $blue); - if ($font_type == 'gd') { - foreach ($text as $k => $v) { - if ($this->image_text_direction == 'v') { - imagestringup($image_dst, - $this->image_text_font, - $text_x + $k * ($line_width + ($k > 0 && $k < (sizeof($text)) ? $this->image_text_line_spacing : 0)), - $text_y + $text_height - (2 * $this->image_text_padding_y) - ($this->image_text_alignment == 'l' ? 0 : (($t_height - strlen($v) * $char_width) / ($this->image_text_alignment == 'r' ? 1 : 2))), - $v, - $text_color); - } else { - imagestring($image_dst, - $this->image_text_font, - $text_x + ($this->image_text_alignment == 'l' ? 0 : (($t_width - strlen($v) * $char_width) / ($this->image_text_alignment == 'r' ? 1 : 2))), - $text_y + $k * ($line_height + ($k > 0 && $k < (sizeof($text)) ? $this->image_text_line_spacing : 0)), - $v, - $text_color); - } - } - } else if ($font_type == 'tt') { - imagettftext($image_dst, - $this->image_text_size, - $this->image_text_angle, - $text_offset_x + ($this->image_dst_x / 2) - ($text_width / 2) + $this->image_text_padding_x, - $text_offset_y + ($this->image_dst_y / 2) - ($text_height / 2) + $this->image_text_padding_y, - $text_color, - $this->image_text_font, - $text); - } - } - } - - // add a reflection - if ($this->image_reflection_height) { - $this->log .= '- add reflection : ' . $this->image_reflection_height . '
'; - // we decode image_reflection_height, which can be a integer, a string in pixels or percentage - $image_reflection_height = $this->image_reflection_height; - if (strpos($image_reflection_height, '%')>0) $image_reflection_height = $this->image_dst_y * (str_replace('%','',$image_reflection_height / 100)); - if (strpos($image_reflection_height, 'px')>0) $image_reflection_height = str_replace('px','',$image_reflection_height); - $image_reflection_height = (int) $image_reflection_height; - if ($image_reflection_height > $this->image_dst_y) $image_reflection_height = $this->image_dst_y; - if (empty($this->image_reflection_opacity)) $this->image_reflection_opacity = 60; - // create the new destination image - $tmp = $this->imagecreatenew($this->image_dst_x, $this->image_dst_y + $image_reflection_height + $this->image_reflection_space, true); - $transparency = $this->image_reflection_opacity; - - // copy the original image - imagecopy($tmp, $image_dst, 0, 0, 0, 0, $this->image_dst_x, $this->image_dst_y + ($this->image_reflection_space < 0 ? $this->image_reflection_space : 0)); - - // we have to make sure the extra bit is the right color, or transparent - if ($image_reflection_height + $this->image_reflection_space > 0) { - // use the background color if present - if (!empty($this->image_background_color)) { - list($red, $green, $blue) = $this->getcolors($this->image_background_color); - $fill = imagecolorallocate($tmp, $red, $green, $blue); - } else { - $fill = imagecolorallocatealpha($tmp, 0, 0, 0, 127); - } - // fill in from the edge of the extra bit - imagefill($tmp, round($this->image_dst_x / 2), $this->image_dst_y + $image_reflection_height + $this->image_reflection_space - 1, $fill); - } - - // copy the reflection - for ($y = 0; $y < $image_reflection_height; $y++) { - for ($x = 0; $x < $this->image_dst_x; $x++) { - $pixel_b = imagecolorsforindex($tmp, imagecolorat($tmp, $x, $y + $this->image_dst_y + $this->image_reflection_space)); - $pixel_o = imagecolorsforindex($image_dst, imagecolorat($image_dst, $x, $this->image_dst_y - $y - 1 + ($this->image_reflection_space < 0 ? $this->image_reflection_space : 0))); - $alpha_o = 1 - ($pixel_o['alpha'] / 127); - $alpha_b = 1 - ($pixel_b['alpha'] / 127); - $opacity = $alpha_o * $transparency / 100; - if ($opacity > 0) { - $red = round((($pixel_o['red'] * $opacity) + ($pixel_b['red'] ) * $alpha_b) / ($alpha_b + $opacity)); - $green = round((($pixel_o['green'] * $opacity) + ($pixel_b['green']) * $alpha_b) / ($alpha_b + $opacity)); - $blue = round((($pixel_o['blue'] * $opacity) + ($pixel_b['blue'] ) * $alpha_b) / ($alpha_b + $opacity)); - $alpha = ($opacity + $alpha_b); - if ($alpha > 1) $alpha = 1; - $alpha = round((1 - $alpha) * 127); - $color = imagecolorallocatealpha($tmp, $red, $green, $blue, $alpha); - imagesetpixel($tmp, $x, $y + $this->image_dst_y + $this->image_reflection_space, $color); - } - } - if ($transparency > 0) $transparency = $transparency - ($this->image_reflection_opacity / $image_reflection_height); - } - - // copy the resulting image into the destination image - $this->image_dst_y = $this->image_dst_y + $image_reflection_height + $this->image_reflection_space; - $image_dst = $this->imagetransfer($tmp, $image_dst); - } - - // change opacity - if ($gd_version >= 2 && is_numeric($this->image_opacity) && $this->image_opacity < 100) { - $this->log .= '- change opacity
'; - // create the new destination image - $tmp = $this->imagecreatenew($this->image_dst_x, $this->image_dst_y, true); - for($y=0; $y < $this->image_dst_y; $y++) { - for($x=0; $x < $this->image_dst_x; $x++) { - $pixel = imagecolorsforindex($image_dst, imagecolorat($image_dst, $x, $y)); - $alpha = $pixel['alpha'] + round((127 - $pixel['alpha']) * (100 - $this->image_opacity) / 100); - if ($alpha > 127) $alpha = 127; - if ($alpha > 0) { - $color = imagecolorallocatealpha($tmp, $pixel['red'] , $pixel['green'], $pixel['blue'], $alpha); - imagesetpixel($tmp, $x, $y, $color); - } - } - } - // copy the resulting image into the destination image - $image_dst = $this->imagetransfer($tmp, $image_dst); - } - - // reduce the JPEG image to a set desired size - if (is_numeric($this->jpeg_size) && $this->jpeg_size > 0 && ($this->image_convert == 'jpeg' || $this->image_convert == 'jpg')) { - // inspired by: JPEGReducer class version 1, 25 November 2004, Author: Huda M ElMatsani, justhuda at netscape dot net - $this->log .= '- JPEG desired file size : ' . $this->jpeg_size . '
'; - // calculate size of each image. 75%, 50%, and 25% quality - ob_start(); imagejpeg($image_dst,null,75); $buffer = ob_get_contents(); ob_end_clean(); - $size75 = strlen($buffer); - ob_start(); imagejpeg($image_dst,null,50); $buffer = ob_get_contents(); ob_end_clean(); - $size50 = strlen($buffer); - ob_start(); imagejpeg($image_dst,null,25); $buffer = ob_get_contents(); ob_end_clean(); - $size25 = strlen($buffer); - - // make sure we won't divide by 0 - if ($size50 == $size25) $size50++; - if ($size75 == $size50 || $size75 == $size25) $size75++; - - // calculate gradient of size reduction by quality - $mgrad1 = 25 / ($size50-$size25); - $mgrad2 = 25 / ($size75-$size50); - $mgrad3 = 50 / ($size75-$size25); - $mgrad = ($mgrad1 + $mgrad2 + $mgrad3) / 3; - // result of approx. quality factor for expected size - $q_factor = round($mgrad * ($this->jpeg_size - $size50) + 50); - - if ($q_factor<1) { - $this->jpeg_quality=1; - } elseif ($q_factor>100) { - $this->jpeg_quality=100; - } else { - $this->jpeg_quality=$q_factor; - } - $this->log .= '    JPEG quality factor set to ' . $this->jpeg_quality . '
'; - } - - // converts image from true color, and fix transparency if needed - $this->log .= '- converting...
'; - $this->image_dst_type = $this->image_convert; - switch($this->image_convert) { - case 'gif': - // if the image is true color, we convert it to a palette - if (imageistruecolor($image_dst)) { - $this->log .= '    true color to palette
'; - // creates a black and white mask - $mask = array(array()); - for ($x = 0; $x < $this->image_dst_x; $x++) { - for ($y = 0; $y < $this->image_dst_y; $y++) { - $pixel = imagecolorsforindex($image_dst, imagecolorat($image_dst, $x, $y)); - $mask[$x][$y] = $pixel['alpha']; - } - } - list($red, $green, $blue) = $this->getcolors($this->image_default_color); - // first, we merge the image with the background color, so we know which colors we will have - for ($x = 0; $x < $this->image_dst_x; $x++) { - for ($y = 0; $y < $this->image_dst_y; $y++) { - if ($mask[$x][$y] > 0){ - // we have some transparency. we combine the color with the default color - $pixel = imagecolorsforindex($image_dst, imagecolorat($image_dst, $x, $y)); - $alpha = ($mask[$x][$y] / 127); - $pixel['red'] = round(($pixel['red'] * (1 -$alpha) + $red * ($alpha))); - $pixel['green'] = round(($pixel['green'] * (1 -$alpha) + $green * ($alpha))); - $pixel['blue'] = round(($pixel['blue'] * (1 -$alpha) + $blue * ($alpha))); - $color = imagecolorallocate($image_dst, $pixel['red'], $pixel['green'], $pixel['blue']); - imagesetpixel($image_dst, $x, $y, $color); - } - } - } - // transforms the true color image into palette, with its merged default color - if (empty($this->image_background_color)) { - imagetruecolortopalette($image_dst, true, 255); - $transparency = imagecolorallocate($image_dst, 254, 1, 253); - imagecolortransparent($image_dst, $transparency); - // make the transparent areas transparent - for ($x = 0; $x < $this->image_dst_x; $x++) { - for ($y = 0; $y < $this->image_dst_y; $y++) { - // we test wether we have enough opacity to justify keeping the color - if ($mask[$x][$y] > 120) imagesetpixel($image_dst, $x, $y, $transparency); - } - } - } - unset($mask); - } - break; - case 'jpg': - case 'bmp': - // if the image doesn't support any transparency, then we merge it with the default color - $this->log .= '    fills in transparency with default color
'; - list($red, $green, $blue) = $this->getcolors($this->image_default_color); - $transparency = imagecolorallocate($image_dst, $red, $green, $blue); - // make the transaparent areas transparent - for ($x = 0; $x < $this->image_dst_x; $x++) { - for ($y = 0; $y < $this->image_dst_y; $y++) { - // we test wether we have some transparency, in which case we will merge the colors - if (imageistruecolor($image_dst)) { - $rgba = imagecolorat($image_dst, $x, $y); - $pixel = array('red' => ($rgba >> 16) & 0xFF, - 'green' => ($rgba >> 8) & 0xFF, - 'blue' => $rgba & 0xFF, - 'alpha' => ($rgba & 0x7F000000) >> 24); - } else { - $pixel = imagecolorsforindex($image_dst, imagecolorat($image_dst, $x, $y)); - } - if ($pixel['alpha'] == 127) { - // we have full transparency. we make the pixel transparent - imagesetpixel($image_dst, $x, $y, $transparency); - } else if ($pixel['alpha'] > 0) { - // we have some transparency. we combine the color with the default color - $alpha = ($pixel['alpha'] / 127); - $pixel['red'] = round(($pixel['red'] * (1 -$alpha) + $red * ($alpha))); - $pixel['green'] = round(($pixel['green'] * (1 -$alpha) + $green * ($alpha))); - $pixel['blue'] = round(($pixel['blue'] * (1 -$alpha) + $blue * ($alpha))); - $color = imagecolorclosest($image_dst, $pixel['red'], $pixel['green'], $pixel['blue']); - imagesetpixel($image_dst, $x, $y, $color); - } - } - } - - break; - default: - break; - } - - // interlace options - if($this->image_interlace) imageinterlace($image_dst, true); - - // outputs image - $this->log .= '- saving image...
'; - switch($this->image_convert) { - case 'jpeg': - case 'jpg': - if (!$return_mode) { - $result = @imagejpeg($image_dst, $this->file_dst_pathname, $this->jpeg_quality); - } else { - ob_start(); - $result = @imagejpeg($image_dst, null, $this->jpeg_quality); - $return_content = ob_get_contents(); - ob_end_clean(); - } - if (!$result) { - $this->processed = false; - $this->error = $this->translate('file_create', array('JPEG')); - } else { - $this->log .= '    JPEG image created
'; - } - break; - case 'png': - imagealphablending( $image_dst, false ); - imagesavealpha( $image_dst, true ); - if (!$return_mode) { - if (is_numeric($this->png_compression) && version_compare(PHP_VERSION, '5.1.2') >= 0) { - $result = @imagepng($image_dst, $this->file_dst_pathname, $this->png_compression); - } else { - $result = @imagepng($image_dst, $this->file_dst_pathname); - } - } else { - ob_start(); - if (is_numeric($this->png_compression) && version_compare(PHP_VERSION, '5.1.2') >= 0) { - $result = @imagepng($image_dst, null, $this->png_compression); - } else { - $result = @imagepng($image_dst); - } - $return_content = ob_get_contents(); - ob_end_clean(); - } - if (!$result) { - $this->processed = false; - $this->error = $this->translate('file_create', array('PNG')); - } else { - $this->log .= '    PNG image created
'; - } - break; - case 'gif': - if (!$return_mode) { - $result = @imagegif($image_dst, $this->file_dst_pathname); - } else { - ob_start(); - $result = @imagegif($image_dst); - $return_content = ob_get_contents(); - ob_end_clean(); - } - if (!$result) { - $this->processed = false; - $this->error = $this->translate('file_create', array('GIF')); - } else { - $this->log .= '    GIF image created
'; - } - break; - case 'bmp': - if (!$return_mode) { - $result = $this->imagebmp($image_dst, $this->file_dst_pathname); - } else { - ob_start(); - $result = $this->imagebmp($image_dst); - $return_content = ob_get_contents(); - ob_end_clean(); - } - if (!$result) { - $this->processed = false; - $this->error = $this->translate('file_create', array('BMP')); - } else { - $this->log .= '    BMP image created
'; - } - break; - - default: - $this->processed = false; - $this->error = $this->translate('no_conversion_type'); - } - if ($this->processed) { - if (is_resource($image_src)) imagedestroy($image_src); - if (is_resource($image_dst)) imagedestroy($image_dst); - $this->log .= '    image objects destroyed
'; - } - } - - } else { - $this->log .= '- no image processing wanted
'; - - if (!$return_mode) { - // copy the file to its final destination. we don't use move_uploaded_file here - // if we happen to have open_basedir restrictions, it is a temp file that we copy, not the original uploaded file - if (!copy($this->file_src_pathname, $this->file_dst_pathname)) { - $this->processed = false; - $this->error = $this->translate('copy_failed'); - } - } else { - // returns the file, so that its content can be received by the caller - $return_content = @file_get_contents($this->file_src_pathname); - if ($return_content === FALSE) { - $this->processed = false; - $this->error = $this->translate('reading_failed'); - } - } - } - } - - if ($this->processed) { - $this->log .= '- process OK
'; - } else { - $this->log .= '- error: ' . $this->error . '
'; - } - - // we reinit all the vars - $this->init(); - - // we may return the image content - if ($return_mode) return $return_content; - - } - - /** - * Deletes the uploaded file from its temporary location - * - * When PHP uploads a file, it stores it in a temporary location. - * When you {@link process} the file, you actually copy the resulting file to the given location, it doesn't alter the original file. - * Once you have processed the file as many times as you wanted, you can delete the uploaded file. - * If there is open_basedir restrictions, the uploaded file is in fact a temporary file - * - * You might want not to use this function if you work on local files, as it will delete the source file - * - * @access public - */ - function clean() { - $this->log .= 'cleanup
'; - $this->log .= '- delete temp file ' . $this->file_src_pathname . '
'; - @unlink($this->file_src_pathname); - } - - - /** - * Opens a BMP image - * - * This function has been written by DHKold, and is used with permission of the author - * - * @access public - */ - function imagecreatefrombmp($filename) { - if (! $f1 = fopen($filename,"rb")) return false; - - $file = unpack("vfile_type/Vfile_size/Vreserved/Vbitmap_offset", fread($f1,14)); - if ($file['file_type'] != 19778) return false; - - $bmp = unpack('Vheader_size/Vwidth/Vheight/vplanes/vbits_per_pixel'. - '/Vcompression/Vsize_bitmap/Vhoriz_resolution'. - '/Vvert_resolution/Vcolors_used/Vcolors_important', fread($f1,40)); - $bmp['colors'] = pow(2,$bmp['bits_per_pixel']); - if ($bmp['size_bitmap'] == 0) $bmp['size_bitmap'] = $file['file_size'] - $file['bitmap_offset']; - $bmp['bytes_per_pixel'] = $bmp['bits_per_pixel']/8; - $bmp['bytes_per_pixel2'] = ceil($bmp['bytes_per_pixel']); - $bmp['decal'] = ($bmp['width']*$bmp['bytes_per_pixel']/4); - $bmp['decal'] -= floor($bmp['width']*$bmp['bytes_per_pixel']/4); - $bmp['decal'] = 4-(4*$bmp['decal']); - if ($bmp['decal'] == 4) $bmp['decal'] = 0; - - $palette = array(); - if ($bmp['colors'] < 16777216) { - $palette = unpack('V'.$bmp['colors'], fread($f1,$bmp['colors']*4)); - } - - $im = fread($f1,$bmp['size_bitmap']); - $vide = chr(0); - - $res = imagecreatetruecolor($bmp['width'],$bmp['height']); - $P = 0; - $Y = $bmp['height']-1; - while ($Y >= 0) { - $X=0; - while ($X < $bmp['width']) { - if ($bmp['bits_per_pixel'] == 24) - $color = unpack("V",substr($im,$P,3).$vide); - elseif ($bmp['bits_per_pixel'] == 16) { - $color = unpack("n",substr($im,$P,2)); - $color[1] = $palette[$color[1]+1]; - } elseif ($bmp['bits_per_pixel'] == 8) { - $color = unpack("n",$vide.substr($im,$P,1)); - $color[1] = $palette[$color[1]+1]; - } elseif ($bmp['bits_per_pixel'] == 4) { - $color = unpack("n",$vide.substr($im,floor($P),1)); - if (($P*2)%2 == 0) $color[1] = ($color[1] >> 4) ; else $color[1] = ($color[1] & 0x0F); - $color[1] = $palette[$color[1]+1]; - } elseif ($bmp['bits_per_pixel'] == 1) { - $color = unpack("n",$vide.substr($im,floor($P),1)); - if (($P*8)%8 == 0) $color[1] = $color[1] >>7; - elseif (($P*8)%8 == 1) $color[1] = ($color[1] & 0x40)>>6; - elseif (($P*8)%8 == 2) $color[1] = ($color[1] & 0x20)>>5; - elseif (($P*8)%8 == 3) $color[1] = ($color[1] & 0x10)>>4; - elseif (($P*8)%8 == 4) $color[1] = ($color[1] & 0x8)>>3; - elseif (($P*8)%8 == 5) $color[1] = ($color[1] & 0x4)>>2; - elseif (($P*8)%8 == 6) $color[1] = ($color[1] & 0x2)>>1; - elseif (($P*8)%8 == 7) $color[1] = ($color[1] & 0x1); - $color[1] = $palette[$color[1]+1]; - } else - return FALSE; - imagesetpixel($res,$X,$Y,$color[1]); - $X++; - $P += $bmp['bytes_per_pixel']; - } - $Y--; - $P+=$bmp['decal']; - } - fclose($f1); - return $res; - } - - /** - * Saves a BMP image - * - * This function has been published on the PHP website, and can be used freely - * - * @access public - */ - function imagebmp(&$im, $filename = "") { - - if (!$im) return false; - $w = imagesx($im); - $h = imagesy($im); - $result = ''; - - // if the image is not true color, we convert it first - if (!imageistruecolor($im)) { - $tmp = imagecreatetruecolor($w, $h); - imagecopy($tmp, $im, 0, 0, 0, 0, $w, $h); - imagedestroy($im); - $im = & $tmp; - } - - $biBPLine = $w * 3; - $biStride = ($biBPLine + 3) & ~3; - $biSizeImage = $biStride * $h; - $bfOffBits = 54; - $bfSize = $bfOffBits + $biSizeImage; - - $result .= substr('BM', 0, 2); - $result .= pack ('VvvV', $bfSize, 0, 0, $bfOffBits); - $result .= pack ('VVVvvVVVVVV', 40, $w, $h, 1, 24, 0, $biSizeImage, 0, 0, 0, 0); - - $numpad = $biStride - $biBPLine; - for ($y = $h - 1; $y >= 0; --$y) { - for ($x = 0; $x < $w; ++$x) { - $col = imagecolorat ($im, $x, $y); - $result .= substr(pack ('V', $col), 0, 3); - } - for ($i = 0; $i < $numpad; ++$i) - $result .= pack ('C', 0); - } - - if($filename==""){ - echo $result; - } else { - $file = fopen($filename, "wb"); - fwrite($file, $result); - fclose($file); - } - return true; - } -} - -?> diff --git a/interfaceServices/dbConfig.php b/interfaceServices/dbConfig.php deleted file mode 100644 index 31b3302..0000000 --- a/interfaceServices/dbConfig.php +++ /dev/null @@ -1,17 +0,0 @@ -setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); -} -catch(PDOException $e) { - echo $e->getMessage(); -} - -?> \ No newline at end of file diff --git a/interfaceServices/fake_loginInterface.php b/interfaceServices/fake_loginInterface.php deleted file mode 100644 index eab508e..0000000 --- a/interfaceServices/fake_loginInterface.php +++ /dev/null @@ -1,36 +0,0 @@ -setIssuer("http://pugludos.com") - ->setIssuedAt(time()) - ->set("userName", "ckoch") - ->sign($signer, "testing") - ->getToken(); - - $userData = array(); - $userDatap["id"] = "12345"; - $userDatap["username"] = "admin"; - $userDatap["firstName"] = "TestFirst"; - $userDatap["lastName"] = "TestLast"; - $userDatap["token"] = (string)$token; - - echo( json_encode( $userDatap ) ); -}else{ - http_response_code(400); -} - -?> diff --git a/interfaceServices/gamesInterface.php b/interfaceServices/gamesInterface.php deleted file mode 100644 index ca4f8b8..0000000 --- a/interfaceServices/gamesInterface.php +++ /dev/null @@ -1,2756 +0,0 @@ -queries = array( - 'list_tables'=>'SELECT - "TABLE_NAME","TABLE_COMMENT" - FROM - "INFORMATION_SCHEMA"."TABLES" - WHERE - "TABLE_SCHEMA" = ?', - 'reflect_table'=>'SELECT - "TABLE_NAME" - FROM - "INFORMATION_SCHEMA"."TABLES" - WHERE - "TABLE_NAME" COLLATE \'utf8_bin\' = ? AND - "TABLE_SCHEMA" = ?', - 'reflect_pk'=>'SELECT - "COLUMN_NAME" - FROM - "INFORMATION_SCHEMA"."COLUMNS" - WHERE - "COLUMN_KEY" = \'PRI\' AND - "TABLE_NAME" = ? AND - "TABLE_SCHEMA" = ?', - 'reflect_belongs_to'=>'SELECT - "TABLE_NAME","COLUMN_NAME", - "REFERENCED_TABLE_NAME","REFERENCED_COLUMN_NAME" - FROM - "INFORMATION_SCHEMA"."KEY_COLUMN_USAGE" - WHERE - "TABLE_NAME" COLLATE \'utf8_bin\' = ? AND - "REFERENCED_TABLE_NAME" COLLATE \'utf8_bin\' IN ? AND - "TABLE_SCHEMA" = ? AND - "REFERENCED_TABLE_SCHEMA" = ?', - 'reflect_has_many'=>'SELECT - "TABLE_NAME","COLUMN_NAME", - "REFERENCED_TABLE_NAME","REFERENCED_COLUMN_NAME" - FROM - "INFORMATION_SCHEMA"."KEY_COLUMN_USAGE" - WHERE - "TABLE_NAME" COLLATE \'utf8_bin\' IN ? AND - "REFERENCED_TABLE_NAME" COLLATE \'utf8_bin\' = ? AND - "TABLE_SCHEMA" = ? AND - "REFERENCED_TABLE_SCHEMA" = ?', - 'reflect_habtm'=>'SELECT - k1."TABLE_NAME", k1."COLUMN_NAME", - k1."REFERENCED_TABLE_NAME", k1."REFERENCED_COLUMN_NAME", - k2."TABLE_NAME", k2."COLUMN_NAME", - k2."REFERENCED_TABLE_NAME", k2."REFERENCED_COLUMN_NAME" - FROM - "INFORMATION_SCHEMA"."KEY_COLUMN_USAGE" k1, - "INFORMATION_SCHEMA"."KEY_COLUMN_USAGE" k2 - WHERE - k1."TABLE_SCHEMA" = ? AND - k2."TABLE_SCHEMA" = ? AND - k1."REFERENCED_TABLE_SCHEMA" = ? AND - k2."REFERENCED_TABLE_SCHEMA" = ? AND - k1."TABLE_NAME" COLLATE \'utf8_bin\' = k2."TABLE_NAME" COLLATE \'utf8_bin\' AND - k1."REFERENCED_TABLE_NAME" COLLATE \'utf8_bin\' = ? AND - k2."REFERENCED_TABLE_NAME" COLLATE \'utf8_bin\' IN ?', - 'reflect_columns'=> 'SELECT - "COLUMN_NAME", "COLUMN_DEFAULT", "IS_NULLABLE", "DATA_TYPE", "CHARACTER_MAXIMUM_LENGTH" - FROM - "INFORMATION_SCHEMA"."COLUMNS" - WHERE - "TABLE_NAME" = ? AND - "TABLE_SCHEMA" = ? - ORDER BY - "ORDINAL_POSITION"' - ); - } - - public function getSql($name) { - return isset($this->queries[$name])?$this->queries[$name]:false; - } - - public function connect($hostname,$username,$password,$database,$port,$socket,$charset) { - $db = mysqli_init(); - if (defined('MYSQLI_OPT_INT_AND_FLOAT_NATIVE')) { - mysqli_options($db,MYSQLI_OPT_INT_AND_FLOAT_NATIVE,true); - } - $success = mysqli_real_connect($db,$hostname,$username,$password,$database,$port,$socket,MYSQLI_CLIENT_FOUND_ROWS); - if (!$success) { - throw new \Exception('Connect failed. '.mysqli_connect_error()); - } - if (!mysqli_set_charset($db,$charset)) { - throw new \Exception('Error setting charset. '.mysqli_error($db)); - } - if (!mysqli_query($db,'SET SESSION sql_mode = \'ANSI_QUOTES\';')) { - throw new \Exception('Error setting ANSI quotes. '.mysqli_error($db)); - } - $this->db = $db; - } - - public function query($sql,$params=array()) { - $db = $this->db; - $sql = preg_replace_callback('/\!|\?/', function ($matches) use (&$db,&$params) { - $param = array_shift($params); - if ($matches[0]=='!') { - $key = preg_replace('/[^a-zA-Z0-9\-_=<> ]/','',is_object($param)?$param->key:$param); - if (is_object($param) && $param->type=='hex') { - return "HEX(\"$key\") as \"$key\""; - } - if (is_object($param) && $param->type=='wkt') { - return "ST_AsText(\"$key\") as \"$key\""; - } - return '"'.$key.'"'; - } else { - if (is_array($param)) return '('.implode(',',array_map(function($v) use (&$db) { - return "'".mysqli_real_escape_string($db,$v)."'"; - },$param)).')'; - if (is_object($param) && $param->type=='hex') { - return "x'".$param->value."'"; - } - if (is_object($param) && $param->type=='wkt') { - return "ST_GeomFromText('".mysqli_real_escape_string($db,$param->value)."')"; - } - if ($param===null) return 'NULL'; - return "'".mysqli_real_escape_string($db,$param)."'"; - } - }, $sql); - //if (!strpos($sql,'INFORMATION_SCHEMA')) echo "\n$sql\n"; - //if (!strpos($sql,'INFORMATION_SCHEMA')) file_put_contents('log.txt',"\n$sql\n",FILE_APPEND); - return mysqli_query($db,$sql); - } - - public function fetchAssoc($result) { - return mysqli_fetch_assoc($result); - } - - public function fetchRow($result) { - return mysqli_fetch_row($result); - } - - public function insertId($result) { - return mysqli_insert_id($this->db); - } - - public function affectedRows($result) { - return mysqli_affected_rows($this->db); - } - - public function close($result) { - return mysqli_free_result($result); - } - - public function fetchFields($table) { - $result = $this->query('SELECT * FROM ! WHERE 1=2;',array($table)); - return mysqli_fetch_fields($result); - } - - public function addLimitToSql($sql,$limit,$offset) { - return "$sql LIMIT $limit OFFSET $offset"; - } - - public function likeEscape($string) { - return addcslashes($string,'%_'); - } - - public function convertFilter($field, $comparator, $value) { - return false; - } - - public function isNumericType($field) { - return in_array($field->type,array(1,2,3,4,5,6,8,9)); - } - - public function isBinaryType($field) { - //echo "$field->name: $field->type ($field->flags)\n"; - return (($field->flags & 128) && (($field->type>=249 && $field->type<=252) || ($field->type>=253 && $field->type<=254 && $field->charsetnr==63))); - } - - public function isGeometryType($field) { - return ($field->type==255); - } - - public function isJsonType($field) { - return ($field->type==245); - } - - public function getDefaultCharset() { - return 'utf8'; - } - - public function beginTransaction() { - mysqli_query($this->db,'BEGIN'); - //return mysqli_begin_transaction($this->db); - } - - public function commitTransaction() { - mysqli_query($this->db,'COMMIT'); - //return mysqli_commit($this->db); - } - - public function rollbackTransaction() { - mysqli_query($this->db,'ROLLBACK'); - //return mysqli_rollback($this->db); - } - - public function jsonEncode($object) { - return json_encode($object); - } - - public function jsonDecode($string) { - return json_decode($string); - } -} - -class PostgreSQL implements DatabaseInterface { - - protected $db; - protected $queries; - - public function __construct() { - $this->queries = array( - 'list_tables'=>'select - "table_name",\'\' as "table_comment" - from - "information_schema"."tables" - where - "table_schema" = \'public\' and - "table_catalog" = ?', - 'reflect_table'=>'select - "table_name" - from - "information_schema"."tables" - where - "table_name" = ? and - "table_schema" = \'public\' and - "table_catalog" = ?', - 'reflect_pk'=>'select - "column_name" - from - "information_schema"."table_constraints" tc, - "information_schema"."key_column_usage" ku - where - tc."constraint_type" = \'PRIMARY KEY\' and - tc."constraint_name" = ku."constraint_name" and - ku."table_name" = ? and - ku."table_schema" = \'public\' and - ku."table_catalog" = ?', - 'reflect_belongs_to'=>'select - cu1."table_name",cu1."column_name", - cu2."table_name",cu2."column_name" - from - "information_schema".referential_constraints rc, - "information_schema".key_column_usage cu1, - "information_schema".key_column_usage cu2 - where - cu1."constraint_name" = rc."constraint_name" and - cu2."constraint_name" = rc."unique_constraint_name" and - cu1."table_name" = ? and - cu2."table_name" in ? and - cu1."table_schema" = \'public\' and - cu2."table_schema" = \'public\' and - cu1."table_catalog" = ? and - cu2."table_catalog" = ?', - 'reflect_has_many'=>'select - cu1."table_name",cu1."column_name", - cu2."table_name",cu2."column_name" - from - "information_schema".referential_constraints rc, - "information_schema".key_column_usage cu1, - "information_schema".key_column_usage cu2 - where - cu1."constraint_name" = rc."constraint_name" and - cu2."constraint_name" = rc."unique_constraint_name" and - cu1."table_name" in ? and - cu2."table_name" = ? and - cu1."table_schema" = \'public\' and - cu2."table_schema" = \'public\' and - cu1."table_catalog" = ? and - cu2."table_catalog" = ?', - 'reflect_habtm'=>'select - cua1."table_name",cua1."column_name", - cua2."table_name",cua2."column_name", - cub1."table_name",cub1."column_name", - cub2."table_name",cub2."column_name" - from - "information_schema".referential_constraints rca, - "information_schema".referential_constraints rcb, - "information_schema".key_column_usage cua1, - "information_schema".key_column_usage cua2, - "information_schema".key_column_usage cub1, - "information_schema".key_column_usage cub2 - where - cua1."constraint_name" = rca."constraint_name" and - cua2."constraint_name" = rca."unique_constraint_name" and - cub1."constraint_name" = rcb."constraint_name" and - cub2."constraint_name" = rcb."unique_constraint_name" and - cua1."table_catalog" = ? and - cub1."table_catalog" = ? and - cua2."table_catalog" = ? and - cub2."table_catalog" = ? and - cua1."table_schema" = \'public\' and - cub1."table_schema" = \'public\' and - cua2."table_schema" = \'public\' and - cub2."table_schema" = \'public\' and - cua1."table_name" = cub1."table_name" and - cua2."table_name" = ? and - cub2."table_name" in ?', - 'reflect_columns'=> 'select - "column_name", "column_default", "is_nullable", "data_type", "character_maximum_length" - from - "information_schema"."columns" - where - "table_name" = ? and - "table_schema" = \'public\' and - "table_catalog" = ? - order by - "ordinal_position"' - ); - } - - public function getSql($name) { - return isset($this->queries[$name])?$this->queries[$name]:false; - } - - public function connect($hostname,$username,$password,$database,$port,$socket,$charset) { - $e = function ($v) { return str_replace(array('\'','\\'),array('\\\'','\\\\'),$v); }; - $conn_string = ''; - if ($hostname || $socket) { - if ($socket) $hostname = $e($socket); - else $hostname = $e($hostname); - $conn_string.= " host='$hostname'"; - } - if ($port) { - $port = ($port+0); - $conn_string.= " port='$port'"; - } - if ($database) { - $database = $e($database); - $conn_string.= " dbname='$database'"; - } - if ($username) { - $username = $e($username); - $conn_string.= " user='$username'"; - } - if ($password) { - $password = $e($password); - $conn_string.= " password='$password'"; - } - if ($charset) { - $charset = $e($charset); - $conn_string.= " options='--client_encoding=$charset'"; - } - $db = pg_connect($conn_string); - $this->db = $db; - } - - public function query($sql,$params=array()) { - $db = $this->db; - $sql = preg_replace_callback('/\!|\?/', function ($matches) use (&$db,&$params) { - $param = array_shift($params); - if ($matches[0]=='!') { - $key = preg_replace('/[^a-zA-Z0-9\-_=<> ]/','',is_object($param)?$param->key:$param); - if (is_object($param) && $param->type=='hex') { - return "encode(\"$key\",'hex') as \"$key\""; - } - if (is_object($param) && $param->type=='wkt') { - return "ST_AsText(\"$key\") as \"$key\""; - } - return '"'.$key.'"'; - } else { - if (is_array($param)) return '('.implode(',',array_map(function($v) use (&$db) { - return "'".pg_escape_string($db,$v)."'"; - },$param)).')'; - if (is_object($param) && $param->type=='hex') { - return "'\x".$param->value."'"; - } - if (is_object($param) && $param->type=='wkt') { - return "ST_GeomFromText('".pg_escape_string($db,$param->value)."')"; - } - if ($param===null) return 'NULL'; - return "'".pg_escape_string($db,$param)."'"; - } - }, $sql); - if (strtoupper(substr($sql,0,6))=='INSERT') { - $sql .= ' RETURNING id;'; - } - //echo "\n$sql\n"; - return @pg_query($db,$sql); - } - - public function fetchAssoc($result) { - return pg_fetch_assoc($result); - } - - public function fetchRow($result) { - return pg_fetch_row($result); - } - - public function insertId($result) { - list($id) = pg_fetch_row($result); - return (int)$id; - } - - public function affectedRows($result) { - return pg_affected_rows($result); - } - - public function close($result) { - return pg_free_result($result); - } - - public function fetchFields($table) { - $result = $this->query('SELECT * FROM ! WHERE 1=2;',array($table)); - $keys = array(); - for($i=0;$itype, array('int2', 'int4', 'int8', 'float4', 'float8')); - } - - public function isBinaryType($field) { - return $field->type == 'bytea'; - } - - public function isGeometryType($field) { - return $field->type == 'geometry'; - } - - public function isJsonType($field) { - return in_array($field->type,array('json','jsonb')); - } - - public function getDefaultCharset() { - return 'UTF8'; - } - - public function beginTransaction() { - return $this->query('BEGIN'); - } - - public function commitTransaction() { - return $this->query('COMMIT'); - } - - public function rollbackTransaction() { - return $this->query('ROLLBACK'); - } - - public function jsonEncode($object) { - return json_encode($object); - } - - public function jsonDecode($string) { - return json_decode($string); - } -} - -class SQLServer implements DatabaseInterface { - - protected $db; - protected $queries; - - public function __construct() { - $this->queries = array( - 'list_tables'=>'SELECT - "TABLE_NAME",\'\' as "TABLE_COMMENT" - FROM - "INFORMATION_SCHEMA"."TABLES" - WHERE - "TABLE_CATALOG" = ?', - 'reflect_table'=>'SELECT - "TABLE_NAME" - FROM - "INFORMATION_SCHEMA"."TABLES" - WHERE - "TABLE_NAME" = ? AND - "TABLE_CATALOG" = ?', - 'reflect_pk'=>'SELECT - "COLUMN_NAME" - FROM - "INFORMATION_SCHEMA"."TABLE_CONSTRAINTS" tc, - "INFORMATION_SCHEMA"."KEY_COLUMN_USAGE" ku - WHERE - tc."CONSTRAINT_TYPE" = \'PRIMARY KEY\' AND - tc."CONSTRAINT_NAME" = ku."CONSTRAINT_NAME" AND - ku."TABLE_NAME" = ? AND - ku."TABLE_CATALOG" = ?', - 'reflect_belongs_to'=>'SELECT - cu1."TABLE_NAME",cu1."COLUMN_NAME", - cu2."TABLE_NAME",cu2."COLUMN_NAME" - FROM - "INFORMATION_SCHEMA".REFERENTIAL_CONSTRAINTS rc, - "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cu1, - "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cu2 - WHERE - cu1."CONSTRAINT_NAME" = rc."CONSTRAINT_NAME" AND - cu2."CONSTRAINT_NAME" = rc."UNIQUE_CONSTRAINT_NAME" AND - cu1."TABLE_NAME" = ? AND - cu2."TABLE_NAME" IN ? AND - cu1."TABLE_CATALOG" = ? AND - cu2."TABLE_CATALOG" = ?', - 'reflect_has_many'=>'SELECT - cu1."TABLE_NAME",cu1."COLUMN_NAME", - cu2."TABLE_NAME",cu2."COLUMN_NAME" - FROM - "INFORMATION_SCHEMA".REFERENTIAL_CONSTRAINTS rc, - "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cu1, - "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cu2 - WHERE - cu1."CONSTRAINT_NAME" = rc."CONSTRAINT_NAME" AND - cu2."CONSTRAINT_NAME" = rc."UNIQUE_CONSTRAINT_NAME" AND - cu1."TABLE_NAME" IN ? AND - cu2."TABLE_NAME" = ? AND - cu1."TABLE_CATALOG" = ? AND - cu2."TABLE_CATALOG" = ?', - 'reflect_habtm'=>'SELECT - cua1."TABLE_NAME",cua1."COLUMN_NAME", - cua2."TABLE_NAME",cua2."COLUMN_NAME", - cub1."TABLE_NAME",cub1."COLUMN_NAME", - cub2."TABLE_NAME",cub2."COLUMN_NAME" - FROM - "INFORMATION_SCHEMA".REFERENTIAL_CONSTRAINTS rca, - "INFORMATION_SCHEMA".REFERENTIAL_CONSTRAINTS rcb, - "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cua1, - "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cua2, - "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cub1, - "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cub2 - WHERE - cua1."CONSTRAINT_NAME" = rca."CONSTRAINT_NAME" AND - cua2."CONSTRAINT_NAME" = rca."UNIQUE_CONSTRAINT_NAME" AND - cub1."CONSTRAINT_NAME" = rcb."CONSTRAINT_NAME" AND - cub2."CONSTRAINT_NAME" = rcb."UNIQUE_CONSTRAINT_NAME" AND - cua1."TABLE_CATALOG" = ? AND - cub1."TABLE_CATALOG" = ? AND - cua2."TABLE_CATALOG" = ? AND - cub2."TABLE_CATALOG" = ? AND - cua1."TABLE_NAME" = cub1."TABLE_NAME" AND - cua2."TABLE_NAME" = ? AND - cub2."TABLE_NAME" IN ?', - 'reflect_columns'=> 'SELECT - "COLUMN_NAME", "COLUMN_DEFAULT", "IS_NULLABLE", "DATA_TYPE", "CHARACTER_MAXIMUM_LENGTH" - FROM - "INFORMATION_SCHEMA"."COLUMNS" - WHERE - "TABLE_NAME" LIKE ? AND - "TABLE_CATALOG" = ? - ORDER BY - "ORDINAL_POSITION"' - ); - } - - public function getSql($name) { - return isset($this->queries[$name])?$this->queries[$name]:false; - } - - public function connect($hostname,$username,$password,$database,$port,$socket,$charset) { - $connectionInfo = array(); - if ($port) $hostname.=','.$port; - if ($username) $connectionInfo['UID']=$username; - if ($password) $connectionInfo['PWD']=$password; - if ($database) $connectionInfo['Database']=$database; - if ($charset) $connectionInfo['CharacterSet']=$charset; - $connectionInfo['QuotedId']=1; - $connectionInfo['ReturnDatesAsStrings']=1; - - $db = sqlsrv_connect($hostname, $connectionInfo); - if (!$db) { - throw new \Exception('Connect failed. '.print_r( sqlsrv_errors(), true)); - } - if ($socket) { - throw new \Exception('Socket connection is not supported.'); - } - $this->db = $db; - } - - public function query($sql,$params=array()) { - $args = array(); - $db = $this->db; - $sql = preg_replace_callback('/\!|\?/', function ($matches) use (&$db,&$params,&$args) { - static $i=-1; - $i++; - $param = $params[$i]; - if ($matches[0]=='!') { - $key = preg_replace('/[^a-zA-Z0-9\-_=<> ]/','',is_object($param)?$param->key:$param); - if (is_object($param) && $param->type=='hex') { - return "CONVERT(varchar(max), \"$key\", 2) as \"$key\""; - } - if (is_object($param) && $param->type=='wkt') { - return "\"$key\".STAsText() as \"$key\""; - } - return '"'.$key.'"'; - } else { - // This is workaround because SQLSRV cannot accept NULL in a param - if ($matches[0]=='?' && is_null($param)) { - return 'NULL'; - } - if (is_array($param)) { - $args = array_merge($args,$param); - return '('.implode(',',str_split(str_repeat('?',count($param)))).')'; - } - if (is_object($param) && $param->type=='hex') { - $args[] = $param->value; - return 'CONVERT(VARBINARY(MAX),?,2)'; - } - if (is_object($param) && $param->type=='wkt') { - $args[] = $param->value; - return 'geometry::STGeomFromText(?,0)'; - } - $args[] = $param; - return '?'; - } - }, $sql); - //var_dump($params); - //echo "\n$sql\n"; - //var_dump($args); - //file_put_contents('sql.txt',"\n$sql\n".var_export($args,true)."\n",FILE_APPEND); - if (strtoupper(substr($sql,0,6))=='INSERT') { - $sql .= ';SELECT SCOPE_IDENTITY()'; - } - return sqlsrv_query($db,$sql,$args)?:null; - } - - public function fetchAssoc($result) { - return sqlsrv_fetch_array($result, SQLSRV_FETCH_ASSOC); - } - - public function fetchRow($result) { - return sqlsrv_fetch_array($result, SQLSRV_FETCH_NUMERIC); - } - - public function insertId($result) { - sqlsrv_next_result($result); - sqlsrv_fetch($result); - return (int)sqlsrv_get_field($result, 0); - } - - public function affectedRows($result) { - return sqlsrv_rows_affected($result); - } - - public function close($result) { - return sqlsrv_free_stmt($result); - } - - public function fetchFields($table) { - $result = $this->query('SELECT * FROM ! WHERE 1=2;',array($table)); - //var_dump(sqlsrv_field_metadata($result)); - return array_map(function($a){ - $p = array(); - foreach ($a as $k=>$v) { - $p[strtolower($k)] = $v; - } - return (object)$p; - },sqlsrv_field_metadata($result)); - } - - public function addLimitToSql($sql,$limit,$offset) { - return "$sql OFFSET $offset ROWS FETCH NEXT $limit ROWS ONLY"; - } - - public function likeEscape($string) { - return str_replace(array('%','_'),array('[%]','[_]'),$string); - } - - public function convertFilter($field, $comparator, $value) { - $comparator = strtolower($comparator); - if ($comparator[0]!='n') { - switch ($comparator) { - case 'sco': return array('!.STContains(geometry::STGeomFromText(?,0))=1',$field,$value); - case 'scr': return array('!.STCrosses(geometry::STGeomFromText(?,0))=1',$field,$value); - case 'sdi': return array('!.STDisjoint(geometry::STGeomFromText(?,0))=1',$field,$value); - case 'seq': return array('!.STEquals(geometry::STGeomFromText(?,0))=1',$field,$value); - case 'sin': return array('!.STIntersects(geometry::STGeomFromText(?,0))=1',$field,$value); - case 'sov': return array('!.STOverlaps(geometry::STGeomFromText(?,0))=1',$field,$value); - case 'sto': return array('!.STTouches(geometry::STGeomFromText(?,0))=1',$field,$value); - case 'swi': return array('!.STWithin(geometry::STGeomFromText(?,0))=1',$field,$value); - case 'sic': return array('!.STIsClosed()=1',$field); - case 'sis': return array('!.STIsSimple()=1',$field); - case 'siv': return array('!.STIsValid()=1',$field); - } - } else { - switch ($comparator) { - case 'nsco': return array('!.STContains(geometry::STGeomFromText(?,0))=0',$field,$value); - case 'nscr': return array('!.STCrosses(geometry::STGeomFromText(?,0))=0',$field,$value); - case 'nsdi': return array('!.STDisjoint(geometry::STGeomFromText(?,0))=0',$field,$value); - case 'nseq': return array('!.STEquals(geometry::STGeomFromText(?,0))=0',$field,$value); - case 'nsin': return array('!.STIntersects(geometry::STGeomFromText(?,0))=0',$field,$value); - case 'nsov': return array('!.STOverlaps(geometry::STGeomFromText(?,0))=0',$field,$value); - case 'nsto': return array('!.STTouches(geometry::STGeomFromText(?,0))=0',$field,$value); - case 'nswi': return array('!.STWithin(geometry::STGeomFromText(?,0))=0',$field,$value); - case 'nsic': return array('!.STIsClosed()=0',$field); - case 'nsis': return array('!.STIsSimple()=0',$field); - case 'nsiv': return array('!.STIsValid()=0',$field); - } - } - return false; - } - - public function isNumericType($field) { - return in_array($field->type,array(-6,-5,4,5,2,6,7)); - } - - public function isBinaryType($field) { - return ($field->type>=-4 && $field->type<=-2); - } - - public function isGeometryType($field) { - return ($field->type==-151); - } - - public function isJsonType($field) { - return ($field->type==-152); - } - - public function getDefaultCharset() { - return 'UTF-8'; - } - - public function beginTransaction() { - return sqlsrv_begin_transaction($this->db); - } - - public function commitTransaction() { - return sqlsrv_commit($this->db); - } - - public function rollbackTransaction() { - return sqlsrv_rollback($this->db); - } - - public function jsonEncode($object) { - $a = $object; - $d = new DOMDocument(); - $c = $d->createElement("root"); - $d->appendChild($c); - $t = function($v) { - $type = gettype($v); - switch($type) { - case 'integer': return 'number'; - case 'double': return 'number'; - default: return strtolower($type); - } - }; - $f = function($f,$c,$a,$s=false) use ($t,$d) { - $c->setAttribute('type', $t($a)); - if ($t($a) != 'array' && $t($a) != 'object') { - if ($t($a) == 'boolean') { - $c->appendChild($d->createTextNode($a?'true':'false')); - } else { - $c->appendChild($d->createTextNode($a)); - } - } else { - foreach($a as $k=>$v) { - if ($k == '__type' && $t($a) == 'object') { - $c->setAttribute('__type', $v); - } else { - if ($t($v) == 'object') { - $ch = $c->appendChild($d->createElementNS(null, $s ? 'item' : $k)); - $f($f, $ch, $v); - } else if ($t($v) == 'array') { - $ch = $c->appendChild($d->createElementNS(null, $s ? 'item' : $k)); - $f($f, $ch, $v, true); - } else { - $va = $d->createElementNS(null, $s ? 'item' : $k); - if ($t($v) == 'boolean') { - $va->appendChild($d->createTextNode($v?'true':'false')); - } else { - $va->appendChild($d->createTextNode($v)); - } - $ch = $c->appendChild($va); - $ch->setAttribute('type', $t($v)); - } - } - } - } - }; - $f($f,$c,$a,$t($a)=='array'); - return $d->saveXML($d->documentElement); - } - - public function jsonDecode($string) { - $a = dom_import_simplexml(simplexml_load_string($string)); - $t = function($v) { - return $v->getAttribute('type'); - }; - $f = function($f,$a) use ($t) { - $c = null; - if ($t($a)=='null') { - $c = null; - } else if ($t($a)=='boolean') { - $b = substr(strtolower($a->textContent),0,1); - $c = in_array($b,array('1','t')); - } else if ($t($a)=='number') { - $c = $a->textContent+0; - } else if ($t($a)=='string') { - $c = $a->textContent; - } else if ($t($a)=='object') { - $c = array(); - if ($a->getAttribute('__type')) { - $c['__type'] = $a->getAttribute('__type'); - } - for ($i=0;$i<$a->childNodes->length;$i++) { - $v = $a->childNodes[$i]; - $c[$v->nodeName] = $f($f,$v); - } - $c = (object)$c; - } else if ($t($a)=='array') { - $c = array(); - for ($i=0;$i<$a->childNodes->length;$i++) { - $v = $a->childNodes[$i]; - $c[$i] = $f($f,$v); - } - } - return $c; - }; - $c = $f($f,$a); - return $c; - } -} - -class SQLite implements DatabaseInterface { - - protected $db; - protected $queries; - - public function __construct() { - $this->queries = array( - 'list_tables'=>'SELECT - "name", "" - FROM - "sys/tables"', - 'reflect_table'=>'SELECT - "name" - FROM - "sys/tables" - WHERE - "name"=?', - 'reflect_pk'=>'SELECT - "name" - FROM - "sys/columns" - WHERE - "pk"=1 AND - "self"=?', - 'reflect_belongs_to'=>'SELECT - "self", "from", - "table", "to" - FROM - "sys/foreign_keys" - WHERE - "self" = ? AND - "table" IN ? AND - ? like "%" AND - ? like "%"', - 'reflect_has_many'=>'SELECT - "self", "from", - "table", "to" - FROM - "sys/foreign_keys" - WHERE - "self" IN ? AND - "table" = ? AND - ? like "%" AND - ? like "%"', - 'reflect_habtm'=>'SELECT - k1."self", k1."from", - k1."table", k1."to", - k2."self", k2."from", - k2."table", k2."to" - FROM - "sys/foreign_keys" k1, - "sys/foreign_keys" k2 - WHERE - ? like "%" AND - ? like "%" AND - ? like "%" AND - ? like "%" AND - k1."self" = k2."self" AND - k1."table" = ? AND - k2."table" IN ?', - 'reflect_columns'=> 'SELECT - "name", "dflt_value", case when "notnull"==1 then \'no\' else \'yes\' end as "nullable", "type", 2147483647 - FROM - "sys/columns" - WHERE - "self"=? - ORDER BY - "cid"' - ); - } - - public function getSql($name) { - return isset($this->queries[$name])?$this->queries[$name]:false; - } - - public function connect($hostname,$username,$password,$database,$port,$socket,$charset) { - $this->db = new SQLite3($database); - // optimizations - $this->db->querySingle('PRAGMA synchronous = NORMAL'); - $this->db->querySingle('PRAGMA foreign_keys = on'); - $reflection = $this->db->querySingle('SELECT name FROM sqlite_master WHERE type = "table" and name like "sys/%"'); - if (!$reflection) { - //create reflection tables - $this->query('CREATE table "sys/version" ("version" integer)'); - $this->query('CREATE table "sys/tables" ("name" text)'); - $this->query('CREATE table "sys/columns" ("self" text,"cid" integer,"name" text,"type" integer,"notnull" integer,"dflt_value" integer,"pk" integer)'); - $this->query('CREATE table "sys/foreign_keys" ("self" text,"id" integer,"seq" integer,"table" text,"from" text,"to" text,"on_update" text,"on_delete" text,"match" text)'); - } - $version = $this->db->querySingle('pragma schema_version'); - if ($version != $this->db->querySingle('SELECT "version" from "sys/version"')) { - // reflection may take a while - set_time_limit(3600); - // update version data - $this->query('DELETE FROM "sys/version"'); - $this->query('INSERT into "sys/version" ("version") VALUES (?)',array($version)); - // update tables data - $this->query('DELETE FROM "sys/tables"'); - $result = $this->query('SELECT * FROM sqlite_master WHERE (type = "table" or type = "view") and name not like "sys/%" and name<>"sqlite_sequence"'); - $tables = array(); - while ($row = $this->fetchAssoc($result)) { - $tables[] = $row['name']; - $this->query('INSERT into "sys/tables" ("name") VALUES (?)',array($row['name'])); - } - // update columns and foreign_keys data - $this->query('DELETE FROM "sys/columns"'); - $this->query('DELETE FROM "sys/foreign_keys"'); - foreach ($tables as $table) { - $result = $this->query('pragma table_info(!)',array($table)); - while ($row = $this->fetchRow($result)) { - array_unshift($row, $table); - $this->query('INSERT into "sys/columns" ("self","cid","name","type","notnull","dflt_value","pk") VALUES (?,?,?,?,?,?,?)',$row); - } - $result = $this->query('pragma foreign_key_list(!)',array($table)); - while ($row = $this->fetchRow($result)) { - array_unshift($row, $table); - $this->query('INSERT into "sys/foreign_keys" ("self","id","seq","table","from","to","on_update","on_delete","match") VALUES (?,?,?,?,?,?,?,?,?)',$row); - } - } - } - } - - public function query($sql,$params=array()) { - $db = $this->db; - $sql = preg_replace_callback('/\!|\?/', function ($matches) use (&$db,&$params) { - $param = array_shift($params); - if ($matches[0]=='!') { - $key = preg_replace('/[^a-zA-Z0-9\-_=<> ]/','',is_object($param)?$param->key:$param); - return '"'.$key.'"'; - } else { - if (is_array($param)) return '('.implode(',',array_map(function($v) use (&$db) { - return "'".$db->escapeString($v)."'"; - },$param)).')'; - if (is_object($param) && $param->type=='hex') { - return "'".$db->escapeString($param->value)."'"; - } - if (is_object($param) && $param->type=='wkt') { - return "'".$db->escapeString($param->value)."'"; - } - if ($param===null) return 'NULL'; - return "'".$db->escapeString($param)."'"; - } - }, $sql); - //echo "\n$sql\n"; - try { $result=$db->query($sql); } catch(\Exception $e) { $result=null; } - return $result; - } - - public function fetchAssoc($result) { - return $result->fetchArray(SQLITE3_ASSOC); - } - - public function fetchRow($result) { - return $result->fetchArray(SQLITE3_NUM); - } - - public function insertId($result) { - return $this->db->lastInsertRowID(); - } - - public function affectedRows($result) { - return $this->db->changes(); - } - - public function close($result) { - return $result->finalize(); - } - - public function fetchFields($table) { - $result = $this->query('SELECT * FROM "sys/columns" WHERE "self"=?;',array($table)); - $fields = array(); - while ($row = $this->fetchAssoc($result)){ - $fields[strtolower($row['name'])] = (object)$row; - } - return $fields; - } - - public function addLimitToSql($sql,$limit,$offset) { - return "$sql LIMIT $limit OFFSET $offset"; - } - - public function likeEscape($string) { - return addcslashes($string,'%_'); - } - - public function convertFilter($field, $comparator, $value) { - return false; - } - - public function isNumericType($field) { - return in_array($field->type,array('integer','real')); - } - - public function isBinaryType($field) { - return (substr($field->type,0,4)=='data'); - } - - public function isGeometryType($field) { - return in_array($field->type,array('geometry')); - } - - public function isJsonType($field) { - return in_array($field->type,array('json','jsonb')); - } - - public function getDefaultCharset() { - return 'utf8'; - } - - public function beginTransaction() { - return $this->query('BEGIN'); - } - - public function commitTransaction() { - return $this->query('COMMIT'); - } - - public function rollbackTransaction() { - return $this->query('ROLLBACK'); - } - - public function jsonEncode($object) { - return json_encode($object); - } - - public function jsonDecode($string) { - return json_decode($string); - } -} - -class PHP_CRUD_API { - - protected $db; - protected $settings; - - protected function mapMethodToAction($method,$key) { - switch ($method) { - case 'OPTIONS': return 'headers'; - case 'GET': return ($key===false)?'list':'read'; - case 'PUT': return 'update'; - case 'POST': return 'create'; - case 'DELETE': return 'delete'; - case 'PATCH': return 'increment'; - default: $this->exitWith404('method'); - } - return false; - } - - protected function parseRequestParameter(&$request,$characters) { - if ($request==='') return false; - $pos = strpos($request,'/'); - $value = $pos?substr($request,0,$pos):$request; - $request = $pos?substr($request,$pos+1):''; - if (!$characters) return $value; - return preg_replace("/[^$characters]/",'',$value); - } - - protected function parseGetParameter($get,$name,$characters) { - $value = isset($get[$name])?$get[$name]:false; - return $characters?preg_replace("/[^$characters]/",'',$value):$value; - } - - protected function parseGetParameterArray($get,$name,$characters) { - $values = isset($get[$name])?$get[$name]:false; - if (!is_array($values)) $values = array($values); - if ($characters) { - foreach ($values as &$value) { - $value = preg_replace("/[^$characters]/",'',$value); - } - } - return $values; - } - - protected function applyBeforeHandler(&$action,&$database,&$table,&$ids,&$callback,&$inputs) { - if (is_callable($callback,true)) { - $max = count($ids)?:count($inputs); - $values = array('action'=>$action,'database'=>$database,'table'=>$table); - for ($i=0;$i<$max;$i++) { - $action = $values['action']; - $database = $values['database']; - $table = $values['table']; - if (!isset($ids[$i])) $ids[$i] = false; - if (!isset($inputs[$i])) $inputs[$i] = false; - $callback($action,$database,$table,$ids[$i],$inputs[$i]); - } - } - } - - protected function applyAfterHandler($parameters,$outputs) { - $callback = $parameters['after']; - if (is_callable($callback,true)) { - $action = $parameters['action']; - $database = $parameters['database']; - $table = $parameters['tables'][0]; - $ids = $parameters['key'][0]; - $inputs = $parameters['inputs']; - $max = max(count($ids),count($inputs)); - for ($i=0;$i<$max;$i++) { - $id = isset($ids[$i])?$ids[$i]:false; - $input = isset($inputs[$i])?$inputs[$i]:false; - $output = is_array($outputs)?$outputs[$i]:$outputs; - $callback($action,$database,$table,$id,$input,$output); - } - } - } - - protected function applyTableAuthorizer($callback,$action,$database,&$tables) { - if (is_callable($callback,true)) foreach ($tables as $i=>$table) { - if (!$callback($action,$database,$table)) { - unset($tables[$i]); - } - } - } - - protected function applyRecordFilter($callback,$action,$database,$tables,&$filters) { - if (is_callable($callback,true)) foreach ($tables as $i=>$table) { - $this->addFilters($filters,$table,array($table=>'and'),$callback($action,$database,$table)); - } - } - - protected function applyTenancyFunction($callback,$action,$database,$fields,&$filters) { - if (is_callable($callback,true)) foreach ($fields as $table=>$keys) { - foreach ($keys as $field) { - $v = $callback($action,$database,$table,$field->name); - if ($v!==null) { - if (is_array($v)) $this->addFilter($filters,$table,'and',$field->name,'in',implode(',',$v)); - else $this->addFilter($filters,$table,'and',$field->name,'eq',$v); - } - } - } - } - - protected function applyColumnAuthorizer($callback,$action,$database,&$fields) { - if (is_callable($callback,true)) foreach ($fields as $table=>$keys) { - foreach ($keys as $field) { - if (!$callback($action,$database,$table,$field->name)) { - unset($fields[$table][$field->name]); - } - } - } - } - - protected function applyInputTenancy($callback,$action,$database,$table,&$input,$keys) { - if (is_callable($callback,true)) foreach ($keys as $key=>$field) { - $v = $callback($action,$database,$table,$key); - if ($v!==null && (isset($input->$key) || $action=='create')) { - if (is_array($v)) { - if (!count($v)) { - $input->$key = null; - } elseif (!isset($input->$key)) { - $input->$key = $v[0]; - } elseif (!in_array($input->$key,$v)) { - $input->$key = null; - } - } else { - $input->$key = $v; - } - } - } - } - - protected function applyInputSanitizer($callback,$action,$database,$table,&$input,$keys) { - if (is_callable($callback,true)) foreach ((array)$input as $key=>$value) { - if (isset($keys[$key])) { - $input->$key = $callback($action,$database,$table,$key,$keys[$key]->type,$value); - } - } - } - - protected function applyInputValidator($callback,$action,$database,$table,$input,$keys,$context) { - $errors = array(); - if (is_callable($callback,true)) foreach ((array)$input as $key=>$value) { - if (isset($keys[$key])) { - $error = $callback($action,$database,$table,$key,$keys[$key]->type,$value,$context); - if ($error!==true && $error!==null) $errors[$key] = $error; - } - } - if (!empty($errors)) $this->exitWith422($errors); - } - - protected function processTableAndIncludeParameters($database,$table,$include,$action) { - $blacklist = array('information_schema','mysql','sys','pg_catalog'); - if (in_array(strtolower($database), $blacklist)) return array(); - $table_list = array(); - if ($result = $this->db->query($this->db->getSql('reflect_table'),array($table,$database))) { - while ($row = $this->db->fetchRow($result)) $table_list[] = $row[0]; - $this->db->close($result); - } - if (empty($table_list)) $this->exitWith404('entity'); - if ($action=='list') { - foreach (explode(',',$include) as $table) { - if ($result = $this->db->query($this->db->getSql('reflect_table'),array($table,$database))) { - while ($row = $this->db->fetchRow($result)) $table_list[] = $row[0]; - $this->db->close($result); - } - } - } - return $table_list; - } - - protected function exitWith404($type) { - if (isset($_SERVER['REQUEST_METHOD'])) { - header('Content-Type:',true,404); - die("Not found ($type)"); - } else { - throw new \Exception("Not found ($type)"); - } - } - - protected function exitWith400($type) { - if (isset($_SERVER['REQUEST_METHOD'])) { - header('Content-Type:',true,400); - die("The request could not be understood by the server due to malformed syntax. The client SHOULD NOT repeat the request without modifications. ($type)"); - } else { - throw new \Exception("Bad request ($type)"); - } - } - - protected function exitWith422($object) { - if (isset($_SERVER['REQUEST_METHOD'])) { - header('Content-Type:',true,422); - die(json_encode($object)); - } else { - throw new \Exception(json_encode($object)); - } - } - - protected function headersCommand($parameters) { - $headers = array(); - $headers[]='Access-Control-Allow-Headers: Content-Type, X-XSRF-TOKEN'; - $headers[]='Access-Control-Allow-Methods: OPTIONS, GET, PUT, POST, DELETE, PATCH'; - $headers[]='Access-Control-Allow-Credentials: true'; - $headers[]='Access-Control-Max-Age: 1728000'; - if (isset($_SERVER['REQUEST_METHOD'])) { - foreach ($headers as $header) header($header); - } else { - echo json_encode($headers); - } - return false; - } - - protected function startOutput() { - if (isset($_SERVER['REQUEST_METHOD'])) { - header('Content-Type: application/json; charset=utf-8'); - } - } - - protected function findPrimaryKeys($table,$database) { - $fields = array(); - if ($result = $this->db->query($this->db->getSql('reflect_pk'),array($table,$database))) { - while ($row = $this->db->fetchRow($result)) { - $fields[] = $row[0]; - } - $this->db->close($result); - } - return $fields; - } - - protected function processKeyParameter($key,$tables,$database) { - if ($key===false) return false; - $fields = $this->findPrimaryKeys($tables[0],$database); - if (count($fields)!=1) $this->exitWith404('1pk'); - return array(explode(',',$key),$fields[0]); - } - - protected function processOrderingsParameter($orderings) { - if (!$orderings) return false; - foreach ($orderings as &$order) { - $order = explode(',',$order,2); - if (count($order)<2) $order[1]='ASC'; - if (!strlen($order[0])) return false; - $direction = strtoupper($order[1]); - if (in_array($direction,array('ASC','DESC'))) { - $order[1] = $direction; - } - } - return $orderings; - } - - protected function convertFilter($field, $comparator, $value) { - $result = $this->db->convertFilter($field,$comparator,$value); - if ($result) return $result; - // default behavior - $comparator = strtolower($comparator); - if ($comparator[0]!='n') { - if (strlen($comparator)==2) { - switch ($comparator) { - case 'cs': return array('! LIKE ?',$field,'%'.$this->db->likeEscape($value).'%'); - case 'sw': return array('! LIKE ?',$field,$this->db->likeEscape($value).'%'); - case 'ew': return array('! LIKE ?',$field,'%'.$this->db->likeEscape($value)); - case 'eq': return array('! = ?',$field,$value); - case 'lt': return array('! < ?',$field,$value); - case 'le': return array('! <= ?',$field,$value); - case 'ge': return array('! >= ?',$field,$value); - case 'gt': return array('! > ?',$field,$value); - case 'bt': - $v = explode(',',$value); - if (count($v)<2) return false; - return array('! BETWEEN ? AND ?',$field,$v[0],$v[1]); - case 'in': return array('! IN ?',$field,explode(',',$value)); - case 'is': return array('! IS NULL',$field); - } - } else { - switch ($comparator) { - case 'sco': return array('ST_Contains(!,ST_GeomFromText(?))=TRUE',$field,$value); - case 'scr': return array('ST_Crosses(!,ST_GeomFromText(?))=TRUE',$field,$value); - case 'sdi': return array('ST_Disjoint(!,ST_GeomFromText(?))=TRUE',$field,$value); - case 'seq': return array('ST_Equals(!,ST_GeomFromText(?))=TRUE',$field,$value); - case 'sin': return array('ST_Intersects(!,ST_GeomFromText(?))=TRUE',$field,$value); - case 'sov': return array('ST_Overlaps(!,ST_GeomFromText(?))=TRUE',$field,$value); - case 'sto': return array('ST_Touches(!,ST_GeomFromText(?))=TRUE',$field,$value); - case 'swi': return array('ST_Within(!,ST_GeomFromText(?))=TRUE',$field,$value); - case 'sic': return array('ST_IsClosed(!)=TRUE',$field); - case 'sis': return array('ST_IsSimple(!)=TRUE',$field); - case 'siv': return array('ST_IsValid(!)=TRUE',$field); - } - } - } else { - if (strlen($comparator)==2) { - switch ($comparator) { - case 'ne': return $this->convertFilter($field, 'neq', $value); // deprecated - case 'ni': return $this->convertFilter($field, 'nin', $value); // deprecated - case 'no': return $this->convertFilter($field, 'nis', $value); // deprecated - } - } elseif (strlen($comparator)==3) { - switch ($comparator) { - case 'ncs': return array('! NOT LIKE ?',$field,'%'.$this->db->likeEscape($value).'%'); - case 'nsw': return array('! NOT LIKE ?',$field,$this->db->likeEscape($value).'%'); - case 'new': return array('! NOT LIKE ?',$field,'%'.$this->db->likeEscape($value)); - case 'neq': return array('! <> ?',$field,$value); - case 'nlt': return array('! >= ?',$field,$value); - case 'nle': return array('! > ?',$field,$value); - case 'nge': return array('! < ?',$field,$value); - case 'ngt': return array('! <= ?',$field,$value); - case 'nbt': - $v = explode(',',$value); - if (count($v)<2) return false; - return array('! NOT BETWEEN ? AND ?',$field,$v[0],$v[1]); - case 'nin': return array('! NOT IN ?',$field,explode(',',$value)); - case 'nis': return array('! IS NOT NULL',$field); - } - } else { - switch ($comparator) { - case 'nsco': return array('ST_Contains(!,ST_GeomFromText(?))=FALSE',$field,$value); - case 'nscr': return array('ST_Crosses(!,ST_GeomFromText(?))=FALSE',$field,$value); - case 'nsdi': return array('ST_Disjoint(!,ST_GeomFromText(?))=FALSE',$field,$value); - case 'nseq': return array('ST_Equals(!,ST_GeomFromText(?))=FALSE',$field,$value); - case 'nsin': return array('ST_Intersects(!,ST_GeomFromText(?))=FALSE',$field,$value); - case 'nsov': return array('ST_Overlaps(!,ST_GeomFromText(?))=FALSE',$field,$value); - case 'nsto': return array('ST_Touches(!,ST_GeomFromText(?))=FALSE',$field,$value); - case 'nswi': return array('ST_Within(!,ST_GeomFromText(?))=FALSE',$field,$value); - case 'nsic': return array('ST_IsClosed(!)=FALSE',$field); - case 'nsis': return array('ST_IsSimple(!)=FALSE',$field); - case 'nsiv': return array('ST_IsValid(!)=FALSE',$field); - } - } - } - return false; - } - - public function addFilter(&$filters,$table,$and,$field,$comparator,$value) { - if (!isset($filters[$table])) $filters[$table] = array(); - if (!isset($filters[$table][$and])) $filters[$table][$and] = array(); - $filter = $this->convertFilter($field,$comparator,$value); - if ($filter) $filters[$table][$and][] = $filter; - } - - public function addFilters(&$filters,$table,$satisfy,$filterStrings) { - if ($filterStrings) { - for ($i=0;$i=2) { - if (strpos($parts[0],'.')) list($t,$f) = explode('.',$parts[0],2); - else list($t,$f) = array($table,$parts[0]); - $comparator = $parts[1]; - $value = isset($parts[2])?$parts[2]:null; - $and = isset($satisfy[$t])?$satisfy[$t]:'and'; - $this->addFilter($filters,$t,$and,$f,$comparator,$value); - } - } - } - } - - protected function processSatisfyParameter($tables,$satisfyString) { - $satisfy = array(); - foreach (explode(',',$satisfyString) as $str) { - if (strpos($str,'.')) list($t,$s) = explode('.',$str,2); - else list($t,$s) = array($tables[0],$str); - $and = ($s && strtolower($s)=='any')?'or':'and'; - $satisfy[$t] = $and; - } - return $satisfy; - } - - protected function processFiltersParameter($tables,$satisfy,$filterStrings) { - $filters = array(); - $this->addFilters($filters,$tables[0],$satisfy,$filterStrings); - return $filters; - } - - protected function processPageParameter($page) { - if (!$page) return false; - $page = explode(',',$page,2); - if (count($page)<2) $page[1]=20; - $page[0] = ($page[0]-1)*$page[1]; - return $page; - } - - protected function retrieveObject($key,$fields,$filters,$tables) { - if (!$key) return false; - $table = $tables[0]; - $params = array(); - $sql = 'SELECT '; - $this->convertOutputs($sql,$params,$fields[$table]); - $sql .= ' FROM !'; - $params[] = $table; - $this->addFilter($filters,$table,'and',$key[1],'eq',$key[0][0]); - $this->addWhereFromFilters($filters[$table],$sql,$params); - $object = null; - if ($result = $this->db->query($sql,$params)) { - $object = $this->fetchAssoc($result,$fields[$table]); - $this->db->close($result); - } - return $object; - } - - protected function retrieveObjects($key,$fields,$filters,$tables) { - $keyField = $key[1]; - $keys = $key[0]; - $rows = array(); - foreach ($keys as $key) { - $result = $this->retrieveObject(array(array($key),$keyField),$fields,$filters,$tables); - if ($result===null) { - return null; - } - $rows[] = $result; - } - return $rows; - } - - protected function createObject($input,$tables) { - if (!$input) return false; - $input = (array)$input; - $keys = implode(',',str_split(str_repeat('!', count($input)))); - $values = implode(',',str_split(str_repeat('?', count($input)))); - $params = array_merge(array_keys($input),array_values($input)); - array_unshift($params, $tables[0]); - $result = $this->db->query('INSERT INTO ! ('.$keys.') VALUES ('.$values.')',$params); - if (!$result) return null; - $insertId = $this->db->insertId($result); - return $insertId; - } - - protected function createObjects($inputs,$tables) { - if (!$inputs) return false; - $ids = array(); - $this->db->beginTransaction(); - foreach ($inputs as $input) { - $result = $this->createObject($input,$tables); - if ($result===null) { - $this->db->rollbackTransaction(); - return null; - } - $ids[] = $result; - } - $this->db->commitTransaction(); - return $ids; - } - - protected function updateObject($key,$input,$filters,$tables) { - if (!$input) return null; - $input = (array)$input; - $table = $tables[0]; - $sql = 'UPDATE ! SET '; - $params = array($table); - foreach (array_keys($input) as $j=>$k) { - if ($j) $sql .= ','; - $v = $input[$k]; - $sql .= '!=?'; - $params[] = $k; - $params[] = $v; - } - $this->addFilter($filters,$table,'and',$key[1],'eq',$key[0][0]); - $this->addWhereFromFilters($filters[$table],$sql,$params); - $result = $this->db->query($sql,$params); - if (!$result) return null; - return $this->db->affectedRows($result); - } - - protected function updateObjects($key,$inputs,$filters,$tables) { - if (!$inputs) return null; - $keyField = $key[1]; - $keys = $key[0]; - if (count(array_filter($inputs))!=count(array_filter($keys))) { - $this->exitWith404('subject'); - } - $rows = array(); - $this->db->beginTransaction(); - foreach ($inputs as $i=>$input) { - $result = $this->updateObject(array(array($keys[$i]),$keyField),$input,$filters,$tables); - if ($result===null) { - $this->db->rollbackTransaction(); - return null; - } - $rows[] = $result; - } - $this->db->commitTransaction(); - return $rows; - } - - protected function deleteObject($key,$filters,$tables) { - $table = $tables[0]; - $sql = 'DELETE FROM !'; - $params = array($table); - $this->addFilter($filters,$table,'and',$key[1],'eq',$key[0][0]); - $this->addWhereFromFilters($filters[$table],$sql,$params); - $result = $this->db->query($sql,$params); - if (!$result) return null; - return $this->db->affectedRows($result); - } - - protected function deleteObjects($key,$filters,$tables) { - $keyField = $key[1]; - $keys = $key[0]; - $rows = array(); - $this->db->beginTransaction(); - foreach ($keys as $key) { - $result = $this->deleteObject(array(array($key),$keyField),$filters,$tables); - if ($result===null) { - $this->db->rollbackTransaction(); - return null; - } - $rows[] = $result; - } - $this->db->commitTransaction(); - return $rows; - } - - protected function incrementObject($key,$input,$filters,$tables,$fields) { - if (!$input) return null; - $input = (array)$input; - $table = $tables[0]; - $sql = 'UPDATE ! SET '; - $params = array($table); - foreach (array_keys($input) as $j=>$k) { - if ($j) $sql .= ','; - $v = $input[$k]; - if ($this->db->isNumericType($fields[$table][$k])) { - $sql .= '!=!+?'; - $params[] = $k; - $params[] = $k; - $params[] = $v; - } else { - $sql .= '!=!'; - $params[] = $k; - $params[] = $k; - } - } - $this->addFilter($filters,$table,'and',$key[1],'eq',$key[0][0]); - $this->addWhereFromFilters($filters[$table],$sql,$params); - $result = $this->db->query($sql,$params); - if (!$result) return null; - return $this->db->affectedRows($result); - } - - protected function incrementObjects($key,$inputs,$filters,$tables,$fields) { - if (!$inputs) return null; - $keyField = $key[1]; - $keys = $key[0]; - if (count(array_filter($inputs))!=count(array_filter($keys))) { - $this->exitWith404('subject'); - } - $rows = array(); - $this->db->beginTransaction(); - foreach ($inputs as $i=>$input) { - $result = $this->incrementObject(array(array($keys[$i]),$keyField),$input,$filters,$tables,$fields); - if ($result===null) { - $this->db->rollbackTransaction(); - return null; - } - $rows[] = $result; - } - $this->db->commitTransaction(); - return $rows; - } - - protected function findRelations($tables,$database,$auto_include) { - $tableset = array(); - $collect = array(); - $select = array(); - - while (count($tables)>1) { - $table0 = array_shift($tables); - $tableset[] = $table0; - - $result = $this->db->query($this->db->getSql('reflect_belongs_to'),array($table0,$tables,$database,$database)); - while ($row = $this->db->fetchRow($result)) { - if (!$auto_include && !in_array($row[0],array_merge($tables,$tableset))) continue; - $collect[$row[0]][$row[1]]=array(); - $select[$row[2]][$row[3]]=array($row[0],$row[1]); - if (!in_array($row[0],$tableset)) $tableset[] = $row[0]; - } - $result = $this->db->query($this->db->getSql('reflect_has_many'),array($tables,$table0,$database,$database)); - while ($row = $this->db->fetchRow($result)) { - if (!$auto_include && !in_array($row[2],array_merge($tables,$tableset))) continue; - $collect[$row[2]][$row[3]]=array(); - $select[$row[0]][$row[1]]=array($row[2],$row[3]); - if (!in_array($row[2],$tableset)) $tableset[] = $row[2]; - } - $result = $this->db->query($this->db->getSql('reflect_habtm'),array($database,$database,$database,$database,$table0,$tables)); - while ($row = $this->db->fetchRow($result)) { - if (!$auto_include && !in_array($row[2],array_merge($tables,$tableset))) continue; - if (!$auto_include && !in_array($row[4],array_merge($tables,$tableset))) continue; - $collect[$row[2]][$row[3]]=array(); - $select[$row[0]][$row[1]]=array($row[2],$row[3]); - $collect[$row[4]][$row[5]]=array(); - $select[$row[6]][$row[7]]=array($row[4],$row[5]); - if (!in_array($row[2],$tableset)) $tableset[] = $row[2]; - if (!in_array($row[4],$tableset)) $tableset[] = $row[4]; - } - } - $tableset[] = array_shift($tables); - $tableset = array_unique($tableset); - return array($tableset,$collect,$select); - } - - protected function retrieveInputs($data) { - $data = trim($data, " \t\n\r"); - if (strlen($data)==0) { - $input = false; - } else if ($data[0]=='{' || $data[0]=='[') { - $input = json_decode($data); - $causeCode = json_last_error(); - if ($causeCode !== JSON_ERROR_NONE) { - $errorString = "Error decoding input JSON. json_last_error code: " . $causeCode; - $this->exitWith400($errorString); - } - } else { - parse_str($data, $input); - foreach ($input as $key => $value) { - if (substr($key,-9)=='__is_null') { - $input[substr($key,0,-9)] = null; - unset($input[$key]); - } - } - $input = (object)$input; - } - return is_array($input)?$input:array($input); - } - - protected function getRelationShipColumns($select) { - $keep = array(); - foreach ($select as $table=>$keys) { - foreach ($keys as $key=>$other) { - if (!isset($keep[$table])) $keep[$table] = array(); - $keep[$table][$key]=true; - list($table2,$key2) = $other; - if (!isset($keep[$table2])) $keep[$table2] = array(); - $keep[$table2][$key2]=true; - } - } - return $keep; - } - - protected function findFields($tables,$columns,$exclude,$select,$database) { - $fields = array(); - if ($select && ($columns || $exclude)) { - $keep = $this->getRelationShipColumns($select); - } else { - $keep = false; - } - foreach ($tables as $i=>$table) { - $fields[$table] = $this->findTableFields($table,$database); - $fields[$table] = $this->filterFieldsByColumns($fields[$table],$columns,$keep,$i==0,$table); - $fields[$table] = $this->filterFieldsByExclude($fields[$table],$exclude,$keep,$i==0,$table); - } - return $fields; - } - - protected function filterFieldsByColumns($fields,$columns,$keep,$first,$table) { - if ($columns) { - $columns = explode(',',$columns); - foreach (array_keys($fields) as $key) { - $delete = true; - foreach ($columns as $column) { - if (strpos($column,'.')) { - if ($column=="$table.$key" || $column=="$table.*") { - $delete = false; - } - } elseif ($first) { - if ($column==$key || $column=="*") { - $delete = false; - } - } - } - if ($delete && !isset($keep[$table][$key])) { - unset($fields[$key]); - } - } - } - return $fields; - } - - protected function filterFieldsByExclude($fields,$exclude,$keep,$first,$table) { - if ($exclude) { - $columns = explode(',',$exclude); - foreach (array_keys($fields) as $key) { - $delete = false; - foreach ($columns as $column) { - if (strpos($column,'.')) { - if ($column=="$table.$key" || $column=="$table.*") { - $delete = true; - } - } elseif ($first) { - if ($column==$key || $column=="*") { - $delete = true; - } - } - } - if ($delete && !isset($keep[$table][$key])) { - unset($fields[$key]); - } - } - } - return $fields; - } - - protected function findTableFields($table,$database) { - $fields = array(); - foreach ($this->db->fetchFields($table) as $field) { - $fields[$field->name] = $field; - } - return $fields; - } - - protected function filterInputByFields($input,$fields) { - if ($fields) foreach (array_keys((array)$input) as $key) { - if (!isset($fields[$key])) { - unset($input->$key); - } - } - return $input; - } - - protected function convertInputs(&$input,$fields) { - foreach ($fields as $key=>$field) { - if (isset($input->$key) && $input->$key && $this->db->isBinaryType($field)) { - $value = $input->$key; - $value = str_pad(strtr($value, '-_', '+/'), ceil(strlen($value) / 4) * 4, '=', STR_PAD_RIGHT); - $input->$key = (object)array('type'=>'hex','value'=>bin2hex(base64_decode($value))); - } - if (isset($input->$key) && $input->$key && $this->db->isGeometryType($field)) { - $input->$key = (object)array('type'=>'wkt','value'=>$input->$key); - } - if (isset($input->$key) && $input->$key && $this->db->isJsonType($field)) { - $input->$key = $this->db->jsonEncode($input->$key); - } - } - } - - protected function convertOutputs(&$sql, &$params, $fields) { - $sql .= implode(',',str_split(str_repeat('!',count($fields)))); - foreach ($fields as $key=>$field) { - if ($this->db->isBinaryType($field)) { - $params[] = (object)array('type'=>'hex','key'=>$key); - } - else if ($this->db->isGeometryType($field)) { - $params[] = (object)array('type'=>'wkt','key'=>$key); - } - else { - $params[] = $key; - } - } - } - - protected function convertTypes($result,&$values,&$fields) { - foreach ($values as $i=>$v) { - if (is_string($v)) { - if ($this->db->isNumericType($fields[$i])) { - $values[$i] = $v + 0; - } - else if ($this->db->isBinaryType($fields[$i])) { - $values[$i] = base64_encode(pack("H*",$v)); - } - else if ($this->db->isJsonType($fields[$i])) { - $values[$i] = $this->db->jsonDecode($v); - } - } - } - } - - protected function fetchAssoc($result,$fields=false) { - $values = $this->db->fetchAssoc($result); - if ($values && $fields) { - $this->convertTypes($result,$values,$fields); - } - return $values; - } - - protected function fetchRow($result,$fields=false) { - $values = $this->db->fetchRow($result,$fields); - if ($values && $fields) { - $fields = array_values($fields); - $this->convertTypes($result,$values,$fields); - } - return $values; - } - - protected function getParameters($settings) { - extract($settings); - - $table = $this->parseRequestParameter($request, 'a-zA-Z0-9\-_'); - $key = $this->parseRequestParameter($request, 'a-zA-Z0-9\-_,'); // auto-increment or uuid - $action = $this->mapMethodToAction($method,$key); - $include = $this->parseGetParameter($get, 'include', 'a-zA-Z0-9\-_,'); - $page = $this->parseGetParameter($get, 'page', '0-9,'); - $filters = $this->parseGetParameterArray($get, 'filter', false); - $satisfy = $this->parseGetParameter($get, 'satisfy', 'a-zA-Z0-9\-_,.'); - $columns = $this->parseGetParameter($get, 'columns', 'a-zA-Z0-9\-_,.*'); - $exclude = $this->parseGetParameter($get, 'exclude', 'a-zA-Z0-9\-_,.*'); - $orderings = $this->parseGetParameterArray($get, 'order', 'a-zA-Z0-9\-_,'); - $transform = $this->parseGetParameter($get, 'transform', 't1'); - - $tables = $this->processTableAndIncludeParameters($database,$table,$include,$action); - $key = $this->processKeyParameter($key,$tables,$database); - $satisfy = $this->processSatisfyParameter($tables,$satisfy); - $filters = $this->processFiltersParameter($tables,$satisfy,$filters); - $page = $this->processPageParameter($page); - $orderings = $this->processOrderingsParameter($orderings); - - // reflection - list($tables,$collect,$select) = $this->findRelations($tables,$database,$auto_include); - $fields = $this->findFields($tables,$columns,$exclude,$select,$database); - - // permissions - if ($table_authorizer) $this->applyTableAuthorizer($table_authorizer,$action,$database,$tables); - if (!isset($tables[0])) $this->exitWith404('entity'); - if ($record_filter) $this->applyRecordFilter($record_filter,$action,$database,$tables,$filters); - if ($tenancy_function) $this->applyTenancyFunction($tenancy_function,$action,$database,$fields,$filters); - if ($column_authorizer) $this->applyColumnAuthorizer($column_authorizer,$action,$database,$fields); - - // input - $inputs = $this->retrieveInputs($post); - foreach ($inputs as $k=>$context) { - $input = $this->filterInputByFields($context,$fields[$tables[0]]); - - if ($tenancy_function) $this->applyInputTenancy($tenancy_function,$action,$database,$tables[0],$input,$fields[$tables[0]]); - if ($input_sanitizer) $this->applyInputSanitizer($input_sanitizer,$action,$database,$tables[0],$input,$fields[$tables[0]]); - if ($input_validator) $this->applyInputValidator($input_validator,$action,$database,$tables[0],$input,$fields[$tables[0]],$context); - - $this->convertInputs($input,$fields[$tables[0]]); - $inputs[$k] = $input; - } - - if ($before) { - $this->applyBeforeHandler($action,$database,$tables[0],$key[0],$before,$inputs); - } - - return compact('action','database','tables','key','page','filters','fields','orderings','transform','inputs','collect','select','before','after'); - } - - protected function addWhereFromFilters($filters,&$sql,&$params) { - $first = true; - if (isset($filters['or'])) { - $first = false; - $sql .= ' WHERE ('; - foreach ($filters['or'] as $i=>$filter) { - $sql .= $i==0?'':' OR '; - $sql .= $filter[0]; - for ($i=1;$i$filter) { - $sql .= $first?' WHERE ':' AND '; - $sql .= $filter[0]; - for ($i=1;$i$ordering) { - $sql .= $i==0?' ORDER BY ':', '; - $sql .= '! '.$ordering[1]; - $params[] = $ordering[0]; - } - } - - protected function listCommandInternal($parameters) { - extract($parameters); - echo '{'; - $table = array_shift($tables); - // first table - $count = false; - echo '"'.$table.'":{'; - if (is_array($orderings) && is_array($page)) { - $params = array(); - $sql = 'SELECT COUNT(*) FROM !'; - $params[] = $table; - if (isset($filters[$table])) { - $this->addWhereFromFilters($filters[$table],$sql,$params); - } - if ($result = $this->db->query($sql,$params)) { - while ($pages = $this->db->fetchRow($result)) { - $count = (int)$pages[0]; - } - } - } - $params = array(); - $sql = 'SELECT '; - $this->convertOutputs($sql,$params,$fields[$table]); - $sql .= ' FROM !'; - $params[] = $table; - if (isset($filters[$table])) { - $this->addWhereFromFilters($filters[$table],$sql,$params); - } - if (is_array($orderings)) { - $this->addOrderByFromOrderings($orderings,$sql,$params); - } - if (is_array($orderings) && is_array($page)) { - $sql = $this->db->addLimitToSql($sql,$page[1],$page[0]); - } - if ($result = $this->db->query($sql,$params)) { - echo '"columns":'; - $keys = array_keys($fields[$table]); - echo json_encode($keys); - $keys = array_flip($keys); - echo ',"records":['; - $first_row = true; - while ($row = $this->fetchRow($result,$fields[$table])) { - if ($first_row) $first_row = false; - else echo ','; - if (isset($collect[$table])) { - foreach (array_keys($collect[$table]) as $field) { - $collect[$table][$field][] = $row[$keys[$field]]; - } - } - echo json_encode($row); - } - $this->db->close($result); - echo ']'; - if ($count) echo ','; - } - if ($count) echo '"results":'.$count; - echo '}'; - // other tables - foreach ($tables as $t=>$table) { - echo ','; - echo '"'.$table.'":{'; - $params = array(); - $sql = 'SELECT '; - $this->convertOutputs($sql,$params,$fields[$table]); - $sql .= ' FROM !'; - $params[] = $table; - if (isset($select[$table])) { - echo '"relations":{'; - $first_row = true; - foreach ($select[$table] as $field => $path) { - $values = $collect[$path[0]][$path[1]]; - if ($values) { - $this->addFilter($filters,$table,'and',$field,'in',implode(',',$values)); - } - if ($first_row) $first_row = false; - else echo ','; - echo '"'.$field.'":"'.implode('.',$path).'"'; - } - echo '}'; - } - if (isset($filters[$table])) { - $this->addWhereFromFilters($filters[$table],$sql,$params); - } - if ($result = $this->db->query($sql,$params)) { - if (isset($select[$table])) echo ','; - echo '"columns":'; - $keys = array_keys($fields[$table]); - echo json_encode($keys); - $keys = array_flip($keys); - echo ',"records":['; - $first_row = true; - while ($row = $this->fetchRow($result,$fields[$table])) { - if ($first_row) $first_row = false; - else echo ','; - if (isset($collect[$table])) { - foreach (array_keys($collect[$table]) as $field) { - $collect[$table][$field][]=$row[$keys[$field]]; - } - } - echo json_encode($row); - } - $this->db->close($result); - echo ']'; - } - echo '}'; - } - echo '}'; - } - - protected function readCommand($parameters) { - extract($parameters); - if (count($key[0])>1) $object = $this->retrieveObjects($key,$fields,$filters,$tables); - else $object = $this->retrieveObject($key,$fields,$filters,$tables); - if (!$object) $this->exitWith404('object'); - $this->startOutput(); - echo json_encode($object); - return false; - } - - protected function createCommand($parameters) { - extract($parameters); - if (!$inputs || !$inputs[0]) $this->exitWith404('input'); - if (count($inputs)>1) return $this->createObjects($inputs,$tables); - return $this->createObject($inputs[0],$tables); - } - - protected function updateCommand($parameters) { - extract($parameters); - if (!$inputs || !$inputs[0]) $this->exitWith404('subject'); - if (count($inputs)>1) return $this->updateObjects($key,$inputs,$filters,$tables); - return $this->updateObject($key,$inputs[0],$filters,$tables); - } - - protected function deleteCommand($parameters) { - extract($parameters); - if (count($key[0])>1) return $this->deleteObjects($key,$filters,$tables); - return $this->deleteObject($key,$filters,$tables); - } - - protected function incrementCommand($parameters) { - extract($parameters); - if (!$inputs || !$inputs[0]) $this->exitWith404('subject'); - if (count($inputs)>1) return $this->incrementObjects($key,$inputs,$filters,$tables,$fields); - return $this->incrementObject($key,$inputs[0],$filters,$tables,$fields); - } - - protected function listCommand($parameters) { - extract($parameters); - $this->startOutput(); - if ($transform) { - ob_start(); - } - $this->listCommandInternal($parameters); - if ($transform) { - $content = ob_get_contents(); - ob_end_clean(); - $data = json_decode($content,true); - echo json_encode(self::php_crud_api_transform($data)); - } - return false; - } - - protected function retrievePostData() { - if ($_FILES) { - $files = array(); - foreach ($_FILES as $name => $file) { - foreach ($file as $key => $value) { - switch ($key) { - case 'tmp_name': $files[$name] = $value?base64_encode(file_get_contents($value)):''; break; - default: $files[$name.'_'.$key] = $value; - } - } - } - return http_build_query(array_merge($files,$_POST)); - } - return file_get_contents('php://input'); - } - - public function __construct($config) { - extract($config); - - // initialize - $dbengine = isset($dbengine)?$dbengine:null; - $hostname = isset($hostname)?$hostname:null; - $username = isset($username)?$username:null; - $password = isset($password)?$password:null; - $database = isset($database)?$database:null; - $port = isset($port)?$port:null; - $socket = isset($socket)?$socket:null; - $charset = isset($charset)?$charset:null; - - $table_authorizer = isset($table_authorizer)?$table_authorizer:null; - $record_filter = isset($record_filter)?$record_filter:null; - $column_authorizer = isset($column_authorizer)?$column_authorizer:null; - $tenancy_function = isset($tenancy_function)?$tenancy_function:null; - $input_sanitizer = isset($input_sanitizer)?$input_sanitizer:null; - $input_validator = isset($input_validator)?$input_validator:null; - $auto_include = isset($auto_include)?$auto_include:null; - $allow_origin = isset($allow_origin)?$allow_origin:null; - $before = isset($before)?$before:null; - $after = isset($after)?$after:null; - - $db = isset($db)?$db:null; - $method = isset($method)?$method:null; - $request = isset($request)?$request:null; - $get = isset($get)?$get:null; - $post = isset($post)?$post:null; - $origin = isset($origin)?$origin:null; - - // defaults - if (!$dbengine) { - $dbengine = 'MySQL'; - } - if (!$method) { - $method = $_SERVER['REQUEST_METHOD']; - } - if (!$request) { - $request = isset($_SERVER['PATH_INFO'])?$_SERVER['PATH_INFO']:''; - if (!$request) { - $request = isset($_SERVER['ORIG_PATH_INFO'])?$_SERVER['ORIG_PATH_INFO']:''; - $request = $request!=$_SERVER['SCRIPT_NAME']?$request:''; - } - } - if (!$get) { - $get = $_GET; - } - if (!$post) { - $post = $this->retrievePostData(); - } - if (!$origin) { - $origin = isset($_SERVER['HTTP_ORIGIN'])?$_SERVER['HTTP_ORIGIN']:''; - } - - // connect - $request = trim($request,'/'); - if (!$database) { - $database = $this->parseRequestParameter($request, 'a-zA-Z0-9\-_'); - } - if (!$db) { - $db = new $dbengine(); - if (!$charset) { - $charset = $db->getDefaultCharset(); - } - $db->connect($hostname,$username,$password,$database,$port,$socket,$charset); - } - if ($auto_include===null) { - $auto_include = true; - } - if ($allow_origin===null) { - $allow_origin = '*'; - } - - $this->db = $db; - $this->settings = compact('method', 'request', 'get', 'post', 'origin', 'database', 'table_authorizer', 'record_filter', 'column_authorizer', 'tenancy_function', 'input_sanitizer', 'input_validator', 'before', 'after', 'auto_include', 'allow_origin'); - } - - public static function php_crud_api_transform(&$tables) { - $get_objects = function (&$tables,$table_name,$where_index=false,$match_value=false) use (&$get_objects) { - $objects = array(); - if (isset($tables[$table_name]['records'])) { - foreach ($tables[$table_name]['records'] as $record) { - if ($where_index===false || $record[$where_index]==$match_value) { - $object = array(); - foreach ($tables[$table_name]['columns'] as $index=>$column) { - $object[$column] = $record[$index]; - foreach ($tables as $relation=>$reltable) { - if (isset($reltable['relations'])) { - foreach ($reltable['relations'] as $key=>$target) { - if ($target == "$table_name.$column") { - $column_indices = array_flip($reltable['columns']); - $object[$relation] = $get_objects($tables,$relation,$column_indices[$key],$record[$index]); - } - } - } - } - } - $objects[] = $object; - } - } - } - return $objects; - }; - $tree = array(); - foreach ($tables as $name=>$table) { - if (!isset($table['relations'])) { - $tree[$name] = $get_objects($tables,$name); - if (isset($table['results'])) { - $tree['_results'] = $table['results']; - } - } - } - return $tree; - } - - protected function swagger($settings) { - extract($settings); - - $tables = array(); - if ($result = $this->db->query($this->db->getSql('list_tables'),array($database))) { - while ($row = $this->db->fetchRow($result)) { - $table = array( - 'name'=>$row[0], - 'comments'=>$row[1], - 'root_actions'=>array( - array('name'=>'list','method'=>'get'), - array('name'=>'create','method'=>'post'), - ), - 'id_actions'=>array( - array('name'=>'read','method'=>'get'), - array('name'=>'update','method'=>'put'), - array('name'=>'delete','method'=>'delete'), - array('name'=>'increment','method'=>'patch'), - ), - ); - $tables[] = $table; - } - $this->db->close($result); - } - - $table_names = array_map(function($v){ return $v['name'];},$tables); - foreach ($tables as $t=>$table) { - $table_list = array($table['name']); - $table_fields = $this->findFields($table_list,false,false,false,$database); - - // extensions - $result = $this->db->query($this->db->getSql('reflect_belongs_to'),array($table_list[0],$table_names,$database,$database)); - while ($row = $this->db->fetchRow($result)) { - $table_fields[$table['name']][$row[1]]->references=array($row[2],$row[3]); - } - $result = $this->db->query($this->db->getSql('reflect_has_many'),array($table_names,$table_list[0],$database,$database)); - while ($row = $this->db->fetchRow($result)) { - $table_fields[$table['name']][$row[3]]->referenced[]=array($row[0],$row[1]); - } - $primaryKeys = $this->findPrimaryKeys($table_list[0],$database); - foreach ($primaryKeys as $primaryKey) { - $table_fields[$table['name']][$primaryKey]->primaryKey = true; - } - $result = $this->db->query($this->db->getSql('reflect_columns'),array($table_list[0],$database)); - while ($row = $this->db->fetchRow($result)) { - $table_fields[$table['name']][$row[0]]->required = strtolower($row[2])=='no' && $row[1]===null; - $table_fields[$table['name']][$row[0]]->{'x-nullable'} = strtolower($row[2])=='yes'; - $table_fields[$table['name']][$row[0]]->{'x-dbtype'} = $row[3]; - if ($this->db->isNumericType($table_fields[$table['name']][$row[0]])) { - if (strpos(strtolower($table_fields[$table['name']][$row[0]]->{'x-dbtype'}),'int')!==false) { - $table_fields[$table['name']][$row[0]]->type = 'integer'; - if ($row[1]!==null) $table_fields[$table['name']][$row[0]]->default = (int)$row[1]; - } else { - $table_fields[$table['name']][$row[0]]->type = 'number'; - if ($row[1]!==null) $table_fields[$table['name']][$row[0]]->default = (float)$row[1]; - } - } else { - if ($this->db->isBinaryType($table_fields[$table['name']][$row[0]])) { - $table_fields[$table['name']][$row[0]]->format = 'byte'; - } else if ($this->db->isGeometryType($table_fields[$table['name']][$row[0]])) { - $table_fields[$table['name']][$row[0]]->format = 'wkt'; - } else if ($this->db->isJsonType($table_fields[$table['name']][$row[0]])) { - $table_fields[$table['name']][$row[0]]->format = 'json'; - } - $table_fields[$table['name']][$row[0]]->type = 'string'; - if ($row[1]!==null) $table_fields[$table['name']][$row[0]]->default = $row[1]; - if ($row[4]!==null) $table_fields[$table['name']][$row[0]]->maxLength = (int)$row[4]; - } - } - - foreach (array('root_actions','id_actions') as $path) { - foreach ($table[$path] as $i=>$action) { - $table_list = array($table['name']); - $fields = $table_fields; - if ($table_authorizer) $this->applyTableAuthorizer($table_authorizer,$action['name'],$database,$table_list); - if ($column_authorizer) $this->applyColumnAuthorizer($column_authorizer,$action['name'],$database,$fields); - if (!$table_list || !$fields[$table['name']]) $tables[$t][$path][$i] = false; - else $tables[$t][$path][$i]['fields'] = $fields[$table['name']]; - } - // remove unauthorized tables and tables without fields - $tables[$t][$path] = array_values(array_filter($tables[$t][$path])); - } - if (!$tables[$t]['root_actions']&&!$tables[$t]['id_actions']) $tables[$t] = false; - } - $tables = array_merge(array_filter($tables)); - //var_dump($tables);die(); - - header('Content-Type: application/json; charset=utf-8'); - echo '{"swagger":"2.0",'; - echo '"info":{'; - echo '"title":"'.$database.'",'; - echo '"description":"API generated with [PHP-CRUD-API](https://github.com/mevdschee/php-crud-api)",'; - echo '"version":"1.0.0"'; - echo '},'; - echo '"host":"'.$_SERVER['HTTP_HOST'].'",'; - echo '"basePath":"'.$_SERVER['SCRIPT_NAME'].'",'; - echo '"schemes":["http'.((!empty($_SERVER['HTTPS'])&&$_SERVER['HTTPS']!=='off')?'s':'').'"],'; - echo '"consumes":["application/json"],'; - echo '"produces":["application/json"],'; - echo '"tags":['; - foreach ($tables as $i=>$table) { - if ($i>0) echo ','; - echo '{'; - echo '"name":"'.$table['name'].'",'; - echo '"description":"'.$table['comments'].'"'; - echo '}'; - } - echo '],'; - echo '"paths":{'; - foreach ($tables as $i=>$table) { - if ($table['root_actions']) { - if ($i>0) echo ','; - echo '"/'.$table['name'].'":{'; - foreach ($table['root_actions'] as $j=>$action) { - if ($j>0) echo ','; - echo '"'.$action['method'].'":{'; - echo '"tags":["'.$table['name'].'"],'; - echo '"summary":"'.ucfirst($action['name']).'",'; - if ($action['name']=='list') { - echo '"parameters":['; - echo '{'; - echo '"name":"exclude",'; - echo '"in":"query",'; - echo '"description":"One or more related entities (comma separated).",'; - echo '"required":false,'; - echo '"type":"string"'; - echo '},'; - echo '{'; - echo '"name":"include",'; - echo '"in":"query",'; - echo '"description":"One or more related entities (comma separated).",'; - echo '"required":false,'; - echo '"type":"string"'; - echo '},'; - echo '{'; - echo '"name":"order",'; - echo '"in":"query",'; - echo '"description":"Column you want to sort on and the sort direction (comma separated). Example: id,desc",'; - echo '"required":false,'; - echo '"type":"string"'; - echo '},'; - echo '{'; - echo '"name":"page",'; - echo '"in":"query",'; - echo '"description":"Page number and page size (comma separated). NB: You cannot use \"page\" without \"order\"! Example: 1,10",'; - echo '"required":false,'; - echo '"type":"string"'; - echo '},'; - echo '{'; - echo '"name":"transform",'; - echo '"in":"query",'; - echo '"description":"Transform the records to object format. NB: This can also be done client-side in JavaScript!",'; - echo '"required":false,'; - echo '"type":"boolean"'; - echo '},'; - echo '{'; - echo '"name":"columns",'; - echo '"in":"query",'; - echo '"description":"The table columns you want to retrieve (comma separated). Example: posts.*,categories.name",'; - echo '"required":false,'; - echo '"type":"string"'; - echo '},'; - echo '{'; - echo '"name":"filter[]",'; - echo '"in":"query",'; - echo '"description":"Filters to be applied. Each filter consists of a column, an operator and a value (comma separated). Example: id,eq,1",'; - echo '"required":false,'; - echo '"type":"array",'; - echo '"collectionFormat":"multi",'; - echo '"items":{"type":"string"}'; - echo '},'; - echo '{'; - echo '"name":"satisfy",'; - echo '"in":"query",'; - echo '"description":"Should all filters match (default)? Or any?",'; - echo '"required":false,'; - echo '"type":"string",'; - echo '"enum":["any"]'; - echo '}'; - echo '],'; - echo '"responses":{'; - echo '"200":{'; - echo '"description":"An array of '.$table['name'].'",'; - echo '"schema":{'; - echo '"type": "object",'; - echo '"properties": {'; - echo '"'.$table['name'].'": {'; - echo '"type":"array",'; - echo '"items":{'; - echo '"type": "object",'; - echo '"properties": {'; - foreach (array_keys($action['fields']) as $k=>$field) { - if ($k>0) echo ','; - echo '"'.$field.'": {'; - echo '"type": '.json_encode($action['fields'][$field]->type); - if (isset($action['fields'][$field]->format)) { - echo ',"format": '.json_encode($action['fields'][$field]->format); - } - echo ',"x-dbtype": '.json_encode($action['fields'][$field]->{'x-dbtype'}); - echo ',"x-nullable": '.json_encode($action['fields'][$field]->{'x-nullable'}); - if (isset($action['fields'][$field]->maxLength) && $action['fields'][$field]->maxLength>0) { - echo ',"maxLength": '.json_encode($action['fields'][$field]->maxLength); - } - if (isset($action['fields'][$field]->default)) { - echo ',"default": '.json_encode($action['fields'][$field]->default); - } - if (isset($action['fields'][$field]->referenced)) { - echo ',"x-referenced": '.json_encode($action['fields'][$field]->referenced); - } - if (isset($action['fields'][$field]->references)) { - echo ',"x-references": '.json_encode($action['fields'][$field]->references); - } - if (isset($action['fields'][$field]->primaryKey)) { - echo ',"x-primary-key": true'; - } - echo '}'; - } - echo '}'; //properties - echo '}'; //items - echo '}'; //table - echo '}'; //properties - echo '}'; //schema - echo '}'; //200 - echo '}'; //responses - } - if ($action['name']=='create') { - echo '"parameters":[{'; - echo '"name":"item",'; - echo '"in":"body",'; - echo '"description":"Item to create.",'; - echo '"required":true,'; - echo '"schema":{'; - echo '"type": "object",'; - $required_fields = array_keys(array_filter($action['fields'],function($f){ return $f->required; })); - if (count($required_fields) > 0) { - echo '"required":'.json_encode($required_fields).','; - } - echo '"properties": {'; - foreach (array_keys($action['fields']) as $k=>$field) { - if ($k>0) echo ','; - echo '"'.$field.'": {'; - echo '"type": '.json_encode($action['fields'][$field]->type); - if (isset($action['fields'][$field]->format)) { - echo ',"format": '.json_encode($action['fields'][$field]->format); - } - echo ',"x-dbtype": '.json_encode($action['fields'][$field]->{'x-dbtype'}); - echo ',"x-nullable": '.json_encode($action['fields'][$field]->{'x-nullable'}); - if (isset($action['fields'][$field]->maxLength)) { - echo ',"maxLength": '.json_encode($action['fields'][$field]->maxLength); - } - if (isset($action['fields'][$field]->default)) { - echo ',"default": '.json_encode($action['fields'][$field]->default); - } - if (isset($action['fields'][$field]->referenced)) { - echo ',"x-referenced": '.json_encode($action['fields'][$field]->referenced); - } - if (isset($action['fields'][$field]->references)) { - echo ',"x-references": '.json_encode($action['fields'][$field]->references); - } - if (isset($action['fields'][$field]->primaryKey)) { - echo ',"x-primary-key": true'; - } - echo '}'; - } - echo '}'; //properties - echo '}'; //schema - echo '}],'; - echo '"responses":{'; - echo '"200":{'; - echo '"description":"Identifier of created item.",'; - echo '"schema":{'; - echo '"type":"integer"'; - echo '}';//schema - echo '}';//200 - echo '}';//responses - } - echo '}';//method - } - echo '}'; - } - if ($table['id_actions']) { - if ($i>0 || $table['root_actions']) echo ','; - echo '"/'.$table['name'].'/{id}":{'; - foreach ($table['id_actions'] as $j=>$action) { - if ($j>0) echo ','; - echo '"'.$action['method'].'":{'; - echo '"tags":["'.$table['name'].'"],'; - echo '"summary":"'.ucfirst($action['name']).'",'; - echo '"parameters":['; - echo '{'; - echo '"name":"id",'; - echo '"in":"path",'; - echo '"description":"Identifier for item.",'; - echo '"required":true,'; - echo '"type":"string"'; - echo '}'; - if ($action['name']=='update' || $action['name']=='increment') { - echo ',{'; - echo '"name":"item",'; - echo '"in":"body",'; - echo '"description":"Properties of item to update.",'; - echo '"required":true,'; - echo '"schema":{'; - echo '"type": "object",'; - $required_fields = array_keys(array_filter($action['fields'],function($f){ return $f->required; })); - if (count($required_fields) > 0) { - echo '"required":'.json_encode($required_fields).','; - } - echo '"properties": {'; - foreach (array_keys($action['fields']) as $k=>$field) { - if ($k>0) echo ','; - echo '"'.$field.'": {'; - echo '"type": '.json_encode($action['fields'][$field]->type); - if (isset($action['fields'][$field]->format)) { - echo ',"format": '.json_encode($action['fields'][$field]->format); - } - echo ',"x-dbtype": '.json_encode($action['fields'][$field]->{'x-dbtype'}); - echo ',"x-nullable": '.json_encode($action['fields'][$field]->{'x-nullable'}); - if (isset($action['fields'][$field]->maxLength)) { - echo ',"maxLength": '.json_encode($action['fields'][$field]->maxLength); - } - if (isset($action['fields'][$field]->default)) { - echo ',"default": '.json_encode($action['fields'][$field]->default); - } - if (isset($action['fields'][$field]->referenced)) { - echo ',"x-referenced": '.json_encode($action['fields'][$field]->referenced); - } - if (isset($action['fields'][$field]->references)) { - echo ',"x-references": '.json_encode($action['fields'][$field]->references); - } - if (isset($action['fields'][$field]->primaryKey)) { - echo ',"x-primary-key": true'; - } - echo '}'; - } - echo '}'; //properties - echo '}'; //schema - echo '}'; - } - echo '],'; - if ($action['name']=='read') { - echo '"responses":{'; - echo '"200":{'; - echo '"description":"The requested item.",'; - echo '"schema":{'; - echo '"type": "object",'; - echo '"properties": {'; - foreach (array_keys($action['fields']) as $k=>$field) { - if ($k>0) echo ','; - echo '"'.$field.'": {'; - echo '"type": '.json_encode($action['fields'][$field]->type); - if (isset($action['fields'][$field]->format)) { - echo ',"format": '.json_encode($action['fields'][$field]->format); - } - echo ',"x-dbtype": '.json_encode($action['fields'][$field]->{'x-dbtype'}); - echo ',"x-nullable": '.json_encode($action['fields'][$field]->{'x-nullable'}); - if (isset($action['fields'][$field]->maxLength)) { - echo ',"maxLength": '.json_encode($action['fields'][$field]->maxLength); - } - if (isset($action['fields'][$field]->default)) { - echo ',"default": '.json_encode($action['fields'][$field]->default); - } - if (isset($action['fields'][$field]->referenced)) { - echo ',"x-referenced": '.json_encode($action['fields'][$field]->referenced); - } - if (isset($action['fields'][$field]->references)) { - echo ',"x-references": '.json_encode($action['fields'][$field]->references); - } - if (isset($action['fields'][$field]->primaryKey)) { - echo ',"x-primary-key": true'; - } - echo '}'; - } - echo '}'; //properties - echo '}'; //schema - echo '}'; - echo '}'; - } else { - echo '"responses":{'; - echo '"200":{'; - echo '"description":"Number of affected rows.",'; - echo '"schema":{'; - echo '"type":"integer"'; - echo '}'; - echo '}'; - echo '}'; - } - echo '}'; - } - echo '}'; - } - } - echo '}'; - echo '}'; - } - - protected function allowOrigin($origin,$allowOrigins) { - if (isset($_SERVER['REQUEST_METHOD'])) { - header('Access-Control-Allow-Credentials: true'); - foreach (explode(',',$allowOrigins) as $o) { - if (preg_match('/^'.str_replace('\*','.*',preg_quote(strtolower(trim($o)))).'$/',$origin)) { - header('Access-Control-Allow-Origin: '.$origin); - break; - } - } - } - } - - public function executeCommand() { - if ($this->settings['origin']) { - $this->allowOrigin($this->settings['origin'],$this->settings['allow_origin']); - } - if (!$this->settings['request']) { - $this->swagger($this->settings); - } else { - $parameters = $this->getParameters($this->settings); - switch($parameters['action']){ - case 'list': $output = $this->listCommand($parameters); break; - case 'read': $output = $this->readCommand($parameters); break; - case 'create': $output = $this->createCommand($parameters); break; - case 'update': $output = $this->updateCommand($parameters); break; - case 'delete': $output = $this->deleteCommand($parameters); break; - case 'increment': $output = $this->incrementCommand($parameters); break; - case 'headers': $output = $this->headersCommand($parameters); break; - default: $output = false; - } - if ($output!==false) { - $this->startOutput(); - echo json_encode($output); - } - if ($parameters['after']) { - $this->applyAfterHandler($parameters,$output); - } - } - } -} - -// require 'auth.php'; // from the PHP-API-AUTH project, see: https://github.com/mevdschee/php-api-auth - -// uncomment the lines below for token+session based authentication (see "login_token.html" + "login_token.php"): - -// $auth = new PHP_API_AUTH(array( -// 'secret'=>'someVeryLongPassPhraseChangeMe', -// )); -// if ($auth->executeCommand()) exit(0); -// if (empty($_SESSION['user']) || !$auth->hasValidCsrfToken()) { -// header('HTTP/1.0 401 Unauthorized'); -// exit(0); -// } - -// uncomment the lines below for form+session based authentication (see "login.html"): - -// $auth = new PHP_API_AUTH(array( -// 'authenticator'=>function($user,$pass){ $_SESSION['user']=($user=='admin' && $pass=='admin'); } -// )); -// if ($auth->executeCommand()) exit(0); -// if (empty($_SESSION['user']) || !$auth->hasValidCsrfToken()) { -// header('HTTP/1.0 401 Unauthorized'); -// exit(0); -// } - -// uncomment the lines below when running in stand-alone mode: - - $api = new PHP_CRUD_API(array( - 'dbengine'=>'MySQL', - 'hostname'=>'localhost', - 'username'=>'lazyp_workadmin', - 'password'=>'GH5fZF0iCtLnHLrz', - 'database'=>'LudosData', - 'charset'=>'utf8mb4' - )); - $api->executeCommand(); - -// For Microsoft SQL Server 2012 use: - -// $api = new PHP_CRUD_API(array( -// 'dbengine'=>'SQLServer', -// 'hostname'=>'(local)', -// 'username'=>'', -// 'password'=>'', -// 'database'=>'xxx', -// 'charset'=>'UTF-8' -// )); -// $api->executeCommand(); - -// For PostgreSQL 9 use: - -// $api = new PHP_CRUD_API(array( -// 'dbengine'=>'PostgreSQL', -// 'hostname'=>'localhost', -// 'username'=>'xxx', -// 'password'=>'xxx', -// 'database'=>'xxx', -// 'charset'=>'UTF8' -// )); -// $api->executeCommand(); - -// For SQLite 3 use: - -// $api = new PHP_CRUD_API(array( -// 'dbengine'=>'SQLite', -// 'database'=>'data/blog.db', -// )); -// $api->executeCommand(); diff --git a/interfaceServices/imageUpload.php b/interfaceServices/imageUpload.php deleted file mode 100644 index 3453cd8..0000000 --- a/interfaceServices/imageUpload.php +++ /dev/null @@ -1,79 +0,0 @@ -uploaded ){ - $handle->file_new_name_body = $newFileName; - $handle->image_resize = true; - $handle->image_x = 250; - $handle->image_ratio_y = true; - $handle->image_convert = 'png'; - $handle->process( $target_dir ); - - if( $handle->processed ){ - $handle->clean(); - echo $newFileName; - }else{ - echo 'error : ' . $handle->error; - } -} - - - -/* -// Check if image file is a actual image or fake image -//if(isset($_POST["submit"])) { - $check = getimagesize($_FILES["fileToUpload"]["tmp_name"]); - if($check !== false) { - //echo "File is an image - " . $check["mime"] . "."; - $uploadOk = 1; - } else { - //echo "File is not an image."; - $uploadOk = 0; - } -//} -// Check if file already exists -if (file_exists($target_file)) { - //echo "Sorry, file already exists."; - $uploadOk = 0; -} -// Check file size -if ($_FILES["fileToUpload"]["size"] > 5000000) { - //echo "Sorry, your file is too large."; - $uploadOk = 0; -} -// Allow certain file formats -//echo("|" . $imageFileType . "|
"); -if($imageFileType != "jpg" && $imageFileType != "png" && $imageFileType != "jpeg" -&& $imageFileType != "gif" ) { - //echo "Sorry, only JPG, JPEG, PNG & GIF files are allowed."; - $uploadOk = 0; -} -// Check if $uploadOk is set to 0 by an error -if ($uploadOk == 0) { - echo( 0 ); - //echo "Sorry, your file was not uploaded."; - // if everything is ok, try to upload file -} else { - //echo( $target_file ); - if (move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], $target_file)) { - //echo "The file ". basename( $_FILES["fileToUpload"]["name"]). " has been uploaded."; - echo( 1 ); - } else { - //echo "Sorry, there was an error uploading your file."; - echo( 0 ); - } -} -*/ -?> diff --git a/interfaceServices/jwttest2_NOT_USED.php b/interfaceServices/jwttest2_NOT_USED.php deleted file mode 100644 index a4c458c..0000000 --- a/interfaceServices/jwttest2_NOT_USED.php +++ /dev/null @@ -1,21 +0,0 @@ -setIssuer("http://pugludos.com") - ->setIssuedAt(time()) - ->setExpiration(time() + 3600) - ->set("userName", "ckoch") - ->sign($signer, "testing") - ->getToken(); - -echo( $token ); - -/* used to verify token */ -var_dump($token->verify($signer, 'testing')); - -?> \ No newline at end of file diff --git a/interfaceServices/loginInterface.php b/interfaceServices/loginInterface.php deleted file mode 100644 index faca087..0000000 --- a/interfaceServices/loginInterface.php +++ /dev/null @@ -1,63 +0,0 @@ -prepare('SELECT * FROM users WHERE userName = :userName'); -$stmt->execute(array( - ':userName' => $userName - )); - -$data = $stmt->fetch(PDO::FETCH_ASSOC); - -if( $data == false ){ - http_response_code(400); - die(); -}else { - if( hash_equals($hashedPassword,$data['password'] ) ) { - if( $data['userName'] == "ckoch" ){ - $signer = new Sha256(); - $token = (new Builder()) - ->setIssuer("http://pugludos.com") - ->setIssuedAt(time()) - ->set("userName", $data['userId']) - ->sign($signer, "testing") - ->getToken(); - - $userData = array(); - $userDatap["id"] = $data['userId']; - $userDatap["username"] = $data['userName']; - $userDatap["firstName"] = $data['firstName']; - $userDatap["lastName"] = $data['lastName']; - $userDatap["email"] = $data['email']; - $userDatap["art"] = $data['art']; - $userDatap["token"] = (string)$token; - - echo( json_encode( $userDatap ) ); - die(); - }else{ - http_response_code(400); - die(); - } - }else{ - http_response_code(400); - die(); - } -} - -?> \ No newline at end of file diff --git a/interfaceServices/phpcheck_NOT_USED.php b/interfaceServices/phpcheck_NOT_USED.php deleted file mode 100644 index 968c8df..0000000 --- a/interfaceServices/phpcheck_NOT_USED.php +++ /dev/null @@ -1,3 +0,0 @@ - \ No newline at end of file diff --git a/interfaceServices/registrationCreation_NOT_USED.php b/interfaceServices/registrationCreation_NOT_USED.php deleted file mode 100644 index 5e58261..0000000 --- a/interfaceServices/registrationCreation_NOT_USED.php +++ /dev/null @@ -1,56 +0,0 @@ -getTimestamp() . $userName; - -$passwordSalt = "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"; - -$hashedPassword = crypt( $password, $passwordSalt ); -$hashedId = crypt( $id, $passwordSalt ); - -/* -For login: -if (hash_equals($hashed_password, crypt($user_input, $hashed_password))) { - echo "Password verified!"; -} -*/ - -$returnData["password"] = $hashedPassword; -$returnData["id"] = $hashedId; - -//echo( json_encode( $returnData ) ); - -$url = 'http://192.241.155.78/api.php/users/'; -$fields = array( - 'firstName' => urlencode( $firstName ), - 'lastName' => urlencode( $lastName ), - 'email' => urlencode( $email ), - 'userName' => urlencode( $userName ), - 'password' => urlencode( $hashedPassword ), - 'userId' => urlencode( $hashedId ), - 'id' => urlencode( $newUser ) -); - -$ch = curl_init(); - -curl_setopt($ch,CURLOPT_URL, $url); -curl_setopt($ch, CURLOPT_POST, 1); -curl_setopt($ch,CURLOPT_POSTFIELDS, json_encode($fields)); -curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - -$result = curl_exec( $ch ); - -curl_close( $ch ); - -echo( $result ); - -?> \ No newline at end of file diff --git a/interfaceServices/registrationInterface.php b/interfaceServices/registrationInterface.php deleted file mode 100644 index 3c60ed9..0000000 --- a/interfaceServices/registrationInterface.php +++ /dev/null @@ -1,2775 +0,0 @@ -queries = array( - 'list_tables'=>'SELECT - "TABLE_NAME","TABLE_COMMENT" - FROM - "INFORMATION_SCHEMA"."TABLES" - WHERE - "TABLE_SCHEMA" = ?', - 'reflect_table'=>'SELECT - "TABLE_NAME" - FROM - "INFORMATION_SCHEMA"."TABLES" - WHERE - "TABLE_NAME" COLLATE \'utf8_bin\' = ? AND - "TABLE_SCHEMA" = ?', - 'reflect_pk'=>'SELECT - "COLUMN_NAME" - FROM - "INFORMATION_SCHEMA"."COLUMNS" - WHERE - "COLUMN_KEY" = \'PRI\' AND - "TABLE_NAME" = ? AND - "TABLE_SCHEMA" = ?', - 'reflect_belongs_to'=>'SELECT - "TABLE_NAME","COLUMN_NAME", - "REFERENCED_TABLE_NAME","REFERENCED_COLUMN_NAME" - FROM - "INFORMATION_SCHEMA"."KEY_COLUMN_USAGE" - WHERE - "TABLE_NAME" COLLATE \'utf8_bin\' = ? AND - "REFERENCED_TABLE_NAME" COLLATE \'utf8_bin\' IN ? AND - "TABLE_SCHEMA" = ? AND - "REFERENCED_TABLE_SCHEMA" = ?', - 'reflect_has_many'=>'SELECT - "TABLE_NAME","COLUMN_NAME", - "REFERENCED_TABLE_NAME","REFERENCED_COLUMN_NAME" - FROM - "INFORMATION_SCHEMA"."KEY_COLUMN_USAGE" - WHERE - "TABLE_NAME" COLLATE \'utf8_bin\' IN ? AND - "REFERENCED_TABLE_NAME" COLLATE \'utf8_bin\' = ? AND - "TABLE_SCHEMA" = ? AND - "REFERENCED_TABLE_SCHEMA" = ?', - 'reflect_habtm'=>'SELECT - k1."TABLE_NAME", k1."COLUMN_NAME", - k1."REFERENCED_TABLE_NAME", k1."REFERENCED_COLUMN_NAME", - k2."TABLE_NAME", k2."COLUMN_NAME", - k2."REFERENCED_TABLE_NAME", k2."REFERENCED_COLUMN_NAME" - FROM - "INFORMATION_SCHEMA"."KEY_COLUMN_USAGE" k1, - "INFORMATION_SCHEMA"."KEY_COLUMN_USAGE" k2 - WHERE - k1."TABLE_SCHEMA" = ? AND - k2."TABLE_SCHEMA" = ? AND - k1."REFERENCED_TABLE_SCHEMA" = ? AND - k2."REFERENCED_TABLE_SCHEMA" = ? AND - k1."TABLE_NAME" COLLATE \'utf8_bin\' = k2."TABLE_NAME" COLLATE \'utf8_bin\' AND - k1."REFERENCED_TABLE_NAME" COLLATE \'utf8_bin\' = ? AND - k2."REFERENCED_TABLE_NAME" COLLATE \'utf8_bin\' IN ?', - 'reflect_columns'=> 'SELECT - "COLUMN_NAME", "COLUMN_DEFAULT", "IS_NULLABLE", "DATA_TYPE", "CHARACTER_MAXIMUM_LENGTH" - FROM - "INFORMATION_SCHEMA"."COLUMNS" - WHERE - "TABLE_NAME" = ? AND - "TABLE_SCHEMA" = ? - ORDER BY - "ORDINAL_POSITION"' - ); - } - - public function getSql($name) { - return isset($this->queries[$name])?$this->queries[$name]:false; - } - - public function connect($hostname,$username,$password,$database,$port,$socket,$charset) { - $db = mysqli_init(); - if (defined('MYSQLI_OPT_INT_AND_FLOAT_NATIVE')) { - mysqli_options($db,MYSQLI_OPT_INT_AND_FLOAT_NATIVE,true); - } - $success = mysqli_real_connect($db,$hostname,$username,$password,$database,$port,$socket,MYSQLI_CLIENT_FOUND_ROWS); - if (!$success) { - throw new \Exception('Connect failed. '.mysqli_connect_error()); - } - if (!mysqli_set_charset($db,$charset)) { - throw new \Exception('Error setting charset. '.mysqli_error($db)); - } - if (!mysqli_query($db,'SET SESSION sql_mode = \'ANSI_QUOTES\';')) { - throw new \Exception('Error setting ANSI quotes. '.mysqli_error($db)); - } - $this->db = $db; - } - - public function query($sql,$params=array()) { - $db = $this->db; - $sql = preg_replace_callback('/\!|\?/', function ($matches) use (&$db,&$params) { - $param = array_shift($params); - if ($matches[0]=='!') { - $key = preg_replace('/[^a-zA-Z0-9\-_=<> ]/','',is_object($param)?$param->key:$param); - if (is_object($param) && $param->type=='hex') { - return "HEX(\"$key\") as \"$key\""; - } - if (is_object($param) && $param->type=='wkt') { - return "ST_AsText(\"$key\") as \"$key\""; - } - return '"'.$key.'"'; - } else { - if (is_array($param)) return '('.implode(',',array_map(function($v) use (&$db) { - return "'".mysqli_real_escape_string($db,$v)."'"; - },$param)).')'; - if (is_object($param) && $param->type=='hex') { - return "x'".$param->value."'"; - } - if (is_object($param) && $param->type=='wkt') { - return "ST_GeomFromText('".mysqli_real_escape_string($db,$param->value)."')"; - } - if ($param===null) return 'NULL'; - return "'".mysqli_real_escape_string($db,$param)."'"; - } - }, $sql); - //if (!strpos($sql,'INFORMATION_SCHEMA')) echo "\n$sql\n"; - //if (!strpos($sql,'INFORMATION_SCHEMA')) file_put_contents('log.txt',"\n$sql\n",FILE_APPEND); - return mysqli_query($db,$sql); - } - - public function fetchAssoc($result) { - return mysqli_fetch_assoc($result); - } - - public function fetchRow($result) { - return mysqli_fetch_row($result); - } - - public function insertId($result) { - return mysqli_insert_id($this->db); - } - - public function affectedRows($result) { - return mysqli_affected_rows($this->db); - } - - public function close($result) { - return mysqli_free_result($result); - } - - public function fetchFields($table) { - $result = $this->query('SELECT * FROM ! WHERE 1=2;',array($table)); - return mysqli_fetch_fields($result); - } - - public function addLimitToSql($sql,$limit,$offset) { - return "$sql LIMIT $limit OFFSET $offset"; - } - - public function likeEscape($string) { - return addcslashes($string,'%_'); - } - - public function convertFilter($field, $comparator, $value) { - return false; - } - - public function isNumericType($field) { - return in_array($field->type,array(1,2,3,4,5,6,8,9)); - } - - public function isBinaryType($field) { - //echo "$field->name: $field->type ($field->flags)\n"; - return (($field->flags & 128) && (($field->type>=249 && $field->type<=252) || ($field->type>=253 && $field->type<=254 && $field->charsetnr==63))); - } - - public function isGeometryType($field) { - return ($field->type==255); - } - - public function isJsonType($field) { - return ($field->type==245); - } - - public function getDefaultCharset() { - return 'utf8'; - } - - public function beginTransaction() { - mysqli_query($this->db,'BEGIN'); - //return mysqli_begin_transaction($this->db); - } - - public function commitTransaction() { - mysqli_query($this->db,'COMMIT'); - //return mysqli_commit($this->db); - } - - public function rollbackTransaction() { - mysqli_query($this->db,'ROLLBACK'); - //return mysqli_rollback($this->db); - } - - public function jsonEncode($object) { - return json_encode($object); - } - - public function jsonDecode($string) { - return json_decode($string); - } -} - -class PostgreSQL implements DatabaseInterface { - - protected $db; - protected $queries; - - public function __construct() { - $this->queries = array( - 'list_tables'=>'select - "table_name",\'\' as "table_comment" - from - "information_schema"."tables" - where - "table_schema" = \'public\' and - "table_catalog" = ?', - 'reflect_table'=>'select - "table_name" - from - "information_schema"."tables" - where - "table_name" = ? and - "table_schema" = \'public\' and - "table_catalog" = ?', - 'reflect_pk'=>'select - "column_name" - from - "information_schema"."table_constraints" tc, - "information_schema"."key_column_usage" ku - where - tc."constraint_type" = \'PRIMARY KEY\' and - tc."constraint_name" = ku."constraint_name" and - ku."table_name" = ? and - ku."table_schema" = \'public\' and - ku."table_catalog" = ?', - 'reflect_belongs_to'=>'select - cu1."table_name",cu1."column_name", - cu2."table_name",cu2."column_name" - from - "information_schema".referential_constraints rc, - "information_schema".key_column_usage cu1, - "information_schema".key_column_usage cu2 - where - cu1."constraint_name" = rc."constraint_name" and - cu2."constraint_name" = rc."unique_constraint_name" and - cu1."table_name" = ? and - cu2."table_name" in ? and - cu1."table_schema" = \'public\' and - cu2."table_schema" = \'public\' and - cu1."table_catalog" = ? and - cu2."table_catalog" = ?', - 'reflect_has_many'=>'select - cu1."table_name",cu1."column_name", - cu2."table_name",cu2."column_name" - from - "information_schema".referential_constraints rc, - "information_schema".key_column_usage cu1, - "information_schema".key_column_usage cu2 - where - cu1."constraint_name" = rc."constraint_name" and - cu2."constraint_name" = rc."unique_constraint_name" and - cu1."table_name" in ? and - cu2."table_name" = ? and - cu1."table_schema" = \'public\' and - cu2."table_schema" = \'public\' and - cu1."table_catalog" = ? and - cu2."table_catalog" = ?', - 'reflect_habtm'=>'select - cua1."table_name",cua1."column_name", - cua2."table_name",cua2."column_name", - cub1."table_name",cub1."column_name", - cub2."table_name",cub2."column_name" - from - "information_schema".referential_constraints rca, - "information_schema".referential_constraints rcb, - "information_schema".key_column_usage cua1, - "information_schema".key_column_usage cua2, - "information_schema".key_column_usage cub1, - "information_schema".key_column_usage cub2 - where - cua1."constraint_name" = rca."constraint_name" and - cua2."constraint_name" = rca."unique_constraint_name" and - cub1."constraint_name" = rcb."constraint_name" and - cub2."constraint_name" = rcb."unique_constraint_name" and - cua1."table_catalog" = ? and - cub1."table_catalog" = ? and - cua2."table_catalog" = ? and - cub2."table_catalog" = ? and - cua1."table_schema" = \'public\' and - cub1."table_schema" = \'public\' and - cua2."table_schema" = \'public\' and - cub2."table_schema" = \'public\' and - cua1."table_name" = cub1."table_name" and - cua2."table_name" = ? and - cub2."table_name" in ?', - 'reflect_columns'=> 'select - "column_name", "column_default", "is_nullable", "data_type", "character_maximum_length" - from - "information_schema"."columns" - where - "table_name" = ? and - "table_schema" = \'public\' and - "table_catalog" = ? - order by - "ordinal_position"' - ); - } - - public function getSql($name) { - return isset($this->queries[$name])?$this->queries[$name]:false; - } - - public function connect($hostname,$username,$password,$database,$port,$socket,$charset) { - $e = function ($v) { return str_replace(array('\'','\\'),array('\\\'','\\\\'),$v); }; - $conn_string = ''; - if ($hostname || $socket) { - if ($socket) $hostname = $e($socket); - else $hostname = $e($hostname); - $conn_string.= " host='$hostname'"; - } - if ($port) { - $port = ($port+0); - $conn_string.= " port='$port'"; - } - if ($database) { - $database = $e($database); - $conn_string.= " dbname='$database'"; - } - if ($username) { - $username = $e($username); - $conn_string.= " user='$username'"; - } - if ($password) { - $password = $e($password); - $conn_string.= " password='$password'"; - } - if ($charset) { - $charset = $e($charset); - $conn_string.= " options='--client_encoding=$charset'"; - } - $db = pg_connect($conn_string); - $this->db = $db; - } - - public function query($sql,$params=array()) { - $db = $this->db; - $sql = preg_replace_callback('/\!|\?/', function ($matches) use (&$db,&$params) { - $param = array_shift($params); - if ($matches[0]=='!') { - $key = preg_replace('/[^a-zA-Z0-9\-_=<> ]/','',is_object($param)?$param->key:$param); - if (is_object($param) && $param->type=='hex') { - return "encode(\"$key\",'hex') as \"$key\""; - } - if (is_object($param) && $param->type=='wkt') { - return "ST_AsText(\"$key\") as \"$key\""; - } - return '"'.$key.'"'; - } else { - if (is_array($param)) return '('.implode(',',array_map(function($v) use (&$db) { - return "'".pg_escape_string($db,$v)."'"; - },$param)).')'; - if (is_object($param) && $param->type=='hex') { - return "'\x".$param->value."'"; - } - if (is_object($param) && $param->type=='wkt') { - return "ST_GeomFromText('".pg_escape_string($db,$param->value)."')"; - } - if ($param===null) return 'NULL'; - return "'".pg_escape_string($db,$param)."'"; - } - }, $sql); - if (strtoupper(substr($sql,0,6))=='INSERT') { - $sql .= ' RETURNING id;'; - } - //echo "\n$sql\n"; - return @pg_query($db,$sql); - } - - public function fetchAssoc($result) { - return pg_fetch_assoc($result); - } - - public function fetchRow($result) { - return pg_fetch_row($result); - } - - public function insertId($result) { - list($id) = pg_fetch_row($result); - return (int)$id; - } - - public function affectedRows($result) { - return pg_affected_rows($result); - } - - public function close($result) { - return pg_free_result($result); - } - - public function fetchFields($table) { - $result = $this->query('SELECT * FROM ! WHERE 1=2;',array($table)); - $keys = array(); - for($i=0;$itype, array('int2', 'int4', 'int8', 'float4', 'float8')); - } - - public function isBinaryType($field) { - return $field->type == 'bytea'; - } - - public function isGeometryType($field) { - return $field->type == 'geometry'; - } - - public function isJsonType($field) { - return in_array($field->type,array('json','jsonb')); - } - - public function getDefaultCharset() { - return 'UTF8'; - } - - public function beginTransaction() { - return $this->query('BEGIN'); - } - - public function commitTransaction() { - return $this->query('COMMIT'); - } - - public function rollbackTransaction() { - return $this->query('ROLLBACK'); - } - - public function jsonEncode($object) { - return json_encode($object); - } - - public function jsonDecode($string) { - return json_decode($string); - } -} - -class SQLServer implements DatabaseInterface { - - protected $db; - protected $queries; - - public function __construct() { - $this->queries = array( - 'list_tables'=>'SELECT - "TABLE_NAME",\'\' as "TABLE_COMMENT" - FROM - "INFORMATION_SCHEMA"."TABLES" - WHERE - "TABLE_CATALOG" = ?', - 'reflect_table'=>'SELECT - "TABLE_NAME" - FROM - "INFORMATION_SCHEMA"."TABLES" - WHERE - "TABLE_NAME" = ? AND - "TABLE_CATALOG" = ?', - 'reflect_pk'=>'SELECT - "COLUMN_NAME" - FROM - "INFORMATION_SCHEMA"."TABLE_CONSTRAINTS" tc, - "INFORMATION_SCHEMA"."KEY_COLUMN_USAGE" ku - WHERE - tc."CONSTRAINT_TYPE" = \'PRIMARY KEY\' AND - tc."CONSTRAINT_NAME" = ku."CONSTRAINT_NAME" AND - ku."TABLE_NAME" = ? AND - ku."TABLE_CATALOG" = ?', - 'reflect_belongs_to'=>'SELECT - cu1."TABLE_NAME",cu1."COLUMN_NAME", - cu2."TABLE_NAME",cu2."COLUMN_NAME" - FROM - "INFORMATION_SCHEMA".REFERENTIAL_CONSTRAINTS rc, - "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cu1, - "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cu2 - WHERE - cu1."CONSTRAINT_NAME" = rc."CONSTRAINT_NAME" AND - cu2."CONSTRAINT_NAME" = rc."UNIQUE_CONSTRAINT_NAME" AND - cu1."TABLE_NAME" = ? AND - cu2."TABLE_NAME" IN ? AND - cu1."TABLE_CATALOG" = ? AND - cu2."TABLE_CATALOG" = ?', - 'reflect_has_many'=>'SELECT - cu1."TABLE_NAME",cu1."COLUMN_NAME", - cu2."TABLE_NAME",cu2."COLUMN_NAME" - FROM - "INFORMATION_SCHEMA".REFERENTIAL_CONSTRAINTS rc, - "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cu1, - "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cu2 - WHERE - cu1."CONSTRAINT_NAME" = rc."CONSTRAINT_NAME" AND - cu2."CONSTRAINT_NAME" = rc."UNIQUE_CONSTRAINT_NAME" AND - cu1."TABLE_NAME" IN ? AND - cu2."TABLE_NAME" = ? AND - cu1."TABLE_CATALOG" = ? AND - cu2."TABLE_CATALOG" = ?', - 'reflect_habtm'=>'SELECT - cua1."TABLE_NAME",cua1."COLUMN_NAME", - cua2."TABLE_NAME",cua2."COLUMN_NAME", - cub1."TABLE_NAME",cub1."COLUMN_NAME", - cub2."TABLE_NAME",cub2."COLUMN_NAME" - FROM - "INFORMATION_SCHEMA".REFERENTIAL_CONSTRAINTS rca, - "INFORMATION_SCHEMA".REFERENTIAL_CONSTRAINTS rcb, - "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cua1, - "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cua2, - "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cub1, - "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cub2 - WHERE - cua1."CONSTRAINT_NAME" = rca."CONSTRAINT_NAME" AND - cua2."CONSTRAINT_NAME" = rca."UNIQUE_CONSTRAINT_NAME" AND - cub1."CONSTRAINT_NAME" = rcb."CONSTRAINT_NAME" AND - cub2."CONSTRAINT_NAME" = rcb."UNIQUE_CONSTRAINT_NAME" AND - cua1."TABLE_CATALOG" = ? AND - cub1."TABLE_CATALOG" = ? AND - cua2."TABLE_CATALOG" = ? AND - cub2."TABLE_CATALOG" = ? AND - cua1."TABLE_NAME" = cub1."TABLE_NAME" AND - cua2."TABLE_NAME" = ? AND - cub2."TABLE_NAME" IN ?', - 'reflect_columns'=> 'SELECT - "COLUMN_NAME", "COLUMN_DEFAULT", "IS_NULLABLE", "DATA_TYPE", "CHARACTER_MAXIMUM_LENGTH" - FROM - "INFORMATION_SCHEMA"."COLUMNS" - WHERE - "TABLE_NAME" LIKE ? AND - "TABLE_CATALOG" = ? - ORDER BY - "ORDINAL_POSITION"' - ); - } - - public function getSql($name) { - return isset($this->queries[$name])?$this->queries[$name]:false; - } - - public function connect($hostname,$username,$password,$database,$port,$socket,$charset) { - $connectionInfo = array(); - if ($port) $hostname.=','.$port; - if ($username) $connectionInfo['UID']=$username; - if ($password) $connectionInfo['PWD']=$password; - if ($database) $connectionInfo['Database']=$database; - if ($charset) $connectionInfo['CharacterSet']=$charset; - $connectionInfo['QuotedId']=1; - $connectionInfo['ReturnDatesAsStrings']=1; - - $db = sqlsrv_connect($hostname, $connectionInfo); - if (!$db) { - throw new \Exception('Connect failed. '.print_r( sqlsrv_errors(), true)); - } - if ($socket) { - throw new \Exception('Socket connection is not supported.'); - } - $this->db = $db; - } - - public function query($sql,$params=array()) { - $args = array(); - $db = $this->db; - $sql = preg_replace_callback('/\!|\?/', function ($matches) use (&$db,&$params,&$args) { - static $i=-1; - $i++; - $param = $params[$i]; - if ($matches[0]=='!') { - $key = preg_replace('/[^a-zA-Z0-9\-_=<> ]/','',is_object($param)?$param->key:$param); - if (is_object($param) && $param->type=='hex') { - return "CONVERT(varchar(max), \"$key\", 2) as \"$key\""; - } - if (is_object($param) && $param->type=='wkt') { - return "\"$key\".STAsText() as \"$key\""; - } - return '"'.$key.'"'; - } else { - // This is workaround because SQLSRV cannot accept NULL in a param - if ($matches[0]=='?' && is_null($param)) { - return 'NULL'; - } - if (is_array($param)) { - $args = array_merge($args,$param); - return '('.implode(',',str_split(str_repeat('?',count($param)))).')'; - } - if (is_object($param) && $param->type=='hex') { - $args[] = $param->value; - return 'CONVERT(VARBINARY(MAX),?,2)'; - } - if (is_object($param) && $param->type=='wkt') { - $args[] = $param->value; - return 'geometry::STGeomFromText(?,0)'; - } - $args[] = $param; - return '?'; - } - }, $sql); - //var_dump($params); - //echo "\n$sql\n"; - //var_dump($args); - //file_put_contents('sql.txt',"\n$sql\n".var_export($args,true)."\n",FILE_APPEND); - if (strtoupper(substr($sql,0,6))=='INSERT') { - $sql .= ';SELECT SCOPE_IDENTITY()'; - } - return sqlsrv_query($db,$sql,$args)?:null; - } - - public function fetchAssoc($result) { - return sqlsrv_fetch_array($result, SQLSRV_FETCH_ASSOC); - } - - public function fetchRow($result) { - return sqlsrv_fetch_array($result, SQLSRV_FETCH_NUMERIC); - } - - public function insertId($result) { - sqlsrv_next_result($result); - sqlsrv_fetch($result); - return (int)sqlsrv_get_field($result, 0); - } - - public function affectedRows($result) { - return sqlsrv_rows_affected($result); - } - - public function close($result) { - return sqlsrv_free_stmt($result); - } - - public function fetchFields($table) { - $result = $this->query('SELECT * FROM ! WHERE 1=2;',array($table)); - //var_dump(sqlsrv_field_metadata($result)); - return array_map(function($a){ - $p = array(); - foreach ($a as $k=>$v) { - $p[strtolower($k)] = $v; - } - return (object)$p; - },sqlsrv_field_metadata($result)); - } - - public function addLimitToSql($sql,$limit,$offset) { - return "$sql OFFSET $offset ROWS FETCH NEXT $limit ROWS ONLY"; - } - - public function likeEscape($string) { - return str_replace(array('%','_'),array('[%]','[_]'),$string); - } - - public function convertFilter($field, $comparator, $value) { - $comparator = strtolower($comparator); - if ($comparator[0]!='n') { - switch ($comparator) { - case 'sco': return array('!.STContains(geometry::STGeomFromText(?,0))=1',$field,$value); - case 'scr': return array('!.STCrosses(geometry::STGeomFromText(?,0))=1',$field,$value); - case 'sdi': return array('!.STDisjoint(geometry::STGeomFromText(?,0))=1',$field,$value); - case 'seq': return array('!.STEquals(geometry::STGeomFromText(?,0))=1',$field,$value); - case 'sin': return array('!.STIntersects(geometry::STGeomFromText(?,0))=1',$field,$value); - case 'sov': return array('!.STOverlaps(geometry::STGeomFromText(?,0))=1',$field,$value); - case 'sto': return array('!.STTouches(geometry::STGeomFromText(?,0))=1',$field,$value); - case 'swi': return array('!.STWithin(geometry::STGeomFromText(?,0))=1',$field,$value); - case 'sic': return array('!.STIsClosed()=1',$field); - case 'sis': return array('!.STIsSimple()=1',$field); - case 'siv': return array('!.STIsValid()=1',$field); - } - } else { - switch ($comparator) { - case 'nsco': return array('!.STContains(geometry::STGeomFromText(?,0))=0',$field,$value); - case 'nscr': return array('!.STCrosses(geometry::STGeomFromText(?,0))=0',$field,$value); - case 'nsdi': return array('!.STDisjoint(geometry::STGeomFromText(?,0))=0',$field,$value); - case 'nseq': return array('!.STEquals(geometry::STGeomFromText(?,0))=0',$field,$value); - case 'nsin': return array('!.STIntersects(geometry::STGeomFromText(?,0))=0',$field,$value); - case 'nsov': return array('!.STOverlaps(geometry::STGeomFromText(?,0))=0',$field,$value); - case 'nsto': return array('!.STTouches(geometry::STGeomFromText(?,0))=0',$field,$value); - case 'nswi': return array('!.STWithin(geometry::STGeomFromText(?,0))=0',$field,$value); - case 'nsic': return array('!.STIsClosed()=0',$field); - case 'nsis': return array('!.STIsSimple()=0',$field); - case 'nsiv': return array('!.STIsValid()=0',$field); - } - } - return false; - } - - public function isNumericType($field) { - return in_array($field->type,array(-6,-5,4,5,2,6,7)); - } - - public function isBinaryType($field) { - return ($field->type>=-4 && $field->type<=-2); - } - - public function isGeometryType($field) { - return ($field->type==-151); - } - - public function isJsonType($field) { - return ($field->type==-152); - } - - public function getDefaultCharset() { - return 'UTF-8'; - } - - public function beginTransaction() { - return sqlsrv_begin_transaction($this->db); - } - - public function commitTransaction() { - return sqlsrv_commit($this->db); - } - - public function rollbackTransaction() { - return sqlsrv_rollback($this->db); - } - - public function jsonEncode($object) { - $a = $object; - $d = new DOMDocument(); - $c = $d->createElement("root"); - $d->appendChild($c); - $t = function($v) { - $type = gettype($v); - switch($type) { - case 'integer': return 'number'; - case 'double': return 'number'; - default: return strtolower($type); - } - }; - $f = function($f,$c,$a,$s=false) use ($t,$d) { - $c->setAttribute('type', $t($a)); - if ($t($a) != 'array' && $t($a) != 'object') { - if ($t($a) == 'boolean') { - $c->appendChild($d->createTextNode($a?'true':'false')); - } else { - $c->appendChild($d->createTextNode($a)); - } - } else { - foreach($a as $k=>$v) { - if ($k == '__type' && $t($a) == 'object') { - $c->setAttribute('__type', $v); - } else { - if ($t($v) == 'object') { - $ch = $c->appendChild($d->createElementNS(null, $s ? 'item' : $k)); - $f($f, $ch, $v); - } else if ($t($v) == 'array') { - $ch = $c->appendChild($d->createElementNS(null, $s ? 'item' : $k)); - $f($f, $ch, $v, true); - } else { - $va = $d->createElementNS(null, $s ? 'item' : $k); - if ($t($v) == 'boolean') { - $va->appendChild($d->createTextNode($v?'true':'false')); - } else { - $va->appendChild($d->createTextNode($v)); - } - $ch = $c->appendChild($va); - $ch->setAttribute('type', $t($v)); - } - } - } - } - }; - $f($f,$c,$a,$t($a)=='array'); - return $d->saveXML($d->documentElement); - } - - public function jsonDecode($string) { - $a = dom_import_simplexml(simplexml_load_string($string)); - $t = function($v) { - return $v->getAttribute('type'); - }; - $f = function($f,$a) use ($t) { - $c = null; - if ($t($a)=='null') { - $c = null; - } else if ($t($a)=='boolean') { - $b = substr(strtolower($a->textContent),0,1); - $c = in_array($b,array('1','t')); - } else if ($t($a)=='number') { - $c = $a->textContent+0; - } else if ($t($a)=='string') { - $c = $a->textContent; - } else if ($t($a)=='object') { - $c = array(); - if ($a->getAttribute('__type')) { - $c['__type'] = $a->getAttribute('__type'); - } - for ($i=0;$i<$a->childNodes->length;$i++) { - $v = $a->childNodes[$i]; - $c[$v->nodeName] = $f($f,$v); - } - $c = (object)$c; - } else if ($t($a)=='array') { - $c = array(); - for ($i=0;$i<$a->childNodes->length;$i++) { - $v = $a->childNodes[$i]; - $c[$i] = $f($f,$v); - } - } - return $c; - }; - $c = $f($f,$a); - return $c; - } -} - -class SQLite implements DatabaseInterface { - - protected $db; - protected $queries; - - public function __construct() { - $this->queries = array( - 'list_tables'=>'SELECT - "name", "" - FROM - "sys/tables"', - 'reflect_table'=>'SELECT - "name" - FROM - "sys/tables" - WHERE - "name"=?', - 'reflect_pk'=>'SELECT - "name" - FROM - "sys/columns" - WHERE - "pk"=1 AND - "self"=?', - 'reflect_belongs_to'=>'SELECT - "self", "from", - "table", "to" - FROM - "sys/foreign_keys" - WHERE - "self" = ? AND - "table" IN ? AND - ? like "%" AND - ? like "%"', - 'reflect_has_many'=>'SELECT - "self", "from", - "table", "to" - FROM - "sys/foreign_keys" - WHERE - "self" IN ? AND - "table" = ? AND - ? like "%" AND - ? like "%"', - 'reflect_habtm'=>'SELECT - k1."self", k1."from", - k1."table", k1."to", - k2."self", k2."from", - k2."table", k2."to" - FROM - "sys/foreign_keys" k1, - "sys/foreign_keys" k2 - WHERE - ? like "%" AND - ? like "%" AND - ? like "%" AND - ? like "%" AND - k1."self" = k2."self" AND - k1."table" = ? AND - k2."table" IN ?', - 'reflect_columns'=> 'SELECT - "name", "dflt_value", case when "notnull"==1 then \'no\' else \'yes\' end as "nullable", "type", 2147483647 - FROM - "sys/columns" - WHERE - "self"=? - ORDER BY - "cid"' - ); - } - - public function getSql($name) { - return isset($this->queries[$name])?$this->queries[$name]:false; - } - - public function connect($hostname,$username,$password,$database,$port,$socket,$charset) { - $this->db = new SQLite3($database); - // optimizations - $this->db->querySingle('PRAGMA synchronous = NORMAL'); - $this->db->querySingle('PRAGMA foreign_keys = on'); - $reflection = $this->db->querySingle('SELECT name FROM sqlite_master WHERE type = "table" and name like "sys/%"'); - if (!$reflection) { - //create reflection tables - $this->query('CREATE table "sys/version" ("version" integer)'); - $this->query('CREATE table "sys/tables" ("name" text)'); - $this->query('CREATE table "sys/columns" ("self" text,"cid" integer,"name" text,"type" integer,"notnull" integer,"dflt_value" integer,"pk" integer)'); - $this->query('CREATE table "sys/foreign_keys" ("self" text,"id" integer,"seq" integer,"table" text,"from" text,"to" text,"on_update" text,"on_delete" text,"match" text)'); - } - $version = $this->db->querySingle('pragma schema_version'); - if ($version != $this->db->querySingle('SELECT "version" from "sys/version"')) { - // reflection may take a while - set_time_limit(3600); - // update version data - $this->query('DELETE FROM "sys/version"'); - $this->query('INSERT into "sys/version" ("version") VALUES (?)',array($version)); - // update tables data - $this->query('DELETE FROM "sys/tables"'); - $result = $this->query('SELECT * FROM sqlite_master WHERE (type = "table" or type = "view") and name not like "sys/%" and name<>"sqlite_sequence"'); - $tables = array(); - while ($row = $this->fetchAssoc($result)) { - $tables[] = $row['name']; - $this->query('INSERT into "sys/tables" ("name") VALUES (?)',array($row['name'])); - } - // update columns and foreign_keys data - $this->query('DELETE FROM "sys/columns"'); - $this->query('DELETE FROM "sys/foreign_keys"'); - foreach ($tables as $table) { - $result = $this->query('pragma table_info(!)',array($table)); - while ($row = $this->fetchRow($result)) { - array_unshift($row, $table); - $this->query('INSERT into "sys/columns" ("self","cid","name","type","notnull","dflt_value","pk") VALUES (?,?,?,?,?,?,?)',$row); - } - $result = $this->query('pragma foreign_key_list(!)',array($table)); - while ($row = $this->fetchRow($result)) { - array_unshift($row, $table); - $this->query('INSERT into "sys/foreign_keys" ("self","id","seq","table","from","to","on_update","on_delete","match") VALUES (?,?,?,?,?,?,?,?,?)',$row); - } - } - } - } - - public function query($sql,$params=array()) { - $db = $this->db; - $sql = preg_replace_callback('/\!|\?/', function ($matches) use (&$db,&$params) { - $param = array_shift($params); - if ($matches[0]=='!') { - $key = preg_replace('/[^a-zA-Z0-9\-_=<> ]/','',is_object($param)?$param->key:$param); - return '"'.$key.'"'; - } else { - if (is_array($param)) return '('.implode(',',array_map(function($v) use (&$db) { - return "'".$db->escapeString($v)."'"; - },$param)).')'; - if (is_object($param) && $param->type=='hex') { - return "'".$db->escapeString($param->value)."'"; - } - if (is_object($param) && $param->type=='wkt') { - return "'".$db->escapeString($param->value)."'"; - } - if ($param===null) return 'NULL'; - return "'".$db->escapeString($param)."'"; - } - }, $sql); - //echo "\n$sql\n"; - try { $result=$db->query($sql); } catch(\Exception $e) { $result=null; } - return $result; - } - - public function fetchAssoc($result) { - return $result->fetchArray(SQLITE3_ASSOC); - } - - public function fetchRow($result) { - return $result->fetchArray(SQLITE3_NUM); - } - - public function insertId($result) { - return $this->db->lastInsertRowID(); - } - - public function affectedRows($result) { - return $this->db->changes(); - } - - public function close($result) { - return $result->finalize(); - } - - public function fetchFields($table) { - $result = $this->query('SELECT * FROM "sys/columns" WHERE "self"=?;',array($table)); - $fields = array(); - while ($row = $this->fetchAssoc($result)){ - $fields[strtolower($row['name'])] = (object)$row; - } - return $fields; - } - - public function addLimitToSql($sql,$limit,$offset) { - return "$sql LIMIT $limit OFFSET $offset"; - } - - public function likeEscape($string) { - return addcslashes($string,'%_'); - } - - public function convertFilter($field, $comparator, $value) { - return false; - } - - public function isNumericType($field) { - return in_array($field->type,array('integer','real')); - } - - public function isBinaryType($field) { - return (substr($field->type,0,4)=='data'); - } - - public function isGeometryType($field) { - return in_array($field->type,array('geometry')); - } - - public function isJsonType($field) { - return in_array($field->type,array('json','jsonb')); - } - - public function getDefaultCharset() { - return 'utf8'; - } - - public function beginTransaction() { - return $this->query('BEGIN'); - } - - public function commitTransaction() { - return $this->query('COMMIT'); - } - - public function rollbackTransaction() { - return $this->query('ROLLBACK'); - } - - public function jsonEncode($object) { - return json_encode($object); - } - - public function jsonDecode($string) { - return json_decode($string); - } -} - -class PHP_CRUD_API { - - protected $db; - protected $settings; - - protected function mapMethodToAction($method,$key) { - switch ($method) { - case 'OPTIONS': return 'headers'; - case 'GET': return ($key===false)?'list':'read'; - case 'PUT': return 'update'; - case 'POST': return 'create'; - case 'DELETE': return 'delete'; - case 'PATCH': return 'increment'; - default: $this->exitWith404('method'); - } - return false; - } - - protected function parseRequestParameter(&$request,$characters) { - if ($request==='') return false; - $pos = strpos($request,'/'); - $value = $pos?substr($request,0,$pos):$request; - $request = $pos?substr($request,$pos+1):''; - if (!$characters) return $value; - return preg_replace("/[^$characters]/",'',$value); - } - - protected function parseGetParameter($get,$name,$characters) { - $value = isset($get[$name])?$get[$name]:false; - return $characters?preg_replace("/[^$characters]/",'',$value):$value; - } - - protected function parseGetParameterArray($get,$name,$characters) { - $values = isset($get[$name])?$get[$name]:false; - if (!is_array($values)) $values = array($values); - if ($characters) { - foreach ($values as &$value) { - $value = preg_replace("/[^$characters]/",'',$value); - } - } - return $values; - } - - protected function applyBeforeHandler(&$action,&$database,&$table,&$ids,&$callback,&$inputs) { - if (is_callable($callback,true)) { - $max = count($ids)?:count($inputs); - $values = array('action'=>$action,'database'=>$database,'table'=>$table); - for ($i=0;$i<$max;$i++) { - $action = $values['action']; - $database = $values['database']; - $table = $values['table']; - if (!isset($ids[$i])) $ids[$i] = false; - if (!isset($inputs[$i])) $inputs[$i] = false; - $callback($action,$database,$table,$ids[$i],$inputs[$i]); - } - } - } - - protected function applyAfterHandler($parameters,$outputs) { - $callback = $parameters['after']; - if (is_callable($callback,true)) { - $action = $parameters['action']; - $database = $parameters['database']; - $table = $parameters['tables'][0]; - $ids = $parameters['key'][0]; - $inputs = $parameters['inputs']; - $max = max(count($ids),count($inputs)); - for ($i=0;$i<$max;$i++) { - $id = isset($ids[$i])?$ids[$i]:false; - $input = isset($inputs[$i])?$inputs[$i]:false; - $output = is_array($outputs)?$outputs[$i]:$outputs; - $callback($action,$database,$table,$id,$input,$output); - } - } - } - - protected function applyTableAuthorizer($callback,$action,$database,&$tables) { - if (is_callable($callback,true)) foreach ($tables as $i=>$table) { - if (!$callback($action,$database,$table)) { - unset($tables[$i]); - } - } - } - - protected function applyRecordFilter($callback,$action,$database,$tables,&$filters) { - if (is_callable($callback,true)) foreach ($tables as $i=>$table) { - $this->addFilters($filters,$table,array($table=>'and'),$callback($action,$database,$table)); - } - } - - protected function applyTenancyFunction($callback,$action,$database,$fields,&$filters) { - if (is_callable($callback,true)) foreach ($fields as $table=>$keys) { - foreach ($keys as $field) { - $v = $callback($action,$database,$table,$field->name); - if ($v!==null) { - if (is_array($v)) $this->addFilter($filters,$table,'and',$field->name,'in',implode(',',$v)); - else $this->addFilter($filters,$table,'and',$field->name,'eq',$v); - } - } - } - } - - protected function applyColumnAuthorizer($callback,$action,$database,&$fields) { - if (is_callable($callback,true)) foreach ($fields as $table=>$keys) { - foreach ($keys as $field) { - if (!$callback($action,$database,$table,$field->name)) { - unset($fields[$table][$field->name]); - } - } - } - } - - protected function applyInputTenancy($callback,$action,$database,$table,&$input,$keys) { - if (is_callable($callback,true)) foreach ($keys as $key=>$field) { - $v = $callback($action,$database,$table,$key); - if ($v!==null && (isset($input->$key) || $action=='create')) { - if (is_array($v)) { - if (!count($v)) { - $input->$key = null; - } elseif (!isset($input->$key)) { - $input->$key = $v[0]; - } elseif (!in_array($input->$key,$v)) { - $input->$key = null; - } - } else { - $input->$key = $v; - } - } - } - } - - protected function applyInputSanitizer($callback,$action,$database,$table,&$input,$keys) { - if (is_callable($callback,true)) foreach ((array)$input as $key=>$value) { - if (isset($keys[$key])) { - $input->$key = $callback($action,$database,$table,$key,$keys[$key]->type,$value); - } - } - } - - protected function applyInputValidator($callback,$action,$database,$table,$input,$keys,$context) { - $errors = array(); - if (is_callable($callback,true)) foreach ((array)$input as $key=>$value) { - if (isset($keys[$key])) { - $error = $callback($action,$database,$table,$key,$keys[$key]->type,$value,$context); - if ($error!==true && $error!==null) $errors[$key] = $error; - } - } - if (!empty($errors)) $this->exitWith422($errors); - } - - protected function processTableAndIncludeParameters($database,$table,$include,$action) { - $blacklist = array('information_schema','mysql','sys','pg_catalog'); - if (in_array(strtolower($database), $blacklist)) return array(); - $table_list = array(); - if ($result = $this->db->query($this->db->getSql('reflect_table'),array($table,$database))) { - while ($row = $this->db->fetchRow($result)) $table_list[] = $row[0]; - $this->db->close($result); - } - if (empty($table_list)) $this->exitWith404('entity'); - if ($action=='list') { - foreach (explode(',',$include) as $table) { - if ($result = $this->db->query($this->db->getSql('reflect_table'),array($table,$database))) { - while ($row = $this->db->fetchRow($result)) $table_list[] = $row[0]; - $this->db->close($result); - } - } - } - return $table_list; - } - - protected function exitWith404($type) { - if (isset($_SERVER['REQUEST_METHOD'])) { - header('Content-Type:',true,404); - die("Not found ($type)"); - } else { - throw new \Exception("Not found ($type)"); - } - } - - protected function exitWith400($type) { - if (isset($_SERVER['REQUEST_METHOD'])) { - header('Content-Type:',true,400); - die("The request could not be understood by the server due to malformed syntax. The client SHOULD NOT repeat the request without modifications. ($type)"); - } else { - throw new \Exception("Bad request ($type)"); - } - } - - protected function exitWith422($object) { - if (isset($_SERVER['REQUEST_METHOD'])) { - header('Content-Type:',true,422); - die(json_encode($object)); - } else { - throw new \Exception(json_encode($object)); - } - } - - protected function headersCommand($parameters) { - $headers = array(); - $headers[]='Access-Control-Allow-Headers: Content-Type, X-XSRF-TOKEN'; - $headers[]='Access-Control-Allow-Methods: OPTIONS, GET, PUT, POST, DELETE, PATCH'; - $headers[]='Access-Control-Allow-Credentials: true'; - $headers[]='Access-Control-Max-Age: 1728000'; - if (isset($_SERVER['REQUEST_METHOD'])) { - foreach ($headers as $header) header($header); - } else { - echo json_encode($headers); - } - return false; - } - - protected function startOutput() { - if (isset($_SERVER['REQUEST_METHOD'])) { - header('Content-Type: application/json; charset=utf-8'); - } - } - - protected function findPrimaryKeys($table,$database) { - $fields = array(); - if ($result = $this->db->query($this->db->getSql('reflect_pk'),array($table,$database))) { - while ($row = $this->db->fetchRow($result)) { - $fields[] = $row[0]; - } - $this->db->close($result); - } - return $fields; - } - - protected function processKeyParameter($key,$tables,$database) { - if ($key===false) return false; - $fields = $this->findPrimaryKeys($tables[0],$database); - if (count($fields)!=1) $this->exitWith404('1pk'); - return array(explode(',',$key),$fields[0]); - } - - protected function processOrderingsParameter($orderings) { - if (!$orderings) return false; - foreach ($orderings as &$order) { - $order = explode(',',$order,2); - if (count($order)<2) $order[1]='ASC'; - if (!strlen($order[0])) return false; - $direction = strtoupper($order[1]); - if (in_array($direction,array('ASC','DESC'))) { - $order[1] = $direction; - } - } - return $orderings; - } - - protected function convertFilter($field, $comparator, $value) { - $result = $this->db->convertFilter($field,$comparator,$value); - if ($result) return $result; - // default behavior - $comparator = strtolower($comparator); - if ($comparator[0]!='n') { - if (strlen($comparator)==2) { - switch ($comparator) { - case 'cs': return array('! LIKE ?',$field,'%'.$this->db->likeEscape($value).'%'); - case 'sw': return array('! LIKE ?',$field,$this->db->likeEscape($value).'%'); - case 'ew': return array('! LIKE ?',$field,'%'.$this->db->likeEscape($value)); - case 'eq': return array('! = ?',$field,$value); - case 'lt': return array('! < ?',$field,$value); - case 'le': return array('! <= ?',$field,$value); - case 'ge': return array('! >= ?',$field,$value); - case 'gt': return array('! > ?',$field,$value); - case 'bt': - $v = explode(',',$value); - if (count($v)<2) return false; - return array('! BETWEEN ? AND ?',$field,$v[0],$v[1]); - case 'in': return array('! IN ?',$field,explode(',',$value)); - case 'is': return array('! IS NULL',$field); - } - } else { - switch ($comparator) { - case 'sco': return array('ST_Contains(!,ST_GeomFromText(?))=TRUE',$field,$value); - case 'scr': return array('ST_Crosses(!,ST_GeomFromText(?))=TRUE',$field,$value); - case 'sdi': return array('ST_Disjoint(!,ST_GeomFromText(?))=TRUE',$field,$value); - case 'seq': return array('ST_Equals(!,ST_GeomFromText(?))=TRUE',$field,$value); - case 'sin': return array('ST_Intersects(!,ST_GeomFromText(?))=TRUE',$field,$value); - case 'sov': return array('ST_Overlaps(!,ST_GeomFromText(?))=TRUE',$field,$value); - case 'sto': return array('ST_Touches(!,ST_GeomFromText(?))=TRUE',$field,$value); - case 'swi': return array('ST_Within(!,ST_GeomFromText(?))=TRUE',$field,$value); - case 'sic': return array('ST_IsClosed(!)=TRUE',$field); - case 'sis': return array('ST_IsSimple(!)=TRUE',$field); - case 'siv': return array('ST_IsValid(!)=TRUE',$field); - } - } - } else { - if (strlen($comparator)==2) { - switch ($comparator) { - case 'ne': return $this->convertFilter($field, 'neq', $value); // deprecated - case 'ni': return $this->convertFilter($field, 'nin', $value); // deprecated - case 'no': return $this->convertFilter($field, 'nis', $value); // deprecated - } - } elseif (strlen($comparator)==3) { - switch ($comparator) { - case 'ncs': return array('! NOT LIKE ?',$field,'%'.$this->db->likeEscape($value).'%'); - case 'nsw': return array('! NOT LIKE ?',$field,$this->db->likeEscape($value).'%'); - case 'new': return array('! NOT LIKE ?',$field,'%'.$this->db->likeEscape($value)); - case 'neq': return array('! <> ?',$field,$value); - case 'nlt': return array('! >= ?',$field,$value); - case 'nle': return array('! > ?',$field,$value); - case 'nge': return array('! < ?',$field,$value); - case 'ngt': return array('! <= ?',$field,$value); - case 'nbt': - $v = explode(',',$value); - if (count($v)<2) return false; - return array('! NOT BETWEEN ? AND ?',$field,$v[0],$v[1]); - case 'nin': return array('! NOT IN ?',$field,explode(',',$value)); - case 'nis': return array('! IS NOT NULL',$field); - } - } else { - switch ($comparator) { - case 'nsco': return array('ST_Contains(!,ST_GeomFromText(?))=FALSE',$field,$value); - case 'nscr': return array('ST_Crosses(!,ST_GeomFromText(?))=FALSE',$field,$value); - case 'nsdi': return array('ST_Disjoint(!,ST_GeomFromText(?))=FALSE',$field,$value); - case 'nseq': return array('ST_Equals(!,ST_GeomFromText(?))=FALSE',$field,$value); - case 'nsin': return array('ST_Intersects(!,ST_GeomFromText(?))=FALSE',$field,$value); - case 'nsov': return array('ST_Overlaps(!,ST_GeomFromText(?))=FALSE',$field,$value); - case 'nsto': return array('ST_Touches(!,ST_GeomFromText(?))=FALSE',$field,$value); - case 'nswi': return array('ST_Within(!,ST_GeomFromText(?))=FALSE',$field,$value); - case 'nsic': return array('ST_IsClosed(!)=FALSE',$field); - case 'nsis': return array('ST_IsSimple(!)=FALSE',$field); - case 'nsiv': return array('ST_IsValid(!)=FALSE',$field); - } - } - } - return false; - } - - public function addFilter(&$filters,$table,$and,$field,$comparator,$value) { - if (!isset($filters[$table])) $filters[$table] = array(); - if (!isset($filters[$table][$and])) $filters[$table][$and] = array(); - $filter = $this->convertFilter($field,$comparator,$value); - if ($filter) $filters[$table][$and][] = $filter; - } - - public function addFilters(&$filters,$table,$satisfy,$filterStrings) { - if ($filterStrings) { - for ($i=0;$i=2) { - if (strpos($parts[0],'.')) list($t,$f) = explode('.',$parts[0],2); - else list($t,$f) = array($table,$parts[0]); - $comparator = $parts[1]; - $value = isset($parts[2])?$parts[2]:null; - $and = isset($satisfy[$t])?$satisfy[$t]:'and'; - $this->addFilter($filters,$t,$and,$f,$comparator,$value); - } - } - } - } - - protected function processSatisfyParameter($tables,$satisfyString) { - $satisfy = array(); - foreach (explode(',',$satisfyString) as $str) { - if (strpos($str,'.')) list($t,$s) = explode('.',$str,2); - else list($t,$s) = array($tables[0],$str); - $and = ($s && strtolower($s)=='any')?'or':'and'; - $satisfy[$t] = $and; - } - return $satisfy; - } - - protected function processFiltersParameter($tables,$satisfy,$filterStrings) { - $filters = array(); - $this->addFilters($filters,$tables[0],$satisfy,$filterStrings); - return $filters; - } - - protected function processPageParameter($page) { - if (!$page) return false; - $page = explode(',',$page,2); - if (count($page)<2) $page[1]=20; - $page[0] = ($page[0]-1)*$page[1]; - return $page; - } - - protected function retrieveObject($key,$fields,$filters,$tables) { - if (!$key) return false; - $table = $tables[0]; - $params = array(); - $sql = 'SELECT '; - $this->convertOutputs($sql,$params,$fields[$table]); - $sql .= ' FROM !'; - $params[] = $table; - $this->addFilter($filters,$table,'and',$key[1],'eq',$key[0][0]); - $this->addWhereFromFilters($filters[$table],$sql,$params); - $object = null; - if ($result = $this->db->query($sql,$params)) { - $object = $this->fetchAssoc($result,$fields[$table]); - $this->db->close($result); - } - return $object; - } - - protected function retrieveObjects($key,$fields,$filters,$tables) { - $keyField = $key[1]; - $keys = $key[0]; - $rows = array(); - foreach ($keys as $key) { - $result = $this->retrieveObject(array(array($key),$keyField),$fields,$filters,$tables); - if ($result===null) { - return null; - } - $rows[] = $result; - } - return $rows; - } - - protected function createObject($input,$tables) { - if (!$input) return false; - $input = (array)$input; - - - /* START: Crypt password and userId. */ - - $date = new DateTime(); - $id = $date->getTimestamp() . $input['userName']; - - $passwordSalt = "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"; - - $hashedPassword = crypt( $input['password'], $passwordSalt ); - $hashedId = crypt( $id, $passwordSalt ); - - $input['password'] = $hashedPassword; - $input['userId'] = $hashedId; - - /* END: Crypt password and userId. */ - - $keys = implode(',',str_split(str_repeat('!', count($input)))); - $values = implode(',',str_split(str_repeat('?', count($input)))); - $params = array_merge(array_keys($input),array_values($input)); - array_unshift($params, $tables[0]); - $result = $this->db->query('INSERT INTO ! ('.$keys.') VALUES ('.$values.')',$params); - if (!$result) return null; - $insertId = $this->db->insertId($result); - return $insertId; - } - - protected function createObjects($inputs,$tables) { - - if (!$inputs) return false; - $ids = array(); - $this->db->beginTransaction(); - foreach ($inputs as $input) { - $result = $this->createObject($input,$tables); - if ($result===null) { - $this->db->rollbackTransaction(); - return null; - } - $ids[] = $result; - } - $this->db->commitTransaction(); - return $ids; - } - - protected function updateObject($key,$input,$filters,$tables) { - if (!$input) return null; - $input = (array)$input; - $table = $tables[0]; - $sql = 'UPDATE ! SET '; - $params = array($table); - foreach (array_keys($input) as $j=>$k) { - if ($j) $sql .= ','; - $v = $input[$k]; - $sql .= '!=?'; - $params[] = $k; - $params[] = $v; - } - $this->addFilter($filters,$table,'and',$key[1],'eq',$key[0][0]); - $this->addWhereFromFilters($filters[$table],$sql,$params); - $result = $this->db->query($sql,$params); - if (!$result) return null; - return $this->db->affectedRows($result); - } - - protected function updateObjects($key,$inputs,$filters,$tables) { - if (!$inputs) return null; - $keyField = $key[1]; - $keys = $key[0]; - if (count(array_filter($inputs))!=count(array_filter($keys))) { - $this->exitWith404('subject'); - } - $rows = array(); - $this->db->beginTransaction(); - foreach ($inputs as $i=>$input) { - $result = $this->updateObject(array(array($keys[$i]),$keyField),$input,$filters,$tables); - if ($result===null) { - $this->db->rollbackTransaction(); - return null; - } - $rows[] = $result; - } - $this->db->commitTransaction(); - return $rows; - } - - protected function deleteObject($key,$filters,$tables) { - $table = $tables[0]; - $sql = 'DELETE FROM !'; - $params = array($table); - $this->addFilter($filters,$table,'and',$key[1],'eq',$key[0][0]); - $this->addWhereFromFilters($filters[$table],$sql,$params); - $result = $this->db->query($sql,$params); - if (!$result) return null; - return $this->db->affectedRows($result); - } - - protected function deleteObjects($key,$filters,$tables) { - $keyField = $key[1]; - $keys = $key[0]; - $rows = array(); - $this->db->beginTransaction(); - foreach ($keys as $key) { - $result = $this->deleteObject(array(array($key),$keyField),$filters,$tables); - if ($result===null) { - $this->db->rollbackTransaction(); - return null; - } - $rows[] = $result; - } - $this->db->commitTransaction(); - return $rows; - } - - protected function incrementObject($key,$input,$filters,$tables,$fields) { - if (!$input) return null; - $input = (array)$input; - $table = $tables[0]; - $sql = 'UPDATE ! SET '; - $params = array($table); - foreach (array_keys($input) as $j=>$k) { - if ($j) $sql .= ','; - $v = $input[$k]; - if ($this->db->isNumericType($fields[$table][$k])) { - $sql .= '!=!+?'; - $params[] = $k; - $params[] = $k; - $params[] = $v; - } else { - $sql .= '!=!'; - $params[] = $k; - $params[] = $k; - } - } - $this->addFilter($filters,$table,'and',$key[1],'eq',$key[0][0]); - $this->addWhereFromFilters($filters[$table],$sql,$params); - $result = $this->db->query($sql,$params); - if (!$result) return null; - return $this->db->affectedRows($result); - } - - protected function incrementObjects($key,$inputs,$filters,$tables,$fields) { - if (!$inputs) return null; - $keyField = $key[1]; - $keys = $key[0]; - if (count(array_filter($inputs))!=count(array_filter($keys))) { - $this->exitWith404('subject'); - } - $rows = array(); - $this->db->beginTransaction(); - foreach ($inputs as $i=>$input) { - $result = $this->incrementObject(array(array($keys[$i]),$keyField),$input,$filters,$tables,$fields); - if ($result===null) { - $this->db->rollbackTransaction(); - return null; - } - $rows[] = $result; - } - $this->db->commitTransaction(); - return $rows; - } - - protected function findRelations($tables,$database,$auto_include) { - $tableset = array(); - $collect = array(); - $select = array(); - - while (count($tables)>1) { - $table0 = array_shift($tables); - $tableset[] = $table0; - - $result = $this->db->query($this->db->getSql('reflect_belongs_to'),array($table0,$tables,$database,$database)); - while ($row = $this->db->fetchRow($result)) { - if (!$auto_include && !in_array($row[0],array_merge($tables,$tableset))) continue; - $collect[$row[0]][$row[1]]=array(); - $select[$row[2]][$row[3]]=array($row[0],$row[1]); - if (!in_array($row[0],$tableset)) $tableset[] = $row[0]; - } - $result = $this->db->query($this->db->getSql('reflect_has_many'),array($tables,$table0,$database,$database)); - while ($row = $this->db->fetchRow($result)) { - if (!$auto_include && !in_array($row[2],array_merge($tables,$tableset))) continue; - $collect[$row[2]][$row[3]]=array(); - $select[$row[0]][$row[1]]=array($row[2],$row[3]); - if (!in_array($row[2],$tableset)) $tableset[] = $row[2]; - } - $result = $this->db->query($this->db->getSql('reflect_habtm'),array($database,$database,$database,$database,$table0,$tables)); - while ($row = $this->db->fetchRow($result)) { - if (!$auto_include && !in_array($row[2],array_merge($tables,$tableset))) continue; - if (!$auto_include && !in_array($row[4],array_merge($tables,$tableset))) continue; - $collect[$row[2]][$row[3]]=array(); - $select[$row[0]][$row[1]]=array($row[2],$row[3]); - $collect[$row[4]][$row[5]]=array(); - $select[$row[6]][$row[7]]=array($row[4],$row[5]); - if (!in_array($row[2],$tableset)) $tableset[] = $row[2]; - if (!in_array($row[4],$tableset)) $tableset[] = $row[4]; - } - } - $tableset[] = array_shift($tables); - $tableset = array_unique($tableset); - return array($tableset,$collect,$select); - } - - protected function retrieveInputs($data) { - $data = trim($data, " \t\n\r"); - if (strlen($data)==0) { - $input = false; - } else if ($data[0]=='{' || $data[0]=='[') { - $input = json_decode($data); - $causeCode = json_last_error(); - if ($causeCode !== JSON_ERROR_NONE) { - $errorString = "Error decoding input JSON. json_last_error code: " . $causeCode; - $this->exitWith400($errorString); - } - } else { - parse_str($data, $input); - foreach ($input as $key => $value) { - if (substr($key,-9)=='__is_null') { - $input[substr($key,0,-9)] = null; - unset($input[$key]); - } - } - $input = (object)$input; - } - return is_array($input)?$input:array($input); - } - - protected function getRelationShipColumns($select) { - $keep = array(); - foreach ($select as $table=>$keys) { - foreach ($keys as $key=>$other) { - if (!isset($keep[$table])) $keep[$table] = array(); - $keep[$table][$key]=true; - list($table2,$key2) = $other; - if (!isset($keep[$table2])) $keep[$table2] = array(); - $keep[$table2][$key2]=true; - } - } - return $keep; - } - - protected function findFields($tables,$columns,$exclude,$select,$database) { - $fields = array(); - if ($select && ($columns || $exclude)) { - $keep = $this->getRelationShipColumns($select); - } else { - $keep = false; - } - foreach ($tables as $i=>$table) { - $fields[$table] = $this->findTableFields($table,$database); - $fields[$table] = $this->filterFieldsByColumns($fields[$table],$columns,$keep,$i==0,$table); - $fields[$table] = $this->filterFieldsByExclude($fields[$table],$exclude,$keep,$i==0,$table); - } - return $fields; - } - - protected function filterFieldsByColumns($fields,$columns,$keep,$first,$table) { - if ($columns) { - $columns = explode(',',$columns); - foreach (array_keys($fields) as $key) { - $delete = true; - foreach ($columns as $column) { - if (strpos($column,'.')) { - if ($column=="$table.$key" || $column=="$table.*") { - $delete = false; - } - } elseif ($first) { - if ($column==$key || $column=="*") { - $delete = false; - } - } - } - if ($delete && !isset($keep[$table][$key])) { - unset($fields[$key]); - } - } - } - return $fields; - } - - protected function filterFieldsByExclude($fields,$exclude,$keep,$first,$table) { - if ($exclude) { - $columns = explode(',',$exclude); - foreach (array_keys($fields) as $key) { - $delete = false; - foreach ($columns as $column) { - if (strpos($column,'.')) { - if ($column=="$table.$key" || $column=="$table.*") { - $delete = true; - } - } elseif ($first) { - if ($column==$key || $column=="*") { - $delete = true; - } - } - } - if ($delete && !isset($keep[$table][$key])) { - unset($fields[$key]); - } - } - } - return $fields; - } - - protected function findTableFields($table,$database) { - $fields = array(); - foreach ($this->db->fetchFields($table) as $field) { - $fields[$field->name] = $field; - } - return $fields; - } - - protected function filterInputByFields($input,$fields) { - if ($fields) foreach (array_keys((array)$input) as $key) { - if (!isset($fields[$key])) { - unset($input->$key); - } - } - return $input; - } - - protected function convertInputs(&$input,$fields) { - foreach ($fields as $key=>$field) { - if (isset($input->$key) && $input->$key && $this->db->isBinaryType($field)) { - $value = $input->$key; - $value = str_pad(strtr($value, '-_', '+/'), ceil(strlen($value) / 4) * 4, '=', STR_PAD_RIGHT); - $input->$key = (object)array('type'=>'hex','value'=>bin2hex(base64_decode($value))); - } - if (isset($input->$key) && $input->$key && $this->db->isGeometryType($field)) { - $input->$key = (object)array('type'=>'wkt','value'=>$input->$key); - } - if (isset($input->$key) && $input->$key && $this->db->isJsonType($field)) { - $input->$key = $this->db->jsonEncode($input->$key); - } - } - } - - protected function convertOutputs(&$sql, &$params, $fields) { - $sql .= implode(',',str_split(str_repeat('!',count($fields)))); - foreach ($fields as $key=>$field) { - if ($this->db->isBinaryType($field)) { - $params[] = (object)array('type'=>'hex','key'=>$key); - } - else if ($this->db->isGeometryType($field)) { - $params[] = (object)array('type'=>'wkt','key'=>$key); - } - else { - $params[] = $key; - } - } - } - - protected function convertTypes($result,&$values,&$fields) { - foreach ($values as $i=>$v) { - if (is_string($v)) { - if ($this->db->isNumericType($fields[$i])) { - $values[$i] = $v + 0; - } - else if ($this->db->isBinaryType($fields[$i])) { - $values[$i] = base64_encode(pack("H*",$v)); - } - else if ($this->db->isJsonType($fields[$i])) { - $values[$i] = $this->db->jsonDecode($v); - } - } - } - } - - protected function fetchAssoc($result,$fields=false) { - $values = $this->db->fetchAssoc($result); - if ($values && $fields) { - $this->convertTypes($result,$values,$fields); - } - return $values; - } - - protected function fetchRow($result,$fields=false) { - $values = $this->db->fetchRow($result,$fields); - if ($values && $fields) { - $fields = array_values($fields); - $this->convertTypes($result,$values,$fields); - } - return $values; - } - - protected function getParameters($settings) { - extract($settings); - - $table = $this->parseRequestParameter($request, 'a-zA-Z0-9\-_'); - $key = $this->parseRequestParameter($request, 'a-zA-Z0-9\-_,'); // auto-increment or uuid - $action = $this->mapMethodToAction($method,$key); - $include = $this->parseGetParameter($get, 'include', 'a-zA-Z0-9\-_,'); - $page = $this->parseGetParameter($get, 'page', '0-9,'); - $filters = $this->parseGetParameterArray($get, 'filter', false); - $satisfy = $this->parseGetParameter($get, 'satisfy', 'a-zA-Z0-9\-_,.'); - $columns = $this->parseGetParameter($get, 'columns', 'a-zA-Z0-9\-_,.*'); - $exclude = $this->parseGetParameter($get, 'exclude', 'a-zA-Z0-9\-_,.*'); - $orderings = $this->parseGetParameterArray($get, 'order', 'a-zA-Z0-9\-_,'); - $transform = $this->parseGetParameter($get, 'transform', 't1'); - - $tables = $this->processTableAndIncludeParameters($database,$table,$include,$action); - $key = $this->processKeyParameter($key,$tables,$database); - $satisfy = $this->processSatisfyParameter($tables,$satisfy); - $filters = $this->processFiltersParameter($tables,$satisfy,$filters); - $page = $this->processPageParameter($page); - $orderings = $this->processOrderingsParameter($orderings); - - // reflection - list($tables,$collect,$select) = $this->findRelations($tables,$database,$auto_include); - $fields = $this->findFields($tables,$columns,$exclude,$select,$database); - - // permissions - if ($table_authorizer) $this->applyTableAuthorizer($table_authorizer,$action,$database,$tables); - if (!isset($tables[0])) $this->exitWith404('entity'); - if ($record_filter) $this->applyRecordFilter($record_filter,$action,$database,$tables,$filters); - if ($tenancy_function) $this->applyTenancyFunction($tenancy_function,$action,$database,$fields,$filters); - if ($column_authorizer) $this->applyColumnAuthorizer($column_authorizer,$action,$database,$fields); - - // input - $inputs = $this->retrieveInputs($post); - foreach ($inputs as $k=>$context) { - $input = $this->filterInputByFields($context,$fields[$tables[0]]); - - if ($tenancy_function) $this->applyInputTenancy($tenancy_function,$action,$database,$tables[0],$input,$fields[$tables[0]]); - if ($input_sanitizer) $this->applyInputSanitizer($input_sanitizer,$action,$database,$tables[0],$input,$fields[$tables[0]]); - if ($input_validator) $this->applyInputValidator($input_validator,$action,$database,$tables[0],$input,$fields[$tables[0]],$context); - - $this->convertInputs($input,$fields[$tables[0]]); - $inputs[$k] = $input; - } - - if ($before) { - $this->applyBeforeHandler($action,$database,$tables[0],$key[0],$before,$inputs); - } - - return compact('action','database','tables','key','page','filters','fields','orderings','transform','inputs','collect','select','before','after'); - } - - protected function addWhereFromFilters($filters,&$sql,&$params) { - $first = true; - if (isset($filters['or'])) { - $first = false; - $sql .= ' WHERE ('; - foreach ($filters['or'] as $i=>$filter) { - $sql .= $i==0?'':' OR '; - $sql .= $filter[0]; - for ($i=1;$i$filter) { - $sql .= $first?' WHERE ':' AND '; - $sql .= $filter[0]; - for ($i=1;$i$ordering) { - $sql .= $i==0?' ORDER BY ':', '; - $sql .= '! '.$ordering[1]; - $params[] = $ordering[0]; - } - } - - protected function listCommandInternal($parameters) { - extract($parameters); - echo '{'; - $table = array_shift($tables); - // first table - $count = false; - echo '"'.$table.'":{'; - if (is_array($orderings) && is_array($page)) { - $params = array(); - $sql = 'SELECT COUNT(*) FROM !'; - $params[] = $table; - if (isset($filters[$table])) { - $this->addWhereFromFilters($filters[$table],$sql,$params); - } - if ($result = $this->db->query($sql,$params)) { - while ($pages = $this->db->fetchRow($result)) { - $count = (int)$pages[0]; - } - } - } - $params = array(); - $sql = 'SELECT '; - $this->convertOutputs($sql,$params,$fields[$table]); - $sql .= ' FROM !'; - $params[] = $table; - if (isset($filters[$table])) { - $this->addWhereFromFilters($filters[$table],$sql,$params); - } - if (is_array($orderings)) { - $this->addOrderByFromOrderings($orderings,$sql,$params); - } - if (is_array($orderings) && is_array($page)) { - $sql = $this->db->addLimitToSql($sql,$page[1],$page[0]); - } - if ($result = $this->db->query($sql,$params)) { - echo '"columns":'; - $keys = array_keys($fields[$table]); - echo json_encode($keys); - $keys = array_flip($keys); - echo ',"records":['; - $first_row = true; - while ($row = $this->fetchRow($result,$fields[$table])) { - if ($first_row) $first_row = false; - else echo ','; - if (isset($collect[$table])) { - foreach (array_keys($collect[$table]) as $field) { - $collect[$table][$field][] = $row[$keys[$field]]; - } - } - echo json_encode($row); - } - $this->db->close($result); - echo ']'; - if ($count) echo ','; - } - if ($count) echo '"results":'.$count; - echo '}'; - // other tables - foreach ($tables as $t=>$table) { - echo ','; - echo '"'.$table.'":{'; - $params = array(); - $sql = 'SELECT '; - $this->convertOutputs($sql,$params,$fields[$table]); - $sql .= ' FROM !'; - $params[] = $table; - if (isset($select[$table])) { - echo '"relations":{'; - $first_row = true; - foreach ($select[$table] as $field => $path) { - $values = $collect[$path[0]][$path[1]]; - if ($values) { - $this->addFilter($filters,$table,'and',$field,'in',implode(',',$values)); - } - if ($first_row) $first_row = false; - else echo ','; - echo '"'.$field.'":"'.implode('.',$path).'"'; - } - echo '}'; - } - if (isset($filters[$table])) { - $this->addWhereFromFilters($filters[$table],$sql,$params); - } - if ($result = $this->db->query($sql,$params)) { - if (isset($select[$table])) echo ','; - echo '"columns":'; - $keys = array_keys($fields[$table]); - echo json_encode($keys); - $keys = array_flip($keys); - echo ',"records":['; - $first_row = true; - while ($row = $this->fetchRow($result,$fields[$table])) { - if ($first_row) $first_row = false; - else echo ','; - if (isset($collect[$table])) { - foreach (array_keys($collect[$table]) as $field) { - $collect[$table][$field][]=$row[$keys[$field]]; - } - } - echo json_encode($row); - } - $this->db->close($result); - echo ']'; - } - echo '}'; - } - echo '}'; - } - - protected function readCommand($parameters) { - extract($parameters); - if (count($key[0])>1) $object = $this->retrieveObjects($key,$fields,$filters,$tables); - else $object = $this->retrieveObject($key,$fields,$filters,$tables); - if (!$object) $this->exitWith404('object'); - $this->startOutput(); - echo json_encode($object); - return false; - } - - protected function createCommand($parameters) { - extract($parameters); - if (!$inputs || !$inputs[0]) $this->exitWith404('input'); - if (count($inputs)>1) return $this->createObjects($inputs,$tables); - return $this->createObject($inputs[0],$tables); - - } - - protected function updateCommand($parameters) { - extract($parameters); - if (!$inputs || !$inputs[0]) $this->exitWith404('subject'); - if (count($inputs)>1) return $this->updateObjects($key,$inputs,$filters,$tables); - return $this->updateObject($key,$inputs[0],$filters,$tables); - } - - protected function deleteCommand($parameters) { - extract($parameters); - if (count($key[0])>1) return $this->deleteObjects($key,$filters,$tables); - return $this->deleteObject($key,$filters,$tables); - } - - protected function incrementCommand($parameters) { - extract($parameters); - if (!$inputs || !$inputs[0]) $this->exitWith404('subject'); - if (count($inputs)>1) return $this->incrementObjects($key,$inputs,$filters,$tables,$fields); - return $this->incrementObject($key,$inputs[0],$filters,$tables,$fields); - } - - protected function listCommand($parameters) { - extract($parameters); - $this->startOutput(); - if ($transform) { - ob_start(); - } - $this->listCommandInternal($parameters); - if ($transform) { - $content = ob_get_contents(); - ob_end_clean(); - $data = json_decode($content,true); - echo json_encode(self::php_crud_api_transform($data)); - } - return false; - } - - protected function retrievePostData() { - if ($_FILES) { - $files = array(); - foreach ($_FILES as $name => $file) { - foreach ($file as $key => $value) { - switch ($key) { - case 'tmp_name': $files[$name] = $value?base64_encode(file_get_contents($value)):''; break; - default: $files[$name.'_'.$key] = $value; - } - } - } - return http_build_query(array_merge($files,$_POST)); - } - return file_get_contents('php://input'); - } - - public function __construct($config) { - extract($config); - - // initialize - $dbengine = isset($dbengine)?$dbengine:null; - $hostname = isset($hostname)?$hostname:null; - $username = isset($username)?$username:null; - $password = isset($password)?$password:null; - $database = isset($database)?$database:null; - $port = isset($port)?$port:null; - $socket = isset($socket)?$socket:null; - $charset = isset($charset)?$charset:null; - - $table_authorizer = isset($table_authorizer)?$table_authorizer:null; - $record_filter = isset($record_filter)?$record_filter:null; - $column_authorizer = isset($column_authorizer)?$column_authorizer:null; - $tenancy_function = isset($tenancy_function)?$tenancy_function:null; - $input_sanitizer = isset($input_sanitizer)?$input_sanitizer:null; - $input_validator = isset($input_validator)?$input_validator:null; - $auto_include = isset($auto_include)?$auto_include:null; - $allow_origin = isset($allow_origin)?$allow_origin:null; - $before = isset($before)?$before:null; - $after = isset($after)?$after:null; - - $db = isset($db)?$db:null; - $method = isset($method)?$method:null; - $request = isset($request)?$request:null; - $get = isset($get)?$get:null; - $post = isset($post)?$post:null; - $origin = isset($origin)?$origin:null; - - // defaults - if (!$dbengine) { - $dbengine = 'MySQL'; - } - if (!$method) { - $method = $_SERVER['REQUEST_METHOD']; - } - if (!$request) { - $request = isset($_SERVER['PATH_INFO'])?$_SERVER['PATH_INFO']:''; - if (!$request) { - $request = isset($_SERVER['ORIG_PATH_INFO'])?$_SERVER['ORIG_PATH_INFO']:''; - $request = $request!=$_SERVER['SCRIPT_NAME']?$request:''; - } - } - if (!$get) { - $get = $_GET; - } - if (!$post) { - $post = $this->retrievePostData(); - } - if (!$origin) { - $origin = isset($_SERVER['HTTP_ORIGIN'])?$_SERVER['HTTP_ORIGIN']:''; - } - - // connect - $request = trim($request,'/'); - if (!$database) { - $database = $this->parseRequestParameter($request, 'a-zA-Z0-9\-_'); - } - if (!$db) { - $db = new $dbengine(); - if (!$charset) { - $charset = $db->getDefaultCharset(); - } - $db->connect($hostname,$username,$password,$database,$port,$socket,$charset); - } - if ($auto_include===null) { - $auto_include = true; - } - if ($allow_origin===null) { - $allow_origin = '*'; - } - - $this->db = $db; - $this->settings = compact('method', 'request', 'get', 'post', 'origin', 'database', 'table_authorizer', 'record_filter', 'column_authorizer', 'tenancy_function', 'input_sanitizer', 'input_validator', 'before', 'after', 'auto_include', 'allow_origin'); - } - - public static function php_crud_api_transform(&$tables) { - $get_objects = function (&$tables,$table_name,$where_index=false,$match_value=false) use (&$get_objects) { - $objects = array(); - if (isset($tables[$table_name]['records'])) { - foreach ($tables[$table_name]['records'] as $record) { - if ($where_index===false || $record[$where_index]==$match_value) { - $object = array(); - foreach ($tables[$table_name]['columns'] as $index=>$column) { - $object[$column] = $record[$index]; - foreach ($tables as $relation=>$reltable) { - if (isset($reltable['relations'])) { - foreach ($reltable['relations'] as $key=>$target) { - if ($target == "$table_name.$column") { - $column_indices = array_flip($reltable['columns']); - $object[$relation] = $get_objects($tables,$relation,$column_indices[$key],$record[$index]); - } - } - } - } - } - $objects[] = $object; - } - } - } - return $objects; - }; - $tree = array(); - foreach ($tables as $name=>$table) { - if (!isset($table['relations'])) { - $tree[$name] = $get_objects($tables,$name); - if (isset($table['results'])) { - $tree['_results'] = $table['results']; - } - } - } - return $tree; - } - - protected function swagger($settings) { - extract($settings); - - $tables = array(); - if ($result = $this->db->query($this->db->getSql('list_tables'),array($database))) { - while ($row = $this->db->fetchRow($result)) { - $table = array( - 'name'=>$row[0], - 'comments'=>$row[1], - 'root_actions'=>array( - array('name'=>'list','method'=>'get'), - array('name'=>'create','method'=>'post'), - ), - 'id_actions'=>array( - array('name'=>'read','method'=>'get'), - array('name'=>'update','method'=>'put'), - array('name'=>'delete','method'=>'delete'), - array('name'=>'increment','method'=>'patch'), - ), - ); - $tables[] = $table; - } - $this->db->close($result); - } - - $table_names = array_map(function($v){ return $v['name'];},$tables); - foreach ($tables as $t=>$table) { - $table_list = array($table['name']); - $table_fields = $this->findFields($table_list,false,false,false,$database); - - // extensions - $result = $this->db->query($this->db->getSql('reflect_belongs_to'),array($table_list[0],$table_names,$database,$database)); - while ($row = $this->db->fetchRow($result)) { - $table_fields[$table['name']][$row[1]]->references=array($row[2],$row[3]); - } - $result = $this->db->query($this->db->getSql('reflect_has_many'),array($table_names,$table_list[0],$database,$database)); - while ($row = $this->db->fetchRow($result)) { - $table_fields[$table['name']][$row[3]]->referenced[]=array($row[0],$row[1]); - } - $primaryKeys = $this->findPrimaryKeys($table_list[0],$database); - foreach ($primaryKeys as $primaryKey) { - $table_fields[$table['name']][$primaryKey]->primaryKey = true; - } - $result = $this->db->query($this->db->getSql('reflect_columns'),array($table_list[0],$database)); - while ($row = $this->db->fetchRow($result)) { - $table_fields[$table['name']][$row[0]]->required = strtolower($row[2])=='no' && $row[1]===null; - $table_fields[$table['name']][$row[0]]->{'x-nullable'} = strtolower($row[2])=='yes'; - $table_fields[$table['name']][$row[0]]->{'x-dbtype'} = $row[3]; - if ($this->db->isNumericType($table_fields[$table['name']][$row[0]])) { - if (strpos(strtolower($table_fields[$table['name']][$row[0]]->{'x-dbtype'}),'int')!==false) { - $table_fields[$table['name']][$row[0]]->type = 'integer'; - if ($row[1]!==null) $table_fields[$table['name']][$row[0]]->default = (int)$row[1]; - } else { - $table_fields[$table['name']][$row[0]]->type = 'number'; - if ($row[1]!==null) $table_fields[$table['name']][$row[0]]->default = (float)$row[1]; - } - } else { - if ($this->db->isBinaryType($table_fields[$table['name']][$row[0]])) { - $table_fields[$table['name']][$row[0]]->format = 'byte'; - } else if ($this->db->isGeometryType($table_fields[$table['name']][$row[0]])) { - $table_fields[$table['name']][$row[0]]->format = 'wkt'; - } else if ($this->db->isJsonType($table_fields[$table['name']][$row[0]])) { - $table_fields[$table['name']][$row[0]]->format = 'json'; - } - $table_fields[$table['name']][$row[0]]->type = 'string'; - if ($row[1]!==null) $table_fields[$table['name']][$row[0]]->default = $row[1]; - if ($row[4]!==null) $table_fields[$table['name']][$row[0]]->maxLength = (int)$row[4]; - } - } - - foreach (array('root_actions','id_actions') as $path) { - foreach ($table[$path] as $i=>$action) { - $table_list = array($table['name']); - $fields = $table_fields; - if ($table_authorizer) $this->applyTableAuthorizer($table_authorizer,$action['name'],$database,$table_list); - if ($column_authorizer) $this->applyColumnAuthorizer($column_authorizer,$action['name'],$database,$fields); - if (!$table_list || !$fields[$table['name']]) $tables[$t][$path][$i] = false; - else $tables[$t][$path][$i]['fields'] = $fields[$table['name']]; - } - // remove unauthorized tables and tables without fields - $tables[$t][$path] = array_values(array_filter($tables[$t][$path])); - } - if (!$tables[$t]['root_actions']&&!$tables[$t]['id_actions']) $tables[$t] = false; - } - $tables = array_merge(array_filter($tables)); - //var_dump($tables);die(); - - header('Content-Type: application/json; charset=utf-8'); - echo '{"swagger":"2.0",'; - echo '"info":{'; - echo '"title":"'.$database.'",'; - echo '"description":"API generated with [PHP-CRUD-API](https://github.com/mevdschee/php-crud-api)",'; - echo '"version":"1.0.0"'; - echo '},'; - echo '"host":"'.$_SERVER['HTTP_HOST'].'",'; - echo '"basePath":"'.$_SERVER['SCRIPT_NAME'].'",'; - echo '"schemes":["http'.((!empty($_SERVER['HTTPS'])&&$_SERVER['HTTPS']!=='off')?'s':'').'"],'; - echo '"consumes":["application/json"],'; - echo '"produces":["application/json"],'; - echo '"tags":['; - foreach ($tables as $i=>$table) { - if ($i>0) echo ','; - echo '{'; - echo '"name":"'.$table['name'].'",'; - echo '"description":"'.$table['comments'].'"'; - echo '}'; - } - echo '],'; - echo '"paths":{'; - foreach ($tables as $i=>$table) { - if ($table['root_actions']) { - if ($i>0) echo ','; - echo '"/'.$table['name'].'":{'; - foreach ($table['root_actions'] as $j=>$action) { - if ($j>0) echo ','; - echo '"'.$action['method'].'":{'; - echo '"tags":["'.$table['name'].'"],'; - echo '"summary":"'.ucfirst($action['name']).'",'; - if ($action['name']=='list') { - echo '"parameters":['; - echo '{'; - echo '"name":"exclude",'; - echo '"in":"query",'; - echo '"description":"One or more related entities (comma separated).",'; - echo '"required":false,'; - echo '"type":"string"'; - echo '},'; - echo '{'; - echo '"name":"include",'; - echo '"in":"query",'; - echo '"description":"One or more related entities (comma separated).",'; - echo '"required":false,'; - echo '"type":"string"'; - echo '},'; - echo '{'; - echo '"name":"order",'; - echo '"in":"query",'; - echo '"description":"Column you want to sort on and the sort direction (comma separated). Example: id,desc",'; - echo '"required":false,'; - echo '"type":"string"'; - echo '},'; - echo '{'; - echo '"name":"page",'; - echo '"in":"query",'; - echo '"description":"Page number and page size (comma separated). NB: You cannot use \"page\" without \"order\"! Example: 1,10",'; - echo '"required":false,'; - echo '"type":"string"'; - echo '},'; - echo '{'; - echo '"name":"transform",'; - echo '"in":"query",'; - echo '"description":"Transform the records to object format. NB: This can also be done client-side in JavaScript!",'; - echo '"required":false,'; - echo '"type":"boolean"'; - echo '},'; - echo '{'; - echo '"name":"columns",'; - echo '"in":"query",'; - echo '"description":"The table columns you want to retrieve (comma separated). Example: posts.*,categories.name",'; - echo '"required":false,'; - echo '"type":"string"'; - echo '},'; - echo '{'; - echo '"name":"filter[]",'; - echo '"in":"query",'; - echo '"description":"Filters to be applied. Each filter consists of a column, an operator and a value (comma separated). Example: id,eq,1",'; - echo '"required":false,'; - echo '"type":"array",'; - echo '"collectionFormat":"multi",'; - echo '"items":{"type":"string"}'; - echo '},'; - echo '{'; - echo '"name":"satisfy",'; - echo '"in":"query",'; - echo '"description":"Should all filters match (default)? Or any?",'; - echo '"required":false,'; - echo '"type":"string",'; - echo '"enum":["any"]'; - echo '}'; - echo '],'; - echo '"responses":{'; - echo '"200":{'; - echo '"description":"An array of '.$table['name'].'",'; - echo '"schema":{'; - echo '"type": "object",'; - echo '"properties": {'; - echo '"'.$table['name'].'": {'; - echo '"type":"array",'; - echo '"items":{'; - echo '"type": "object",'; - echo '"properties": {'; - foreach (array_keys($action['fields']) as $k=>$field) { - if ($k>0) echo ','; - echo '"'.$field.'": {'; - echo '"type": '.json_encode($action['fields'][$field]->type); - if (isset($action['fields'][$field]->format)) { - echo ',"format": '.json_encode($action['fields'][$field]->format); - } - echo ',"x-dbtype": '.json_encode($action['fields'][$field]->{'x-dbtype'}); - echo ',"x-nullable": '.json_encode($action['fields'][$field]->{'x-nullable'}); - if (isset($action['fields'][$field]->maxLength) && $action['fields'][$field]->maxLength>0) { - echo ',"maxLength": '.json_encode($action['fields'][$field]->maxLength); - } - if (isset($action['fields'][$field]->default)) { - echo ',"default": '.json_encode($action['fields'][$field]->default); - } - if (isset($action['fields'][$field]->referenced)) { - echo ',"x-referenced": '.json_encode($action['fields'][$field]->referenced); - } - if (isset($action['fields'][$field]->references)) { - echo ',"x-references": '.json_encode($action['fields'][$field]->references); - } - if (isset($action['fields'][$field]->primaryKey)) { - echo ',"x-primary-key": true'; - } - echo '}'; - } - echo '}'; //properties - echo '}'; //items - echo '}'; //table - echo '}'; //properties - echo '}'; //schema - echo '}'; //200 - echo '}'; //responses - } - if ($action['name']=='create') { - echo '"parameters":[{'; - echo '"name":"item",'; - echo '"in":"body",'; - echo '"description":"Item to create.",'; - echo '"required":true,'; - echo '"schema":{'; - echo '"type": "object",'; - $required_fields = array_keys(array_filter($action['fields'],function($f){ return $f->required; })); - if (count($required_fields) > 0) { - echo '"required":'.json_encode($required_fields).','; - } - echo '"properties": {'; - foreach (array_keys($action['fields']) as $k=>$field) { - if ($k>0) echo ','; - echo '"'.$field.'": {'; - echo '"type": '.json_encode($action['fields'][$field]->type); - if (isset($action['fields'][$field]->format)) { - echo ',"format": '.json_encode($action['fields'][$field]->format); - } - echo ',"x-dbtype": '.json_encode($action['fields'][$field]->{'x-dbtype'}); - echo ',"x-nullable": '.json_encode($action['fields'][$field]->{'x-nullable'}); - if (isset($action['fields'][$field]->maxLength)) { - echo ',"maxLength": '.json_encode($action['fields'][$field]->maxLength); - } - if (isset($action['fields'][$field]->default)) { - echo ',"default": '.json_encode($action['fields'][$field]->default); - } - if (isset($action['fields'][$field]->referenced)) { - echo ',"x-referenced": '.json_encode($action['fields'][$field]->referenced); - } - if (isset($action['fields'][$field]->references)) { - echo ',"x-references": '.json_encode($action['fields'][$field]->references); - } - if (isset($action['fields'][$field]->primaryKey)) { - echo ',"x-primary-key": true'; - } - echo '}'; - } - echo '}'; //properties - echo '}'; //schema - echo '}],'; - echo '"responses":{'; - echo '"200":{'; - echo '"description":"Identifier of created item.",'; - echo '"schema":{'; - echo '"type":"integer"'; - echo '}';//schema - echo '}';//200 - echo '}';//responses - } - echo '}';//method - } - echo '}'; - } - if ($table['id_actions']) { - if ($i>0 || $table['root_actions']) echo ','; - echo '"/'.$table['name'].'/{id}":{'; - foreach ($table['id_actions'] as $j=>$action) { - if ($j>0) echo ','; - echo '"'.$action['method'].'":{'; - echo '"tags":["'.$table['name'].'"],'; - echo '"summary":"'.ucfirst($action['name']).'",'; - echo '"parameters":['; - echo '{'; - echo '"name":"id",'; - echo '"in":"path",'; - echo '"description":"Identifier for item.",'; - echo '"required":true,'; - echo '"type":"string"'; - echo '}'; - if ($action['name']=='update' || $action['name']=='increment') { - echo ',{'; - echo '"name":"item",'; - echo '"in":"body",'; - echo '"description":"Properties of item to update.",'; - echo '"required":true,'; - echo '"schema":{'; - echo '"type": "object",'; - $required_fields = array_keys(array_filter($action['fields'],function($f){ return $f->required; })); - if (count($required_fields) > 0) { - echo '"required":'.json_encode($required_fields).','; - } - echo '"properties": {'; - foreach (array_keys($action['fields']) as $k=>$field) { - if ($k>0) echo ','; - echo '"'.$field.'": {'; - echo '"type": '.json_encode($action['fields'][$field]->type); - if (isset($action['fields'][$field]->format)) { - echo ',"format": '.json_encode($action['fields'][$field]->format); - } - echo ',"x-dbtype": '.json_encode($action['fields'][$field]->{'x-dbtype'}); - echo ',"x-nullable": '.json_encode($action['fields'][$field]->{'x-nullable'}); - if (isset($action['fields'][$field]->maxLength)) { - echo ',"maxLength": '.json_encode($action['fields'][$field]->maxLength); - } - if (isset($action['fields'][$field]->default)) { - echo ',"default": '.json_encode($action['fields'][$field]->default); - } - if (isset($action['fields'][$field]->referenced)) { - echo ',"x-referenced": '.json_encode($action['fields'][$field]->referenced); - } - if (isset($action['fields'][$field]->references)) { - echo ',"x-references": '.json_encode($action['fields'][$field]->references); - } - if (isset($action['fields'][$field]->primaryKey)) { - echo ',"x-primary-key": true'; - } - echo '}'; - } - echo '}'; //properties - echo '}'; //schema - echo '}'; - } - echo '],'; - if ($action['name']=='read') { - echo '"responses":{'; - echo '"200":{'; - echo '"description":"The requested item.",'; - echo '"schema":{'; - echo '"type": "object",'; - echo '"properties": {'; - foreach (array_keys($action['fields']) as $k=>$field) { - if ($k>0) echo ','; - echo '"'.$field.'": {'; - echo '"type": '.json_encode($action['fields'][$field]->type); - if (isset($action['fields'][$field]->format)) { - echo ',"format": '.json_encode($action['fields'][$field]->format); - } - echo ',"x-dbtype": '.json_encode($action['fields'][$field]->{'x-dbtype'}); - echo ',"x-nullable": '.json_encode($action['fields'][$field]->{'x-nullable'}); - if (isset($action['fields'][$field]->maxLength)) { - echo ',"maxLength": '.json_encode($action['fields'][$field]->maxLength); - } - if (isset($action['fields'][$field]->default)) { - echo ',"default": '.json_encode($action['fields'][$field]->default); - } - if (isset($action['fields'][$field]->referenced)) { - echo ',"x-referenced": '.json_encode($action['fields'][$field]->referenced); - } - if (isset($action['fields'][$field]->references)) { - echo ',"x-references": '.json_encode($action['fields'][$field]->references); - } - if (isset($action['fields'][$field]->primaryKey)) { - echo ',"x-primary-key": true'; - } - echo '}'; - } - echo '}'; //properties - echo '}'; //schema - echo '}'; - echo '}'; - } else { - echo '"responses":{'; - echo '"200":{'; - echo '"description":"Number of affected rows.",'; - echo '"schema":{'; - echo '"type":"integer"'; - echo '}'; - echo '}'; - echo '}'; - } - echo '}'; - } - echo '}'; - } - } - echo '}'; - echo '}'; - } - - protected function allowOrigin($origin,$allowOrigins) { - if (isset($_SERVER['REQUEST_METHOD'])) { - header('Access-Control-Allow-Credentials: true'); - foreach (explode(',',$allowOrigins) as $o) { - if (preg_match('/^'.str_replace('\*','.*',preg_quote(strtolower(trim($o)))).'$/',$origin)) { - header('Access-Control-Allow-Origin: '.$origin); - break; - } - } - } - } - - public function executeCommand() { - if ($this->settings['origin']) { - $this->allowOrigin($this->settings['origin'],$this->settings['allow_origin']); - } - if (!$this->settings['request']) { - $this->swagger($this->settings); - } else { - $parameters = $this->getParameters($this->settings); - switch($parameters['action']){ - case 'list': $output = $this->listCommand($parameters); break; - case 'read': $output = $this->readCommand($parameters); break; - case 'create': $output = $this->createCommand($parameters); break; - case 'update': $output = $this->updateCommand($parameters); break; - case 'delete': $output = $this->deleteCommand($parameters); break; - case 'increment': $output = $this->incrementCommand($parameters); break; - case 'headers': $output = $this->headersCommand($parameters); break; - default: $output = false; - } - if ($output!==false) { - $this->startOutput(); - echo json_encode($output); - } - if ($parameters['after']) { - $this->applyAfterHandler($parameters,$output); - } - } - } -} - -// require 'auth.php'; // from the PHP-API-AUTH project, see: https://github.com/mevdschee/php-api-auth - -// uncomment the lines below for token+session based authentication (see "login_token.html" + "login_token.php"): - -// $auth = new PHP_API_AUTH(array( -// 'secret'=>'someVeryLongPassPhraseChangeMe', -// )); -// if ($auth->executeCommand()) exit(0); -// if (empty($_SESSION['user']) || !$auth->hasValidCsrfToken()) { -// header('HTTP/1.0 401 Unauthorized'); -// exit(0); -// } - -// uncomment the lines below for form+session based authentication (see "login.html"): - -// $auth = new PHP_API_AUTH(array( -// 'authenticator'=>function($user,$pass){ $_SESSION['user']=($user=='admin' && $pass=='admin'); } -// )); -// if ($auth->executeCommand()) exit(0); -// if (empty($_SESSION['user']) || !$auth->hasValidCsrfToken()) { -// header('HTTP/1.0 401 Unauthorized'); -// exit(0); -// } - -// uncomment the lines below when running in stand-alone mode: - - $api = new PHP_CRUD_API(array( - 'dbengine'=>'MySQL', - 'hostname'=>'localhost', - 'username'=>'lazyp_workadmin', - 'password'=>'GH5fZF0iCtLnHLrz', - 'database'=>'LudosData', - 'charset'=>'utf8mb4' - )); - $api->executeCommand(); - -// For Microsoft SQL Server 2012 use: - -// $api = new PHP_CRUD_API(array( -// 'dbengine'=>'SQLServer', -// 'hostname'=>'(local)', -// 'username'=>'', -// 'password'=>'', -// 'database'=>'xxx', -// 'charset'=>'UTF-8' -// )); -// $api->executeCommand(); - -// For PostgreSQL 9 use: - -// $api = new PHP_CRUD_API(array( -// 'dbengine'=>'PostgreSQL', -// 'hostname'=>'localhost', -// 'username'=>'xxx', -// 'password'=>'xxx', -// 'database'=>'xxx', -// 'charset'=>'UTF8' -// )); -// $api->executeCommand(); - -// For SQLite 3 use: - -// $api = new PHP_CRUD_API(array( -// 'dbengine'=>'SQLite', -// 'database'=>'data/blog.db', -// )); -// $api->executeCommand(); diff --git a/interfaceServices/registration_NOT_USED.php b/interfaceServices/registration_NOT_USED.php deleted file mode 100644 index 87c6dd4..0000000 --- a/interfaceServices/registration_NOT_USED.php +++ /dev/null @@ -1,28 +0,0 @@ -getTimestamp(); - -$returnData = array(); -$date = new DateTime(); - -$passwordSalt = "sexfamemoney$U046qKlL$moneyfamesex"; - - -$hashedPassword = crypt( $password, $passwordSalt ); -$hashedId = crypt( $id, $passwordSalt ); - -/* -For login: -if (hash_equals($hashed_password, crypt($user_input, $hashed_password))) { - echo "Password verified!"; -} -*/ - -$returnData["password"] = $hashed_password; -$returnData["id"] = $hashedId; - -echo( json_encode( $returnData ) ); - -?> \ No newline at end of file diff --git a/karma.conf.js b/karma.conf.js deleted file mode 100644 index af139fa..0000000 --- a/karma.conf.js +++ /dev/null @@ -1,33 +0,0 @@ -// Karma configuration file, see link for more information -// https://karma-runner.github.io/1.0/config/configuration-file.html - -module.exports = function (config) { - config.set({ - basePath: '', - frameworks: ['jasmine', '@angular/cli'], - plugins: [ - require('karma-jasmine'), - require('karma-chrome-launcher'), - require('karma-jasmine-html-reporter'), - require('karma-coverage-istanbul-reporter'), - require('@angular/cli/plugins/karma') - ], - client:{ - clearContext: false // leave Jasmine Spec Runner output visible in browser - }, - coverageIstanbulReporter: { - reports: [ 'html', 'lcovonly' ], - fixWebpackSourcePaths: true - }, - angularCli: { - environment: 'dev' - }, - reporters: ['progress', 'kjhtml'], - port: 9876, - colors: true, - logLevel: config.LOG_INFO, - autoWatch: true, - browsers: ['Chrome'], - singleRun: false - }); -}; diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index b1c8565..0000000 --- a/package-lock.json +++ /dev/null @@ -1,12419 +0,0 @@ -{ - "name": "ludos-data", - "version": "0.0.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@angular-devkit/build-optimizer": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.3.2.tgz", - "integrity": "sha512-U0BCZtThq5rUfY08shHXpxe8ZhSsiYB/cJjUvAWRTs/ORrs8pbngS6xwseQws8d/vHoVrtqGD9GU9h8AmFRERQ==", - "dev": true, - "requires": { - "loader-utils": "1.1.0", - "source-map": "0.5.7", - "typescript": "2.6.2", - "webpack-sources": "1.1.0" - }, - "dependencies": { - "typescript": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.6.2.tgz", - "integrity": "sha1-PFtv1/beCRQmkCfwPAlGdY92c6Q=", - "dev": true - } - } - }, - "@angular-devkit/core": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-0.3.2.tgz", - "integrity": "sha512-zABk/iP7YX5SVbmK4e+IX7j2d0D37MQJQiKgWdV3JzfvVJhNJzddiirtT980pIafoq+KyvTgVwXtc+vnux0oeQ==", - "dev": true, - "requires": { - "ajv": "5.5.2", - "chokidar": "1.7.0", - "rxjs": "5.5.6", - "source-map": "0.5.7" - }, - "dependencies": { - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", - "dev": true, - "requires": { - "co": "4.6.0", - "fast-deep-equal": "1.1.0", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.3.1" - } - } - } - }, - "@angular-devkit/schematics": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-0.3.2.tgz", - "integrity": "sha512-B6zZoqvHaTJy+vVdA6EtlxnCdGMa5elCa4j9lQLC3JI8DLvMXUWkCIPVbPzJ/GSRR9nsKWpvYMYaJyfBDUqfhw==", - "dev": true, - "requires": { - "@ngtools/json-schema": "1.2.0", - "rxjs": "5.5.6" - } - }, - "@angular/animations": { - "version": "5.2.10", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-5.2.10.tgz", - "integrity": "sha512-QNYXqnti8BeFriNaZ/juLnO6l0MVlVNUmLycC9ma+pdTiEJl8rtgZ0WXxgOCjScyKpInkWn2J+m9FI/78SYFpw==", - "requires": { - "tslib": "1.9.0" - } - }, - "@angular/cdk": { - "version": "5.2.5", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-5.2.5.tgz", - "integrity": "sha512-GN8m1d+VcCE9+Bgwv06Y8YJKyZ0i9ZIq2ZPBcJYt+KVgnVVRg4JkyUNxud07LNsvzOX22DquHqmIZiC4hAG7Ag==", - "requires": { - "tslib": "1.9.0" - } - }, - "@angular/cli": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-1.7.1.tgz", - "integrity": "sha512-sFftjn+COiNVs7JpeUNQAKT4iZaryZVrne+jw7XtuyRZ0uAm/aaxEdczMgoK0cB98M38t6xsaGnXsKwolpMfaA==", - "dev": true, - "requires": { - "@angular-devkit/build-optimizer": "0.3.2", - "@angular-devkit/core": "0.3.2", - "@angular-devkit/schematics": "0.3.2", - "@ngtools/json-schema": "1.2.0", - "@ngtools/webpack": "1.10.1", - "@schematics/angular": "0.3.2", - "@schematics/package-update": "0.3.2", - "ajv": "6.2.0", - "autoprefixer": "7.2.6", - "cache-loader": "1.2.0", - "chalk": "2.2.2", - "circular-dependency-plugin": "4.4.0", - "clean-css": "4.1.9", - "common-tags": "1.7.2", - "copy-webpack-plugin": "4.4.2", - "core-object": "3.1.5", - "denodeify": "1.2.1", - "ember-cli-string-utils": "1.1.0", - "extract-text-webpack-plugin": "3.0.2", - "file-loader": "1.1.9", - "fs-extra": "4.0.3", - "glob": "7.1.2", - "html-webpack-plugin": "2.30.1", - "istanbul-instrumenter-loader": "3.0.0", - "karma-source-map-support": "1.2.0", - "less": "2.7.3", - "less-loader": "4.0.5", - "license-webpack-plugin": "1.1.2", - "loader-utils": "1.1.0", - "lodash": "4.17.5", - "memory-fs": "0.4.1", - "minimatch": "3.0.4", - "node-modules-path": "1.0.1", - "node-sass": "4.7.2", - "nopt": "4.0.1", - "opn": "5.1.0", - "portfinder": "1.0.13", - "postcss": "6.0.19", - "postcss-import": "11.1.0", - "postcss-loader": "2.1.1", - "postcss-url": "7.3.1", - "raw-loader": "0.5.1", - "resolve": "1.5.0", - "rxjs": "5.5.6", - "sass-loader": "6.0.6", - "semver": "5.5.0", - "silent-error": "1.1.0", - "source-map-support": "0.4.18", - "style-loader": "0.19.1", - "stylus": "0.54.5", - "stylus-loader": "3.0.2", - "uglifyjs-webpack-plugin": "1.2.2", - "url-loader": "0.6.2", - "webpack": "3.11.0", - "webpack-dev-middleware": "1.12.2", - "webpack-dev-server": "2.11.1", - "webpack-merge": "4.1.2", - "webpack-sources": "1.1.0", - "webpack-subresource-integrity": "1.0.4" - } - }, - "@angular/common": { - "version": "5.2.6", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-5.2.6.tgz", - "integrity": "sha512-gJrUKW9rDeVGP0pBNGDEEP/U+vBtgIVd1+52X5mc+dNFuUQdQ2kNTK5+fbDfwVstEWE86gloHlG2GS2Ga94R3Q==", - "requires": { - "tslib": "1.9.0" - } - }, - "@angular/compiler": { - "version": "5.2.6", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-5.2.6.tgz", - "integrity": "sha512-RVIIIbCmJwkfmL1jYmwTV2ve5k2JaNqVXKg8eT/wWiaCeqqZOydbIdcBUfgRxqn4ABZYgaeDNmjrsMl6acKv0A==", - "requires": { - "tslib": "1.9.0" - } - }, - "@angular/compiler-cli": { - "version": "5.2.6", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-5.2.6.tgz", - "integrity": "sha512-HKA6AvM6LZVkNFEDoQ9cRzPkiUpLIuJ+ndACg8cXkEDV30FetBiNC2p8viB14fdSzcuTnU+MULODhW/Tk3OAqA==", - "dev": true, - "requires": { - "chokidar": "1.7.0", - "minimist": "1.2.0", - "reflect-metadata": "0.1.12", - "tsickle": "0.27.2" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } - } - }, - "@angular/core": { - "version": "5.2.6", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-5.2.6.tgz", - "integrity": "sha512-BOkF7RM4VcqfIlQeOz17FucfocUmyZBsGIWxVSggeCBz2pQDyOUJ1IqrDh5c4yldW9G4Gjhhn/AkPykvPevI3w==", - "requires": { - "tslib": "1.9.0" - } - }, - "@angular/flex-layout": { - "version": "5.0.0-beta.14", - "resolved": "https://registry.npmjs.org/@angular/flex-layout/-/flex-layout-5.0.0-beta.14.tgz", - "integrity": "sha512-/fsOqXFUKdCmzzZx0bZ0HCYwcV+BSbVuIgOhaCrZKHj2rqiWKKPgj1ErU3HMT68bBBGag0u0skTdLGtrBorRIA==", - "requires": { - "tslib": "1.9.0" - } - }, - "@angular/forms": { - "version": "5.2.6", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-5.2.6.tgz", - "integrity": "sha512-Zo0uADD9nx6esfRx7oLLqw4uiA4zjvBh+MVeJj6ZfL7TYOJzLYyawt4qsvJFQ02tweldjs/duWaaf4tLHf6L0g==", - "requires": { - "tslib": "1.9.0" - } - }, - "@angular/http": { - "version": "5.2.6", - "resolved": "https://registry.npmjs.org/@angular/http/-/http-5.2.6.tgz", - "integrity": "sha512-8ecA0HrDY88vO9YKl6aG82budd0+vwFoECmZ9xRCiNu+HqlgJ7siyLzwdjllmoi90pJbvhQITytfy2zEmDBNIA==", - "requires": { - "tslib": "1.9.0" - } - }, - "@angular/language-service": { - "version": "5.2.6", - "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-5.2.6.tgz", - "integrity": "sha512-46PaLwRCVhzOb3/zvvznSqF1WQ7ITADCnFuhc09TS1YKH2yf62sOjnnuAo1ZZDdN6UI/Y1zajaBrhpupTit9Rw==", - "dev": true - }, - "@angular/material": { - "version": "5.2.5", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-5.2.5.tgz", - "integrity": "sha512-IltfBeTJWnmZehOQNQ7KoFs7MGWuZTe0g21hIitGkusVNt1cIoTD24xKH5jwztjH19c04IgiwonpurMKM6pBCQ==", - "requires": { - "tslib": "1.9.0" - } - }, - "@angular/platform-browser": { - "version": "5.2.6", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-5.2.6.tgz", - "integrity": "sha512-5jP0TeOCCM2SfXjC8306x6p3hdj7+GLuWsXuKyqozqdnr69RKI0vssY0XUzU0If/YsLWVoW/aY6wuiF8ybfIuA==", - "requires": { - "tslib": "1.9.0" - } - }, - "@angular/platform-browser-dynamic": { - "version": "5.2.6", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-5.2.6.tgz", - "integrity": "sha512-forNn/W2nYDGfHTw7qWX20d6FCICeI1hYoGGAXcjuODmFix1ebzFaxG1IOzGZqf/J3Zj43pEMdPMEp5tiukSVA==", - "requires": { - "tslib": "1.9.0" - } - }, - "@angular/router": { - "version": "5.2.6", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-5.2.6.tgz", - "integrity": "sha512-10Otnr5nmDWrlCpR5DTuDZmLj2hGf8WX1yFkfQ8H4EJWYe3YCTwGvz5D/smrWL6m6I2rOFgYOO3rFj9iYvMumA==", - "requires": { - "tslib": "1.9.0" - } - }, - "@ngtools/json-schema": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ngtools/json-schema/-/json-schema-1.2.0.tgz", - "integrity": "sha512-pMh+HDc6mOjUO3agRfB1tInimo7hf67u+0Cska2bfXFe6oU7rSMnr5PLVtiZVgwMoBHpx/6XjBymvcnWPo2Uzg==", - "dev": true - }, - "@ngtools/webpack": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-1.10.1.tgz", - "integrity": "sha512-Pa2FUy9n2Pu7kkTho6ADfHyypTmDMY8/HT7y9G3tZdaEsS7CjFMdchN5Dx+TCATGVh+G6FLS2mjgXBiGVTmbWw==", - "dev": true, - "requires": { - "chalk": "2.2.2", - "enhanced-resolve": "3.4.1", - "loader-utils": "1.1.0", - "magic-string": "0.22.4", - "semver": "5.5.0", - "source-map": "0.5.7", - "tree-kill": "1.2.0", - "webpack-sources": "1.1.0" - } - }, - "@schematics/angular": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-0.3.2.tgz", - "integrity": "sha512-Elrk0BA951s0ScFZU0AWrpUeJBYVR52DZ1QTIO5R0AhwEd1PW4olI8szPLGQlVW5Sd6H0FA/fyFLIvn2r9v6Rw==", - "dev": true, - "requires": { - "typescript": "2.6.2" - }, - "dependencies": { - "typescript": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.6.2.tgz", - "integrity": "sha1-PFtv1/beCRQmkCfwPAlGdY92c6Q=", - "dev": true - } - } - }, - "@schematics/package-update": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@schematics/package-update/-/package-update-0.3.2.tgz", - "integrity": "sha512-7aVP4994Hu8vRdTTohXkfGWEwLhrdNP3EZnWyBootm5zshWqlQojUGweZe5zwewsKcixeVOiy2YtW+aI4aGSLA==", - "dev": true, - "requires": { - "rxjs": "5.5.6", - "semver": "5.5.0", - "semver-intersect": "1.3.1" - } - }, - "@types/jasmine": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-2.8.6.tgz", - "integrity": "sha512-clg9raJTY0EOo5pVZKX3ZlMjlYzVU73L71q5OV1jhE2Uezb7oF94jh4CvwrW6wInquQAdhOxJz5VDF2TLUGmmA==", - "dev": true - }, - "@types/jasminewd2": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/jasminewd2/-/jasminewd2-2.0.3.tgz", - "integrity": "sha512-hYDVmQZT5VA2kigd4H4bv7vl/OhlympwREUemqBdOqtrYTo5Ytm12a5W5/nGgGYdanGVxj0x/VhZ7J3hOg/YKg==", - "dev": true, - "requires": { - "@types/jasmine": "2.8.6" - } - }, - "@types/node": { - "version": "6.0.101", - "resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.101.tgz", - "integrity": "sha512-IQ7V3D6+kK1DArTqTBrnl3M+YgJZLw8ta8w3Q9xjR79HaJzMAoTbZ8TNzUTztrkCKPTqIstE2exdbs1FzsYLUw==", - "dev": true - }, - "@types/q": { - "version": "0.0.32", - "resolved": "https://registry.npmjs.org/@types/q/-/q-0.0.32.tgz", - "integrity": "sha1-vShOV8hPEyXacCur/IKlMoGQwMU=", - "dev": true - }, - "@types/selenium-webdriver": { - "version": "2.53.43", - "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-2.53.43.tgz", - "integrity": "sha512-UBYHWph6P3tutkbXpW6XYg9ZPbTKjw/YC2hGG1/GEvWwTbvezBUv3h+mmUFw79T3RFPnmedpiXdOBbXX+4l0jg==", - "dev": true - }, - "@types/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-FKjsOVbC6B7bdSB5CuzyHCkK69I=", - "dev": true - }, - "@types/strip-json-comments": { - "version": "0.0.30", - "resolved": "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz", - "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==", - "dev": true - }, - "JSONStream": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.2.tgz", - "integrity": "sha1-wQI3G27Dp887hHygDCC7D85Mbeo=", - "dev": true, - "requires": { - "jsonparse": "1.3.1", - "through": "2.3.8" - } - }, - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true - }, - "accepts": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.4.tgz", - "integrity": "sha1-hiRnWMfdbSGmR0/whKR0DsBesh8=", - "dev": true, - "requires": { - "mime-types": "2.1.18", - "negotiator": "0.6.1" - } - }, - "acorn": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.4.1.tgz", - "integrity": "sha512-XLmq3H/BVvW6/GbxKryGxWORz1ebilSsUDlyC27bXhWGWAZWkGwS6FLHjOlwFXNFoWFQEO/Df4u0YYd0K3BQgQ==", - "dev": true - }, - "acorn-dynamic-import": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz", - "integrity": "sha1-x1K9IQvvZ5UBtsbLf8hPj0cVjMQ=", - "dev": true, - "requires": { - "acorn": "4.0.13" - }, - "dependencies": { - "acorn": { - "version": "4.0.13", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", - "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=", - "dev": true - } - } - }, - "acorn-node": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.3.0.tgz", - "integrity": "sha512-efP54n3d1aLfjL2UMdaXa6DsswwzJeI5rqhbFvXMrKiJ6eJFpf+7R0zN7t8IC+XKn2YOAFAv6xbBNgHUkoHWLw==", - "dev": true, - "requires": { - "acorn": "5.4.1", - "xtend": "4.0.1" - } - }, - "addressparser": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/addressparser/-/addressparser-1.0.1.tgz", - "integrity": "sha1-R6++GiqSYhkdtoOOT9HTm0CCF0Y=", - "dev": true, - "optional": true - }, - "adm-zip": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.7.tgz", - "integrity": "sha1-hgbCy/HEJs6MjsABdER/1Jtur8E=", - "dev": true - }, - "after": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", - "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=", - "dev": true - }, - "agent-base": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-2.1.1.tgz", - "integrity": "sha1-1t4Q1a9hMtW9aSQn1G/FOFOQlMc=", - "dev": true, - "requires": { - "extend": "3.0.1", - "semver": "5.0.3" - }, - "dependencies": { - "semver": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.0.3.tgz", - "integrity": "sha1-d0Zt5YnNXTyV8TiqeLxWmjy10no=", - "dev": true - } - } - }, - "ajv": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.2.0.tgz", - "integrity": "sha1-r6wpW7qgFSRJ5SJ0LkVHwa6TKNI=", - "dev": true, - "requires": { - "fast-deep-equal": "1.1.0", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.3.1" - } - }, - "ajv-keywords": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.1.0.tgz", - "integrity": "sha1-rCsnk5xUPpXSwG5/f1wnvkqlQ74=", - "dev": true - }, - "align-text": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", - "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", - "dev": true, - "requires": { - "kind-of": "3.2.2", - "longest": "1.0.1", - "repeat-string": "1.6.1" - } - }, - "amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", - "dev": true - }, - "amqplib": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.5.2.tgz", - "integrity": "sha512-l9mCs6LbydtHqRniRwYkKdqxVa6XMz3Vw1fh+2gJaaVgTM6Jk3o8RccAKWKtlhT1US5sWrFh+KKxsVUALURSIA==", - "dev": true, - "optional": true, - "requires": { - "bitsyntax": "0.0.4", - "bluebird": "3.5.1", - "buffer-more-ints": "0.0.2", - "readable-stream": "1.1.14", - "safe-buffer": "5.1.1" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true, - "optional": true - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "dev": true, - "optional": true, - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "0.0.1", - "string_decoder": "0.10.31" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true, - "optional": true - } - } - }, - "ansi-html": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", - "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", - "dev": true - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", - "dev": true, - "requires": { - "color-convert": "1.9.1" - } - }, - "anymatch": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", - "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", - "dev": true, - "requires": { - "micromatch": "2.3.11", - "normalize-path": "2.1.1" - } - }, - "app-root-path": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-2.0.1.tgz", - "integrity": "sha1-zWLc+OT9WkF+/GZNLlsQZTxlG0Y=", - "dev": true - }, - "append-transform": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-0.4.0.tgz", - "integrity": "sha1-126/jKlNJ24keja61EpLdKthGZE=", - "dev": true, - "requires": { - "default-require-extensions": "1.0.0" - } - }, - "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "dev": true - }, - "are-we-there-yet": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", - "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", - "dev": true, - "requires": { - "delegates": "1.0.0", - "readable-stream": "2.3.4" - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "1.0.3" - } - }, - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "dev": true, - "requires": { - "arr-flatten": "1.1.0" - } - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true - }, - "array-filter": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-0.0.1.tgz", - "integrity": "sha1-fajPLiZijtcygDWB/SH2fKzS7uw=", - "dev": true - }, - "array-find-index": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", - "dev": true - }, - "array-flatten": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.1.tgz", - "integrity": "sha1-Qmu52oQJDBg42BLIFQryCoMx4pY=", - "dev": true - }, - "array-includes": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz", - "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=", - "dev": true, - "requires": { - "define-properties": "1.1.2", - "es-abstract": "1.10.0" - } - }, - "array-map": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/array-map/-/array-map-0.0.0.tgz", - "integrity": "sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI=", - "dev": true - }, - "array-reduce": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/array-reduce/-/array-reduce-0.0.0.tgz", - "integrity": "sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys=", - "dev": true - }, - "array-slice": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", - "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=", - "dev": true - }, - "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "dev": true, - "requires": { - "array-uniq": "1.0.3" - } - }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "dev": true - }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", - "dev": true - }, - "arraybuffer.slice": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", - "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==", - "dev": true - }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "dev": true - }, - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", - "dev": true, - "optional": true - }, - "asn1": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", - "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==", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "inherits": "2.0.3", - "minimalistic-assert": "1.0.0" - } - }, - "assert": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", - "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", - "dev": true, - "requires": { - "util": "0.10.3" - } - }, - "assert-plus": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", - "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=", - "dev": true - }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true - }, - "ast-types": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.11.2.tgz", - "integrity": "sha512-aL+pcOQ+6dpWd0xrUe+Obo2CgdkFvsntkXEmzZKqEN4cR0PStF+1MBuc4V+YZsv4Q36luvyjG7F4lc+wH2bmag==", - "dev": true, - "optional": true - }, - "astw": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/astw/-/astw-2.2.0.tgz", - "integrity": "sha1-e9QXhNMkk5h66yOba04cV6hzuRc=", - "dev": true, - "requires": { - "acorn": "4.0.13" - }, - "dependencies": { - "acorn": { - "version": "4.0.13", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", - "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=", - "dev": true - } - } - }, - "async": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", - "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", - "dev": true, - "requires": { - "lodash": "4.17.5" - } - }, - "async-each": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", - "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", - "dev": true - }, - "async-foreach": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz", - "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=", - "dev": true, - "optional": true - }, - "async-limiter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "atob": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.0.3.tgz", - "integrity": "sha1-GcenYEc3dEaPILLS0DNyrX1Mv10=", - "dev": true - }, - "autoprefixer": { - "version": "7.2.6", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-7.2.6.tgz", - "integrity": "sha512-Iq8TRIB+/9eQ8rbGhcP7ct5cYb/3qjNYAR2SnzLCEcwF6rvVOax8+9+fccgXk4bEhQGjOZd5TLhsksmAdsbGqQ==", - "dev": true, - "requires": { - "browserslist": "2.11.3", - "caniuse-lite": "1.0.30000810", - "normalize-range": "0.1.2", - "num2fraction": "1.2.2", - "postcss": "6.0.19", - "postcss-value-parser": "3.3.0" - } - }, - "aws-sign2": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", - "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=", - "dev": true - }, - "aws4": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", - "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=", - "dev": true - }, - "axios": { - "version": "0.15.3", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.15.3.tgz", - "integrity": "sha1-LJ1jiy4ZGgjqHWzJiOrda6W9wFM=", - "dev": true, - "optional": true, - "requires": { - "follow-redirects": "1.0.0" - } - }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "dev": true, - "requires": { - "chalk": "1.1.3", - "esutils": "2.0.2", - "js-tokens": "3.0.2" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "babel-generator": { - "version": "6.26.1", - "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", - "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", - "dev": true, - "requires": { - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "detect-indent": "4.0.0", - "jsesc": "1.3.0", - "lodash": "4.17.5", - "source-map": "0.5.7", - "trim-right": "1.0.1" - } - }, - "babel-messages": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", - "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.5.3", - "regenerator-runtime": "0.11.1" - } - }, - "babel-template": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", - "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "lodash": "4.17.5" - } - }, - "babel-traverse": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", - "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", - "dev": true, - "requires": { - "babel-code-frame": "6.26.0", - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "debug": "2.6.9", - "globals": "9.18.0", - "invariant": "2.2.3", - "lodash": "4.17.5" - } - }, - "babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "esutils": "2.0.2", - "lodash": "4.17.5", - "to-fast-properties": "1.0.3" - } - }, - "babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", - "dev": true - }, - "backo2": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", - "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", - "dev": true - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "requires": { - "cache-base": "1.0.1", - "class-utils": "0.3.6", - "component-emitter": "1.2.1", - "define-property": "1.0.0", - "isobject": "3.0.1", - "mixin-deep": "1.3.1", - "pascalcase": "0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "1.0.2" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "base64-arraybuffer": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", - "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=", - "dev": true - }, - "base64-js": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.3.tgz", - "integrity": "sha512-MsAhsUW1GxCdgYSO6tAfZrNapmUKk7mWx/k5mFY/A1gBtkaCaNapTg+FExCw1r9yeaZhqx/xPg43xgTFH6KL5w==", - "dev": true - }, - "base64id": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", - "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=", - "dev": true - }, - "batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", - "dev": true - }, - "bcrypt-pbkdf": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", - "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", - "dev": true, - "optional": true, - "requires": { - "tweetnacl": "0.14.5" - } - }, - "better-assert": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", - "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", - "dev": true, - "requires": { - "callsite": "1.0.0" - } - }, - "big.js": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", - "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", - "dev": true - }, - "binary-extensions": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", - "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=", - "dev": true - }, - "bitsyntax": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/bitsyntax/-/bitsyntax-0.0.4.tgz", - "integrity": "sha1-6xDMb4K4xJDj6FaY8H6D1G4MuoI=", - "dev": true, - "optional": true, - "requires": { - "buffer-more-ints": "0.0.2" - } - }, - "bl": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.1.2.tgz", - "integrity": "sha1-/cqHGplxOqANGeO7ukHER4emU5g=", - "dev": true, - "optional": true, - "requires": { - "readable-stream": "2.0.6" - }, - "dependencies": { - "process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", - "dev": true, - "optional": true - }, - "readable-stream": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", - "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", - "dev": true, - "optional": true, - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "string_decoder": "0.10.31", - "util-deprecate": "1.0.2" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true, - "optional": true - } - } - }, - "blob": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.4.tgz", - "integrity": "sha1-vPEwUspURj8w+fx+lbmkdjCpSSE=", - "dev": true - }, - "block-stream": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", - "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", - "dev": true, - "optional": true, - "requires": { - "inherits": "2.0.3" - } - }, - "blocking-proxy": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/blocking-proxy/-/blocking-proxy-0.0.5.tgz", - "integrity": "sha1-RikF4Nz76pcPQao3Ij3anAexkSs=", - "dev": true, - "requires": { - "minimist": "1.2.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } - } - }, - "bluebird": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==", - "dev": true - }, - "bn.js": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", - "dev": true - }, - "body-parser": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", - "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", - "dev": true, - "requires": { - "bytes": "3.0.0", - "content-type": "1.0.4", - "debug": "2.6.9", - "depd": "1.1.2", - "http-errors": "1.6.2", - "iconv-lite": "0.4.19", - "on-finished": "2.3.0", - "qs": "6.5.1", - "raw-body": "2.3.2", - "type-is": "1.6.16" - }, - "dependencies": { - "qs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", - "dev": true - } - } - }, - "bonjour": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", - "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", - "dev": true, - "requires": { - "array-flatten": "2.1.1", - "deep-equal": "1.0.1", - "dns-equal": "1.0.0", - "dns-txt": "2.0.2", - "multicast-dns": "6.2.3", - "multicast-dns-service-types": "1.1.0" - } - }, - "boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", - "dev": true - }, - "boom": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", - "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", - "dev": true, - "requires": { - "hoek": "2.16.3" - } - }, - "bootstrap": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.0.0.tgz", - "integrity": "sha512-gulJE5dGFo6Q61V/whS6VM4WIyrlydXfCgkE+Gxe5hjrJ8rXLLZlALq7zq2RPhOc45PSwQpJkrTnc2KgD6cvmA==" - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", - "dev": true, - "requires": { - "expand-range": "1.8.2", - "preserve": "0.2.0", - "repeat-element": "1.1.2" - } - }, - "brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", - "dev": true - }, - "browser-pack": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/browser-pack/-/browser-pack-6.0.4.tgz", - "integrity": "sha512-Q4Rvn7P6ObyWfc4stqLWHtG1MJ8vVtjgT24Zbu+8UTzxYuZouqZsmNRRTFVMY/Ux0eIKv1d+JWzsInTX+fdHPQ==", - "dev": true, - "requires": { - "JSONStream": "1.3.2", - "combine-source-map": "0.8.0", - "defined": "1.0.0", - "safe-buffer": "5.1.1", - "through2": "2.0.3", - "umd": "3.0.1" - } - }, - "browser-resolve": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.2.tgz", - "integrity": "sha1-j/CbCixCFxihBRwmCzLkj0QpOM4=", - "dev": true, - "requires": { - "resolve": "1.1.7" - }, - "dependencies": { - "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", - "dev": true - } - } - }, - "browserify": { - "version": "14.5.0", - "resolved": "https://registry.npmjs.org/browserify/-/browserify-14.5.0.tgz", - "integrity": "sha512-gKfOsNQv/toWz+60nSPfYzuwSEdzvV2WdxrVPUbPD/qui44rAkB3t3muNtmmGYHqrG56FGwX9SUEQmzNLAeS7g==", - "dev": true, - "requires": { - "JSONStream": "1.3.2", - "assert": "1.4.1", - "browser-pack": "6.0.4", - "browser-resolve": "1.11.2", - "browserify-zlib": "0.2.0", - "buffer": "5.1.0", - "cached-path-relative": "1.0.1", - "concat-stream": "1.5.2", - "console-browserify": "1.1.0", - "constants-browserify": "1.0.0", - "crypto-browserify": "3.12.0", - "defined": "1.0.0", - "deps-sort": "2.0.0", - "domain-browser": "1.1.7", - "duplexer2": "0.1.4", - "events": "1.1.1", - "glob": "7.1.2", - "has": "1.0.1", - "htmlescape": "1.1.1", - "https-browserify": "1.0.0", - "inherits": "2.0.3", - "insert-module-globals": "7.0.1", - "labeled-stream-splicer": "2.0.0", - "module-deps": "4.1.1", - "os-browserify": "0.3.0", - "parents": "1.0.1", - "path-browserify": "0.0.0", - "process": "0.11.10", - "punycode": "1.4.1", - "querystring-es3": "0.2.1", - "read-only-stream": "2.0.0", - "readable-stream": "2.3.4", - "resolve": "1.5.0", - "shasum": "1.0.2", - "shell-quote": "1.6.1", - "stream-browserify": "2.0.1", - "stream-http": "2.8.0", - "string_decoder": "1.0.3", - "subarg": "1.0.0", - "syntax-error": "1.4.0", - "through2": "2.0.3", - "timers-browserify": "1.4.2", - "tty-browserify": "0.0.0", - "url": "0.11.0", - "util": "0.10.3", - "vm-browserify": "0.0.4", - "xtend": "4.0.1" - }, - "dependencies": { - "buffer": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.1.0.tgz", - "integrity": "sha512-YkIRgwsZwJWTnyQrsBTWefizHh+8GYj3kbL1BTiAQ/9pwpino0G7B2gp5tx/FUBqUlvtxV85KNR3mwfAtv15Yw==", - "dev": true, - "requires": { - "base64-js": "1.2.3", - "ieee754": "1.1.8" - } - }, - "concat-stream": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.2.tgz", - "integrity": "sha1-cIl4Yk2FavQaWnQd790mHadSwmY=", - "dev": true, - "requires": { - "inherits": "2.0.3", - "readable-stream": "2.0.6", - "typedarray": "0.0.6" - }, - "dependencies": { - "readable-stream": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", - "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", - "dev": true, - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "string_decoder": "0.10.31", - "util-deprecate": "1.0.2" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - } - } - }, - "domain-browser": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.1.7.tgz", - "integrity": "sha1-hnqksJP6oF8d4IwG9NeyH9+GmLw=", - "dev": true - }, - "process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", - "dev": true - }, - "timers-browserify": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz", - "integrity": "sha1-ycWLV1voQHN1y14kYtrO50NZ9B0=", - "dev": true, - "requires": { - "process": "0.11.10" - } - } - } - }, - "browserify-aes": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.1.1.tgz", - "integrity": "sha512-UGnTYAnB2a3YuYKIRy1/4FB2HdM866E0qC46JXvVTYKlBlZlnvfpSfY6OKfXZAkv70eJ2a1SqzpAo5CRhZGDFg==", - "dev": true, - "requires": { - "buffer-xor": "1.0.3", - "cipher-base": "1.0.4", - "create-hash": "1.1.3", - "evp_bytestokey": "1.0.3", - "inherits": "2.0.3", - "safe-buffer": "5.1.1" - } - }, - "browserify-cipher": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.0.tgz", - "integrity": "sha1-mYgkSHS/XtTijalWZtzWasj8Njo=", - "dev": true, - "requires": { - "browserify-aes": "1.1.1", - "browserify-des": "1.0.0", - "evp_bytestokey": "1.0.3" - } - }, - "browserify-des": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.0.tgz", - "integrity": "sha1-2qJ3cXRwki7S/hhZQRihdUOXId0=", - "dev": true, - "requires": { - "cipher-base": "1.0.4", - "des.js": "1.0.0", - "inherits": "2.0.3" - } - }, - "browserify-rsa": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", - "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "randombytes": "2.0.6" - } - }, - "browserify-sign": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", - "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "browserify-rsa": "4.0.1", - "create-hash": "1.1.3", - "create-hmac": "1.1.6", - "elliptic": "6.4.0", - "inherits": "2.0.3", - "parse-asn1": "5.1.0" - } - }, - "browserify-zlib": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", - "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", - "dev": true, - "requires": { - "pako": "1.0.6" - } - }, - "browserslist": { - "version": "2.11.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.11.3.tgz", - "integrity": "sha512-yWu5cXT7Av6mVwzWc8lMsJMHWn4xyjSuGYi4IozbVTLUOEYPSagUB8kiMDUHA1fS3zjr8nkxkn9jdvug4BBRmA==", - "dev": true, - "requires": { - "caniuse-lite": "1.0.30000810", - "electron-to-chromium": "1.3.34" - } - }, - "buffer": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", - "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", - "dev": true, - "requires": { - "base64-js": "1.2.3", - "ieee754": "1.1.8", - "isarray": "1.0.0" - } - }, - "buffer-indexof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", - "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==", - "dev": true - }, - "buffer-more-ints": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-0.0.2.tgz", - "integrity": "sha1-JrOIXRD6E9t/wBquOquHAZngEkw=", - "dev": true - }, - "buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", - "dev": true - }, - "buildmail": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/buildmail/-/buildmail-4.0.1.tgz", - "integrity": "sha1-h393OLeHKYccmhBeO4N9K+EaenI=", - "dev": true, - "optional": true, - "requires": { - "addressparser": "1.0.1", - "libbase64": "0.1.0", - "libmime": "3.0.0", - "libqp": "1.1.0", - "nodemailer-fetch": "1.6.0", - "nodemailer-shared": "1.1.0", - "punycode": "1.4.1" - } - }, - "builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", - "dev": true - }, - "builtin-status-codes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", - "dev": true - }, - "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", - "dev": true - }, - "cacache": { - "version": "10.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-10.0.4.tgz", - "integrity": "sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA==", - "dev": true, - "requires": { - "bluebird": "3.5.1", - "chownr": "1.0.1", - "glob": "7.1.2", - "graceful-fs": "4.1.11", - "lru-cache": "4.1.1", - "mississippi": "2.0.0", - "mkdirp": "0.5.1", - "move-concurrently": "1.0.1", - "promise-inflight": "1.0.1", - "rimraf": "2.6.2", - "ssri": "5.2.4", - "unique-filename": "1.1.0", - "y18n": "4.0.0" - } - }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "requires": { - "collection-visit": "1.0.0", - "component-emitter": "1.2.1", - "get-value": "2.0.6", - "has-value": "1.0.0", - "isobject": "3.0.1", - "set-value": "2.0.0", - "to-object-path": "0.3.0", - "union-value": "1.0.0", - "unset-value": "1.0.0" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "cache-loader": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/cache-loader/-/cache-loader-1.2.0.tgz", - "integrity": "sha512-E95knP7jxy2bF/HKuw5gCEXm06tp7/sEjewNF39ezyVBnVmNzB9bnXflEFBvrqZrswsCmgiCbiIc7xIeVXW7Gw==", - "dev": true, - "requires": { - "async": "2.6.0", - "loader-utils": "1.1.0", - "mkdirp": "0.5.1", - "schema-utils": "0.4.5" - } - }, - "cached-path-relative": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.0.1.tgz", - "integrity": "sha1-0JxLUoAKpMB44t2BqGmqyQ0uVOc=", - "dev": true - }, - "callsite": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", - "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", - "dev": true - }, - "camel-case": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", - "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", - "dev": true, - "requires": { - "no-case": "2.3.2", - "upper-case": "1.1.3" - } - }, - "camelcase": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", - "dev": true - }, - "camelcase-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", - "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", - "dev": true, - "requires": { - "camelcase": "2.1.1", - "map-obj": "1.0.1" - } - }, - "caniuse-lite": { - "version": "1.0.30000810", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000810.tgz", - "integrity": "sha512-/0Q00Oie9C72P8zQHtFvzmkrMC3oOFUnMWjCy5F2+BE8lzICm91hQPhh0+XIsAFPKOe2Dh3pKgbRmU3EKxfldA==", - "dev": true - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true - }, - "center-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", - "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", - "dev": true, - "requires": { - "align-text": "0.1.4", - "lazy-cache": "1.0.4" - }, - "dependencies": { - "lazy-cache": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", - "dev": true - } - } - }, - "chalk": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.2.2.tgz", - "integrity": "sha512-LvixLAQ4MYhbf7hgL4o5PeK32gJKvVzDRiSNIApDofQvyhl8adgG2lJVXn4+ekQoK7HL9RF8lqxwerpe0x2pCw==", - "dev": true, - "requires": { - "ansi-styles": "3.2.0", - "escape-string-regexp": "1.0.5", - "supports-color": "4.5.0" - } - }, - "chokidar": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", - "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", - "dev": true, - "requires": { - "anymatch": "1.3.2", - "async-each": "1.0.1", - "fsevents": "1.1.3", - "glob-parent": "2.0.0", - "inherits": "2.0.3", - "is-binary-path": "1.0.1", - "is-glob": "2.0.1", - "path-is-absolute": "1.0.1", - "readdirp": "2.1.0" - } - }, - "chownr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz", - "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=", - "dev": true - }, - "cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dev": true, - "requires": { - "inherits": "2.0.3", - "safe-buffer": "5.1.1" - } - }, - "circular-dependency-plugin": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/circular-dependency-plugin/-/circular-dependency-plugin-4.4.0.tgz", - "integrity": "sha512-yEFtUNUYT4jBykEX5ZOHw+5goA3glGZr9wAXIQqoyakjz5H5TeUmScnWRc52douAhb9eYzK3s7V6bXfNnjFdzg==", - "dev": true - }, - "circular-json": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.5.1.tgz", - "integrity": "sha512-UjgcRlTAhAkLeXmDe2wK7ktwy/tgAqxiSndTIPiFZuIPLZmzHzWMwUIe9h9m/OokypG7snxCDEuwJshGBdPvaw==", - "dev": true - }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "requires": { - "arr-union": "3.1.0", - "define-property": "0.2.5", - "isobject": "3.0.1", - "static-extend": "0.1.2" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "0.1.6" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "0.1.6", - "is-data-descriptor": "0.1.4", - "kind-of": "5.1.0" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "clean-css": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.1.9.tgz", - "integrity": "sha1-Nc7ornaHpJuYA09w3gDE7dOCYwE=", - "dev": true, - "requires": { - "source-map": "0.5.7" - } - }, - "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", - "dev": true, - "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wrap-ansi": "2.1.0" - } - }, - "clone": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", - "integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=", - "dev": true - }, - "clone-deep": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.3.0.tgz", - "integrity": "sha1-NIxhrpzb4O3+BT2R/0zFIdeQ7eg=", - "dev": true, - "requires": { - "for-own": "1.0.0", - "is-plain-object": "2.0.4", - "kind-of": "3.2.2", - "shallow-clone": "0.1.2" - }, - "dependencies": { - "for-own": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", - "dev": true, - "requires": { - "for-in": "1.0.2" - } - } - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, - "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 - }, - "codelyzer": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/codelyzer/-/codelyzer-4.1.0.tgz", - "integrity": "sha512-a3FCIAS3FNQIACvj7KA4iKvH3c6r7X6t6zXsrtV797QGYPQyCwD1fIEd9yV+ZDamijF3YaZ5fbB7QbUMOJGC/g==", - "dev": true, - "requires": { - "app-root-path": "2.0.1", - "css-selector-tokenizer": "0.7.0", - "cssauron": "1.4.0", - "semver-dsl": "1.0.1", - "source-map": "0.5.7", - "sprintf-js": "1.0.3" - } - }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, - "requires": { - "map-visit": "1.0.0", - "object-visit": "1.0.1" - } - }, - "color-convert": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", - "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "colors": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", - "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", - "dev": true - }, - "combine-lists": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/combine-lists/-/combine-lists-1.0.1.tgz", - "integrity": "sha1-RYwH4J4NkA/Ci3Cj/sLazR0st/Y=", - "dev": true, - "requires": { - "lodash": "4.17.5" - } - }, - "combine-source-map": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.8.0.tgz", - "integrity": "sha1-pY0N8ELBhvz4IqjoAV9UUNLXmos=", - "dev": true, - "requires": { - "convert-source-map": "1.1.3", - "inline-source-map": "0.6.2", - "lodash.memoize": "3.0.4", - "source-map": "0.5.7" - }, - "dependencies": { - "convert-source-map": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", - "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=", - "dev": true - } - } - }, - "combined-stream": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", - "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", - "dev": true, - "requires": { - "delayed-stream": "1.0.0" - } - }, - "commander": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.14.1.tgz", - "integrity": "sha512-+YR16o3rK53SmWHU3rEM3tPAh2rwb1yPcQX5irVn7mb0gXbwuCCrnkbV5+PBfETdfg1vui07nM6PCG1zndcjQw==", - "dev": true - }, - "common-tags": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.7.2.tgz", - "integrity": "sha512-joj9ZlUOjCrwdbmiLqafeUSgkUM74NqhLsZtSqDmhKudaIY197zTrb8JMl31fMnCUuxwFT23eC/oWvrZzDLRJQ==", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "dev": true - }, - "component-bind": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", - "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=", - "dev": true - }, - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", - "dev": true - }, - "component-inherit": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", - "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=", - "dev": true - }, - "compressible": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.13.tgz", - "integrity": "sha1-DRAgq5JLL9tNYnmHXH1tq6a6p6k=", - "dev": true, - "requires": { - "mime-db": "1.33.0" - } - }, - "compression": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.2.tgz", - "integrity": "sha1-qv+81qr4VLROuygDU9WtFlH1mmk=", - "dev": true, - "requires": { - "accepts": "1.3.4", - "bytes": "3.0.0", - "compressible": "2.0.13", - "debug": "2.6.9", - "on-headers": "1.0.1", - "safe-buffer": "5.1.1", - "vary": "1.1.2" - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "concat-stream": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", - "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", - "dev": true, - "requires": { - "inherits": "2.0.3", - "readable-stream": "2.3.4", - "typedarray": "0.0.6" - } - }, - "connect": { - "version": "3.6.6", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.6.6.tgz", - "integrity": "sha1-Ce/2xVr3I24TcTWnJXSFi2eG9SQ=", - "dev": true, - "requires": { - "debug": "2.6.9", - "finalhandler": "1.1.0", - "parseurl": "1.3.2", - "utils-merge": "1.0.1" - } - }, - "connect-history-api-fallback": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz", - "integrity": "sha1-sGhzk0vF40T+9hGhlqb6rgruAVo=", - "dev": true - }, - "console-browserify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", - "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", - "dev": true, - "requires": { - "date-now": "0.1.4" - } - }, - "console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "dev": true - }, - "constants-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", - "dev": true - }, - "content-disposition": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", - "dev": true - }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "dev": true - }, - "convert-source-map": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.1.tgz", - "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=", - "dev": true - }, - "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", - "dev": true - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", - "dev": true - }, - "copy-concurrently": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", - "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", - "dev": true, - "requires": { - "aproba": "1.2.0", - "fs-write-stream-atomic": "1.0.10", - "iferr": "0.1.5", - "mkdirp": "0.5.1", - "rimraf": "2.6.2", - "run-queue": "1.0.3" - } - }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true - }, - "copy-webpack-plugin": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-4.4.2.tgz", - "integrity": "sha512-tf1XKKQ5h+BPvXJ5/zx2xKVdF0/6J8XNvhB6fdmIReMnAfQGMbzph8F7ok2QF9kqWMfIgkCxwzk1zXkYqcLIqg==", - "dev": true, - "requires": { - "cacache": "10.0.4", - "find-cache-dir": "1.0.0", - "globby": "7.1.1", - "is-glob": "4.0.0", - "loader-utils": "0.2.17", - "minimatch": "3.0.4", - "p-limit": "1.2.0", - "serialize-javascript": "1.4.0" - }, - "dependencies": { - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-glob": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", - "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", - "dev": true, - "requires": { - "is-extglob": "2.1.1" - } - }, - "loader-utils": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", - "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", - "dev": true, - "requires": { - "big.js": "3.2.0", - "emojis-list": "2.1.0", - "json5": "0.5.1", - "object-assign": "4.1.1" - } - } - } - }, - "core-js": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.3.tgz", - "integrity": "sha1-isw4NFgk8W2DZbfJtCWRaOjtYD4=" - }, - "core-object": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/core-object/-/core-object-3.1.5.tgz", - "integrity": "sha512-sA2/4+/PZ/KV6CKgjrVrrUVBKCkdDO02CUlQ0YKTQoYUwPYNOtOAcWlbYhd5v/1JqYaA6oZ4sDlOU4ppVw6Wbg==", - "dev": true, - "requires": { - "chalk": "2.2.2" - } - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "cosmiconfig": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-2.2.2.tgz", - "integrity": "sha512-GiNXLwAFPYHy25XmTPpafYvn3CLAkJ8FLsscq78MQd1Kh0OU6Yzhn4eV2MVF4G9WEQZoWEGltatdR+ntGPMl5A==", - "dev": true, - "requires": { - "is-directory": "0.3.1", - "js-yaml": "3.10.0", - "minimist": "1.2.0", - "object-assign": "4.1.1", - "os-homedir": "1.0.2", - "parse-json": "2.2.0", - "require-from-string": "1.2.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } - } - }, - "create-ecdh": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.0.tgz", - "integrity": "sha1-iIxyNZbN92EvZJgjPuvXo1MBc30=", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "elliptic": "6.4.0" - } - }, - "create-hash": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz", - "integrity": "sha1-YGBCrIuSYnUPSDyt2rD1gZFy2P0=", - "dev": true, - "requires": { - "cipher-base": "1.0.4", - "inherits": "2.0.3", - "ripemd160": "2.0.1", - "sha.js": "2.4.10" - } - }, - "create-hmac": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.6.tgz", - "integrity": "sha1-rLniIaThe9sHbpBlfEK5PjcmzwY=", - "dev": true, - "requires": { - "cipher-base": "1.0.4", - "create-hash": "1.1.3", - "inherits": "2.0.3", - "ripemd160": "2.0.1", - "safe-buffer": "5.1.1", - "sha.js": "2.4.10" - } - }, - "cross-spawn": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz", - "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=", - "dev": true, - "optional": true, - "requires": { - "lru-cache": "4.1.1", - "which": "1.3.0" - } - }, - "cryptiles": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", - "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", - "dev": true, - "requires": { - "boom": "2.10.1" - } - }, - "crypto-browserify": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", - "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", - "dev": true, - "requires": { - "browserify-cipher": "1.0.0", - "browserify-sign": "4.0.4", - "create-ecdh": "4.0.0", - "create-hash": "1.1.3", - "create-hmac": "1.1.6", - "diffie-hellman": "5.0.2", - "inherits": "2.0.3", - "pbkdf2": "3.0.14", - "public-encrypt": "4.0.0", - "randombytes": "2.0.6", - "randomfill": "1.0.4" - } - }, - "css-parse": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/css-parse/-/css-parse-1.7.0.tgz", - "integrity": "sha1-Mh9s9zeCpv91ERE5D8BeLGV9jJs=", - "dev": true - }, - "css-select": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", - "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", - "dev": true, - "requires": { - "boolbase": "1.0.0", - "css-what": "2.1.0", - "domutils": "1.5.1", - "nth-check": "1.0.1" - } - }, - "css-selector-tokenizer": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz", - "integrity": "sha1-5piEdK6MlTR3v15+/s/OzNnPTIY=", - "dev": true, - "requires": { - "cssesc": "0.1.0", - "fastparse": "1.1.1", - "regexpu-core": "1.0.0" - } - }, - "css-what": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.0.tgz", - "integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0=", - "dev": true - }, - "cssauron": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/cssauron/-/cssauron-1.4.0.tgz", - "integrity": "sha1-pmAt/34EqDBtwNuaVR6S6LVmKtg=", - "dev": true, - "requires": { - "through": "2.3.8" - } - }, - "cssesc": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz", - "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=", - "dev": true - }, - "cuint": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz", - "integrity": "sha1-QICG1AlVDCYxFVYZ6fp7ytw7mRs=", - "dev": true - }, - "currently-unhandled": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", - "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", - "dev": true, - "requires": { - "array-find-index": "1.0.2" - } - }, - "custom-event": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", - "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", - "dev": true - }, - "cyclist": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz", - "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=", - "dev": true - }, - "d": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", - "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", - "dev": true, - "requires": { - "es5-ext": "0.10.39" - } - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, - "requires": { - "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - } - } - }, - "data-uri-to-buffer": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-1.2.0.tgz", - "integrity": "sha512-vKQ9DTQPN1FLYiiEEOQ6IBGFqvjCa5rSK3cWMy/Nespm5d/x3dGFT9UBZnkLxCwua/IXBi2TYnwTEpsOvhC4UQ==", - "dev": true, - "optional": true - }, - "date-format": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-1.2.0.tgz", - "integrity": "sha1-YV6CjiM90aubua4JUODOzPpuytg=", - "dev": true - }, - "date-now": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", - "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", - "dev": true - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true - }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true - }, - "deep-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", - "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", - "dev": true - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true, - "optional": true - }, - "default-require-extensions": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-1.0.0.tgz", - "integrity": "sha1-836hXT4T/9m0N9M+GnW1+5eHTLg=", - "dev": true, - "requires": { - "strip-bom": "2.0.0" - } - }, - "define-properties": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", - "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", - "dev": true, - "requires": { - "foreach": "2.0.5", - "object-keys": "1.0.11" - } - }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "requires": { - "is-descriptor": "1.0.2", - "isobject": "3.0.1" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "defined": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", - "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", - "dev": true - }, - "degenerator": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-1.0.4.tgz", - "integrity": "sha1-/PSQo37OJmRk2cxDGrmMWBnO0JU=", - "dev": true, - "optional": true, - "requires": { - "ast-types": "0.11.2", - "escodegen": "1.9.1", - "esprima": "3.1.3" - }, - "dependencies": { - "esprima": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", - "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", - "dev": true, - "optional": true - } - } - }, - "del": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz", - "integrity": "sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=", - "dev": true, - "requires": { - "globby": "6.1.0", - "is-path-cwd": "1.0.0", - "is-path-in-cwd": "1.0.0", - "p-map": "1.2.0", - "pify": "3.0.0", - "rimraf": "2.6.2" - }, - "dependencies": { - "globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", - "dev": true, - "requires": { - "array-union": "1.0.2", - "glob": "7.1.2", - "object-assign": "4.1.1", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } - } - } - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true - }, - "delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", - "dev": true - }, - "denodeify": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz", - "integrity": "sha1-OjYof1A05pnnV3kBBSwubJQlFjE=", - "dev": true - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "dev": true - }, - "deps-sort": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/deps-sort/-/deps-sort-2.0.0.tgz", - "integrity": "sha1-CRckkC6EZYJg65EHSMzNGvbiH7U=", - "dev": true, - "requires": { - "JSONStream": "1.3.2", - "shasum": "1.0.2", - "subarg": "1.0.0", - "through2": "2.0.3" - } - }, - "des.js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", - "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", - "dev": true, - "requires": { - "inherits": "2.0.3", - "minimalistic-assert": "1.0.0" - } - }, - "desandro-matches-selector": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/desandro-matches-selector/-/desandro-matches-selector-2.0.2.tgz", - "integrity": "sha1-cXvu1NwT59jzdi9wem1YpndCGOE=" - }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", - "dev": true - }, - "detect-indent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", - "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", - "dev": true, - "requires": { - "repeating": "2.0.1" - } - }, - "detect-node": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.3.tgz", - "integrity": "sha1-ogM8CcyOFY03dI+951B4Mr1s4Sc=", - "dev": true - }, - "detective": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/detective/-/detective-4.7.1.tgz", - "integrity": "sha512-H6PmeeUcZloWtdt4DAkFyzFL94arpHr3NOwwmVILFiy+9Qd4JTxxXrzfyGk/lmct2qVGBwTSwSXagqu2BxmWig==", - "dev": true, - "requires": { - "acorn": "5.4.1", - "defined": "1.0.0" - } - }, - "di": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", - "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", - "dev": true - }, - "diff": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.4.0.tgz", - "integrity": "sha512-QpVuMTEoJMF7cKzi6bvWhRulU1fZqZnvyVQgNhPaxxuTYwyjn/j1v9falseQ/uXWwPnO56RBfwtg4h/EQXmucA==", - "dev": true - }, - "diffie-hellman": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.2.tgz", - "integrity": "sha1-tYNXOScM/ias9jIJn97SoH8gnl4=", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "miller-rabin": "4.0.1", - "randombytes": "2.0.6" - } - }, - "dir-glob": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz", - "integrity": "sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag==", - "dev": true, - "requires": { - "arrify": "1.0.1", - "path-type": "3.0.0" - } - }, - "dns-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", - "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=", - "dev": true - }, - "dns-packet": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz", - "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==", - "dev": true, - "requires": { - "ip": "1.1.5", - "safe-buffer": "5.1.1" - } - }, - "dns-txt": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", - "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", - "dev": true, - "requires": { - "buffer-indexof": "1.1.1" - } - }, - "dom-converter": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.1.4.tgz", - "integrity": "sha1-pF71cnuJDJv/5tfIduexnLDhfzs=", - "dev": true, - "requires": { - "utila": "0.3.3" - }, - "dependencies": { - "utila": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/utila/-/utila-0.3.3.tgz", - "integrity": "sha1-1+jn1+MJEHCSsF+NloiCTWM6QiY=", - "dev": true - } - } - }, - "dom-serialize": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", - "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", - "dev": true, - "requires": { - "custom-event": "1.0.1", - "ent": "2.2.0", - "extend": "3.0.1", - "void-elements": "2.0.1" - } - }, - "dom-serializer": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", - "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", - "dev": true, - "requires": { - "domelementtype": "1.1.3", - "entities": "1.1.1" - }, - "dependencies": { - "domelementtype": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", - "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=", - "dev": true - } - } - }, - "domain-browser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", - "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", - "dev": true - }, - "domelementtype": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", - "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=", - "dev": true - }, - "domhandler": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.1.0.tgz", - "integrity": "sha1-0mRvXlf2w7qxHPbLBdPArPdBJZQ=", - "dev": true, - "requires": { - "domelementtype": "1.3.0" - } - }, - "domutils": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", - "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", - "dev": true, - "requires": { - "dom-serializer": "0.1.0", - "domelementtype": "1.3.0" - } - }, - "double-ended-queue": { - "version": "2.1.0-0", - "resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz", - "integrity": "sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw=", - "dev": true, - "optional": true - }, - "duplexer2": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", - "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", - "dev": true, - "requires": { - "readable-stream": "2.3.4" - } - }, - "duplexify": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.5.3.tgz", - "integrity": "sha512-g8ID9OroF9hKt2POf8YLayy+9594PzmM3scI00/uBXocX3TWNgoB67hjzkFe9ITAbQOne/lLdBxHXvYUM4ZgGA==", - "dev": true, - "requires": { - "end-of-stream": "1.4.1", - "inherits": "2.0.3", - "readable-stream": "2.3.4", - "stream-shift": "1.0.0" - } - }, - "ecc-jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", - "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", - "dev": true, - "optional": true, - "requires": { - "jsbn": "0.1.1" - } - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", - "dev": true - }, - "ejs": { - "version": "2.5.7", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.5.7.tgz", - "integrity": "sha1-zIcsFoiArjxxiXYv1f/ACJbJUYo=", - "dev": true - }, - "electron-to-chromium": { - "version": "1.3.34", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.34.tgz", - "integrity": "sha1-2TSY9AORuwwWpgPYJBuZUUBBV+0=", - "dev": true - }, - "elliptic": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz", - "integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "brorand": "1.1.0", - "hash.js": "1.1.3", - "hmac-drbg": "1.0.1", - "inherits": "2.0.3", - "minimalistic-assert": "1.0.0", - "minimalistic-crypto-utils": "1.0.1" - } - }, - "ember-cli-string-utils": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ember-cli-string-utils/-/ember-cli-string-utils-1.1.0.tgz", - "integrity": "sha1-ObZ3/CgF9VFzc1N2/O8njqpEUqE=", - "dev": true - }, - "emojis-list": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", - "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", - "dev": true - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", - "dev": true - }, - "end-of-stream": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", - "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", - "dev": true, - "requires": { - "once": "1.4.0" - } - }, - "engine.io": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.1.5.tgz", - "integrity": "sha512-D06ivJkYxyRrcEe0bTpNnBQNgP9d3xog+qZlLbui8EsMr/DouQpf5o9FzJnWYHEYE0YsFHllUv2R1dkgYZXHcA==", - "dev": true, - "requires": { - "accepts": "1.3.4", - "base64id": "1.0.0", - "cookie": "0.3.1", - "debug": "3.1.0", - "engine.io-parser": "2.1.2", - "uws": "9.14.0", - "ws": "3.3.3" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, - "engine.io-client": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.1.5.tgz", - "integrity": "sha512-Rv9vgb83zrNVhRircUXHi4mtbJhgy2oWtJOCZEbCLFs2HiDSWmh/aOEj8TwoKsn8zXGqTuQuPSoU4v3E10bR6A==", - "dev": true, - "requires": { - "component-emitter": "1.2.1", - "component-inherit": "0.0.3", - "debug": "3.1.0", - "engine.io-parser": "2.1.2", - "has-cors": "1.1.0", - "indexof": "0.0.1", - "parseqs": "0.0.5", - "parseuri": "0.0.5", - "ws": "3.3.3", - "xmlhttprequest-ssl": "1.5.5", - "yeast": "0.1.2" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, - "engine.io-parser": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.2.tgz", - "integrity": "sha512-dInLFzr80RijZ1rGpx1+56/uFoH7/7InhH3kZt+Ms6hT8tNx3NGW/WNSA/f8As1WkOfkuyb3tnRyuXGxusclMw==", - "dev": true, - "requires": { - "after": "0.8.2", - "arraybuffer.slice": "0.0.7", - "base64-arraybuffer": "0.1.5", - "blob": "0.0.4", - "has-binary2": "1.0.2" - } - }, - "enhanced-resolve": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz", - "integrity": "sha1-BCHjOf1xQZs9oT0Smzl5BAIwR24=", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "memory-fs": "0.4.1", - "object-assign": "4.1.1", - "tapable": "0.2.8" - } - }, - "ent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", - "dev": true - }, - "entities": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", - "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=", - "dev": true - }, - "errno": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", - "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", - "dev": true, - "requires": { - "prr": "1.0.1" - } - }, - "error-ex": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", - "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", - "dev": true, - "requires": { - "is-arrayish": "0.2.1" - } - }, - "es-abstract": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.10.0.tgz", - "integrity": "sha512-/uh/DhdqIOSkAWifU+8nG78vlQxdLckUdI/sPgy0VhuXi2qJ7T8czBmqIYtLQVpCIFYafChnsRsB5pyb1JdmCQ==", - "dev": true, - "requires": { - "es-to-primitive": "1.1.1", - "function-bind": "1.1.1", - "has": "1.0.1", - "is-callable": "1.1.3", - "is-regex": "1.0.4" - } - }, - "es-to-primitive": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz", - "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", - "dev": true, - "requires": { - "is-callable": "1.1.3", - "is-date-object": "1.0.1", - "is-symbol": "1.0.1" - } - }, - "es5-ext": { - "version": "0.10.39", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.39.tgz", - "integrity": "sha512-AlaXZhPHl0po/uxMx1tyrlt1O86M6D5iVaDH8UgLfgek4kXTX6vzsRfJQWC2Ku+aG8pkw1XWzh9eTkwfVrsD5g==", - "dev": true, - "requires": { - "es6-iterator": "2.0.3", - "es6-symbol": "3.1.1" - } - }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", - "dev": true, - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.39", - "es6-symbol": "3.1.1" - } - }, - "es6-map": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", - "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", - "dev": true, - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.39", - "es6-iterator": "2.0.3", - "es6-set": "0.1.5", - "es6-symbol": "3.1.1", - "event-emitter": "0.3.5" - } - }, - "es6-set": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", - "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", - "dev": true, - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.39", - "es6-iterator": "2.0.3", - "es6-symbol": "3.1.1", - "event-emitter": "0.3.5" - } - }, - "es6-symbol": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", - "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", - "dev": true, - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.39" - } - }, - "es6-weak-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", - "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", - "dev": true, - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.39", - "es6-iterator": "2.0.3", - "es6-symbol": "3.1.1" - } - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "escodegen": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.9.1.tgz", - "integrity": "sha512-6hTjO1NAWkHnDk3OqQ4YrCuwwmGHL9S3nPlzBOUG/R44rda3wLNrfvQ5fkSGjyhHFKM7ALPKcKGrwvCLe0lC7Q==", - "dev": true, - "optional": true, - "requires": { - "esprima": "3.1.3", - "estraverse": "4.2.0", - "esutils": "2.0.2", - "optionator": "0.8.2", - "source-map": "0.6.1" - }, - "dependencies": { - "esprima": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", - "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", - "dev": true, - "optional": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true - } - } - }, - "escope": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", - "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", - "dev": true, - "requires": { - "es6-map": "0.1.5", - "es6-weak-map": "2.0.2", - "esrecurse": "4.2.1", - "estraverse": "4.2.0" - } - }, - "esprima": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", - "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", - "dev": true - }, - "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", - "dev": true, - "requires": { - "estraverse": "4.2.0" - } - }, - "estraverse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", - "dev": true - }, - "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", - "dev": true - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", - "dev": true - }, - "ev-emitter": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ev-emitter/-/ev-emitter-1.1.1.tgz", - "integrity": "sha512-ipiDYhdQSCZ4hSbX4rMW+XzNKMD1prg/sTvoVmSLkuQ1MVlwjJQQA+sW8tMYR3BLUr9KjodFV4pvzunvRhd33Q==" - }, - "event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", - "dev": true, - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.39" - } - }, - "eventemitter3": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-1.2.0.tgz", - "integrity": "sha1-HIaZHYFq0eUEdQ5zh0Ik7PO+xQg=", - "dev": true - }, - "events": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", - "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", - "dev": true - }, - "eventsource": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-0.1.6.tgz", - "integrity": "sha1-Cs7ehJ7X3RzMMsgRuxG5RNTykjI=", - "dev": true, - "requires": { - "original": "1.0.0" - } - }, - "evp_bytestokey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "dev": true, - "requires": { - "md5.js": "1.3.4", - "safe-buffer": "5.1.1" - } - }, - "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", - "dev": true, - "requires": { - "cross-spawn": "5.1.0", - "get-stream": "3.0.0", - "is-stream": "1.1.0", - "npm-run-path": "2.0.2", - "p-finally": "1.0.0", - "signal-exit": "3.0.2", - "strip-eof": "1.0.0" - }, - "dependencies": { - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "dev": true, - "requires": { - "lru-cache": "4.1.1", - "shebang-command": "1.2.0", - "which": "1.3.0" - } - } - } - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", - "dev": true - }, - "expand-braces": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/expand-braces/-/expand-braces-0.1.2.tgz", - "integrity": "sha1-SIsdHSRRyz06axks/AMPRMWFX+o=", - "dev": true, - "requires": { - "array-slice": "0.2.3", - "array-unique": "0.2.1", - "braces": "0.1.5" - }, - "dependencies": { - "braces": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-0.1.5.tgz", - "integrity": "sha1-wIVxEIUpHYt1/ddOqw+FlygHEeY=", - "dev": true, - "requires": { - "expand-range": "0.1.1" - } - }, - "expand-range": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-0.1.1.tgz", - "integrity": "sha1-TLjtoJk8pW+k9B/ELzy7TMrf8EQ=", - "dev": true, - "requires": { - "is-number": "0.1.1", - "repeat-string": "0.2.2" - } - }, - "is-number": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-0.1.1.tgz", - "integrity": "sha1-aaevEWlj1HIG7JvZtIoUIW8eOAY=", - "dev": true - }, - "repeat-string": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-0.2.2.tgz", - "integrity": "sha1-x6jTI2BoNiBZp+RlH8aITosftK4=", - "dev": true - } - } - }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", - "dev": true, - "requires": { - "is-posix-bracket": "0.1.1" - } - }, - "expand-range": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", - "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", - "dev": true, - "requires": { - "fill-range": "2.2.3" - } - }, - "express": { - "version": "4.16.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.16.2.tgz", - "integrity": "sha1-41xt/i1kt9ygpc1PIXgb4ymeB2w=", - "dev": true, - "requires": { - "accepts": "1.3.4", - "array-flatten": "1.1.1", - "body-parser": "1.18.2", - "content-disposition": "0.5.2", - "content-type": "1.0.4", - "cookie": "0.3.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "1.1.2", - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "etag": "1.8.1", - "finalhandler": "1.1.0", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "1.1.2", - "on-finished": "2.3.0", - "parseurl": "1.3.2", - "path-to-regexp": "0.1.7", - "proxy-addr": "2.0.3", - "qs": "6.5.1", - "range-parser": "1.2.0", - "safe-buffer": "5.1.1", - "send": "0.16.1", - "serve-static": "1.13.1", - "setprototypeof": "1.1.0", - "statuses": "1.3.1", - "type-is": "1.6.16", - "utils-merge": "1.0.1", - "vary": "1.1.2" - }, - "dependencies": { - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", - "dev": true - }, - "qs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", - "dev": true - } - } - }, - "extend": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", - "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", - "dev": true - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "requires": { - "assign-symbols": "1.0.0", - "is-extendable": "1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "2.0.4" - } - } - } - }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", - "dev": true, - "requires": { - "is-extglob": "1.0.0" - } - }, - "extract-text-webpack-plugin": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extract-text-webpack-plugin/-/extract-text-webpack-plugin-3.0.2.tgz", - "integrity": "sha512-bt/LZ4m5Rqt/Crl2HiKuAl/oqg0psx1tsTLkvWbJen1CtD+fftkZhMaQ9HOtY2gWsl2Wq+sABmMVi9z3DhKWQQ==", - "dev": true, - "requires": { - "async": "2.6.0", - "loader-utils": "1.1.0", - "schema-utils": "0.3.0", - "webpack-sources": "1.1.0" - }, - "dependencies": { - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", - "dev": true, - "requires": { - "co": "4.6.0", - "fast-deep-equal": "1.1.0", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.3.1" - } - }, - "schema-utils": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.3.0.tgz", - "integrity": "sha1-9YdyIs4+kx7a4DnxfrNxbnE3+M8=", - "dev": true, - "requires": { - "ajv": "5.5.2" - } - } - } - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true - }, - "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true, - "optional": true - }, - "fastparse": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.1.tgz", - "integrity": "sha1-0eJkOzipTXWDtHkGDmxK/8lAcfg=", - "dev": true - }, - "faye-websocket": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", - "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", - "dev": true, - "requires": { - "websocket-driver": "0.7.0" - } - }, - "file-loader": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-1.1.9.tgz", - "integrity": "sha512-6ql03hOSoJHBkTB+3De/f7NJse+JXkUwvAf3y4Q5rIcTD0kqJiE3btvLnDcZT+P4t1QYLb9dJ9EI4auzfo3wFA==", - "dev": true, - "requires": { - "loader-utils": "1.1.0", - "schema-utils": "0.4.5" - } - }, - "file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "dev": true, - "optional": true - }, - "filename-regex": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", - "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", - "dev": true - }, - "fileset": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz", - "integrity": "sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=", - "dev": true, - "requires": { - "glob": "7.1.2", - "minimatch": "3.0.4" - } - }, - "fill-range": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", - "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", - "dev": true, - "requires": { - "is-number": "2.1.0", - "isobject": "2.1.0", - "randomatic": "1.1.7", - "repeat-element": "1.1.2", - "repeat-string": "1.6.1" - } - }, - "finalhandler": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", - "integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=", - "dev": true, - "requires": { - "debug": "2.6.9", - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "on-finished": "2.3.0", - "parseurl": "1.3.2", - "statuses": "1.3.1", - "unpipe": "1.0.0" - } - }, - "find-cache-dir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz", - "integrity": "sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=", - "dev": true, - "requires": { - "commondir": "1.0.1", - "make-dir": "1.2.0", - "pkg-dir": "2.0.0" - } - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "2.0.0" - } - }, - "fizzy-ui-utils": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/fizzy-ui-utils/-/fizzy-ui-utils-2.0.7.tgz", - "integrity": "sha512-CZXDVXQ1If3/r8s0T+v+qVeMshhfcuq0rqIFgJnrtd+Bu8GmDmqMjntjUePypVtjHXKJ6V4sw9zeyox34n9aCg==", - "requires": { - "desandro-matches-selector": "2.0.2" - } - }, - "flush-write-stream": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.2.tgz", - "integrity": "sha1-yBuQ2HRnZvGmCaRoCZRsRd2K5Bc=", - "dev": true, - "requires": { - "inherits": "2.0.3", - "readable-stream": "2.3.4" - } - }, - "follow-redirects": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.0.0.tgz", - "integrity": "sha1-jjQpjL0uF28lTv/sdaHHjMhJ/Tc=", - "dev": true, - "optional": true, - "requires": { - "debug": "2.6.9" - } - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true - }, - "for-own": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", - "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", - "dev": true, - "requires": { - "for-in": "1.0.2" - } - }, - "foreach": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", - "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", - "dev": true - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true - }, - "form-data": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", - "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", - "dev": true, - "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.6", - "mime-types": "2.1.18" - } - }, - "forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", - "dev": true - }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "requires": { - "map-cache": "0.2.2" - } - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", - "dev": true - }, - "from2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", - "dev": true, - "requires": { - "inherits": "2.0.3", - "readable-stream": "2.3.4" - } - }, - "fs-access": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz", - "integrity": "sha1-1qh/JiJxzv6+wwxVNAf7mV2od3o=", - "dev": true, - "requires": { - "null-check": "1.0.0" - } - }, - "fs-extra": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", - "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "jsonfile": "4.0.0", - "universalify": "0.1.1" - } - }, - "fs-write-stream-atomic": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", - "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "iferr": "0.1.5", - "imurmurhash": "0.1.4", - "readable-stream": "2.3.4" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "fsevents": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.3.tgz", - "integrity": "sha512-WIr7iDkdmdbxu/Gh6eKEZJL6KPE74/5MEsf2whTOFNxbIoIixogroLdKYqB6FDav4Wavh/lZdzzd3b2KxIXC5Q==", - "dev": true, - "optional": true, - "requires": { - "nan": "2.9.2", - "node-pre-gyp": "0.6.39" - }, - "dependencies": { - "abbrev": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true - }, - "ajv": { - "version": "4.11.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "co": "4.6.0", - "json-stable-stringify": "1.0.1" - } - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "dev": true - }, - "aproba": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "delegates": "1.0.0", - "readable-stream": "2.2.9" - } - }, - "asn1": { - "version": "0.2.3", - "bundled": true, - "dev": true, - "optional": true - }, - "assert-plus": { - "version": "0.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "asynckit": { - "version": "0.4.0", - "bundled": true, - "dev": true, - "optional": true - }, - "aws-sign2": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "aws4": { - "version": "1.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "balanced-match": { - "version": "0.4.2", - "bundled": true, - "dev": true - }, - "bcrypt-pbkdf": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "tweetnacl": "0.14.5" - } - }, - "block-stream": { - "version": "0.0.9", - "bundled": true, - "dev": true, - "requires": { - "inherits": "2.0.3" - } - }, - "boom": { - "version": "2.10.1", - "bundled": true, - "dev": true, - "requires": { - "hoek": "2.16.3" - } - }, - "brace-expansion": { - "version": "1.1.7", - "bundled": true, - "dev": true, - "requires": { - "balanced-match": "0.4.2", - "concat-map": "0.0.1" - } - }, - "buffer-shims": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "caseless": { - "version": "0.12.0", - "bundled": true, - "dev": true, - "optional": true - }, - "co": { - "version": "4.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "dev": true - }, - "combined-stream": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "requires": { - "delayed-stream": "1.0.0" - } - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "dev": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "dev": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "cryptiles": { - "version": "2.0.5", - "bundled": true, - "dev": true, - "requires": { - "boom": "2.10.1" - } - }, - "dashdash": { - "version": "1.14.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "debug": { - "version": "2.6.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ms": "2.0.0" - } - }, - "deep-extend": { - "version": "0.4.2", - "bundled": true, - "dev": true, - "optional": true - }, - "delayed-stream": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "ecc-jsbn": { - "version": "0.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "jsbn": "0.1.1" - } - }, - "extend": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "extsprintf": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "forever-agent": { - "version": "0.6.1", - "bundled": true, - "dev": true, - "optional": true - }, - "form-data": { - "version": "2.1.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.5", - "mime-types": "2.1.15" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "fstream": { - "version": "1.0.11", - "bundled": true, - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "inherits": "2.0.3", - "mkdirp": "0.5.1", - "rimraf": "2.6.1" - } - }, - "fstream-ignore": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "fstream": "1.0.11", - "inherits": "2.0.3", - "minimatch": "3.0.4" - } - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "aproba": "1.1.1", - "console-control-strings": "1.1.0", - "has-unicode": "2.0.1", - "object-assign": "4.1.1", - "signal-exit": "3.0.2", - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wide-align": "1.1.2" - } - }, - "getpass": { - "version": "0.1.7", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "glob": { - "version": "7.1.2", - "bundled": true, - "dev": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "graceful-fs": { - "version": "4.1.11", - "bundled": true, - "dev": true - }, - "har-schema": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "optional": true - }, - "har-validator": { - "version": "4.2.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ajv": "4.11.8", - "har-schema": "1.0.5" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "hawk": { - "version": "3.1.3", - "bundled": true, - "dev": true, - "requires": { - "boom": "2.10.1", - "cryptiles": "2.0.5", - "hoek": "2.16.3", - "sntp": "1.0.9" - } - }, - "hoek": { - "version": "2.16.3", - "bundled": true, - "dev": true - }, - "http-signature": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "0.2.0", - "jsprim": "1.4.0", - "sshpk": "1.13.0" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "dev": true, - "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" - } - }, - "inherits": { - "version": "2.0.3", - "bundled": true, - "dev": true - }, - "ini": { - "version": "1.3.4", - "bundled": true, - "dev": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "requires": { - "number-is-nan": "1.0.1" - } - }, - "is-typedarray": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "isstream": { - "version": "0.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "jodid25519": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "jsbn": "0.1.1" - } - }, - "jsbn": { - "version": "0.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "json-schema": { - "version": "0.2.3", - "bundled": true, - "dev": true, - "optional": true - }, - "json-stable-stringify": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "jsonify": "0.0.0" - } - }, - "json-stringify-safe": { - "version": "5.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "jsonify": { - "version": "0.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "jsprim": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.0.2", - "json-schema": "0.2.3", - "verror": "1.3.6" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "mime-db": { - "version": "1.27.0", - "bundled": true, - "dev": true - }, - "mime-types": { - "version": "2.1.15", - "bundled": true, - "dev": true, - "requires": { - "mime-db": "1.27.0" - } - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "dev": true, - "requires": { - "brace-expansion": "1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "bundled": true, - "dev": true - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "node-pre-gyp": { - "version": "0.6.39", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "detect-libc": "1.0.2", - "hawk": "3.1.3", - "mkdirp": "0.5.1", - "nopt": "4.0.1", - "npmlog": "4.1.0", - "rc": "1.2.1", - "request": "2.81.0", - "rimraf": "2.6.1", - "semver": "5.3.0", - "tar": "2.2.1", - "tar-pack": "3.4.0" - } - }, - "nopt": { - "version": "4.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "abbrev": "1.1.0", - "osenv": "0.1.4" - } - }, - "npmlog": { - "version": "4.1.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "are-we-there-yet": "1.1.4", - "console-control-strings": "1.1.0", - "gauge": "2.7.4", - "set-blocking": "2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "oauth-sign": { - "version": "0.8.2", - "bundled": true, - "dev": true, - "optional": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "requires": { - "wrappy": "1.0.2" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "osenv": { - "version": "0.1.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "performance-now": { - "version": "0.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "process-nextick-args": { - "version": "1.0.7", - "bundled": true, - "dev": true - }, - "punycode": { - "version": "1.4.1", - "bundled": true, - "dev": true, - "optional": true - }, - "qs": { - "version": "6.4.0", - "bundled": true, - "dev": true, - "optional": true - }, - "rc": { - "version": "1.2.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "deep-extend": "0.4.2", - "ini": "1.3.4", - "minimist": "1.2.0", - "strip-json-comments": "2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "readable-stream": { - "version": "2.2.9", - "bundled": true, - "dev": true, - "requires": { - "buffer-shims": "1.0.0", - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "string_decoder": "1.0.1", - "util-deprecate": "1.0.2" - } - }, - "request": { - "version": "2.81.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "aws-sign2": "0.6.0", - "aws4": "1.6.0", - "caseless": "0.12.0", - "combined-stream": "1.0.5", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.1.4", - "har-validator": "4.2.1", - "hawk": "3.1.3", - "http-signature": "1.1.1", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.15", - "oauth-sign": "0.8.2", - "performance-now": "0.2.0", - "qs": "6.4.0", - "safe-buffer": "5.0.1", - "stringstream": "0.0.5", - "tough-cookie": "2.3.2", - "tunnel-agent": "0.6.0", - "uuid": "3.0.1" - } - }, - "rimraf": { - "version": "2.6.1", - "bundled": true, - "dev": true, - "requires": { - "glob": "7.1.2" - } - }, - "safe-buffer": { - "version": "5.0.1", - "bundled": true, - "dev": true - }, - "semver": { - "version": "5.3.0", - "bundled": true, - "dev": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "sntp": { - "version": "1.0.9", - "bundled": true, - "dev": true, - "requires": { - "hoek": "2.16.3" - } - }, - "sshpk": { - "version": "1.13.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "asn1": "0.2.3", - "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.1", - "dashdash": "1.14.1", - "ecc-jsbn": "0.1.1", - "getpass": "0.1.7", - "jodid25519": "1.0.2", - "jsbn": "0.1.1", - "tweetnacl": "0.14.5" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" - } - }, - "string_decoder": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "requires": { - "safe-buffer": "5.0.1" - } - }, - "stringstream": { - "version": "0.0.5", - "bundled": true, - "dev": true, - "optional": true - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "requires": { - "ansi-regex": "2.1.1" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "tar": { - "version": "2.2.1", - "bundled": true, - "dev": true, - "requires": { - "block-stream": "0.0.9", - "fstream": "1.0.11", - "inherits": "2.0.3" - } - }, - "tar-pack": { - "version": "3.4.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "debug": "2.6.8", - "fstream": "1.0.11", - "fstream-ignore": "1.0.5", - "once": "1.4.0", - "readable-stream": "2.2.9", - "rimraf": "2.6.1", - "tar": "2.2.1", - "uid-number": "0.0.6" - } - }, - "tough-cookie": { - "version": "2.3.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "punycode": "1.4.1" - } - }, - "tunnel-agent": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "bundled": true, - "dev": true, - "optional": true - }, - "uid-number": { - "version": "0.0.6", - "bundled": true, - "dev": true, - "optional": true - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "uuid": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "verror": { - "version": "1.3.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "extsprintf": "1.0.2" - } - }, - "wide-align": { - "version": "1.1.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "string-width": "1.0.2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "dev": true - } - } - }, - "fstream": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", - "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "inherits": "2.0.3", - "mkdirp": "0.5.1", - "rimraf": "2.6.2" - } - }, - "ftp": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/ftp/-/ftp-0.3.10.tgz", - "integrity": "sha1-kZfYYa2BQvPmPVqDv+TFn3MwiF0=", - "dev": true, - "optional": true, - "requires": { - "readable-stream": "1.1.14", - "xregexp": "2.0.0" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true, - "optional": true - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "dev": true, - "optional": true, - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "0.0.1", - "string_decoder": "0.10.31" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true, - "optional": true - } - } - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "dev": true, - "requires": { - "aproba": "1.2.0", - "console-control-strings": "1.1.0", - "has-unicode": "2.0.1", - "object-assign": "4.1.1", - "signal-exit": "3.0.2", - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wide-align": "1.1.2" - } - }, - "gaze": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.2.tgz", - "integrity": "sha1-hHIkZ3rbiHDWeSV+0ziP22HkAQU=", - "dev": true, - "optional": true, - "requires": { - "globule": "1.2.0" - } - }, - "generate-function": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", - "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=", - "dev": true, - "optional": true - }, - "generate-object-property": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", - "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", - "dev": true, - "optional": true, - "requires": { - "is-property": "1.0.2" - } - }, - "get-caller-file": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", - "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=", - "dev": true - }, - "get-size": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-size/-/get-size-2.0.2.tgz", - "integrity": "sha1-VV6pirhzLgwCHp4j4iGa3L45jpg=" - }, - "get-stdin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", - "dev": true - }, - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true - }, - "get-uri": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-2.0.1.tgz", - "integrity": "sha512-7aelVrYqCLuVjq2kEKRTH8fXPTC0xKTkM+G7UlFkEwCXY3sFbSxvY375JoFowOAYbkaU47SrBvOefUlLZZ+6QA==", - "dev": true, - "optional": true, - "requires": { - "data-uri-to-buffer": "1.2.0", - "debug": "2.6.9", - "extend": "3.0.1", - "file-uri-to-path": "1.0.0", - "ftp": "0.3.10", - "readable-stream": "2.3.4" - } - }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, - "requires": { - "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - } - } - }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "glob-base": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", - "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", - "dev": true, - "requires": { - "glob-parent": "2.0.0", - "is-glob": "2.0.1" - } - }, - "glob-parent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", - "dev": true, - "requires": { - "is-glob": "2.0.1" - } - }, - "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true - }, - "globby": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz", - "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=", - "dev": true, - "requires": { - "array-union": "1.0.2", - "dir-glob": "2.0.0", - "glob": "7.1.2", - "ignore": "3.3.7", - "pify": "3.0.0", - "slash": "1.0.0" - } - }, - "globule": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.0.tgz", - "integrity": "sha1-HcScaCLdnoovoAuiopUAboZkvQk=", - "dev": true, - "optional": true, - "requires": { - "glob": "7.1.2", - "lodash": "4.17.5", - "minimatch": "3.0.4" - } - }, - "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", - "dev": true - }, - "handle-thing": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-1.2.5.tgz", - "integrity": "sha1-/Xqtcmvxpf0W38KbL3pmAdJxOcQ=", - "dev": true - }, - "handlebars": { - "version": "4.0.11", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.11.tgz", - "integrity": "sha1-Ywo13+ApS8KB7a5v/F0yn8eYLcw=", - "dev": true, - "requires": { - "async": "1.5.2", - "optimist": "0.6.1", - "source-map": "0.4.4", - "uglify-js": "2.8.29" - }, - "dependencies": { - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true - }, - "camelcase": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", - "dev": true, - "optional": true - }, - "cliui": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", - "dev": true, - "optional": true, - "requires": { - "center-align": "0.1.3", - "right-align": "0.1.3", - "wordwrap": "0.0.2" - } - }, - "source-map": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", - "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", - "dev": true, - "requires": { - "amdefine": "1.0.1" - } - }, - "uglify-js": { - "version": "2.8.29", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", - "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", - "dev": true, - "optional": true, - "requires": { - "source-map": "0.5.7", - "uglify-to-browserify": "1.0.2", - "yargs": "3.10.0" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "optional": true - } - } - }, - "yargs": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", - "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", - "dev": true, - "optional": true, - "requires": { - "camelcase": "1.2.1", - "cliui": "2.1.0", - "decamelize": "1.2.0", - "window-size": "0.1.0" - } - } - } - }, - "har-schema": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", - "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=", - "dev": true - }, - "har-validator": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", - "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", - "dev": true, - "requires": { - "ajv": "4.11.8", - "har-schema": "1.0.5" - }, - "dependencies": { - "ajv": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", - "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", - "dev": true, - "requires": { - "co": "4.6.0", - "json-stable-stringify": "1.0.1" - } - } - } - }, - "has": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", - "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", - "dev": true, - "requires": { - "function-bind": "1.1.1" - } - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, - "requires": { - "ansi-regex": "2.1.1" - } - }, - "has-binary2": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.2.tgz", - "integrity": "sha1-6D26SfC5vk0CbSc2U1DZ8D9Uvpg=", - "dev": true, - "requires": { - "isarray": "2.0.1" - }, - "dependencies": { - "isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", - "dev": true - } - } - }, - "has-cors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", - "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=", - "dev": true - }, - "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", - "dev": true - }, - "has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", - "dev": true - }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, - "requires": { - "get-value": "2.0.6", - "has-values": "1.0.0", - "isobject": "3.0.1" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, - "requires": { - "is-number": "3.0.0", - "kind-of": "4.0.0" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "hash-base": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz", - "integrity": "sha1-ZuodhW206KVHDK32/OI65SRO8uE=", - "dev": true, - "requires": { - "inherits": "2.0.3" - } - }, - "hash.js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", - "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", - "dev": true, - "requires": { - "inherits": "2.0.3", - "minimalistic-assert": "1.0.0" - } - }, - "hawk": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", - "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", - "dev": true, - "requires": { - "boom": "2.10.1", - "cryptiles": "2.0.5", - "hoek": "2.16.3", - "sntp": "1.0.9" - } - }, - "he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", - "dev": true - }, - "hipchat-notifier": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/hipchat-notifier/-/hipchat-notifier-1.1.0.tgz", - "integrity": "sha1-ttJJdVQ3wZEII2d5nTupoPI7Ix4=", - "dev": true, - "optional": true, - "requires": { - "lodash": "4.17.5", - "request": "2.81.0" - } - }, - "hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "dev": true, - "requires": { - "hash.js": "1.1.3", - "minimalistic-assert": "1.0.0", - "minimalistic-crypto-utils": "1.0.1" - } - }, - "hoek": { - "version": "2.16.3", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", - "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", - "dev": true - }, - "homedir-polyfill": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz", - "integrity": "sha1-TCu8inWJmP7r9e1oWA921GdotLw=", - "dev": true, - "requires": { - "parse-passwd": "1.0.0" - } - }, - "hosted-git-info": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz", - "integrity": "sha512-pNgbURSuab90KbTqvRPsseaTxOJCZBD0a7t+haSN33piP9cCM4l0CqdzAif2hUqm716UovKB2ROmiabGAKVXyg==", - "dev": true - }, - "hpack.js": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", - "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", - "dev": true, - "requires": { - "inherits": "2.0.3", - "obuf": "1.1.1", - "readable-stream": "2.3.4", - "wbuf": "1.7.2" - } - }, - "html-entities": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz", - "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=", - "dev": true - }, - "html-minifier": { - "version": "3.5.9", - "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.9.tgz", - "integrity": "sha512-EZqO91XJwkj8BeLx9C12sKB/AHoTANaZax39vEOP9f/X/9jgJ3r1O2+neabuHqpz5kJO71TapP9JrtCY39su1A==", - "dev": true, - "requires": { - "camel-case": "3.0.0", - "clean-css": "4.1.9", - "commander": "2.14.1", - "he": "1.1.1", - "ncname": "1.0.0", - "param-case": "2.1.1", - "relateurl": "0.2.7", - "uglify-js": "3.3.12" - } - }, - "html-webpack-plugin": { - "version": "2.30.1", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-2.30.1.tgz", - "integrity": "sha1-f5xCG36pHsRg9WUn1430hO51N9U=", - "dev": true, - "requires": { - "bluebird": "3.5.1", - "html-minifier": "3.5.9", - "loader-utils": "0.2.17", - "lodash": "4.17.5", - "pretty-error": "2.1.1", - "toposort": "1.0.6" - }, - "dependencies": { - "loader-utils": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", - "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", - "dev": true, - "requires": { - "big.js": "3.2.0", - "emojis-list": "2.1.0", - "json5": "0.5.1", - "object-assign": "4.1.1" - } - } - } - }, - "htmlescape": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/htmlescape/-/htmlescape-1.1.1.tgz", - "integrity": "sha1-OgPtwiFLyjtmQko+eVk0lQnLA1E=", - "dev": true - }, - "htmlparser2": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.3.0.tgz", - "integrity": "sha1-zHDQWln2VC5D8OaFyYLhTJJKnv4=", - "dev": true, - "requires": { - "domelementtype": "1.3.0", - "domhandler": "2.1.0", - "domutils": "1.1.6", - "readable-stream": "1.0.34" - }, - "dependencies": { - "domutils": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.1.6.tgz", - "integrity": "sha1-vdw94Jm5ou+sxRxiPyj0FuzFdIU=", - "dev": true, - "requires": { - "domelementtype": "1.3.0" - } - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "dev": true, - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "0.0.1", - "string_decoder": "0.10.31" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - } - } - }, - "http-deceiver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=", - "dev": true - }, - "http-errors": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", - "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", - "dev": true, - "requires": { - "depd": "1.1.1", - "inherits": "2.0.3", - "setprototypeof": "1.0.3", - "statuses": "1.3.1" - }, - "dependencies": { - "depd": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", - "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=", - "dev": true - }, - "setprototypeof": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", - "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=", - "dev": true - } - } - }, - "http-parser-js": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.10.tgz", - "integrity": "sha1-ksnBN0w1CF912zWexWzCV8u5P6Q=", - "dev": true - }, - "http-proxy": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.16.2.tgz", - "integrity": "sha1-Bt/ykpUr9k2+hHH6nfcwZtTzd0I=", - "dev": true, - "requires": { - "eventemitter3": "1.2.0", - "requires-port": "1.0.0" - } - }, - "http-proxy-agent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-1.0.0.tgz", - "integrity": "sha1-zBzjjkU7+YSg93AtLdWcc9CBKEo=", - "dev": true, - "requires": { - "agent-base": "2.1.1", - "debug": "2.6.9", - "extend": "3.0.1" - } - }, - "http-proxy-middleware": { - "version": "0.17.4", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.17.4.tgz", - "integrity": "sha1-ZC6ISIUdZvCdTxJJEoRtuutBuDM=", - "dev": true, - "requires": { - "http-proxy": "1.16.2", - "is-glob": "3.1.0", - "lodash": "4.17.5", - "micromatch": "2.3.11" - }, - "dependencies": { - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "requires": { - "is-extglob": "2.1.1" - } - } - } - }, - "http-signature": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", - "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", - "dev": true, - "requires": { - "assert-plus": "0.2.0", - "jsprim": "1.4.1", - "sshpk": "1.13.1" - } - }, - "httpntlm": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/httpntlm/-/httpntlm-1.6.1.tgz", - "integrity": "sha1-rQFScUOi6Hc8+uapb1hla7UqNLI=", - "dev": true, - "requires": { - "httpreq": "0.4.24", - "underscore": "1.7.0" - } - }, - "httpreq": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/httpreq/-/httpreq-0.4.24.tgz", - "integrity": "sha1-QzX/2CzZaWaKOUZckprGHWOTYn8=", - "dev": true - }, - "https-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", - "dev": true - }, - "https-proxy-agent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-1.0.0.tgz", - "integrity": "sha1-NffabEjOTdv6JkiRrFk+5f+GceY=", - "dev": true, - "requires": { - "agent-base": "2.1.1", - "debug": "2.6.9", - "extend": "3.0.1" - } - }, - "iconv-lite": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", - "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==", - "dev": true - }, - "ieee754": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", - "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=", - "dev": true - }, - "iferr": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", - "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", - "dev": true - }, - "ignore": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.7.tgz", - "integrity": "sha512-YGG3ejvBNHRqu0559EOxxNFihD0AjpvHlC/pdGKd3X3ofe+CoJkYazwNJYTNebqpPKN+VVQbh4ZFn1DivMNuHA==", - "dev": true - }, - "image-size": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", - "integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=", - "dev": true, - "optional": true - }, - "import-local": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-1.0.0.tgz", - "integrity": "sha512-vAaZHieK9qjGo58agRBg+bhHX3hoTZU/Oa3GESWLz7t1U62fk63aHuDJJEteXoDeTCcPmUT+z38gkHPZkkmpmQ==", - "dev": true, - "requires": { - "pkg-dir": "2.0.0", - "resolve-cwd": "2.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "in-publish": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz", - "integrity": "sha1-4g/146KvwmkDILbcVSaCqcf631E=", - "dev": true, - "optional": true - }, - "indent-string": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", - "dev": true, - "requires": { - "repeating": "2.0.1" - } - }, - "indexof": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", - "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", - "dev": true - }, - "infinite-scroll": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/infinite-scroll/-/infinite-scroll-3.0.3.tgz", - "integrity": "sha512-MwhXytjtcGI/TpGvjxS3RuJwG19HkCH4HGsWhOaKRNA1vfNH4ZvHgqO9Ifo2U8ZUjKFRmak4OUrKqHQbFaVdhg==", - "requires": { - "ev-emitter": "1.1.1", - "fizzy-ui-utils": "2.0.7" - } - }, - "inflection": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.10.0.tgz", - "integrity": "sha1-W//LEZetPoEFD44X4hZoCH7p6y8=", - "dev": true, - "optional": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "dev": true - }, - "inline-source-map": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.6.2.tgz", - "integrity": "sha1-+Tk0ccGKedFyT4Y/o4tYY3Ct4qU=", - "dev": true, - "requires": { - "source-map": "0.5.7" - } - }, - "insert-module-globals": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/insert-module-globals/-/insert-module-globals-7.0.1.tgz", - "integrity": "sha1-wDv04BywhtW15azorQr+eInWOMM=", - "dev": true, - "requires": { - "JSONStream": "1.3.2", - "combine-source-map": "0.7.2", - "concat-stream": "1.5.2", - "is-buffer": "1.1.6", - "lexical-scope": "1.2.0", - "process": "0.11.10", - "through2": "2.0.3", - "xtend": "4.0.1" - }, - "dependencies": { - "combine-source-map": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.7.2.tgz", - "integrity": "sha1-CHAxKFazB6h8xKxIbzqaYq7MwJ4=", - "dev": true, - "requires": { - "convert-source-map": "1.1.3", - "inline-source-map": "0.6.2", - "lodash.memoize": "3.0.4", - "source-map": "0.5.7" - } - }, - "concat-stream": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.2.tgz", - "integrity": "sha1-cIl4Yk2FavQaWnQd790mHadSwmY=", - "dev": true, - "requires": { - "inherits": "2.0.3", - "readable-stream": "2.0.6", - "typedarray": "0.0.6" - } - }, - "convert-source-map": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", - "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=", - "dev": true - }, - "process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", - "dev": true - }, - "readable-stream": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", - "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", - "dev": true, - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "string_decoder": "0.10.31", - "util-deprecate": "1.0.2" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - } - } - }, - "internal-ip": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-1.2.0.tgz", - "integrity": "sha1-rp+/k7mEh4eF1QqN4bNWlWBYz1w=", - "dev": true, - "requires": { - "meow": "3.7.0" - } - }, - "interpret": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", - "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=", - "dev": true - }, - "invariant": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.3.tgz", - "integrity": "sha512-7Z5PPegwDTyjbaeCnV0efcyS6vdKAU51kpEmS7QFib3P4822l8ICYyMn7qvJnc+WzLoDsuI9gPMKbJ8pCu8XtA==", - "dev": true, - "requires": { - "loose-envify": "1.3.1" - } - }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", - "dev": true - }, - "ip": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", - "dev": true - }, - "ipaddr.js": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.6.0.tgz", - "integrity": "sha1-4/o1e3c9phnybpXwSdBVxyeW+Gs=", - "dev": true - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - }, - "dependencies": { - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "dev": true, - "requires": { - "binary-extensions": "1.11.0" - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-builtin-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", - "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", - "dev": true, - "requires": { - "builtin-modules": "1.1.1" - } - }, - "is-callable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz", - "integrity": "sha1-hut1OSgF3cM69xySoO7fdO52BLI=", - "dev": true - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - }, - "dependencies": { - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } - } - }, - "is-date-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", - "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", - "dev": true - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" - }, - "dependencies": { - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } - } - }, - "is-directory": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", - "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", - "dev": true - }, - "is-dotfile": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", - "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", - "dev": true - }, - "is-equal-shallow": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", - "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", - "dev": true, - "requires": { - "is-primitive": "2.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, - "is-finite": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", - "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", - "dev": true, - "requires": { - "number-is-nan": "1.0.1" - } - }, - "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.1" - } - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "1.0.0" - } - }, - "is-my-ip-valid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", - "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==", - "dev": true, - "optional": true - }, - "is-my-json-valid": { - "version": "2.17.2", - "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.17.2.tgz", - "integrity": "sha512-IBhBslgngMQN8DDSppmgDv7RNrlFotuuDsKcrCP3+HbFaVivIBU7u9oiiErw8sH4ynx3+gOGQ3q2otkgiSi6kg==", - "dev": true, - "optional": true, - "requires": { - "generate-function": "2.0.0", - "generate-object-property": "1.2.0", - "is-my-ip-valid": "1.0.0", - "jsonpointer": "4.0.1", - "xtend": "4.0.1" - } - }, - "is-number": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", - "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - } - }, - "is-odd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-odd/-/is-odd-2.0.0.tgz", - "integrity": "sha512-OTiixgpZAT1M4NHgS5IguFp/Vz2VI3U7Goh4/HA1adtwyLtSBrxYlcSYkhpAE07s4fKEcjrFxyvtQBND4vFQyQ==", - "dev": true, - "requires": { - "is-number": "4.0.0" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true - } - } - }, - "is-path-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", - "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", - "dev": true - }, - "is-path-in-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz", - "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=", - "dev": true, - "requires": { - "is-path-inside": "1.0.1" - } - }, - "is-path-inside": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", - "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", - "dev": true, - "requires": { - "path-is-inside": "1.0.2" - } - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "3.0.1" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "is-posix-bracket": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", - "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", - "dev": true - }, - "is-primitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", - "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", - "dev": true - }, - "is-property": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", - "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", - "dev": true, - "optional": true - }, - "is-regex": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", - "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", - "dev": true, - "requires": { - "has": "1.0.1" - } - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true - }, - "is-symbol": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz", - "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=", - "dev": true - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "dev": true - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true - }, - "is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", - "dev": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "isbinaryfile": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.2.tgz", - "integrity": "sha1-Sj6XTsDLqQBNP8bN5yCeppNopiE=", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true - }, - "istanbul-api": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-1.2.2.tgz", - "integrity": "sha512-kH5YRdqdbs5hiH4/Rr1Q0cSAGgjh3jTtg8vu9NLebBAoK3adVO4jk81J+TYOkTr2+Q4NLeb1ACvmEt65iG/Vbw==", - "dev": true, - "requires": { - "async": "2.6.0", - "fileset": "2.0.3", - "istanbul-lib-coverage": "1.1.2", - "istanbul-lib-hook": "1.1.0", - "istanbul-lib-instrument": "1.9.2", - "istanbul-lib-report": "1.1.3", - "istanbul-lib-source-maps": "1.2.3", - "istanbul-reports": "1.1.4", - "js-yaml": "3.10.0", - "mkdirp": "0.5.1", - "once": "1.4.0" - } - }, - "istanbul-instrumenter-loader": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-instrumenter-loader/-/istanbul-instrumenter-loader-3.0.0.tgz", - "integrity": "sha512-alLSEFX06ApU75sm5oWcaVNaiss/bgMRiWTct3g0P0ZZTKjR+6QiCcuVOKDI1kWJgwHEnIXsv/dWm783kPpmtw==", - "dev": true, - "requires": { - "convert-source-map": "1.5.1", - "istanbul-lib-instrument": "1.9.2", - "loader-utils": "1.1.0", - "schema-utils": "0.3.0" - }, - "dependencies": { - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", - "dev": true, - "requires": { - "co": "4.6.0", - "fast-deep-equal": "1.1.0", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.3.1" - } - }, - "schema-utils": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.3.0.tgz", - "integrity": "sha1-9YdyIs4+kx7a4DnxfrNxbnE3+M8=", - "dev": true, - "requires": { - "ajv": "5.5.2" - } - } - } - }, - "istanbul-lib-coverage": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.2.tgz", - "integrity": "sha512-tZYA0v5A7qBSsOzcebJJ/z3lk3oSzH62puG78DbBA1+zupipX2CakDyiPV3pOb8He+jBwVimuwB0dTnh38hX0w==", - "dev": true - }, - "istanbul-lib-hook": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-1.1.0.tgz", - "integrity": "sha512-U3qEgwVDUerZ0bt8cfl3dSP3S6opBoOtk3ROO5f2EfBr/SRiD9FQqzwaZBqFORu8W7O0EXpai+k7kxHK13beRg==", - "dev": true, - "requires": { - "append-transform": "0.4.0" - } - }, - "istanbul-lib-instrument": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.9.2.tgz", - "integrity": "sha512-nz8t4HQ2206a/3AXi+NHFWEa844DMpPsgbcUteJbt1j8LX1xg56H9rOMnhvcvVvPbW60qAIyrSk44H8ZDqaSSA==", - "dev": true, - "requires": { - "babel-generator": "6.26.1", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "istanbul-lib-coverage": "1.1.2", - "semver": "5.5.0" - } - }, - "istanbul-lib-report": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-1.1.3.tgz", - "integrity": "sha512-D4jVbMDtT2dPmloPJS/rmeP626N5Pr3Rp+SovrPn1+zPChGHcggd/0sL29jnbm4oK9W0wHjCRsdch9oLd7cm6g==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "1.1.2", - "mkdirp": "0.5.1", - "path-parse": "1.0.5", - "supports-color": "3.2.3" - }, - "dependencies": { - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } - } - } - }, - "istanbul-lib-source-maps": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.3.tgz", - "integrity": "sha512-fDa0hwU/5sDXwAklXgAoCJCOsFsBplVQ6WBldz5UwaqOzmDhUK4nfuR7/G//G2lERlblUNJB8P6e8cXq3a7MlA==", - "dev": true, - "requires": { - "debug": "3.1.0", - "istanbul-lib-coverage": "1.1.2", - "mkdirp": "0.5.1", - "rimraf": "2.6.2", - "source-map": "0.5.7" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, - "istanbul-reports": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-1.1.4.tgz", - "integrity": "sha512-DfSTVOTkuO+kRmbO8Gk650Wqm1WRGr6lrdi2EwDK1vxpS71vdlLd613EpzOKdIFioB5f/scJTjeWBnvd1FWejg==", - "dev": true, - "requires": { - "handlebars": "4.0.11" - } - }, - "jasmine": { - "version": "2.99.0", - "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.99.0.tgz", - "integrity": "sha1-jKctEC5jm4Z8ZImFbg4YqceqQrc=", - "dev": true, - "requires": { - "exit": "0.1.2", - "glob": "7.1.2", - "jasmine-core": "2.99.1" - }, - "dependencies": { - "jasmine-core": { - "version": "2.99.1", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.99.1.tgz", - "integrity": "sha1-5kAN8ea1bhMLYcS80JPap/boyhU=", - "dev": true - } - } - }, - "jasmine-core": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz", - "integrity": "sha1-vMl5rh+f0FcB5F5S5l06XWPxok4=", - "dev": true - }, - "jasmine-spec-reporter": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-4.2.1.tgz", - "integrity": "sha512-FZBoZu7VE5nR7Nilzy+Np8KuVIOxF4oXDPDknehCYBDE080EnlPu0afdZNmpGDBRCUBv3mj5qgqCRmk6W/K8vg==", - "dev": true, - "requires": { - "colors": "1.1.2" - } - }, - "jasminewd2": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/jasminewd2/-/jasminewd2-2.2.0.tgz", - "integrity": "sha1-43zwsX8ZnM4jvqcbIDk5Uka07E4=", - "dev": true - }, - "jquery": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.3.1.tgz", - "integrity": "sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg==" - }, - "js-base64": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.3.tgz", - "integrity": "sha512-H7ErYLM34CvDMto3GbD6xD0JLUGYXR3QTcH6B/tr4Hi/QpSThnCsIp+Sy5FRTw3B0d6py4HcNkW7nO/wdtGWEw==", - "dev": true, - "optional": true - }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true - }, - "js-yaml": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz", - "integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==", - "dev": true, - "requires": { - "argparse": "1.0.10", - "esprima": "4.0.0" - } - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true, - "optional": true - }, - "jsesc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", - "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", - "dev": true - }, - "json-loader": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/json-loader/-/json-loader-0.5.7.tgz", - "integrity": "sha512-QLPs8Dj7lnf3e3QYS1zkCo+4ZwqOiF9d/nZnYozTISxXWCfNs9yuky5rJw4/W34s7POaNlbZmQGaB5NiXCbP4w==", - "dev": true - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true - }, - "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", - "dev": true - }, - "json-stable-stringify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", - "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", - "dev": true, - "requires": { - "jsonify": "0.0.0" - } - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true - }, - "json3": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", - "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", - "dev": true - }, - "json5": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", - "dev": true - }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "dev": true, - "requires": { - "graceful-fs": "4.1.11" - } - }, - "jsonify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", - "dev": true - }, - "jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", - "dev": true - }, - "jsonpointer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", - "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", - "dev": true, - "optional": true - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "dev": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - } - } - }, - "karma": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/karma/-/karma-2.0.0.tgz", - "integrity": "sha512-K9Kjp8CldLyL9ANSUctDyxC7zH3hpqXj/K09qVf06K3T/kXaHtFZ5tQciK7OzQu68FLvI89Na510kqQ2LCbpIw==", - "dev": true, - "requires": { - "bluebird": "3.5.1", - "body-parser": "1.18.2", - "browserify": "14.5.0", - "chokidar": "1.7.0", - "colors": "1.1.2", - "combine-lists": "1.0.1", - "connect": "3.6.6", - "core-js": "2.5.3", - "di": "0.0.1", - "dom-serialize": "2.2.1", - "expand-braces": "0.1.2", - "glob": "7.1.2", - "graceful-fs": "4.1.11", - "http-proxy": "1.16.2", - "isbinaryfile": "3.0.2", - "lodash": "4.17.5", - "log4js": "2.5.3", - "mime": "1.6.0", - "minimatch": "3.0.4", - "optimist": "0.6.1", - "qjobs": "1.2.0", - "range-parser": "1.2.0", - "rimraf": "2.6.2", - "safe-buffer": "5.1.1", - "socket.io": "2.0.4", - "source-map": "0.6.1", - "tmp": "0.0.33", - "useragent": "2.3.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "karma-chrome-launcher": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-2.2.0.tgz", - "integrity": "sha512-uf/ZVpAabDBPvdPdveyk1EPgbnloPvFFGgmRhYLTDH7gEB4nZdSBk8yTU47w1g/drLSx5uMOkjKk7IWKfWg/+w==", - "dev": true, - "requires": { - "fs-access": "1.0.1", - "which": "1.3.0" - } - }, - "karma-coverage-istanbul-reporter": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-1.4.1.tgz", - "integrity": "sha512-5og0toMjgLvsL9+TzGH4Rk1D0nr7pMIRJBg29xP4mHMKy/1KUJ12UzoqI6mBNCRFa4nDvZS2MRrN7p+RkZNWxQ==", - "dev": true, - "requires": { - "istanbul-api": "1.2.2", - "minimatch": "3.0.4" - } - }, - "karma-jasmine": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-1.1.1.tgz", - "integrity": "sha1-b+hA51oRYAydkehLM8RY4cRqNSk=", - "dev": true - }, - "karma-jasmine-html-reporter": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-0.2.2.tgz", - "integrity": "sha1-SKjl7xiAdhfuK14zwRlMNbQ5Ukw=", - "dev": true, - "requires": { - "karma-jasmine": "1.1.1" - } - }, - "karma-source-map-support": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.2.0.tgz", - "integrity": "sha1-G/gee7SwiWJ6s1LsQXnhF8QGpUA=", - "dev": true, - "requires": { - "source-map-support": "0.4.18" - } - }, - "killable": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.0.tgz", - "integrity": "sha1-2ouEvUfeU5WHj5XWTQLyRJ/gXms=", - "dev": true - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - }, - "labeled-stream-splicer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-2.0.0.tgz", - "integrity": "sha1-pS4dE4AkwAuGscDJH2d5GLiuClk=", - "dev": true, - "requires": { - "inherits": "2.0.3", - "isarray": "0.0.1", - "stream-splicer": "2.0.0" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - } - } - }, - "lazy-cache": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.7.tgz", - "integrity": "sha1-f+3fLctu23fRHvHRF6tf/fCrG2U=", - "dev": true - }, - "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "dev": true, - "requires": { - "invert-kv": "1.0.0" - } - }, - "less": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/less/-/less-2.7.3.tgz", - "integrity": "sha512-KPdIJKWcEAb02TuJtaLrhue0krtRLoRoo7x6BNJIBelO00t/CCdJQUnHW5V34OnHMWzIktSalJxRO+FvytQlCQ==", - "dev": true, - "requires": { - "errno": "0.1.7", - "graceful-fs": "4.1.11", - "image-size": "0.5.5", - "mime": "1.6.0", - "mkdirp": "0.5.1", - "promise": "7.3.1", - "request": "2.81.0", - "source-map": "0.5.7" - } - }, - "less-loader": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-4.0.5.tgz", - "integrity": "sha1-rhVadAbKxqzSk9eFWH/P8PR4xN0=", - "dev": true, - "requires": { - "clone": "2.1.1", - "loader-utils": "1.1.0", - "pify": "2.3.0" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } - } - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "optional": true, - "requires": { - "prelude-ls": "1.1.2", - "type-check": "0.3.2" - } - }, - "lexical-scope": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/lexical-scope/-/lexical-scope-1.2.0.tgz", - "integrity": "sha1-/Ope3HBKSzqHls3KQZw6CvryLfQ=", - "dev": true, - "requires": { - "astw": "2.2.0" - } - }, - "libbase64": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/libbase64/-/libbase64-0.1.0.tgz", - "integrity": "sha1-YjUag5VjrF/1vSbxL2Dpgwu3UeY=", - "dev": true - }, - "libmime": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/libmime/-/libmime-3.0.0.tgz", - "integrity": "sha1-UaGp50SOy9Ms2lRCFnW7IbwJPaY=", - "dev": true, - "requires": { - "iconv-lite": "0.4.15", - "libbase64": "0.1.0", - "libqp": "1.1.0" - }, - "dependencies": { - "iconv-lite": { - "version": "0.4.15", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz", - "integrity": "sha1-/iZaIYrGpXz+hUkn6dBMGYJe3es=", - "dev": true - } - } - }, - "libqp": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/libqp/-/libqp-1.1.0.tgz", - "integrity": "sha1-9ebgatdLeU+1tbZpiL9yjvHe2+g=", - "dev": true - }, - "license-webpack-plugin": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-1.1.2.tgz", - "integrity": "sha512-L40JKqFGSJ2z5bKOleYK3IgdOaTCoRx1p+zScf5yMCYQ1HsKrcCGFxVjZYvIWatcqGtdoEC0PZOBFgSaHMmvrw==", - "dev": true, - "requires": { - "ejs": "2.5.7" - } - }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "parse-json": "2.2.0", - "pify": "2.3.0", - "pinkie-promise": "2.0.1", - "strip-bom": "2.0.0" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } - } - }, - "loader-runner": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.3.0.tgz", - "integrity": "sha1-9IKuqC1UPgeSFwDVpG7yb9rGuKI=", - "dev": true - }, - "loader-utils": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz", - "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", - "dev": true, - "requires": { - "big.js": "3.2.0", - "emojis-list": "2.1.0", - "json5": "0.5.1" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "2.0.0", - "path-exists": "3.0.0" - } - }, - "lodash": { - "version": "4.17.5", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", - "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==", - "dev": true - }, - "lodash.assign": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", - "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=", - "dev": true, - "optional": true - }, - "lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", - "dev": true - }, - "lodash.memoize": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz", - "integrity": "sha1-LcvSwofLwKVcxCMovQxzYVDVPj8=", - "dev": true - }, - "lodash.mergewith": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz", - "integrity": "sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ==", - "dev": true, - "optional": true - }, - "lodash.tail": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.tail/-/lodash.tail-4.1.1.tgz", - "integrity": "sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=", - "dev": true - }, - "log4js": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-2.5.3.tgz", - "integrity": "sha512-YL/qpTxYtK0iWWbuKCrevDZz5lh+OjyHHD+mICqpjnYGKdNRBvPeh/1uYjkKUemT1CSO4wwLOwphWMpKAnD9kw==", - "dev": true, - "requires": { - "amqplib": "0.5.2", - "axios": "0.15.3", - "circular-json": "0.5.1", - "date-format": "1.2.0", - "debug": "3.1.0", - "hipchat-notifier": "1.1.0", - "loggly": "1.1.1", - "mailgun-js": "0.7.15", - "nodemailer": "2.7.2", - "redis": "2.8.0", - "semver": "5.5.0", - "slack-node": "0.2.0", - "streamroller": "0.7.0" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, - "loggly": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/loggly/-/loggly-1.1.1.tgz", - "integrity": "sha1-Cg/B0/o6XsRP3HuJe+uipGlc6+4=", - "dev": true, - "optional": true, - "requires": { - "json-stringify-safe": "5.0.1", - "request": "2.75.0", - "timespan": "2.3.0" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true, - "optional": true - }, - "caseless": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", - "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=", - "dev": true, - "optional": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "optional": true, - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "form-data": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.0.0.tgz", - "integrity": "sha1-bwrrrcxdoWwT4ezBETfYX5uIOyU=", - "dev": true, - "optional": true, - "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.6", - "mime-types": "2.1.18" - } - }, - "har-validator": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", - "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=", - "dev": true, - "optional": true, - "requires": { - "chalk": "1.1.3", - "commander": "2.14.1", - "is-my-json-valid": "2.17.2", - "pinkie-promise": "2.0.1" - } - }, - "node-uuid": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", - "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=", - "dev": true, - "optional": true - }, - "qs": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.2.3.tgz", - "integrity": "sha1-HPyyXBCpsrSDBT/zn138kjOQjP4=", - "dev": true, - "optional": true - }, - "request": { - "version": "2.75.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.75.0.tgz", - "integrity": "sha1-0rgmiihtoT6qXQGt9dGMyQ9lfZM=", - "dev": true, - "optional": true, - "requires": { - "aws-sign2": "0.6.0", - "aws4": "1.6.0", - "bl": "1.1.2", - "caseless": "0.11.0", - "combined-stream": "1.0.6", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.0.0", - "har-validator": "2.0.6", - "hawk": "3.1.3", - "http-signature": "1.1.1", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.18", - "node-uuid": "1.4.8", - "oauth-sign": "0.8.2", - "qs": "6.2.3", - "stringstream": "0.0.5", - "tough-cookie": "2.3.3", - "tunnel-agent": "0.4.3" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true, - "optional": true - }, - "tunnel-agent": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", - "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=", - "dev": true, - "optional": true - } - } - }, - "loglevel": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.1.tgz", - "integrity": "sha1-4PyVEztu8nbNyIh82vJKpvFW+Po=", - "dev": true - }, - "longest": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", - "dev": true - }, - "loose-envify": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", - "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", - "dev": true, - "requires": { - "js-tokens": "3.0.2" - } - }, - "loud-rejection": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", - "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", - "dev": true, - "requires": { - "currently-unhandled": "0.4.1", - "signal-exit": "3.0.2" - } - }, - "lower-case": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", - "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=", - "dev": true - }, - "lru-cache": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", - "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==", - "dev": true, - "requires": { - "pseudomap": "1.0.2", - "yallist": "2.1.2" - } - }, - "magic-string": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.22.4.tgz", - "integrity": "sha512-kxBL06p6iO2qPBHsqGK2b3cRwiRGpnmSuVWNhwHcMX7qJOUr1HvricYP1LZOCdkQBUp0jiWg2d6WJwR3vYgByw==", - "dev": true, - "requires": { - "vlq": "0.2.3" - } - }, - "mailcomposer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/mailcomposer/-/mailcomposer-4.0.1.tgz", - "integrity": "sha1-DhxEsqB890DuF9wUm6AJ8Zyt/rQ=", - "dev": true, - "optional": true, - "requires": { - "buildmail": "4.0.1", - "libmime": "3.0.0" - } - }, - "mailgun-js": { - "version": "0.7.15", - "resolved": "https://registry.npmjs.org/mailgun-js/-/mailgun-js-0.7.15.tgz", - "integrity": "sha1-7jZqINrGTDwVwD1sGz4O15UlKrs=", - "dev": true, - "optional": true, - "requires": { - "async": "2.1.5", - "debug": "2.2.0", - "form-data": "2.1.4", - "inflection": "1.10.0", - "is-stream": "1.1.0", - "path-proxy": "1.0.0", - "proxy-agent": "2.0.0", - "q": "1.4.1", - "tsscmp": "1.0.5" - }, - "dependencies": { - "async": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/async/-/async-2.1.5.tgz", - "integrity": "sha1-5YfGhYCZSsZ/xW/4bTrFa9voELw=", - "dev": true, - "optional": true, - "requires": { - "lodash": "4.17.5" - } - }, - "debug": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", - "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", - "dev": true, - "optional": true, - "requires": { - "ms": "0.7.1" - } - }, - "ms": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", - "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", - "dev": true, - "optional": true - } - } - }, - "make-dir": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.2.0.tgz", - "integrity": "sha512-aNUAa4UMg/UougV25bbrU4ZaaKNjJ/3/xnvg/twpmKROPdKZPZ9wGgI0opdZzO8q/zUFawoUuixuOv33eZ61Iw==", - "dev": true, - "requires": { - "pify": "3.0.0" - } - }, - "make-error": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.4.tgz", - "integrity": "sha512-0Dab5btKVPhibSalc9QGXb559ED7G7iLjFXBaj9Wq8O3vorueR5K5jaE3hkG6ZQINyhA/JgG6Qk4qdFQjsYV6g==", - "dev": true - }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true - }, - "map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, - "requires": { - "object-visit": "1.0.1" - } - }, - "masonry-layout": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/masonry-layout/-/masonry-layout-4.2.1.tgz", - "integrity": "sha512-ngJmxszn+JSKreNnrwkjks9OUuwVL2JR8T4iVeE3+g+sJjyoxTLdUNRbYONA25y+nWZn+WZI2GvThRAV+z5Duw==", - "requires": { - "get-size": "2.0.2", - "outlayer": "2.1.1" - } - }, - "material-design-icons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/material-design-icons/-/material-design-icons-3.0.1.tgz", - "integrity": "sha1-mnHEh0chjrylHlGmbaaCA4zct78=" - }, - "md5.js": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz", - "integrity": "sha1-6b296UogpawYsENA/Fdk1bCdkB0=", - "dev": true, - "requires": { - "hash-base": "3.0.4", - "inherits": "2.0.3" - }, - "dependencies": { - "hash-base": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", - "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", - "dev": true, - "requires": { - "inherits": "2.0.3", - "safe-buffer": "5.1.1" - } - } - } - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", - "dev": true - }, - "mem": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", - "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", - "dev": true, - "requires": { - "mimic-fn": "1.2.0" - } - }, - "memory-fs": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", - "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", - "dev": true, - "requires": { - "errno": "0.1.7", - "readable-stream": "2.3.4" - } - }, - "meow": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", - "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", - "dev": true, - "requires": { - "camelcase-keys": "2.1.0", - "decamelize": "1.2.0", - "loud-rejection": "1.6.0", - "map-obj": "1.0.1", - "minimist": "1.2.0", - "normalize-package-data": "2.4.0", - "object-assign": "4.1.1", - "read-pkg-up": "1.0.1", - "redent": "1.0.0", - "trim-newlines": "1.0.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } - } - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", - "dev": true - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", - "dev": true - }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", - "dev": true, - "requires": { - "arr-diff": "2.0.0", - "array-unique": "0.2.1", - "braces": "1.8.5", - "expand-brackets": "0.1.5", - "extglob": "0.3.2", - "filename-regex": "2.0.1", - "is-extglob": "1.0.0", - "is-glob": "2.0.1", - "kind-of": "3.2.2", - "normalize-path": "2.1.1", - "object.omit": "2.0.1", - "parse-glob": "3.0.4", - "regex-cache": "0.4.4" - } - }, - "miller-rabin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", - "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "brorand": "1.1.0" - } - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true - }, - "mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", - "dev": true - }, - "mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", - "dev": true, - "requires": { - "mime-db": "1.33.0" - } - }, - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "dev": true - }, - "minimalistic-assert": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz", - "integrity": "sha1-cCvi3aazf0g2vLP121ZkG2Sh09M=", - "dev": true - }, - "minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", - "dev": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "1.1.11" - } - }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true - }, - "mississippi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-2.0.0.tgz", - "integrity": "sha512-zHo8v+otD1J10j/tC+VNoGK9keCuByhKovAvdn74dmxJl9+mWHnx6EMsDN4lgRoMI/eYo2nchAxniIbUPb5onw==", - "dev": true, - "requires": { - "concat-stream": "1.6.0", - "duplexify": "3.5.3", - "end-of-stream": "1.4.1", - "flush-write-stream": "1.0.2", - "from2": "2.3.0", - "parallel-transform": "1.1.0", - "pump": "2.0.1", - "pumpify": "1.4.0", - "stream-each": "1.2.2", - "through2": "2.0.3" - } - }, - "mixin-deep": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", - "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", - "dev": true, - "requires": { - "for-in": "1.0.2", - "is-extendable": "1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "2.0.4" - } - } - } - }, - "mixin-object": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", - "integrity": "sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=", - "dev": true, - "requires": { - "for-in": "0.1.8", - "is-extendable": "0.1.1" - }, - "dependencies": { - "for-in": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", - "integrity": "sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=", - "dev": true - } - } - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "module-deps": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-4.1.1.tgz", - "integrity": "sha1-IyFYM/HaE/1gbMuAh7RIUty4If0=", - "dev": true, - "requires": { - "JSONStream": "1.3.2", - "browser-resolve": "1.11.2", - "cached-path-relative": "1.0.1", - "concat-stream": "1.5.2", - "defined": "1.0.0", - "detective": "4.7.1", - "duplexer2": "0.1.4", - "inherits": "2.0.3", - "parents": "1.0.1", - "readable-stream": "2.3.4", - "resolve": "1.5.0", - "stream-combiner2": "1.1.1", - "subarg": "1.0.0", - "through2": "2.0.3", - "xtend": "4.0.1" - }, - "dependencies": { - "concat-stream": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.2.tgz", - "integrity": "sha1-cIl4Yk2FavQaWnQd790mHadSwmY=", - "dev": true, - "requires": { - "inherits": "2.0.3", - "readable-stream": "2.0.6", - "typedarray": "0.0.6" - }, - "dependencies": { - "readable-stream": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", - "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", - "dev": true, - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "string_decoder": "0.10.31", - "util-deprecate": "1.0.2" - } - } - } - }, - "process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", - "dev": true - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - } - } - }, - "move-concurrently": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", - "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", - "dev": true, - "requires": { - "aproba": "1.2.0", - "copy-concurrently": "1.0.5", - "fs-write-stream-atomic": "1.0.10", - "mkdirp": "0.5.1", - "rimraf": "2.6.2", - "run-queue": "1.0.3" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "multicast-dns": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", - "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", - "dev": true, - "requires": { - "dns-packet": "1.3.1", - "thunky": "1.0.2" - } - }, - "multicast-dns-service-types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", - "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", - "dev": true - }, - "nan": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.9.2.tgz", - "integrity": "sha512-ltW65co7f3PQWBDbqVvaU1WtFJUsNW7sWWm4HINhbMQIyVyzIeyZ8toX5TC5eeooE6piZoaEh4cZkueSKG3KYw==", - "dev": true, - "optional": true - }, - "nanomatch": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.9.tgz", - "integrity": "sha512-n8R9bS8yQ6eSXaV6jHUpKzD8gLsin02w1HSFiegwrs9E098Ylhw5jdyKPaYqvHknHaSCKTPp7C8dGCQ0q9koXA==", - "dev": true, - "requires": { - "arr-diff": "4.0.0", - "array-unique": "0.3.2", - "define-property": "2.0.2", - "extend-shallow": "3.0.2", - "fragment-cache": "0.2.1", - "is-odd": "2.0.0", - "is-windows": "1.0.2", - "kind-of": "6.0.2", - "object.pick": "1.3.0", - "regex-not": "1.0.2", - "snapdragon": "0.8.1", - "to-regex": "3.0.2" - }, - "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } - } - }, - "ncname": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/ncname/-/ncname-1.0.0.tgz", - "integrity": "sha1-W1etGLHKCShk72Kwse2BlPODtxw=", - "dev": true, - "requires": { - "xml-char-classes": "1.0.0" - } - }, - "negotiator": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", - "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", - "dev": true - }, - "netmask": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/netmask/-/netmask-1.0.6.tgz", - "integrity": "sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU=", - "dev": true, - "optional": true - }, - "no-case": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", - "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", - "dev": true, - "requires": { - "lower-case": "1.1.4" - } - }, - "node-forge": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.1.tgz", - "integrity": "sha1-naYR6giYL0uUIGs760zJZl8gwwA=", - "dev": true - }, - "node-gyp": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.6.2.tgz", - "integrity": "sha1-m/vlRWIoYoSDjnUOrAUpWFP6HGA=", - "dev": true, - "optional": true, - "requires": { - "fstream": "1.0.11", - "glob": "7.1.2", - "graceful-fs": "4.1.11", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "nopt": "3.0.6", - "npmlog": "4.1.2", - "osenv": "0.1.5", - "request": "2.81.0", - "rimraf": "2.6.2", - "semver": "5.3.0", - "tar": "2.2.1", - "which": "1.3.0" - }, - "dependencies": { - "nopt": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", - "dev": true, - "optional": true, - "requires": { - "abbrev": "1.1.1" - } - }, - "semver": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", - "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", - "dev": true, - "optional": true - } - } - }, - "node-libs-browser": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.1.0.tgz", - "integrity": "sha512-5AzFzdoIMb89hBGMZglEegffzgRg+ZFoUmisQ8HI4j1KDdpx13J0taNp2y9xPbur6W61gepGDDotGBVQ7mfUCg==", - "dev": true, - "requires": { - "assert": "1.4.1", - "browserify-zlib": "0.2.0", - "buffer": "4.9.1", - "console-browserify": "1.1.0", - "constants-browserify": "1.0.0", - "crypto-browserify": "3.12.0", - "domain-browser": "1.2.0", - "events": "1.1.1", - "https-browserify": "1.0.0", - "os-browserify": "0.3.0", - "path-browserify": "0.0.0", - "process": "0.11.10", - "punycode": "1.4.1", - "querystring-es3": "0.2.1", - "readable-stream": "2.3.4", - "stream-browserify": "2.0.1", - "stream-http": "2.8.0", - "string_decoder": "1.0.3", - "timers-browserify": "2.0.6", - "tty-browserify": "0.0.0", - "url": "0.11.0", - "util": "0.10.3", - "vm-browserify": "0.0.4" - } - }, - "node-modules-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/node-modules-path/-/node-modules-path-1.0.1.tgz", - "integrity": "sha1-QAlrCM560OoUaAhjr0ScfHWl0cg=", - "dev": true - }, - "node-sass": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.7.2.tgz", - "integrity": "sha512-CaV+wLqZ7//Jdom5aUFCpGNoECd7BbNhjuwdsX/LkXBrHl8eb1Wjw4HvWqcFvhr5KuNgAk8i/myf/MQ1YYeroA==", - "dev": true, - "optional": true, - "requires": { - "async-foreach": "0.1.3", - "chalk": "1.1.3", - "cross-spawn": "3.0.1", - "gaze": "1.1.2", - "get-stdin": "4.0.1", - "glob": "7.1.2", - "in-publish": "2.0.0", - "lodash.assign": "4.2.0", - "lodash.clonedeep": "4.5.0", - "lodash.mergewith": "4.6.1", - "meow": "3.7.0", - "mkdirp": "0.5.1", - "nan": "2.9.2", - "node-gyp": "3.6.2", - "npmlog": "4.1.2", - "request": "2.79.0", - "sass-graph": "2.2.4", - "stdout-stream": "1.4.0", - "true-case-path": "1.0.2" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "caseless": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", - "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=", - "dev": true, - "optional": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "har-validator": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", - "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=", - "dev": true, - "optional": true, - "requires": { - "chalk": "1.1.3", - "commander": "2.14.1", - "is-my-json-valid": "2.17.2", - "pinkie-promise": "2.0.1" - } - }, - "qs": { - "version": "6.3.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.3.2.tgz", - "integrity": "sha1-51vV9uJoEioqDgvaYwslUMFmUCw=", - "dev": true, - "optional": true - }, - "request": { - "version": "2.79.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.79.0.tgz", - "integrity": "sha1-Tf5b9r6LjNw3/Pk+BLZVd3InEN4=", - "dev": true, - "optional": true, - "requires": { - "aws-sign2": "0.6.0", - "aws4": "1.6.0", - "caseless": "0.11.0", - "combined-stream": "1.0.6", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.1.4", - "har-validator": "2.0.6", - "hawk": "3.1.3", - "http-signature": "1.1.1", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.18", - "oauth-sign": "0.8.2", - "qs": "6.3.2", - "stringstream": "0.0.5", - "tough-cookie": "2.3.3", - "tunnel-agent": "0.4.3", - "uuid": "3.2.1" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - }, - "tunnel-agent": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", - "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=", - "dev": true, - "optional": true - } - } - }, - "nodemailer": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-2.7.2.tgz", - "integrity": "sha1-8kLmSa7q45tsftdA73sGHEBNMPk=", - "dev": true, - "optional": true, - "requires": { - "libmime": "3.0.0", - "mailcomposer": "4.0.1", - "nodemailer-direct-transport": "3.3.2", - "nodemailer-shared": "1.1.0", - "nodemailer-smtp-pool": "2.8.2", - "nodemailer-smtp-transport": "2.7.2", - "socks": "1.1.9" - }, - "dependencies": { - "socks": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/socks/-/socks-1.1.9.tgz", - "integrity": "sha1-Yo1+TQSRJDVEWsC25Fk3bLPm1pE=", - "dev": true, - "optional": true, - "requires": { - "ip": "1.1.5", - "smart-buffer": "1.1.15" - } - } - } - }, - "nodemailer-direct-transport": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/nodemailer-direct-transport/-/nodemailer-direct-transport-3.3.2.tgz", - "integrity": "sha1-6W+vuQNYVglH5WkBfZfmBzilCoY=", - "dev": true, - "optional": true, - "requires": { - "nodemailer-shared": "1.1.0", - "smtp-connection": "2.12.0" - } - }, - "nodemailer-fetch": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/nodemailer-fetch/-/nodemailer-fetch-1.6.0.tgz", - "integrity": "sha1-ecSQihwPXzdbc/6IjamCj23JY6Q=", - "dev": true - }, - "nodemailer-shared": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/nodemailer-shared/-/nodemailer-shared-1.1.0.tgz", - "integrity": "sha1-z1mU4v0mjQD1zw+nZ6CBae2wfsA=", - "dev": true, - "requires": { - "nodemailer-fetch": "1.6.0" - } - }, - "nodemailer-smtp-pool": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/nodemailer-smtp-pool/-/nodemailer-smtp-pool-2.8.2.tgz", - "integrity": "sha1-LrlNbPhXgLG0clzoU7nL1ejajHI=", - "dev": true, - "optional": true, - "requires": { - "nodemailer-shared": "1.1.0", - "nodemailer-wellknown": "0.1.10", - "smtp-connection": "2.12.0" - } - }, - "nodemailer-smtp-transport": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/nodemailer-smtp-transport/-/nodemailer-smtp-transport-2.7.2.tgz", - "integrity": "sha1-A9ccdjFPFKx9vHvwM6am0W1n+3c=", - "dev": true, - "optional": true, - "requires": { - "nodemailer-shared": "1.1.0", - "nodemailer-wellknown": "0.1.10", - "smtp-connection": "2.12.0" - } - }, - "nodemailer-wellknown": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/nodemailer-wellknown/-/nodemailer-wellknown-0.1.10.tgz", - "integrity": "sha1-WG24EB2zDLRDjrVGc3pBqtDPE9U=", - "dev": true - }, - "nopt": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", - "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", - "dev": true, - "requires": { - "abbrev": "1.1.1", - "osenv": "0.1.5" - } - }, - "normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", - "dev": true, - "requires": { - "hosted-git-info": "2.5.0", - "is-builtin-module": "1.0.0", - "semver": "5.5.0", - "validate-npm-package-license": "3.0.1" - } - }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "1.1.0" - } - }, - "normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", - "dev": true - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, - "requires": { - "path-key": "2.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "dev": true, - "requires": { - "are-we-there-yet": "1.1.4", - "console-control-strings": "1.1.0", - "gauge": "2.7.4", - "set-blocking": "2.0.0" - } - }, - "nth-check": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz", - "integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=", - "dev": true, - "requires": { - "boolbase": "1.0.0" - } - }, - "null-check": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/null-check/-/null-check-1.0.0.tgz", - "integrity": "sha1-l33/1xdgErnsMNKjnbXPcqBDnt0=", - "dev": true - }, - "num2fraction": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", - "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 - }, - "oauth-sign": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true - }, - "object-component": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", - "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=", - "dev": true - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, - "requires": { - "copy-descriptor": "0.1.1", - "define-property": "0.2.5", - "kind-of": "3.2.2" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "0.1.6" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "0.1.6", - "is-data-descriptor": "0.1.4", - "kind-of": "5.1.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - } - } - }, - "object-keys": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz", - "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=", - "dev": true - }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, - "requires": { - "isobject": "3.0.1" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "object.omit": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", - "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", - "dev": true, - "requires": { - "for-own": "0.1.5", - "is-extendable": "0.1.1" - } - }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, - "requires": { - "isobject": "3.0.1" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "obuf": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.1.tgz", - "integrity": "sha1-EEEktsYCxnlogaBCVB0220OlJk4=", - "dev": true - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "dev": true, - "requires": { - "ee-first": "1.1.1" - } - }, - "on-headers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", - "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=", - "dev": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1.0.2" - } - }, - "opn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/opn/-/opn-5.1.0.tgz", - "integrity": "sha512-iPNl7SyM8L30Rm1sjGdLLheyHVw5YXVfi3SKWJzBI7efxRwHojfRFjwE/OLM6qp9xJYMgab8WicTU1cPoY+Hpg==", - "dev": true, - "requires": { - "is-wsl": "1.1.0" - } - }, - "optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "dev": true, - "requires": { - "minimist": "0.0.8", - "wordwrap": "0.0.2" - } - }, - "optionator": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", - "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", - "dev": true, - "optional": true, - "requires": { - "deep-is": "0.1.3", - "fast-levenshtein": "2.0.6", - "levn": "0.3.0", - "prelude-ls": "1.1.2", - "type-check": "0.3.2", - "wordwrap": "1.0.0" - }, - "dependencies": { - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true, - "optional": true - } - } - }, - "options": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz", - "integrity": "sha1-7CLTEoBrtT5zF3Pnza788cZDEo8=", - "dev": true - }, - "original": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/original/-/original-1.0.0.tgz", - "integrity": "sha1-kUf5P6FpbQS+YeAb1QuurKZWvTs=", - "dev": true, - "requires": { - "url-parse": "1.0.5" - }, - "dependencies": { - "url-parse": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.0.5.tgz", - "integrity": "sha1-CFSGBCKv3P7+tsllxmLUgAFpkns=", - "dev": true, - "requires": { - "querystringify": "0.0.4", - "requires-port": "1.0.0" - } - } - } - }, - "os-browserify": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", - "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", - "dev": true - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "dev": true - }, - "os-locale": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", - "dev": true, - "requires": { - "lcid": "1.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 - }, - "osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "dev": true, - "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" - } - }, - "outlayer": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/outlayer/-/outlayer-2.1.1.tgz", - "integrity": "sha1-KYY7beEOpdrf/8rfoNcokHOH6aI=", - "requires": { - "ev-emitter": "1.1.1", - "fizzy-ui-utils": "2.0.7", - "get-size": "2.0.2" - } - }, - "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-limit": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.2.0.tgz", - "integrity": "sha512-Y/OtIaXtUPr4/YpMv1pCL5L5ed0rumAaAeBSj12F+bSlMdys7i8oQF/GUJmfpTS/QoaRrS/k6pma29haJpsMng==", - "dev": true, - "requires": { - "p-try": "1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "1.2.0" - } - }, - "p-map": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz", - "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==", - "dev": true - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - }, - "pac-proxy-agent": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-1.1.0.tgz", - "integrity": "sha512-QBELCWyLYPgE2Gj+4wUEiMscHrQ8nRPBzYItQNOHWavwBt25ohZHQC4qnd5IszdVVrFbLsQ+dPkm6eqdjJAmwQ==", - "dev": true, - "optional": true, - "requires": { - "agent-base": "2.1.1", - "debug": "2.6.9", - "extend": "3.0.1", - "get-uri": "2.0.1", - "http-proxy-agent": "1.0.0", - "https-proxy-agent": "1.0.0", - "pac-resolver": "2.0.0", - "raw-body": "2.3.2", - "socks-proxy-agent": "2.1.1" - } - }, - "pac-resolver": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-2.0.0.tgz", - "integrity": "sha1-mbiNLxk/ve78HJpSnB8yYKtSd80=", - "dev": true, - "optional": true, - "requires": { - "co": "3.0.6", - "degenerator": "1.0.4", - "ip": "1.0.1", - "netmask": "1.0.6", - "thunkify": "2.1.2" - }, - "dependencies": { - "co": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/co/-/co-3.0.6.tgz", - "integrity": "sha1-FEXyJsXrlWE45oyawwFn6n0ua9o=", - "dev": true, - "optional": true - }, - "ip": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.0.1.tgz", - "integrity": "sha1-x+NWzeoiWucbNtcPLnGpK6TkJZA=", - "dev": true, - "optional": true - } - } - }, - "pako": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz", - "integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==", - "dev": true - }, - "parallel-transform": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.1.0.tgz", - "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=", - "dev": true, - "requires": { - "cyclist": "0.2.2", - "inherits": "2.0.3", - "readable-stream": "2.3.4" - } - }, - "param-case": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", - "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=", - "dev": true, - "requires": { - "no-case": "2.3.2" - } - }, - "parents": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz", - "integrity": "sha1-/t1NK/GTp3dF/nHjcdc8MwfZx1E=", - "dev": true, - "requires": { - "path-platform": "0.11.15" - } - }, - "parse-asn1": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.0.tgz", - "integrity": "sha1-N8T5t+06tlx0gXtfJICTf7+XxxI=", - "dev": true, - "requires": { - "asn1.js": "4.10.1", - "browserify-aes": "1.1.1", - "create-hash": "1.1.3", - "evp_bytestokey": "1.0.3", - "pbkdf2": "3.0.14" - } - }, - "parse-glob": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", - "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", - "dev": true, - "requires": { - "glob-base": "0.3.0", - "is-dotfile": "1.0.3", - "is-extglob": "1.0.0", - "is-glob": "2.0.1" - } - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, - "requires": { - "error-ex": "1.3.1" - } - }, - "parse-passwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", - "dev": true - }, - "parseqs": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", - "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", - "dev": true, - "requires": { - "better-assert": "1.0.2" - } - }, - "parseuri": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", - "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", - "dev": true, - "requires": { - "better-assert": "1.0.2" - } - }, - "parseurl": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", - "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=", - "dev": true - }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true - }, - "path-browserify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", - "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=", - "dev": true - }, - "path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", - "dev": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - }, - "path-parse": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", - "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", - "dev": true - }, - "path-platform": { - "version": "0.11.15", - "resolved": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz", - "integrity": "sha1-6GQhf3TDaFDwhSt43Hv31KVyG/I=", - "dev": true - }, - "path-proxy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/path-proxy/-/path-proxy-1.0.0.tgz", - "integrity": "sha1-GOijaFn8nS8aU7SN7hOFQ8Ag3l4=", - "dev": true, - "optional": true, - "requires": { - "inflection": "1.3.8" - }, - "dependencies": { - "inflection": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.3.8.tgz", - "integrity": "sha1-y9Fg2p91sUw8xjV41POWeEvzAU4=", - "dev": true, - "optional": true - } - } - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", - "dev": true - }, - "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "requires": { - "pify": "3.0.0" - } - }, - "pbkdf2": { - "version": "3.0.14", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.14.tgz", - "integrity": "sha512-gjsZW9O34fm0R7PaLHRJmLLVfSoesxztjPjE9o6R+qtVJij90ltg1joIovN9GKrRW3t1PzhDDG3UMEMFfZ+1wA==", - "dev": true, - "requires": { - "create-hash": "1.1.3", - "create-hmac": "1.1.6", - "ripemd160": "2.0.1", - "safe-buffer": "5.1.1", - "sha.js": "2.4.10" - } - }, - "performance-now": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", - "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=", - "dev": true - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true, - "requires": { - "pinkie": "2.0.4" - } - }, - "pkg-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", - "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", - "dev": true, - "requires": { - "find-up": "2.1.0" - } - }, - "portfinder": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.13.tgz", - "integrity": "sha1-uzLs2HwnEErm7kS1o8y/Drsa7ek=", - "dev": true, - "requires": { - "async": "1.5.2", - "debug": "2.6.9", - "mkdirp": "0.5.1" - }, - "dependencies": { - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true - } - } - }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true - }, - "postcss": { - "version": "6.0.19", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.19.tgz", - "integrity": "sha512-f13HRz0HtVwVaEuW6J6cOUCBLFtymhgyLPV7t4QEk2UD3twRI9IluDcQNdzQdBpiixkXj2OmzejhhTbSbDxNTg==", - "dev": true, - "requires": { - "chalk": "2.3.1", - "source-map": "0.6.1", - "supports-color": "5.2.0" - }, - "dependencies": { - "chalk": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz", - "integrity": "sha512-QUU4ofkDoMIVO7hcx1iPTISs88wsO8jA92RQIm4JAwZvFGGAV2hSAA1NX7oVj2Ej2Q6NDTcRDjPTFrMCRZoJ6g==", - "dev": true, - "requires": { - "ansi-styles": "3.2.0", - "escape-string-regexp": "1.0.5", - "supports-color": "5.2.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz", - "integrity": "sha512-F39vS48la4YvTZUPVeTqsjsFNrvcMwrV3RLZINsmHo+7djCvuUzSIeXOnZ5hmjef4bajL1dNccN+tg5XAliO5Q==", - "dev": true, - "requires": { - "has-flag": "3.0.0" - } - } - } - }, - "postcss-import": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-11.1.0.tgz", - "integrity": "sha512-5l327iI75POonjxkXgdRCUS+AlzAdBx4pOvMEhTKTCjb1p8IEeVR9yx3cPbmN7LIWJLbfnIXxAhoB4jpD0c/Cw==", - "dev": true, - "requires": { - "postcss": "6.0.19", - "postcss-value-parser": "3.3.0", - "read-cache": "1.0.0", - "resolve": "1.5.0" - } - }, - "postcss-load-config": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-1.2.0.tgz", - "integrity": "sha1-U56a/J3chiASHr+djDZz4M5Q0oo=", - "dev": true, - "requires": { - "cosmiconfig": "2.2.2", - "object-assign": "4.1.1", - "postcss-load-options": "1.2.0", - "postcss-load-plugins": "2.3.0" - } - }, - "postcss-load-options": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postcss-load-options/-/postcss-load-options-1.2.0.tgz", - "integrity": "sha1-sJixVZ3awt8EvAuzdfmaXP4rbYw=", - "dev": true, - "requires": { - "cosmiconfig": "2.2.2", - "object-assign": "4.1.1" - } - }, - "postcss-load-plugins": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/postcss-load-plugins/-/postcss-load-plugins-2.3.0.tgz", - "integrity": "sha1-dFdoEWWZrKLwCfrUJrABdQSdjZI=", - "dev": true, - "requires": { - "cosmiconfig": "2.2.2", - "object-assign": "4.1.1" - } - }, - "postcss-loader": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-2.1.1.tgz", - "integrity": "sha512-f0J/DWE/hyO9/LH0WHpXkny/ZZ238sSaG3p1SRBtVZnFWUtD7GXIEgHoBg8cnAeRbmEvUxHQptY46zWfwNYj/w==", - "dev": true, - "requires": { - "loader-utils": "1.1.0", - "postcss": "6.0.19", - "postcss-load-config": "1.2.0", - "schema-utils": "0.4.5" - } - }, - "postcss-url": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/postcss-url/-/postcss-url-7.3.1.tgz", - "integrity": "sha512-Ya5KIjGptgz0OtrVYfi2UbLxVAZ6Emc4Of+Grx4Sf1deWlRpFwLr8FrtkUxfqh+XiZIVkXbjQrddE10ESpNmdA==", - "dev": true, - "requires": { - "mime": "1.6.0", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "postcss": "6.0.19", - "xxhashjs": "0.2.2" - } - }, - "postcss-value-parser": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz", - "integrity": "sha1-h/OPnxj3dKSrTIojL1xc6IcqnRU=", - "dev": true - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "preserve": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", - "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", - "dev": true - }, - "pretty-error": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz", - "integrity": "sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM=", - "dev": true, - "requires": { - "renderkid": "2.0.1", - "utila": "0.4.0" - } - }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", - "dev": true - }, - "promise": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", - "dev": true, - "optional": true, - "requires": { - "asap": "2.0.6" - } - }, - "promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", - "dev": true - }, - "protractor": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/protractor/-/protractor-5.1.2.tgz", - "integrity": "sha1-myIXQXCaTGLVzVPGqt1UpxE36V8=", - "dev": true, - "requires": { - "@types/node": "6.0.101", - "@types/q": "0.0.32", - "@types/selenium-webdriver": "2.53.43", - "blocking-proxy": "0.0.5", - "chalk": "1.1.3", - "glob": "7.1.2", - "jasmine": "2.99.0", - "jasminewd2": "2.2.0", - "optimist": "0.6.1", - "q": "1.4.1", - "saucelabs": "1.3.0", - "selenium-webdriver": "3.0.1", - "source-map-support": "0.4.18", - "webdriver-js-extender": "1.0.0", - "webdriver-manager": "12.0.6" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "del": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", - "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", - "dev": true, - "requires": { - "globby": "5.0.0", - "is-path-cwd": "1.0.0", - "is-path-in-cwd": "1.0.0", - "object-assign": "4.1.1", - "pify": "2.3.0", - "pinkie-promise": "2.0.1", - "rimraf": "2.6.2" - } - }, - "globby": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", - "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", - "dev": true, - "requires": { - "array-union": "1.0.2", - "arrify": "1.0.1", - "glob": "7.1.2", - "object-assign": "4.1.1", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - }, - "webdriver-manager": { - "version": "12.0.6", - "resolved": "https://registry.npmjs.org/webdriver-manager/-/webdriver-manager-12.0.6.tgz", - "integrity": "sha1-PfGkgZdwELTL+MnYXHpXeCjA5ws=", - "dev": true, - "requires": { - "adm-zip": "0.4.7", - "chalk": "1.1.3", - "del": "2.2.2", - "glob": "7.1.2", - "ini": "1.3.5", - "minimist": "1.2.0", - "q": "1.4.1", - "request": "2.81.0", - "rimraf": "2.6.2", - "semver": "5.5.0", - "xml2js": "0.4.19" - } - } - } - }, - "proxy-addr": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.3.tgz", - "integrity": "sha512-jQTChiCJteusULxjBp8+jftSQE5Obdl3k4cnmLA6WXtK6XFuWRnvVL7aCiBqaLPM8c4ph0S4tKna8XvmIwEnXQ==", - "dev": true, - "requires": { - "forwarded": "0.1.2", - "ipaddr.js": "1.6.0" - } - }, - "proxy-agent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-2.0.0.tgz", - "integrity": "sha1-V+tTR6qAXXTsaByyVknbo5yTNJk=", - "dev": true, - "optional": true, - "requires": { - "agent-base": "2.1.1", - "debug": "2.6.9", - "extend": "3.0.1", - "http-proxy-agent": "1.0.0", - "https-proxy-agent": "1.0.0", - "lru-cache": "2.6.5", - "pac-proxy-agent": "1.1.0", - "socks-proxy-agent": "2.1.1" - }, - "dependencies": { - "lru-cache": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.6.5.tgz", - "integrity": "sha1-5W1jVBSO3o13B7WNFDIg/QjfD9U=", - "dev": true, - "optional": true - } - } - }, - "prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", - "dev": true - }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true - }, - "public-encrypt": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.0.tgz", - "integrity": "sha1-OfaZ86RlYN1eusvKaTyvfGXBjMY=", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "browserify-rsa": "4.0.1", - "create-hash": "1.1.3", - "parse-asn1": "5.1.0", - "randombytes": "2.0.6" - } - }, - "pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "dev": true, - "requires": { - "end-of-stream": "1.4.1", - "once": "1.4.0" - } - }, - "pumpify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.4.0.tgz", - "integrity": "sha512-2kmNR9ry+Pf45opRVirpNuIFotsxUGLaYqxIwuR77AYrYRMuFCz9eryHBS52L360O+NcR383CL4QYlMKPq4zYA==", - "dev": true, - "requires": { - "duplexify": "3.5.3", - "inherits": "2.0.3", - "pump": "2.0.1" - } - }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true - }, - "q": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz", - "integrity": "sha1-VXBbzZPF82c1MMLCy8DCs63cKG4=", - "dev": true - }, - "qjobs": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", - "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", - "dev": true - }, - "qs": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", - "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=", - "dev": true - }, - "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "dev": true - }, - "querystring-es3": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", - "dev": true - }, - "querystringify": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-0.0.4.tgz", - "integrity": "sha1-DPf4T5Rj/wrlHExLFC2VvjdyTZw=", - "dev": true - }, - "randomatic": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", - "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", - "dev": true, - "requires": { - "is-number": "3.0.0", - "kind-of": "4.0.0" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "randombytes": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.6.tgz", - "integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==", - "dev": true, - "requires": { - "safe-buffer": "5.1.1" - } - }, - "randomfill": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", - "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", - "dev": true, - "requires": { - "randombytes": "2.0.6", - "safe-buffer": "5.1.1" - } - }, - "range-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", - "dev": true - }, - "raw-body": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", - "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", - "dev": true, - "requires": { - "bytes": "3.0.0", - "http-errors": "1.6.2", - "iconv-lite": "0.4.19", - "unpipe": "1.0.0" - } - }, - "raw-loader": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-0.5.1.tgz", - "integrity": "sha1-DD0L6u2KAclm2Xh793goElKpeao=", - "dev": true - }, - "read-cache": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", - "integrity": "sha1-5mTvMRYRZsl1HNvo28+GtftY93Q=", - "dev": true, - "requires": { - "pify": "2.3.0" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } - } - }, - "read-only-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-only-stream/-/read-only-stream-2.0.0.tgz", - "integrity": "sha1-JyT9aoET1zdkrCiNQ4YnDB2/F/A=", - "dev": true, - "requires": { - "readable-stream": "2.3.4" - } - }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "dev": true, - "requires": { - "load-json-file": "1.1.0", - "normalize-package-data": "2.4.0", - "path-type": "1.1.0" - }, - "dependencies": { - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } - } - }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", - "dev": true, - "requires": { - "find-up": "1.1.2", - "read-pkg": "1.1.0" - }, - "dependencies": { - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "dev": true, - "requires": { - "path-exists": "2.1.0", - "pinkie-promise": "2.0.1" - } - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "dev": true, - "requires": { - "pinkie-promise": "2.0.1" - } - } - } - }, - "readable-stream": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.4.tgz", - "integrity": "sha512-vuYxeWYM+fde14+rajzqgeohAI7YoJcHE7kXDAc4Nk0EbuKnJfqtY9YtRkLo/tqkuF7MsBQRhPnPeyjYITp3ZQ==", - "dev": true, - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.1", - "string_decoder": "1.0.3", - "util-deprecate": "1.0.2" - } - }, - "readdirp": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", - "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "minimatch": "3.0.4", - "readable-stream": "2.3.4", - "set-immediate-shim": "1.0.1" - } - }, - "redent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", - "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", - "dev": true, - "requires": { - "indent-string": "2.1.0", - "strip-indent": "1.0.1" - } - }, - "redis": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/redis/-/redis-2.8.0.tgz", - "integrity": "sha512-M1OkonEQwtRmZv4tEWF2VgpG0JWJ8Fv1PhlgT5+B+uNq2cA3Rt1Yt/ryoR+vQNOQcIEgdCdfH0jr3bDpihAw1A==", - "dev": true, - "optional": true, - "requires": { - "double-ended-queue": "2.1.0-0", - "redis-commands": "1.3.3", - "redis-parser": "2.6.0" - } - }, - "redis-commands": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.3.3.tgz", - "integrity": "sha512-i41GK1SzbNp5nqmuVAMQw9sgar/cvk4YqD6M2RXp2p94D4itY82OZGVs28Jl8JcslGnOdQvlBrDLPt6jYQzuow==", - "dev": true, - "optional": true - }, - "redis-parser": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-2.6.0.tgz", - "integrity": "sha1-Uu0J2srBCPGmMcB+m2mUHnoZUEs=", - "dev": true, - "optional": true - }, - "reflect-metadata": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.12.tgz", - "integrity": "sha512-n+IyV+nGz3+0q3/Yf1ra12KpCyi001bi4XFxSjbiWWjfqb52iTTtpGXmCCAOWWIAn9KEuFZKGqBERHmrtScZ3A==", - "dev": true - }, - "regenerate": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.3.tgz", - "integrity": "sha512-jVpo1GadrDAK59t/0jRx5VxYWQEDkkEKi6+HjE3joFVLfDOh9Xrdh0dF1eSq+BI/SwvTQ44gSscJ8N5zYL61sg==", - "dev": true - }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true - }, - "regex-cache": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", - "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", - "dev": true, - "requires": { - "is-equal-shallow": "0.1.3" - } - }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "requires": { - "extend-shallow": "3.0.2", - "safe-regex": "1.1.0" - } - }, - "regexpu-core": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz", - "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=", - "dev": true, - "requires": { - "regenerate": "1.3.3", - "regjsgen": "0.2.0", - "regjsparser": "0.1.5" - } - }, - "regjsgen": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", - "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", - "dev": true - }, - "regjsparser": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", - "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", - "dev": true, - "requires": { - "jsesc": "0.5.0" - }, - "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", - "dev": true - } - } - }, - "relateurl": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", - "dev": true - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true - }, - "renderkid": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.1.tgz", - "integrity": "sha1-iYyr/Ivt5Le5ETWj/9Mj5YwNsxk=", - "dev": true, - "requires": { - "css-select": "1.2.0", - "dom-converter": "0.1.4", - "htmlparser2": "3.3.0", - "strip-ansi": "3.0.1", - "utila": "0.3.3" - }, - "dependencies": { - "utila": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/utila/-/utila-0.3.3.tgz", - "integrity": "sha1-1+jn1+MJEHCSsF+NloiCTWM6QiY=", - "dev": true - } - } - }, - "repeat-element": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", - "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", - "dev": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true - }, - "repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", - "dev": true, - "requires": { - "is-finite": "1.0.2" - } - }, - "request": { - "version": "2.81.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", - "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", - "dev": true, - "requires": { - "aws-sign2": "0.6.0", - "aws4": "1.6.0", - "caseless": "0.12.0", - "combined-stream": "1.0.6", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.1.4", - "har-validator": "4.2.1", - "hawk": "3.1.3", - "http-signature": "1.1.1", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.18", - "oauth-sign": "0.8.2", - "performance-now": "0.2.0", - "qs": "6.4.0", - "safe-buffer": "5.1.1", - "stringstream": "0.0.5", - "tough-cookie": "2.3.3", - "tunnel-agent": "0.6.0", - "uuid": "3.2.1" - } - }, - "requestretry": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/requestretry/-/requestretry-1.13.0.tgz", - "integrity": "sha512-Lmh9qMvnQXADGAQxsXHP4rbgO6pffCfuR8XUBdP9aitJcLQJxhp7YZK4xAVYXnPJ5E52mwrfiKQtKonPL8xsmg==", - "dev": true, - "optional": true, - "requires": { - "extend": "3.0.1", - "lodash": "4.17.5", - "request": "2.81.0", - "when": "3.7.8" - }, - "dependencies": { - "when": { - "version": "3.7.8", - "resolved": "https://registry.npmjs.org/when/-/when-3.7.8.tgz", - "integrity": "sha1-xxMLan6gRpPoQs3J56Hyqjmjn4I=", - "dev": true, - "optional": true - } - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "require-from-string": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-1.2.1.tgz", - "integrity": "sha1-UpyczvJzgK3+yaL5ZbZJu+5jZBg=", - "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=", - "dev": true - }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", - "dev": true - }, - "resolve": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz", - "integrity": "sha512-hgoSGrc3pjzAPHNBg+KnFcK2HwlHTs/YrAGUr6qgTVUZmXv1UEXXl0bZNBKMA9fud6lRYFdPGz0xXxycPzmmiw==", - "dev": true, - "requires": { - "path-parse": "1.0.5" - } - }, - "resolve-cwd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", - "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", - "dev": true, - "requires": { - "resolve-from": "3.0.0" - } - }, - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", - "dev": true - }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true - }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true - }, - "right-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", - "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", - "dev": true, - "requires": { - "align-text": "0.1.4" - } - }, - "rimraf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", - "dev": true, - "requires": { - "glob": "7.1.2" - } - }, - "ripemd160": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz", - "integrity": "sha1-D0WEKVxTo2KK9+bXmsohzlfRxuc=", - "dev": true, - "requires": { - "hash-base": "2.0.2", - "inherits": "2.0.3" - } - }, - "run-queue": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", - "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", - "dev": true, - "requires": { - "aproba": "1.2.0" - } - }, - "rxjs": { - "version": "5.5.6", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.6.tgz", - "integrity": "sha512-v4Q5HDC0FHAQ7zcBX7T2IL6O5ltl1a2GX4ENjPXg6SjDY69Cmx9v4113C99a4wGF16ClPv5Z8mghuYorVkg/kg==", - "requires": { - "symbol-observable": "1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", - "dev": true - }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, - "requires": { - "ret": "0.1.15" - } - }, - "sass-graph": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz", - "integrity": "sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k=", - "dev": true, - "optional": true, - "requires": { - "glob": "7.1.2", - "lodash": "4.17.5", - "scss-tokenizer": "0.2.3", - "yargs": "7.1.0" - } - }, - "sass-loader": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-6.0.6.tgz", - "integrity": "sha512-c3/Zc+iW+qqDip6kXPYLEgsAu2lf4xz0EZDplB7EmSUMda12U1sGJPetH55B/j9eu0bTtKzKlNPWWyYC7wFNyQ==", - "dev": true, - "requires": { - "async": "2.6.0", - "clone-deep": "0.3.0", - "loader-utils": "1.1.0", - "lodash.tail": "4.1.1", - "pify": "3.0.0" - } - }, - "saucelabs": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/saucelabs/-/saucelabs-1.3.0.tgz", - "integrity": "sha1-0kDoAJ33+ocwbsRXimm6O1xCT+4=", - "dev": true, - "requires": { - "https-proxy-agent": "1.0.0" - } - }, - "sax": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/sax/-/sax-0.5.8.tgz", - "integrity": "sha1-1HLbIo6zMcJQaw6MFVJK25OdEsE=", - "dev": true - }, - "schema-utils": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.5.tgz", - "integrity": "sha512-yYrjb9TX2k/J1Y5UNy3KYdZq10xhYcF8nMpAW6o3hy6Q8WSIEf9lJHG/ePnOBfziPM3fvQwfOwa13U/Fh8qTfA==", - "dev": true, - "requires": { - "ajv": "6.2.0", - "ajv-keywords": "3.1.0" - } - }, - "scss-tokenizer": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz", - "integrity": "sha1-jrBtualyMzOCTT9VMGQRSYR85dE=", - "dev": true, - "optional": true, - "requires": { - "js-base64": "2.4.3", - "source-map": "0.4.4" - }, - "dependencies": { - "source-map": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", - "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", - "dev": true, - "optional": true, - "requires": { - "amdefine": "1.0.1" - } - } - } - }, - "select-hose": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=", - "dev": true - }, - "selenium-webdriver": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.0.1.tgz", - "integrity": "sha1-ot6l2kqX9mcuiefKcnbO+jZRR6c=", - "dev": true, - "requires": { - "adm-zip": "0.4.7", - "rimraf": "2.6.2", - "tmp": "0.0.30", - "xml2js": "0.4.19" - }, - "dependencies": { - "tmp": { - "version": "0.0.30", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.30.tgz", - "integrity": "sha1-ckGdSovn1s51FI/YsyTlk6cRwu0=", - "dev": true, - "requires": { - "os-tmpdir": "1.0.2" - } - } - } - }, - "selfsigned": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.2.tgz", - "integrity": "sha1-tESVgNmZKbZbEKSDiTAaZZIIh1g=", - "dev": true, - "requires": { - "node-forge": "0.7.1" - } - }, - "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", - "dev": true - }, - "semver-dsl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/semver-dsl/-/semver-dsl-1.0.1.tgz", - "integrity": "sha1-02eN5VVeimH2Ke7QJTZq5fJzQKA=", - "dev": true, - "requires": { - "semver": "5.5.0" - } - }, - "semver-intersect": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/semver-intersect/-/semver-intersect-1.3.1.tgz", - "integrity": "sha1-j6hKnhAovSOeRTDRo+GB5pjYhLo=", - "dev": true, - "requires": { - "semver": "5.5.0" - } - }, - "send": { - "version": "0.16.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.16.1.tgz", - "integrity": "sha512-ElCLJdJIKPk6ux/Hocwhk7NFHpI3pVm/IZOYWqUmoxcgeyM+MpxHHKhb8QmlJDX1pU6WrgaHBkVNm73Sv7uc2A==", - "dev": true, - "requires": { - "debug": "2.6.9", - "depd": "1.1.2", - "destroy": "1.0.4", - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "etag": "1.8.1", - "fresh": "0.5.2", - "http-errors": "1.6.2", - "mime": "1.4.1", - "ms": "2.0.0", - "on-finished": "2.3.0", - "range-parser": "1.2.0", - "statuses": "1.3.1" - }, - "dependencies": { - "mime": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", - "dev": true - } - } - }, - "serialize-javascript": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.4.0.tgz", - "integrity": "sha1-fJWFFNtqwkQ6irwGLcn3iGp/YAU=", - "dev": true - }, - "serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", - "dev": true, - "requires": { - "accepts": "1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "1.0.3", - "http-errors": "1.6.2", - "mime-types": "2.1.18", - "parseurl": "1.3.2" - } - }, - "serve-static": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.1.tgz", - "integrity": "sha512-hSMUZrsPa/I09VYFJwa627JJkNs0NrfL1Uzuup+GqHfToR2KcsXFymXSV90hoyw3M+msjFuQly+YzIH/q0MGlQ==", - "dev": true, - "requires": { - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "parseurl": "1.3.2", - "send": "0.16.1" - } - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "set-getter": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/set-getter/-/set-getter-0.1.0.tgz", - "integrity": "sha1-12nBgsnVpR9AkUXy+6guXoboA3Y=", - "dev": true, - "requires": { - "to-object-path": "0.3.0" - } - }, - "set-immediate-shim": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", - "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", - "dev": true - }, - "set-value": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", - "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", - "dev": true, - "requires": { - "extend-shallow": "2.0.1", - "is-extendable": "0.1.1", - "is-plain-object": "2.0.4", - "split-string": "3.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - } - } - }, - "setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", - "dev": true - }, - "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true - }, - "sha.js": { - "version": "2.4.10", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.10.tgz", - "integrity": "sha512-vnwmrFDlOExK4Nm16J2KMWHLrp14lBrjxMxBJpu++EnsuBmpiYaM/MEs46Vxxm/4FvdP5yTwuCTO9it5FSjrqA==", - "dev": true, - "requires": { - "inherits": "2.0.3", - "safe-buffer": "5.1.1" - } - }, - "shallow-clone": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz", - "integrity": "sha1-WQnodLp3EG1zrEFM/sH/yofZcGA=", - "dev": true, - "requires": { - "is-extendable": "0.1.1", - "kind-of": "2.0.1", - "lazy-cache": "0.2.7", - "mixin-object": "2.0.1" - }, - "dependencies": { - "kind-of": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", - "integrity": "sha1-AY7HpM5+OobLkUG+UZ0kyPqpgbU=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "shasum": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/shasum/-/shasum-1.0.2.tgz", - "integrity": "sha1-5wEjENj0F/TetXEhUOVni4euVl8=", - "dev": true, - "requires": { - "json-stable-stringify": "0.0.1", - "sha.js": "2.4.10" - }, - "dependencies": { - "json-stable-stringify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz", - "integrity": "sha1-YRwj6BTbN1Un34URk9tZ3Sryf0U=", - "dev": true, - "requires": { - "jsonify": "0.0.0" - } - } - } - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "requires": { - "shebang-regex": "1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "shell-quote": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.6.1.tgz", - "integrity": "sha1-9HgZSczkAmlxJ0MOo7PFR29IF2c=", - "dev": true, - "requires": { - "array-filter": "0.0.1", - "array-map": "0.0.0", - "array-reduce": "0.0.0", - "jsonify": "0.0.0" - } - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true - }, - "silent-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/silent-error/-/silent-error-1.1.0.tgz", - "integrity": "sha1-IglwbxyFCp8dENDYQJGLRvJuG8k=", - "dev": true, - "requires": { - "debug": "2.6.9" - } - }, - "slack-node": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/slack-node/-/slack-node-0.2.0.tgz", - "integrity": "sha1-3kuN3aqLeT9h29KTgQT9q/N9+jA=", - "dev": true, - "optional": true, - "requires": { - "requestretry": "1.13.0" - } - }, - "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", - "dev": true - }, - "smart-buffer": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-1.1.15.tgz", - "integrity": "sha1-fxFLW2X6s+KjWqd1uxLw0cZJvxY=", - "dev": true - }, - "smtp-connection": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/smtp-connection/-/smtp-connection-2.12.0.tgz", - "integrity": "sha1-1275EnyyPCJZ7bHoNJwujV4tdME=", - "dev": true, - "requires": { - "httpntlm": "1.6.1", - "nodemailer-shared": "1.1.0" - } - }, - "snapdragon": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.1.tgz", - "integrity": "sha1-4StUh/re0+PeoKyR6UAL91tAE3A=", - "dev": true, - "requires": { - "base": "0.11.2", - "debug": "2.6.9", - "define-property": "0.2.5", - "extend-shallow": "2.0.1", - "map-cache": "0.2.2", - "source-map": "0.5.7", - "source-map-resolve": "0.5.1", - "use": "2.0.2" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "0.1.6" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "0.1.6", - "is-data-descriptor": "0.1.4", - "kind-of": "5.1.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "requires": { - "define-property": "1.0.0", - "isobject": "3.0.1", - "snapdragon-util": "3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "1.0.2" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "requires": { - "kind-of": "3.2.2" - } - }, - "sntp": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", - "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", - "dev": true, - "requires": { - "hoek": "2.16.3" - } - }, - "socket.io": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.0.4.tgz", - "integrity": "sha1-waRZDO/4fs8TxyZS8Eb3FrKeYBQ=", - "dev": true, - "requires": { - "debug": "2.6.9", - "engine.io": "3.1.5", - "socket.io-adapter": "1.1.1", - "socket.io-client": "2.0.4", - "socket.io-parser": "3.1.3" - } - }, - "socket.io-adapter": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz", - "integrity": "sha1-KoBeihTWNyEk3ZFZrUUC+MsH8Gs=", - "dev": true - }, - "socket.io-client": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.0.4.tgz", - "integrity": "sha1-CRilUkBtxeVAs4Dc2Xr8SmQzL44=", - "dev": true, - "requires": { - "backo2": "1.0.2", - "base64-arraybuffer": "0.1.5", - "component-bind": "1.0.0", - "component-emitter": "1.2.1", - "debug": "2.6.9", - "engine.io-client": "3.1.5", - "has-cors": "1.1.0", - "indexof": "0.0.1", - "object-component": "0.0.3", - "parseqs": "0.0.5", - "parseuri": "0.0.5", - "socket.io-parser": "3.1.3", - "to-array": "0.1.4" - } - }, - "socket.io-parser": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.1.3.tgz", - "integrity": "sha512-g0a2HPqLguqAczs3dMECuA1RgoGFPyvDqcbaDEdCWY9g59kdUAz3YRmaJBNKXflrHNwB7Q12Gkf/0CZXfdHR7g==", - "dev": true, - "requires": { - "component-emitter": "1.2.1", - "debug": "3.1.0", - "has-binary2": "1.0.2", - "isarray": "2.0.1" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", - "dev": true - } - } - }, - "sockjs": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz", - "integrity": "sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==", - "dev": true, - "requires": { - "faye-websocket": "0.10.0", - "uuid": "3.2.1" - } - }, - "sockjs-client": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.1.4.tgz", - "integrity": "sha1-W6vjhrd15M8U51IJEUUmVAFsixI=", - "dev": true, - "requires": { - "debug": "2.6.9", - "eventsource": "0.1.6", - "faye-websocket": "0.11.1", - "inherits": "2.0.3", - "json3": "3.3.2", - "url-parse": "1.2.0" - }, - "dependencies": { - "faye-websocket": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.1.tgz", - "integrity": "sha1-8O/hjE9W5PQK/H4Gxxn9XuYYjzg=", - "dev": true, - "requires": { - "websocket-driver": "0.7.0" - } - } - } - }, - "socks": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/socks/-/socks-1.1.10.tgz", - "integrity": "sha1-W4t/x8jzQcU+0FbpKbe/Tei6e1o=", - "dev": true, - "requires": { - "ip": "1.1.5", - "smart-buffer": "1.1.15" - } - }, - "socks-proxy-agent": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-2.1.1.tgz", - "integrity": "sha512-sFtmYqdUK5dAMh85H0LEVFUCO7OhJJe1/z2x/Z6mxp3s7/QPf1RkZmpZy+BpuU0bEjcV9npqKjq9Y3kwFUjnxw==", - "dev": true, - "requires": { - "agent-base": "2.1.1", - "extend": "3.0.1", - "socks": "1.1.10" - } - }, - "source-list-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.0.tgz", - "integrity": "sha512-I2UmuJSRr/T8jisiROLU3A3ltr+swpniSmNPI4Ml3ZCX6tVnDsuZzK7F2hl5jTqbZBWCEKlj5HRQiPExXLgE8A==", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "source-map-resolve": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.1.tgz", - "integrity": "sha512-0KW2wvzfxm8NCTb30z0LMNyPqWCdDGE2viwzUaucqJdkTRXtZiSY3I+2A6nVAjmdOy0I4gU8DwnVVGsk9jvP2A==", - "dev": true, - "requires": { - "atob": "2.0.3", - "decode-uri-component": "0.2.0", - "resolve-url": "0.2.1", - "source-map-url": "0.4.0", - "urix": "0.1.0" - } - }, - "source-map-support": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", - "dev": true, - "requires": { - "source-map": "0.5.7" - } - }, - "source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", - "dev": true - }, - "spdx-correct": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", - "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", - "dev": true, - "requires": { - "spdx-license-ids": "1.2.2" - } - }, - "spdx-expression-parse": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz", - "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=", - "dev": true - }, - "spdx-license-ids": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz", - "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=", - "dev": true - }, - "spdy": { - "version": "3.4.7", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-3.4.7.tgz", - "integrity": "sha1-Qv9B7OXMD5mjpsKKq7c/XDsDrLw=", - "dev": true, - "requires": { - "debug": "2.6.9", - "handle-thing": "1.2.5", - "http-deceiver": "1.2.7", - "safe-buffer": "5.1.1", - "select-hose": "2.0.0", - "spdy-transport": "2.0.20" - } - }, - "spdy-transport": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-2.0.20.tgz", - "integrity": "sha1-c15yBUxIayNU/onnAiVgBKOazk0=", - "dev": true, - "requires": { - "debug": "2.6.9", - "detect-node": "2.0.3", - "hpack.js": "2.1.6", - "obuf": "1.1.1", - "readable-stream": "2.3.4", - "safe-buffer": "5.1.1", - "wbuf": "1.7.2" - } - }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "requires": { - "extend-shallow": "3.0.2" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "sshpk": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", - "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", - "dev": true, - "requires": { - "asn1": "0.2.3", - "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.1", - "dashdash": "1.14.1", - "ecc-jsbn": "0.1.1", - "getpass": "0.1.7", - "jsbn": "0.1.1", - "tweetnacl": "0.14.5" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - } - } - }, - "ssri": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-5.2.4.tgz", - "integrity": "sha512-UnEAgMZa15973iH7cUi0AHjJn1ACDIkaMyZILoqwN6yzt+4P81I8tBc5Hl+qwi5auMplZtPQsHrPBR5vJLcQtQ==", - "dev": true, - "requires": { - "safe-buffer": "5.1.1" - } - }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, - "requires": { - "define-property": "0.2.5", - "object-copy": "0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "0.1.6" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "0.1.6", - "is-data-descriptor": "0.1.4", - "kind-of": "5.1.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "statuses": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", - "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=", - "dev": true - }, - "stdout-stream": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.0.tgz", - "integrity": "sha1-osfIWH5U2UJ+qe2zrD8s1SLfN4s=", - "dev": true, - "optional": true, - "requires": { - "readable-stream": "2.3.4" - } - }, - "stream-browserify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", - "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", - "dev": true, - "requires": { - "inherits": "2.0.3", - "readable-stream": "2.3.4" - } - }, - "stream-combiner2": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", - "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=", - "dev": true, - "requires": { - "duplexer2": "0.1.4", - "readable-stream": "2.3.4" - } - }, - "stream-each": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.2.tgz", - "integrity": "sha512-mc1dbFhGBxvTM3bIWmAAINbqiuAk9TATcfIQC8P+/+HJefgaiTlMn2dHvkX8qlI12KeYKSQ1Ua9RrIqrn1VPoA==", - "dev": true, - "requires": { - "end-of-stream": "1.4.1", - "stream-shift": "1.0.0" - } - }, - "stream-http": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.0.tgz", - "integrity": "sha512-sZOFxI/5xw058XIRHl4dU3dZ+TTOIGJR78Dvo0oEAejIt4ou27k+3ne1zYmCV+v7UucbxIFQuOgnkTVHh8YPnw==", - "dev": true, - "requires": { - "builtin-status-codes": "3.0.0", - "inherits": "2.0.3", - "readable-stream": "2.3.4", - "to-arraybuffer": "1.0.1", - "xtend": "4.0.1" - } - }, - "stream-shift": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", - "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", - "dev": true - }, - "stream-splicer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/stream-splicer/-/stream-splicer-2.0.0.tgz", - "integrity": "sha1-G2O+Q4oTPktnHMGTUZdgAXWRDYM=", - "dev": true, - "requires": { - "inherits": "2.0.3", - "readable-stream": "2.3.4" - } - }, - "streamroller": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-0.7.0.tgz", - "integrity": "sha512-WREzfy0r0zUqp3lGO096wRuUp7ho1X6uo/7DJfTlEi0Iv/4gT7YHqXDjKC2ioVGBZtE8QzsQD9nx1nIuoZ57jQ==", - "dev": true, - "requires": { - "date-format": "1.2.0", - "debug": "3.1.0", - "mkdirp": "0.5.1", - "readable-stream": "2.3.4" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.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.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" - } - }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "dev": true, - "requires": { - "safe-buffer": "5.1.1" - } - }, - "stringstream": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", - "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=", - "dev": true - }, - "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.1.1" - } - }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, - "requires": { - "is-utf8": "0.2.1" - } - }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true - }, - "strip-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", - "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", - "dev": true, - "requires": { - "get-stdin": "4.0.1" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - }, - "style-loader": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.19.1.tgz", - "integrity": "sha512-IRE+ijgojrygQi3rsqT0U4dd+UcPCqcVvauZpCnQrGAlEe+FUIyrK93bUDScamesjP08JlQNsFJU+KmPedP5Og==", - "dev": true, - "requires": { - "loader-utils": "1.1.0", - "schema-utils": "0.3.0" - }, - "dependencies": { - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", - "dev": true, - "requires": { - "co": "4.6.0", - "fast-deep-equal": "1.1.0", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.3.1" - } - }, - "schema-utils": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.3.0.tgz", - "integrity": "sha1-9YdyIs4+kx7a4DnxfrNxbnE3+M8=", - "dev": true, - "requires": { - "ajv": "5.5.2" - } - } - } - }, - "stylus": { - "version": "0.54.5", - "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.54.5.tgz", - "integrity": "sha1-QrlWCTHKcJDOhRWnmLqeaqPW3Hk=", - "dev": true, - "requires": { - "css-parse": "1.7.0", - "debug": "2.6.9", - "glob": "7.0.6", - "mkdirp": "0.5.1", - "sax": "0.5.8", - "source-map": "0.1.43" - }, - "dependencies": { - "glob": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz", - "integrity": "sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo=", - "dev": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "source-map": { - "version": "0.1.43", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", - "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", - "dev": true, - "requires": { - "amdefine": "1.0.1" - } - } - } - }, - "stylus-loader": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/stylus-loader/-/stylus-loader-3.0.2.tgz", - "integrity": "sha512-+VomPdZ6a0razP+zinir61yZgpw2NfljeSsdUF5kJuEzlo3khXhY19Fn6l8QQz1GRJGtMCo8nG5C04ePyV7SUA==", - "dev": true, - "requires": { - "loader-utils": "1.1.0", - "lodash.clonedeep": "4.5.0", - "when": "3.6.4" - } - }, - "subarg": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", - "integrity": "sha1-9izxdYHplrSPyWVpn1TAauJouNI=", - "dev": true, - "requires": { - "minimist": "1.2.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } - } - }, - "supports-color": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", - "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", - "dev": true, - "requires": { - "has-flag": "2.0.0" - } - }, - "symbol-observable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", - "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=" - }, - "syntax-error": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.4.0.tgz", - "integrity": "sha512-YPPlu67mdnHGTup2A8ff7BC2Pjq0e0Yp/IyTFN03zWO0RcK07uLcbi7C2KpGR2FvWbaB0+bfE27a+sBKebSo7w==", - "dev": true, - "requires": { - "acorn-node": "1.3.0" - } - }, - "tapable": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.2.8.tgz", - "integrity": "sha1-mTcqXJmb8t8WCvwNdL7U9HlIzSI=", - "dev": true - }, - "tar": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", - "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", - "dev": true, - "optional": true, - "requires": { - "block-stream": "0.0.9", - "fstream": "1.0.11", - "inherits": "2.0.3" - } - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "through2": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", - "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", - "dev": true, - "requires": { - "readable-stream": "2.3.4", - "xtend": "4.0.1" - } - }, - "thunkify": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/thunkify/-/thunkify-2.1.2.tgz", - "integrity": "sha1-+qDp0jDFGsyVyhOjYawFyn4EVT0=", - "dev": true, - "optional": true - }, - "thunky": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.0.2.tgz", - "integrity": "sha1-qGLgGOP7HqLsP85dVWBc9X8kc3E=", - "dev": true - }, - "time-stamp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-2.0.0.tgz", - "integrity": "sha1-lcakRTDhW6jW9KPsuMOj+sRto1c=", - "dev": true - }, - "timers-browserify": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.6.tgz", - "integrity": "sha512-HQ3nbYRAowdVd0ckGFvmJPPCOH/CHleFN/Y0YQCX1DVaB7t+KFvisuyN09fuP8Jtp1CpfSh8O8bMkHbdbPe6Pw==", - "dev": true, - "requires": { - "setimmediate": "1.0.5" - } - }, - "timespan": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/timespan/-/timespan-2.3.0.tgz", - "integrity": "sha1-SQLOBAvRPYRcj1myfp1ZutbzmSk=", - "dev": true, - "optional": true - }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "requires": { - "os-tmpdir": "1.0.2" - } - }, - "to-array": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", - "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=", - "dev": true - }, - "to-arraybuffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", - "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", - "dev": true - }, - "to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", - "dev": true - }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - } - }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "requires": { - "define-property": "2.0.2", - "extend-shallow": "3.0.2", - "regex-not": "1.0.2", - "safe-regex": "1.1.0" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "3.0.0", - "repeat-string": "1.6.1" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - } - } - } - }, - "toposort": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/toposort/-/toposort-1.0.6.tgz", - "integrity": "sha1-wxdI5V0hDv/AD9zcfW5o19e7nOw=", - "dev": true - }, - "tough-cookie": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", - "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", - "dev": true, - "requires": { - "punycode": "1.4.1" - } - }, - "tree-kill": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.0.tgz", - "integrity": "sha512-DlX6dR0lOIRDFxI0mjL9IYg6OTncLm/Zt+JiBhE5OlFcAR8yc9S7FFXU9so0oda47frdM/JFsk7UjNt9vscKcg==", - "dev": true - }, - "trim-newlines": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", - "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", - "dev": true - }, - "trim-right": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", - "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", - "dev": true - }, - "true-case-path": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.2.tgz", - "integrity": "sha1-fskRMJJHZsf1c74wIMNPj9/QDWI=", - "dev": true, - "optional": true, - "requires": { - "glob": "6.0.4" - }, - "dependencies": { - "glob": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", - "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", - "dev": true, - "optional": true, - "requires": { - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - } - } - }, - "ts-node": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-4.1.0.tgz", - "integrity": "sha512-xcZH12oVg9PShKhy3UHyDmuDLV3y7iKwX25aMVPt1SIXSuAfWkFiGPEkg+th8R4YKW/QCxDoW7lJdb15lx6QWg==", - "dev": true, - "requires": { - "arrify": "1.0.1", - "chalk": "2.3.1", - "diff": "3.4.0", - "make-error": "1.3.4", - "minimist": "1.2.0", - "mkdirp": "0.5.1", - "source-map-support": "0.5.3", - "tsconfig": "7.0.0", - "v8flags": "3.0.1", - "yn": "2.0.0" - }, - "dependencies": { - "chalk": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz", - "integrity": "sha512-QUU4ofkDoMIVO7hcx1iPTISs88wsO8jA92RQIm4JAwZvFGGAV2hSAA1NX7oVj2Ej2Q6NDTcRDjPTFrMCRZoJ6g==", - "dev": true, - "requires": { - "ansi-styles": "3.2.0", - "escape-string-regexp": "1.0.5", - "supports-color": "5.2.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.3.tgz", - "integrity": "sha512-eKkTgWYeBOQqFGXRfKabMFdnWepo51vWqEdoeikaEPFiJC7MCU5j2h4+6Q8npkZTeLGbSyecZvRxiSoWl3rh+w==", - "dev": true, - "requires": { - "source-map": "0.6.1" - } - }, - "supports-color": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz", - "integrity": "sha512-F39vS48la4YvTZUPVeTqsjsFNrvcMwrV3RLZINsmHo+7djCvuUzSIeXOnZ5hmjef4bajL1dNccN+tg5XAliO5Q==", - "dev": true, - "requires": { - "has-flag": "3.0.0" - } - } - } - }, - "tsconfig": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz", - "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==", - "dev": true, - "requires": { - "@types/strip-bom": "3.0.0", - "@types/strip-json-comments": "0.0.30", - "strip-bom": "3.0.0", - "strip-json-comments": "2.0.1" - }, - "dependencies": { - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - } - } - }, - "tsickle": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/tsickle/-/tsickle-0.27.2.tgz", - "integrity": "sha512-KW+ZgY0t2cq2Qib1sfdgMiRnk+cr3brUtzZoVWjv+Ot3jNxVorFBUH+6In6hl8Dg7BI2AAFf69NHkwvZNMSFwA==", - "dev": true, - "requires": { - "minimist": "1.2.0", - "mkdirp": "0.5.1", - "source-map": "0.6.1", - "source-map-support": "0.5.3" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.3.tgz", - "integrity": "sha512-eKkTgWYeBOQqFGXRfKabMFdnWepo51vWqEdoeikaEPFiJC7MCU5j2h4+6Q8npkZTeLGbSyecZvRxiSoWl3rh+w==", - "dev": true, - "requires": { - "source-map": "0.6.1" - } - } - } - }, - "tslib": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.0.tgz", - "integrity": "sha512-f/qGG2tUkrISBlQZEjEqoZ3B2+npJjIf04H1wuAv9iA8i04Icp+61KRXxFdha22670NJopsZCIjhC3SnjPRKrQ==" - }, - "tslint": { - "version": "5.9.1", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.9.1.tgz", - "integrity": "sha1-ElX4ej/1frCw4fDmEKi0dIBGya4=", - "dev": true, - "requires": { - "babel-code-frame": "6.26.0", - "builtin-modules": "1.1.1", - "chalk": "2.3.1", - "commander": "2.14.1", - "diff": "3.4.0", - "glob": "7.1.2", - "js-yaml": "3.10.0", - "minimatch": "3.0.4", - "resolve": "1.5.0", - "semver": "5.5.0", - "tslib": "1.9.0", - "tsutils": "2.21.2" - }, - "dependencies": { - "chalk": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz", - "integrity": "sha512-QUU4ofkDoMIVO7hcx1iPTISs88wsO8jA92RQIm4JAwZvFGGAV2hSAA1NX7oVj2Ej2Q6NDTcRDjPTFrMCRZoJ6g==", - "dev": true, - "requires": { - "ansi-styles": "3.2.0", - "escape-string-regexp": "1.0.5", - "supports-color": "5.2.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "supports-color": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz", - "integrity": "sha512-F39vS48la4YvTZUPVeTqsjsFNrvcMwrV3RLZINsmHo+7djCvuUzSIeXOnZ5hmjef4bajL1dNccN+tg5XAliO5Q==", - "dev": true, - "requires": { - "has-flag": "3.0.0" - } - } - } - }, - "tsscmp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.5.tgz", - "integrity": "sha1-fcSjOvcVgatDN9qR2FylQn69mpc=", - "dev": true, - "optional": true - }, - "tsutils": { - "version": "2.21.2", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.21.2.tgz", - "integrity": "sha512-iaIuyjIUeFLdD39MYdzqBuY7Zv6+uGxSwRH4mf+HuzsnznjFz0R2tGrAe0/JvtNh91WrN8UN/DZRFTZNDuVekA==", - "dev": true, - "requires": { - "tslib": "1.9.0" - } - }, - "tty-browserify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", - "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", - "dev": true - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, - "requires": { - "safe-buffer": "5.1.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true, - "optional": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "requires": { - "prelude-ls": "1.1.2" - } - }, - "type-is": { - "version": "1.6.16", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", - "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", - "dev": true, - "requires": { - "media-typer": "0.3.0", - "mime-types": "2.1.18" - } - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, - "typescript": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.5.3.tgz", - "integrity": "sha512-ptLSQs2S4QuS6/OD1eAKG+S5G8QQtrU5RT32JULdZQtM1L3WTi34Wsu48Yndzi8xsObRAB9RPt/KhA9wlpEF6w==", - "dev": true - }, - "uglify-js": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.3.12.tgz", - "integrity": "sha512-4jxrTXlV0HaXTsNILfXW0eey7Qo8qHYM6ih5ZNh45erDWU2GHmKDmekwBTskDb12h+kdd2DBvdzqVb47YzNmTA==", - "dev": true, - "requires": { - "commander": "2.14.1", - "source-map": "0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "uglify-to-browserify": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", - "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", - "dev": true, - "optional": true - }, - "uglifyjs-webpack-plugin": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.2.2.tgz", - "integrity": "sha512-CG/NvzXfemUAm5Y4Guh5eEaJYHtkG7kKNpXEJHp9QpxsFVB5/qKvYWoMaq4sa99ccZ0hM3MK8vQV9XPZB4357A==", - "dev": true, - "requires": { - "cacache": "10.0.4", - "find-cache-dir": "1.0.0", - "schema-utils": "0.4.5", - "serialize-javascript": "1.4.0", - "source-map": "0.6.1", - "uglify-es": "3.3.9", - "webpack-sources": "1.1.0", - "worker-farm": "1.5.4" - }, - "dependencies": { - "commander": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", - "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "uglify-es": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz", - "integrity": "sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==", - "dev": true, - "requires": { - "commander": "2.13.0", - "source-map": "0.6.1" - } - } - } - }, - "ultron": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", - "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", - "dev": true - }, - "umd": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/umd/-/umd-3.0.1.tgz", - "integrity": "sha1-iuVW4RAR9jwllnCKiDclnwGz1g4=", - "dev": true - }, - "underscore": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", - "integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=", - "dev": true - }, - "union-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", - "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", - "dev": true, - "requires": { - "arr-union": "3.1.0", - "get-value": "2.0.6", - "is-extendable": "0.1.1", - "set-value": "0.4.3" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - }, - "set-value": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", - "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", - "dev": true, - "requires": { - "extend-shallow": "2.0.1", - "is-extendable": "0.1.1", - "is-plain-object": "2.0.4", - "to-object-path": "0.3.0" - } - } - } - }, - "unique-filename": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.0.tgz", - "integrity": "sha1-0F8v5AMlYIcfMOk8vnNe6iAVFPM=", - "dev": true, - "requires": { - "unique-slug": "2.0.0" - } - }, - "unique-slug": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.0.tgz", - "integrity": "sha1-22Z258fMBimHj/GWCXx4hVrp9Ks=", - "dev": true, - "requires": { - "imurmurhash": "0.1.4" - } - }, - "universalify": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz", - "integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc=", - "dev": true - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", - "dev": true - }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, - "requires": { - "has-value": "0.3.1", - "isobject": "3.0.1" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, - "requires": { - "get-value": "2.0.6", - "has-values": "0.1.4", - "isobject": "2.1.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "upath": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.0.4.tgz", - "integrity": "sha512-d4SJySNBXDaQp+DPrziv3xGS6w3d2Xt69FijJr86zMPBy23JEloMCEOUBBzuN7xCtjLCnmB9tI/z7SBCahHBOw==", - "dev": true - }, - "upper-case": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", - "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=", - "dev": true - }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true - }, - "url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", - "dev": true, - "requires": { - "punycode": "1.3.2", - "querystring": "0.2.0" - }, - "dependencies": { - "punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", - "dev": true - } - } - }, - "url-loader": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-0.6.2.tgz", - "integrity": "sha512-h3qf9TNn53BpuXTTcpC+UehiRrl0Cv45Yr/xWayApjw6G8Bg2dGke7rIwDQ39piciWCWrC+WiqLjOh3SUp9n0Q==", - "dev": true, - "requires": { - "loader-utils": "1.1.0", - "mime": "1.6.0", - "schema-utils": "0.3.0" - }, - "dependencies": { - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", - "dev": true, - "requires": { - "co": "4.6.0", - "fast-deep-equal": "1.1.0", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.3.1" - } - }, - "schema-utils": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.3.0.tgz", - "integrity": "sha1-9YdyIs4+kx7a4DnxfrNxbnE3+M8=", - "dev": true, - "requires": { - "ajv": "5.5.2" - } - } - } - }, - "url-parse": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.2.0.tgz", - "integrity": "sha512-DT1XbYAfmQP65M/mE6OALxmXzZ/z1+e5zk2TcSKe/KiYbNGZxgtttzC0mR/sjopbpOXcbniq7eIKmocJnUWlEw==", - "dev": true, - "requires": { - "querystringify": "1.0.0", - "requires-port": "1.0.0" - }, - "dependencies": { - "querystringify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-1.0.0.tgz", - "integrity": "sha1-YoYkIRLFtxL6ZU5SZlK/ahP/Bcs=", - "dev": true - } - } - }, - "use": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/use/-/use-2.0.2.tgz", - "integrity": "sha1-riig1y+TvyJCKhii43mZMRLeyOg=", - "dev": true, - "requires": { - "define-property": "0.2.5", - "isobject": "3.0.1", - "lazy-cache": "2.0.2" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "0.1.6" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "0.1.6", - "is-data-descriptor": "0.1.4", - "kind-of": "5.1.0" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - }, - "lazy-cache": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-2.0.2.tgz", - "integrity": "sha1-uRkKT5EzVGlIQIWfio9whNiCImQ=", - "dev": true, - "requires": { - "set-getter": "0.1.0" - } - } - } - }, - "useragent": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.3.0.tgz", - "integrity": "sha512-4AoH4pxuSvHCjqLO04sU6U/uE65BYza8l/KKBS0b0hnUPWi+cQ2BpeTEwejCSx9SPV5/U03nniDTrWx5NrmKdw==", - "dev": true, - "requires": { - "lru-cache": "4.1.1", - "tmp": "0.0.33" - } - }, - "util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", - "dev": true, - "requires": { - "inherits": "2.0.1" - }, - "dependencies": { - "inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", - "dev": true - } - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "utila": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", - "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=", - "dev": true - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", - "dev": true - }, - "uuid": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", - "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==", - "dev": true - }, - "uws": { - "version": "9.14.0", - "resolved": "https://registry.npmjs.org/uws/-/uws-9.14.0.tgz", - "integrity": "sha512-HNMztPP5A1sKuVFmdZ6BPVpBQd5bUjNC8EFMFiICK+oho/OQsAJy5hnIx4btMHiOk8j04f/DbIlqnEZ9d72dqg==", - "dev": true, - "optional": true - }, - "v8flags": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.0.1.tgz", - "integrity": "sha1-3Oj8N5wX2fLJ6e142JzgAFKxt2s=", - "dev": true, - "requires": { - "homedir-polyfill": "1.0.1" - } - }, - "validate-npm-package-license": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", - "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=", - "dev": true, - "requires": { - "spdx-correct": "1.0.2", - "spdx-expression-parse": "1.0.4" - } - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", - "dev": true - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, - "requires": { - "assert-plus": "1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "1.3.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - } - } - }, - "vlq": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/vlq/-/vlq-0.2.3.tgz", - "integrity": "sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==", - "dev": true - }, - "vm-browserify": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", - "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", - "dev": true, - "requires": { - "indexof": "0.0.1" - } - }, - "void-elements": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", - "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", - "dev": true - }, - "watchpack": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.4.0.tgz", - "integrity": "sha1-ShRyvLuVK9Cpu0A2gB+VTfs5+qw=", - "dev": true, - "requires": { - "async": "2.6.0", - "chokidar": "1.7.0", - "graceful-fs": "4.1.11" - } - }, - "wbuf": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.2.tgz", - "integrity": "sha1-1pe5nx9ZUS3ydRvkJ2nBWAtYAf4=", - "dev": true, - "requires": { - "minimalistic-assert": "1.0.0" - } - }, - "webdriver-js-extender": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/webdriver-js-extender/-/webdriver-js-extender-1.0.0.tgz", - "integrity": "sha1-gcUzqeM9W/tZe05j4s2yW1R3dRU=", - "dev": true, - "requires": { - "@types/selenium-webdriver": "2.53.43", - "selenium-webdriver": "2.53.3" - }, - "dependencies": { - "adm-zip": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.4.tgz", - "integrity": "sha1-ph7VrmkFw66lizplfSUDMJEFJzY=", - "dev": true - }, - "sax": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/sax/-/sax-0.6.1.tgz", - "integrity": "sha1-VjsZx8HeiS4Jv8Ty/DDjwn8JUrk=", - "dev": true - }, - "selenium-webdriver": { - "version": "2.53.3", - "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-2.53.3.tgz", - "integrity": "sha1-0p/1qVff8aG0ncRXdW5OS/vc4IU=", - "dev": true, - "requires": { - "adm-zip": "0.4.4", - "rimraf": "2.6.2", - "tmp": "0.0.24", - "ws": "1.1.5", - "xml2js": "0.4.4" - } - }, - "tmp": { - "version": "0.0.24", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.24.tgz", - "integrity": "sha1-1qXhmNFKmDXMby18PZ4wJCjIzxI=", - "dev": true - }, - "ultron": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz", - "integrity": "sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po=", - "dev": true - }, - "ws": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.5.tgz", - "integrity": "sha512-o3KqipXNUdS7wpQzBHSe180lBGO60SoK0yVo3CYJgb2MkobuWuBX6dhkYP5ORCLd55y+SaflMOV5fqAB53ux4w==", - "dev": true, - "requires": { - "options": "0.0.6", - "ultron": "1.0.2" - } - }, - "xml2js": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.4.tgz", - "integrity": "sha1-MREBAAMAiuGSQOuhdJe1fHKcVV0=", - "dev": true, - "requires": { - "sax": "0.6.1", - "xmlbuilder": "9.0.7" - } - } - } - }, - "webpack": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-3.11.0.tgz", - "integrity": "sha512-3kOFejWqj5ISpJk4Qj/V7w98h9Vl52wak3CLiw/cDOfbVTq7FeoZ0SdoHHY9PYlHr50ZS42OfvzE2vB4nncKQg==", - "dev": true, - "requires": { - "acorn": "5.4.1", - "acorn-dynamic-import": "2.0.2", - "ajv": "6.2.0", - "ajv-keywords": "3.1.0", - "async": "2.6.0", - "enhanced-resolve": "3.4.1", - "escope": "3.6.0", - "interpret": "1.1.0", - "json-loader": "0.5.7", - "json5": "0.5.1", - "loader-runner": "2.3.0", - "loader-utils": "1.1.0", - "memory-fs": "0.4.1", - "mkdirp": "0.5.1", - "node-libs-browser": "2.1.0", - "source-map": "0.5.7", - "supports-color": "4.5.0", - "tapable": "0.2.8", - "uglifyjs-webpack-plugin": "0.4.6", - "watchpack": "1.4.0", - "webpack-sources": "1.1.0", - "yargs": "8.0.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 - }, - "camelcase": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", - "dev": true - }, - "cliui": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", - "dev": true, - "requires": { - "center-align": "0.1.3", - "right-align": "0.1.3", - "wordwrap": "0.0.2" - } - }, - "load-json-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "parse-json": "2.2.0", - "pify": "2.3.0", - "strip-bom": "3.0.0" - } - }, - "os-locale": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", - "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", - "dev": true, - "requires": { - "execa": "0.7.0", - "lcid": "1.0.0", - "mem": "1.1.0" - } - }, - "path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", - "dev": true, - "requires": { - "pify": "2.3.0" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, - "read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", - "dev": true, - "requires": { - "load-json-file": "2.0.0", - "normalize-package-data": "2.4.0", - "path-type": "2.0.0" - } - }, - "read-pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", - "dev": true, - "requires": { - "find-up": "2.1.0", - "read-pkg": "2.0.0" - } - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "4.0.0" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "3.0.0" - } - } - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - }, - "uglify-js": { - "version": "2.8.29", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", - "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", - "dev": true, - "requires": { - "source-map": "0.5.7", - "uglify-to-browserify": "1.0.2", - "yargs": "3.10.0" - }, - "dependencies": { - "yargs": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", - "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", - "dev": true, - "requires": { - "camelcase": "1.2.1", - "cliui": "2.1.0", - "decamelize": "1.2.0", - "window-size": "0.1.0" - } - } - } - }, - "uglifyjs-webpack-plugin": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-0.4.6.tgz", - "integrity": "sha1-uVH0q7a9YX5m9j64kUmOORdj4wk=", - "dev": true, - "requires": { - "source-map": "0.5.7", - "uglify-js": "2.8.29", - "webpack-sources": "1.1.0" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", - "dev": true - }, - "yargs": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-8.0.2.tgz", - "integrity": "sha1-YpmpBVsc78lp/355wdkY3Osiw2A=", - "dev": true, - "requires": { - "camelcase": "4.1.0", - "cliui": "3.2.0", - "decamelize": "1.2.0", - "get-caller-file": "1.0.2", - "os-locale": "2.1.0", - "read-pkg-up": "2.0.0", - "require-directory": "2.1.1", - "require-main-filename": "1.0.1", - "set-blocking": "2.0.0", - "string-width": "2.1.1", - "which-module": "2.0.0", - "y18n": "3.2.1", - "yargs-parser": "7.0.0" - }, - "dependencies": { - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true - }, - "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", - "dev": true, - "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wrap-ansi": "2.1.0" - }, - "dependencies": { - "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.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" - } - } - } - } - } - }, - "yargs-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-7.0.0.tgz", - "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=", - "dev": true, - "requires": { - "camelcase": "4.1.0" - }, - "dependencies": { - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true - } - } - } - } - }, - "webpack-core": { - "version": "0.6.9", - "resolved": "https://registry.npmjs.org/webpack-core/-/webpack-core-0.6.9.tgz", - "integrity": "sha1-/FcViMhVjad76e+23r3Fo7FyvcI=", - "dev": true, - "requires": { - "source-list-map": "0.1.8", - "source-map": "0.4.4" - }, - "dependencies": { - "source-list-map": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-0.1.8.tgz", - "integrity": "sha1-xVCyq1Qn9rPyH1r+rYjE9Vh7IQY=", - "dev": true - }, - "source-map": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", - "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", - "dev": true, - "requires": { - "amdefine": "1.0.1" - } - } - } - }, - "webpack-dev-middleware": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-1.12.2.tgz", - "integrity": "sha512-FCrqPy1yy/sN6U/SaEZcHKRXGlqU0DUaEBL45jkUYoB8foVb6wCnbIJ1HKIx+qUFTW+3JpVcCJCxZ8VATL4e+A==", - "dev": true, - "requires": { - "memory-fs": "0.4.1", - "mime": "1.6.0", - "path-is-absolute": "1.0.1", - "range-parser": "1.2.0", - "time-stamp": "2.0.0" - } - }, - "webpack-dev-server": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-2.11.1.tgz", - "integrity": "sha512-ombhu5KsO/85sVshIDTyQ5HF3xjZR3N0sf5Ao6h3vFwpNyzInEzA1GV3QPVjTMLTNckp8PjfG1PFGznzBwS5lg==", - "dev": true, - "requires": { - "ansi-html": "0.0.7", - "array-includes": "3.0.3", - "bonjour": "3.5.0", - "chokidar": "2.0.2", - "compression": "1.7.2", - "connect-history-api-fallback": "1.5.0", - "debug": "3.1.0", - "del": "3.0.0", - "express": "4.16.2", - "html-entities": "1.2.1", - "http-proxy-middleware": "0.17.4", - "import-local": "1.0.0", - "internal-ip": "1.2.0", - "ip": "1.1.5", - "killable": "1.0.0", - "loglevel": "1.6.1", - "opn": "5.1.0", - "portfinder": "1.0.13", - "selfsigned": "1.10.2", - "serve-index": "1.9.1", - "sockjs": "0.3.19", - "sockjs-client": "1.1.4", - "spdy": "3.4.7", - "strip-ansi": "3.0.1", - "supports-color": "5.2.0", - "webpack-dev-middleware": "1.12.2", - "yargs": "6.6.0" - }, - "dependencies": { - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "requires": { - "micromatch": "3.1.9", - "normalize-path": "2.1.1" - } - }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "braces": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.1.tgz", - "integrity": "sha512-SO5lYHA3vO6gz66erVvedSCkp7AKWdv6VcQ2N4ysXfPxdAlxAMMAdwegGGcv1Bqwm7naF1hNdk5d6AAIEHV2nQ==", - "dev": true, - "requires": { - "arr-flatten": "1.1.0", - "array-unique": "0.3.2", - "define-property": "1.0.0", - "extend-shallow": "2.0.1", - "fill-range": "4.0.0", - "isobject": "3.0.1", - "kind-of": "6.0.2", - "repeat-element": "1.1.2", - "snapdragon": "0.8.1", - "snapdragon-node": "2.1.1", - "split-string": "3.1.0", - "to-regex": "3.0.2" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "1.0.2" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - } - } - }, - "camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", - "dev": true - }, - "chokidar": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.2.tgz", - "integrity": "sha512-l32Hw3wqB0L2kGVmSbK/a+xXLDrUEsc84pSgMkmwygHvD7ubRsP/vxxHa5BtB6oix1XLLVCHyYMsckRXxThmZw==", - "dev": true, - "requires": { - "anymatch": "2.0.0", - "async-each": "1.0.1", - "braces": "2.3.1", - "fsevents": "1.1.3", - "glob-parent": "3.1.0", - "inherits": "2.0.3", - "is-binary-path": "1.0.1", - "is-glob": "4.0.0", - "normalize-path": "2.1.1", - "path-is-absolute": "1.0.1", - "readdirp": "2.1.0", - "upath": "1.0.4" - } - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "debug": "2.6.9", - "define-property": "0.2.5", - "extend-shallow": "2.0.1", - "posix-character-classes": "0.1.1", - "regex-not": "1.0.2", - "snapdragon": "0.8.1", - "to-regex": "3.0.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "0.1.6" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "0.1.6", - "is-data-descriptor": "0.1.4", - "kind-of": "5.1.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "0.3.2", - "define-property": "1.0.0", - "expand-brackets": "2.1.4", - "extend-shallow": "2.0.1", - "fragment-cache": "0.2.1", - "regex-not": "1.0.2", - "snapdragon": "0.8.1", - "to-regex": "3.0.2" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "1.0.2" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "2.0.1", - "is-number": "3.0.0", - "repeat-string": "1.6.1", - "to-regex-range": "2.1.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - } - } - }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "dev": true, - "requires": { - "is-glob": "3.1.0", - "path-dirname": "1.0.2" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "requires": { - "is-extglob": "2.1.1" - } - } - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-glob": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", - "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", - "dev": true, - "requires": { - "is-extglob": "2.1.1" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - }, - "micromatch": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.9.tgz", - "integrity": "sha512-SlIz6sv5UPaAVVFRKodKjCg48EbNoIhgetzfK/Cy0v5U52Z6zB136M8tp0UC9jM53LYbmIRihJszvvqpKkfm9g==", - "dev": true, - "requires": { - "arr-diff": "4.0.0", - "array-unique": "0.3.2", - "braces": "2.3.1", - "define-property": "2.0.2", - "extend-shallow": "3.0.2", - "extglob": "2.0.4", - "fragment-cache": "0.2.1", - "kind-of": "6.0.2", - "nanomatch": "1.2.9", - "object.pick": "1.3.0", - "regex-not": "1.0.2", - "snapdragon": "0.8.1", - "to-regex": "3.0.2" - } - }, - "supports-color": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz", - "integrity": "sha512-F39vS48la4YvTZUPVeTqsjsFNrvcMwrV3RLZINsmHo+7djCvuUzSIeXOnZ5hmjef4bajL1dNccN+tg5XAliO5Q==", - "dev": true, - "requires": { - "has-flag": "3.0.0" - } - }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", - "dev": true - }, - "yargs": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz", - "integrity": "sha1-eC7CHvQDNF+DCoCMo9UTr1YGUgg=", - "dev": true, - "requires": { - "camelcase": "3.0.0", - "cliui": "3.2.0", - "decamelize": "1.2.0", - "get-caller-file": "1.0.2", - "os-locale": "1.4.0", - "read-pkg-up": "1.0.1", - "require-directory": "2.1.1", - "require-main-filename": "1.0.1", - "set-blocking": "2.0.0", - "string-width": "1.0.2", - "which-module": "1.0.0", - "y18n": "3.2.1", - "yargs-parser": "4.2.1" - } - }, - "yargs-parser": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-4.2.1.tgz", - "integrity": "sha1-KczqwNxPA8bIe0qfIX3RjJ90hxw=", - "dev": true, - "requires": { - "camelcase": "3.0.0" - } - } - } - }, - "webpack-merge": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.1.2.tgz", - "integrity": "sha512-/0QYwW/H1N/CdXYA2PNPVbsxO3u2Fpz34vs72xm03SRfg6bMNGfMJIQEpQjKRvkG2JvT6oRJFpDtSrwbX8Jzvw==", - "dev": true, - "requires": { - "lodash": "4.17.5" - } - }, - "webpack-sources": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.1.0.tgz", - "integrity": "sha512-aqYp18kPphgoO5c/+NaUvEeACtZjMESmDChuD3NBciVpah3XpMEU9VAAtIaB1BsfJWWTSdv8Vv1m3T0aRk2dUw==", - "dev": true, - "requires": { - "source-list-map": "2.0.0", - "source-map": "0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "webpack-subresource-integrity": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/webpack-subresource-integrity/-/webpack-subresource-integrity-1.0.4.tgz", - "integrity": "sha1-j6yKfo61n8ahZ2ioXJ2U7n+dDts=", - "dev": true, - "requires": { - "webpack-core": "0.6.9" - } - }, - "websocket-driver": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.0.tgz", - "integrity": "sha1-DK+dLXVdk67gSdS90NP+LMoqJOs=", - "dev": true, - "requires": { - "http-parser-js": "0.4.10", - "websocket-extensions": "0.1.3" - } - }, - "websocket-extensions": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", - "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==", - "dev": true - }, - "when": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/when/-/when-3.6.4.tgz", - "integrity": "sha1-RztRfsFZ4rhQBUl6E5g/CVQS404=", - "dev": true - }, - "which": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", - "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", - "dev": true, - "requires": { - "isexe": "2.0.0" - } - }, - "which-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", - "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", - "dev": true - }, - "wide-align": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", - "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", - "dev": true, - "requires": { - "string-width": "1.0.2" - } - }, - "window-size": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", - "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", - "dev": true - }, - "wordwrap": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", - "dev": true - }, - "worker-farm": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.5.4.tgz", - "integrity": "sha512-ITyClEvcfv0ozqJl1vmWFWhvI+OIrkbInYqkEPE50wFPXj8J9Gd3FYf8+CkZJXJJsQBYe+2DvmoK9Zhx5w8W+w==", - "dev": true, - "requires": { - "errno": "0.1.7", - "xtend": "4.0.1" - } - }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "dev": true, - "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "ws": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", - "dev": true, - "requires": { - "async-limiter": "1.0.0", - "safe-buffer": "5.1.1", - "ultron": "1.1.1" - } - }, - "xml-char-classes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/xml-char-classes/-/xml-char-classes-1.0.0.tgz", - "integrity": "sha1-ZGV4SKIP/F31g6Qq2KJ3tFErvE0=", - "dev": true - }, - "xml2js": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", - "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", - "dev": true, - "requires": { - "sax": "1.2.4", - "xmlbuilder": "9.0.7" - }, - "dependencies": { - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true - } - } - }, - "xmlbuilder": { - "version": "9.0.7", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", - "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=", - "dev": true - }, - "xmlhttprequest-ssl": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", - "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=", - "dev": true - }, - "xregexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz", - "integrity": "sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM=", - "dev": true, - "optional": true - }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", - "dev": true - }, - "xxhashjs": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/xxhashjs/-/xxhashjs-0.2.2.tgz", - "integrity": "sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw==", - "dev": true, - "requires": { - "cuint": "0.2.2" - } - }, - "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", - "dev": true - }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true - }, - "yargs": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", - "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=", - "dev": true, - "optional": true, - "requires": { - "camelcase": "3.0.0", - "cliui": "3.2.0", - "decamelize": "1.2.0", - "get-caller-file": "1.0.2", - "os-locale": "1.4.0", - "read-pkg-up": "1.0.1", - "require-directory": "2.1.1", - "require-main-filename": "1.0.1", - "set-blocking": "2.0.0", - "string-width": "1.0.2", - "which-module": "1.0.0", - "y18n": "3.2.1", - "yargs-parser": "5.0.0" - }, - "dependencies": { - "camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", - "dev": true, - "optional": true - }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", - "dev": true, - "optional": true - } - } - }, - "yargs-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz", - "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=", - "dev": true, - "optional": true, - "requires": { - "camelcase": "3.0.0" - }, - "dependencies": { - "camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", - "dev": true, - "optional": true - } - } - }, - "yeast": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", - "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=", - "dev": true - }, - "yn": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", - "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=", - "dev": true - }, - "zone.js": { - "version": "0.8.20", - "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.8.20.tgz", - "integrity": "sha512-FXlA37ErSXCMy5RNBcGFgCI/Zivqzr0D19GuvDxhcYIJc7xkFp6c29DKyODJu0Zo+EMyur/WPPgcBh1EHjB9jA==" - } - } -} diff --git a/package.json b/package.json deleted file mode 100644 index 8b0f229..0000000 --- a/package.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "name": "ludos-data", - "version": "0.0.0", - "license": "MIT", - "scripts": { - "ng": "ng", - "start": "ng serve", - "build": "ng build --prod", - "test": "ng test", - "lint": "ng lint", - "e2e": "ng e2e" - }, - "private": true, - "dependencies": { - "@angular/animations": "^5.2.10", - "@angular/cdk": "^5.2.5", - "@angular/common": "^5.2.0", - "@angular/compiler": "^5.2.0", - "@angular/core": "^5.2.0", - "@angular/flex-layout": "^5.0.0-beta.14", - "@angular/forms": "^5.2.0", - "@angular/http": "^5.2.0", - "@angular/material": "^5.2.5", - "@angular/platform-browser": "^5.2.0", - "@angular/platform-browser-dynamic": "^5.2.0", - "@angular/router": "^5.2.0", - "bootstrap": "^4.0.0", - "core-js": "^2.4.1", - "infinite-scroll": "^3.0.3", - "jquery": "^3.3.1", - "masonry-layout": "^4.2.1", - "material-design-icons": "^3.0.1", - "rxjs": "^5.5.6", - "zone.js": "^0.8.19" - }, - "devDependencies": { - "@angular/cli": "~1.7.0", - "@angular/compiler-cli": "^5.2.0", - "@angular/language-service": "^5.2.0", - "@types/jasmine": "~2.8.3", - "@types/jasminewd2": "~2.0.2", - "@types/node": "~6.0.60", - "codelyzer": "^4.0.1", - "jasmine-core": "~2.8.0", - "jasmine-spec-reporter": "~4.2.1", - "karma": "~2.0.0", - "karma-chrome-launcher": "~2.2.0", - "karma-coverage-istanbul-reporter": "^1.2.1", - "karma-jasmine": "~1.1.0", - "karma-jasmine-html-reporter": "^0.2.2", - "protractor": "~5.1.2", - "ts-node": "~4.1.0", - "tslint": "~5.9.1", - "typescript": "~2.5.3" - } -} diff --git a/protractor.conf.js b/protractor.conf.js deleted file mode 100644 index 7ee3b5e..0000000 --- a/protractor.conf.js +++ /dev/null @@ -1,28 +0,0 @@ -// Protractor configuration file, see link for more information -// https://github.com/angular/protractor/blob/master/lib/config.ts - -const { SpecReporter } = require('jasmine-spec-reporter'); - -exports.config = { - allScriptsTimeout: 11000, - specs: [ - './e2e/**/*.e2e-spec.ts' - ], - capabilities: { - 'browserName': 'chrome' - }, - directConnect: true, - baseUrl: 'http://localhost:4200/', - framework: 'jasmine', - jasmineNodeOpts: { - showColors: true, - defaultTimeoutInterval: 30000, - print: function() {} - }, - onPrepare() { - require('ts-node').register({ - project: 'e2e/tsconfig.e2e.json' - }); - jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); - } -}; diff --git a/src/app/_directives/alert.component.html b/src/app/_directives/alert.component.html deleted file mode 100644 index 4e63a72..0000000 --- a/src/app/_directives/alert.component.html +++ /dev/null @@ -1 +0,0 @@ -
{{message.text}}
\ No newline at end of file diff --git a/src/app/_directives/alert.component.ts b/src/app/_directives/alert.component.ts deleted file mode 100644 index 70638e5..0000000 --- a/src/app/_directives/alert.component.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Component, OnDestroy } from '@angular/core'; -import { Subscription } from 'rxjs/Subscription'; - -import { AlertService } from '../_services/index'; - -@Component({ - moduleId: module.id, - selector: 'alert', - templateUrl: 'alert.component.html' -}) - -export class AlertComponent implements OnDestroy { - private subscription: Subscription; - message: any; - - constructor(private alertService: AlertService) { - // subscribe to alert messages - this.subscription = alertService.getMessage().subscribe(message => { this.message = message; }); - } - - ngOnDestroy(): void { - // unsubscribe on destroy to prevent memory leaks - this.subscription.unsubscribe(); - } -} \ No newline at end of file diff --git a/src/app/_directives/index.ts b/src/app/_directives/index.ts deleted file mode 100644 index efad496..0000000 --- a/src/app/_directives/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './alert.component'; \ No newline at end of file diff --git a/src/app/_guards/auth.guard.ts b/src/app/_guards/auth.guard.ts deleted file mode 100644 index 0d6bc04..0000000 --- a/src/app/_guards/auth.guard.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Injectable } from '@angular/core'; -import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; - -@Injectable() -export class AuthGuard implements CanActivate { - - constructor(private router: Router) { } - - canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { - if (localStorage.getItem('currentUser')) { - return true; - } - - this.router.navigate( ['login'] ); - return false; - } -} \ No newline at end of file diff --git a/src/app/_guards/index.ts b/src/app/_guards/index.ts deleted file mode 100644 index 280e788..0000000 --- a/src/app/_guards/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './auth.guard'; \ No newline at end of file diff --git a/src/app/_helpers/fake-backend.ts b/src/app/_helpers/fake-backend.ts deleted file mode 100644 index 44ea58b..0000000 --- a/src/app/_helpers/fake-backend.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { Injectable } from '@angular/core'; -import { HttpRequest, HttpResponse, HttpHandler, HttpEvent, HttpInterceptor, HTTP_INTERCEPTORS } from '@angular/common/http'; -import { Observable } from 'rxjs/Observable'; -import 'rxjs/add/observable/of'; -import 'rxjs/add/observable/throw'; -import 'rxjs/add/operator/delay'; -import 'rxjs/add/operator/mergeMap'; -import 'rxjs/add/operator/materialize'; -import 'rxjs/add/operator/dematerialize'; - -@Injectable() -export class FakeBackendInterceptor implements HttpInterceptor { - - constructor() { } - - intercept(request: HttpRequest, next: HttpHandler): Observable> { - // array in local storage for registered users - let users: any[] = JSON.parse(localStorage.getItem('users')) || []; - - // wrap in delayed observable to simulate server api call - return Observable.of(null).mergeMap(() => { - - // authenticate - if (request.url.endsWith('/api/authenticate') && request.method === 'POST') { - // find if any user matches login credentials - let filteredUsers = users.filter(user => { - return user.username === request.body.username && user.password === request.body.password; - }); - - if (filteredUsers.length) { - // if login details are valid return 200 OK with user details and fake jwt token - let user = filteredUsers[0]; - let body = { - id: user.id, - username: user.username, - firstName: user.firstName, - lastName: user.lastName, - token: 'fake-jwt-token' - }; - - return Observable.of(new HttpResponse({ status: 200, body: body })); - } else { - // else return 400 bad request - return Observable.throw('Username or password is incorrect'); - } - } - - // get users - if (request.url.endsWith('/api/users') && request.method === 'GET') { - // check for fake auth token in header and return users if valid, this security is implemented server side in a real application - if (request.headers.get('Authorization') === 'Bearer fake-jwt-token') { - return Observable.of(new HttpResponse({ status: 200, body: users })); - } else { - // return 401 not authorised if token is null or invalid - return Observable.throw('Unauthorised'); - } - } - - // get user by id - if (request.url.match(/\/api\/users\/\d+$/) && request.method === 'GET') { - // check for fake auth token in header and return user if valid, this security is implemented server side in a real application - if (request.headers.get('Authorization') === 'Bearer fake-jwt-token') { - // find user by id in users array - let urlParts = request.url.split('/'); - let id = parseInt(urlParts[urlParts.length - 1]); - let matchedUsers = users.filter(user => { return user.id === id; }); - let user = matchedUsers.length ? matchedUsers[0] : null; - - return Observable.of(new HttpResponse({ status: 200, body: user })); - } else { - // return 401 not authorised if token is null or invalid - return Observable.throw('Unauthorised'); - } - } - - // create user - if (request.url.endsWith('/api/users') && request.method === 'POST') { - // get new user object from post body - let newUser = request.body; - - // validation - let duplicateUser = users.filter(user => { return user.username === newUser.username; }).length; - if (duplicateUser) { - return Observable.throw('Username "' + newUser.username + '" is already taken'); - } - - // save new user - newUser.id = users.length + 1; - users.push(newUser); - localStorage.setItem('users', JSON.stringify(users)); - - // respond 200 OK - return Observable.of(new HttpResponse({ status: 200 })); - } - - // delete user - if (request.url.match(/\/api\/users\/\d+$/) && request.method === 'DELETE') { - // check for fake auth token in header and return user if valid, this security is implemented server side in a real application - if (request.headers.get('Authorization') === 'Bearer fake-jwt-token') { - // find user by id in users array - let urlParts = request.url.split('/'); - let id = parseInt(urlParts[urlParts.length - 1]); - for (let i = 0; i < users.length; i++) { - let user = users[i]; - if (user.id === id) { - // delete user - users.splice(i, 1); - localStorage.setItem('users', JSON.stringify(users)); - break; - } - } - - // respond 200 OK - return Observable.of(new HttpResponse({ status: 200 })); - } else { - // return 401 not authorised if token is null or invalid - return Observable.throw('Unauthorised'); - } - } - - // pass through any requests not handled above - return next.handle(request); - - }) - - // call materialize and dematerialize to ensure delay even if an error is thrown (https://github.com/Reactive-Extensions/RxJS/issues/648) - .materialize() - .delay(500) - .dematerialize(); - } -} - -export let fakeBackendProvider = { - // use fake backend in place of Http service for backend-less development - provide: HTTP_INTERCEPTORS, - useClass: FakeBackendInterceptor, - multi: true -}; \ No newline at end of file diff --git a/src/app/_helpers/index.ts b/src/app/_helpers/index.ts deleted file mode 100644 index 76998f9..0000000 --- a/src/app/_helpers/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './fake-backend'; \ No newline at end of file diff --git a/src/app/_models/index.ts b/src/app/_models/index.ts deleted file mode 100644 index 54eb605..0000000 --- a/src/app/_models/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './user'; \ No newline at end of file diff --git a/src/app/_models/user.ts b/src/app/_models/user.ts deleted file mode 100644 index 5c7b0fe..0000000 --- a/src/app/_models/user.ts +++ /dev/null @@ -1,7 +0,0 @@ -export class User { - id: number; - username: string; - password: string; - firstName: string; - lastName: string; -} \ No newline at end of file diff --git a/src/app/_services/alert.service.ts b/src/app/_services/alert.service.ts deleted file mode 100644 index 9abffb6..0000000 --- a/src/app/_services/alert.service.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Injectable } from '@angular/core'; -import { Router, NavigationStart } from '@angular/router'; -import { Observable } from 'rxjs'; -import { Subject } from 'rxjs/Subject'; - -@Injectable() -export class AlertService { - private subject = new Subject(); - private keepAfterNavigationChange = false; - - constructor(private router: Router) { - // clear alert message on route change - router.events.subscribe(event => { - if (event instanceof NavigationStart) { - if (this.keepAfterNavigationChange) { - // only keep for a single location change - this.keepAfterNavigationChange = false; - } else { - // clear alert - this.subject.next(); - } - } - }); - } - - success(message: string, keepAfterNavigationChange = false) { - this.keepAfterNavigationChange = keepAfterNavigationChange; - this.subject.next({ type: 'success', text: message }); - } - - error(message: string, keepAfterNavigationChange = false) { - this.keepAfterNavigationChange = keepAfterNavigationChange; - this.subject.next({ type: 'error', text: message }); - } - - getMessage(): Observable { - return this.subject.asObservable(); - } -} \ No newline at end of file diff --git a/src/app/_services/authentication.service.ts b/src/app/_services/authentication.service.ts deleted file mode 100644 index 9db9cd1..0000000 --- a/src/app/_services/authentication.service.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Injectable } from '@angular/core'; -import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; -import { Observable } from 'rxjs/Observable'; -import 'rxjs/add/operator/map' - -const httpOptions = { - headers: new HttpHeaders({ 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }) - //headers: new HttpHeaders({ 'Content-Type': 'application/json' }) -} - -@Injectable() -export class AuthenticationService { - - /* testing */ - loginUrl = "http://pugludos.com/interfaceServices/loginInterface.php"; - - /* production */ - //loginUrl = "/interfaceServices/fake_loginInterface.php"; - - params; - - constructor(private http: HttpClient) { } - - login( userData ) { - - this.params = new HttpParams({ - fromObject: userData - }); - - return this.http.post(this.loginUrl, this.params, httpOptions) - .map(user => { - // login successful if there's a jwt token in the response - if (user && user.token) { - // store user details and jwt token in local storage to keep user logged in between page refreshes - localStorage.setItem('currentUser', JSON.stringify(user)); - } - - return user; - }); - } - - logout() { - localStorage.removeItem('currentUser'); - } -} \ No newline at end of file diff --git a/src/app/_services/index.ts b/src/app/_services/index.ts deleted file mode 100644 index 80a22fd..0000000 --- a/src/app/_services/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './alert.service'; -export * from './authentication.service'; -export * from './user.service'; \ No newline at end of file diff --git a/src/app/_services/user.service.ts b/src/app/_services/user.service.ts deleted file mode 100644 index 2fc381f..0000000 --- a/src/app/_services/user.service.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Injectable } from '@angular/core'; -import { HttpClient } from '@angular/common/http'; - -import { User } from '../_models/index'; - -@Injectable() -export class UserService { - constructor(private http: HttpClient) { } - - getAll() { - return this.http.get('/api/users'); - } - - getById(id: number) { - return this.http.get('/api/users/' + id); - } - - create(user: User) { - return this.http.post('/api/users', user); - } - - update(user: User) { - return this.http.put('/api/users/' + user.id, user); - } - - delete(id: number) { - return this.http.delete('/api/users/' + id); - } -} \ No newline at end of file diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts deleted file mode 100644 index bc4e6fa..0000000 --- a/src/app/app-routing.module.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { NgModule } from '@angular/core'; -import { RouterModule, Routes } from '@angular/router'; - -import { GameGridComponent } from './game-grid/game-grid.component' -import { ViewCardComponent } from './view-card/view-card.component' -import { LoginComponent } from './login/login.component' -import { RegisterComponent } from './register/register.component' -import { UserComponent } from './user/user.component' -import { AuthGuard } from './_guards/index'; - -const routes: Routes = [ - { path: '', redirectTo: '/login', pathMatch: 'full' }, - { path: 'game-grid', component: GameGridComponent, canActivate: [AuthGuard] }, - { path: 'view-card', component: ViewCardComponent, canActivate: [AuthGuard] }, - { path: 'view-card/:gid', component: ViewCardComponent, canActivate: [AuthGuard] }, - { path: 'login', component: LoginComponent }, - { path: 'register', component: RegisterComponent }, - { path: 'user', component: UserComponent } -]; - -@NgModule({ - imports: [ - RouterModule.forRoot(routes) - ], - exports: [ RouterModule ] -}) -export class AppRoutingModule { } diff --git a/src/app/app.component.css b/src/app/app.component.css deleted file mode 100644 index e69de29..0000000 diff --git a/src/app/app.component.html b/src/app/app.component.html deleted file mode 100644 index c76698b..0000000 --- a/src/app/app.component.html +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts deleted file mode 100644 index bcbdf36..0000000 --- a/src/app/app.component.spec.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { TestBed, async } from '@angular/core/testing'; -import { AppComponent } from './app.component'; -describe('AppComponent', () => { - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ - AppComponent - ], - }).compileComponents(); - })); - it('should create the app', async(() => { - const fixture = TestBed.createComponent(AppComponent); - const app = fixture.debugElement.componentInstance; - expect(app).toBeTruthy(); - })); - it(`should have as title 'app'`, async(() => { - const fixture = TestBed.createComponent(AppComponent); - const app = fixture.debugElement.componentInstance; - expect(app.title).toEqual('app'); - })); - it('should render title in a h1 tag', async(() => { - const fixture = TestBed.createComponent(AppComponent); - fixture.detectChanges(); - const compiled = fixture.debugElement.nativeElement; - expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!'); - })); -}); diff --git a/src/app/app.component.ts b/src/app/app.component.ts deleted file mode 100644 index 7b0f672..0000000 --- a/src/app/app.component.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Component } from '@angular/core'; - -@Component({ - selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.css'] -}) -export class AppComponent { - title = 'app'; -} diff --git a/src/app/app.module.ts b/src/app/app.module.ts deleted file mode 100644 index f64e0f7..0000000 --- a/src/app/app.module.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { BrowserModule } from '@angular/platform-browser'; -import { NgModule } from '@angular/core'; -import {HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; -import { ReactiveFormsModule } from '@angular/forms'; - -import { GamesService } from './games.service'; -import { AbstractControl } from '@angular/forms'; - -import { AppComponent } from './app.component'; -import { GameGridComponent } from './game-grid/game-grid.component'; -import { AppRoutingModule } from './/app-routing.module'; -import { ViewCardComponent } from './view-card/view-card.component'; - -import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; -import {MatCardModule} from '@angular/material'; -import {MatToolbarModule} from '@angular/material/toolbar'; -import {MatMenuModule} from '@angular/material/menu'; -import {MatIconModule} from '@angular/material/icon'; -import {MatButtonModule} from '@angular/material/button'; -import {MatInputModule} from '@angular/material/input'; -import {MatPaginatorModule} from '@angular/material/paginator'; -import {MatFormFieldModule} from '@angular/material/form-field'; -import {MatSelectModule} from '@angular/material/select'; - -import {MatProgressSpinnerModule} from '@angular/material/progress-spinner'; - - -import { LoginComponent } from './login/login.component'; -import { RegisterComponent } from './register/register.component'; - -import { RegistrationService } from './registration.service'; -import { UsernameValidator } from './validators/username.validator' -import { EmailValidator } from './validators/email.validator' - -// used to create fake backend -import { fakeBackendProvider } from './_helpers/index'; - -import { AlertComponent } from './_directives/index'; -import { AuthGuard } from './_guards/index'; -import { AlertService, AuthenticationService, UserService } from './_services/index'; -import { UserComponent } from './user/user.component'; - - - -@NgModule({ - declarations: [ - AppComponent, - GameGridComponent, - ViewCardComponent, - LoginComponent, - RegisterComponent, - AlertComponent, - UserComponent, - ], - imports: [ - BrowserModule, - AppRoutingModule, - HttpClientModule, - ReactiveFormsModule, - BrowserAnimationsModule, - MatCardModule, - MatToolbarModule, - MatMenuModule, - MatIconModule, - MatButtonModule, - MatInputModule, - MatPaginatorModule, - MatFormFieldModule, - MatSelectModule, - MatProgressSpinnerModule - ], - providers: [ - GamesService, - RegistrationService, - UsernameValidator, - EmailValidator, - - AuthGuard, - AlertService, - AuthenticationService, - UserService, - // provider used to create fake backend - fakeBackendProvider - ], - bootstrap: [AppComponent] -}) -export class AppModule { } diff --git a/src/app/game-grid/game-grid.component.css b/src/app/game-grid/game-grid.component.css deleted file mode 100644 index 1837754..0000000 --- a/src/app/game-grid/game-grid.component.css +++ /dev/null @@ -1,62 +0,0 @@ -.parentContainer{ - width: 100%; - height: 100%; -} - -.card{ - min-height: 280px; - width: 250px; - margin: 5px; -} - -.flex-container { - display: flex; - height: auto; - flex-flow: row wrap; - align-items: center; - justify-content: center; -} - -.example-fill-remaining-space { - /* This fills the remaining space, by using flexbox. - Every toolbar row uses a flexbox row layout. */ - flex: 1 1 auto; -} - -.menuButton{ - margin-right: 10px; -} - -.userButton{ - -} -.userButton img{ - width:35px; - height:35px; - border-radius: 50%; -} - -.gameSearchInput{ - margin-left:25px; - border: 0; - border-radius: 4px; - color: #555; - font-size: 16px; - font-weight: 600; - height: 50%; - line-height: 20px; - outline: none; - padding: 0 0 0 15px; - width: 80%; -} - -.app-toolbar { - position: sticky; - position: -webkit-sticky; /* For macOS/iOS Safari */ - top: 0; /* Sets the sticky toolbar to be on top */ - z-index: 1000; /* Ensure that your app's content doesn't overlap the toolbar */ -} - -.editCardButton{ - float: right; -} \ No newline at end of file diff --git a/src/app/game-grid/game-grid.component.html b/src/app/game-grid/game-grid.component.html deleted file mode 100644 index 4634f73..0000000 --- a/src/app/game-grid/game-grid.component.html +++ /dev/null @@ -1,51 +0,0 @@ - - videogame_asset - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

{{game.Title}}

-

{{game.System}}

- - edit - -
-
- -
- - - - -
\ No newline at end of file diff --git a/src/app/game-grid/game-grid.component.spec.ts b/src/app/game-grid/game-grid.component.spec.ts deleted file mode 100644 index f68d92f..0000000 --- a/src/app/game-grid/game-grid.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { GameGridComponent } from './game-grid.component'; - -describe('GameGridComponent', () => { - let component: GameGridComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ GameGridComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(GameGridComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/game-grid/game-grid.component.ts b/src/app/game-grid/game-grid.component.ts deleted file mode 100644 index c7947d0..0000000 --- a/src/app/game-grid/game-grid.component.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { HttpClient } from '@angular/common/http'; -import { ActivatedRoute, Router } from '@angular/router'; - -import { GamesService } from '../games.service'; -import {PageEvent} from '@angular/material'; - - - - -@Component({ - selector: 'app-game-grid', - templateUrl: './game-grid.component.html', - styleUrls: ['./game-grid.component.css'] -}) -export class GameGridComponent implements OnInit { - - gameListSubscription; - rawGamesContent; - gamesData; - - queryFilters = ""; - querryPage = 1; - queryOrder = "Title"; - - length = 100; - pageSize = 10; - pageSizeOptions = [5, 10, 25, 50, 100]; - - currentUser; - - constructor( - private route: ActivatedRoute, - private gamesService: GamesService, - private router: Router - ){ - - } - - ngOnInit() { - this.currentUser = JSON.parse(localStorage.getItem('currentUser')); - this.getGamesList(); - } - - - getGamesList(): any{ - - this.gameListSubscription = this.gamesService.getGames( this.queryFilters, this.querryPage, this.queryOrder, this.pageSize, this.currentUser.id ).subscribe( data => { - - data.games.forEach(function(element) { - if( element.Art.length == 0 ){ - element.Art = "http://lazypug.net/globalAssets/images/temp1.png"; - }else{ - element.Art = "http://pugludos.com/community/uploads/ckoch/"+ element.Art +".png"; - } - }); - - - - - this.length = data["_results"]; - this.gamesData = data.games; - }); - } - - onKey( event: any ){ - if( event.target.value != "" ){ - this.gamesService.searchGamesByText( event.target.value, this.currentUser.token, this.currentUser.id ).subscribe( data => { - data.games.forEach(function(element) { - if( element.Art.length == 0 ){ - element.Art = "http://lazypug.net/globalAssets/images/temp1.png"; - }else{ - element.Art = "http://pugludos.com/community/uploads/ckoch/"+ element.Art +".png"; - } - }); - this.gamesData = data.games; - }); - }else{ - this.getGamesList(); - } - } - - - isEmptyObject(obj) { - return (obj != undefined); - } - - isNotEmptyObject(obj) { - return (obj == undefined); - } - - // MatPaginator Output - pageEvent: PageEvent; - - paginatorChange(event){ - if( event.pageSize != this.pageSize ){ - this.pageSize = event.pageSize; - this.getGamesList(); - } - if( event.pageIndex != (this.querryPage -1) ){ - this.querryPage = event.pageIndex+1; - this.getGamesList(); - } - } - - logOut(){ - localStorage.removeItem('currentUser'); - this.router.navigate( ['login'] ); - } - - - -} diff --git a/src/app/game.ts b/src/app/game.ts deleted file mode 100644 index a337e6d..0000000 --- a/src/app/game.ts +++ /dev/null @@ -1,29 +0,0 @@ -export class Game { - Id: number; - Title: string; - Art: string; - Description: string; - Developer: string; - Dumped: number; - Finished: number; - Genre: string; - Own: number; - Played: number; - Publisher: string; - System: string; - Year: string; - constructor() { - this.Title = ""; - this.Art = ""; - this.Description = ""; - this.Developer = ""; - this.Dumped = 0; - this.Finished = 0; - this.Genre = ""; - this.Own = 0; - this.Played = 0; - this.Publisher = ""; - this.System = ""; - this.Year = ""; - } -} \ No newline at end of file diff --git a/src/app/games.service.spec.ts b/src/app/games.service.spec.ts deleted file mode 100644 index 1d8de92..0000000 --- a/src/app/games.service.spec.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { TestBed, inject } from '@angular/core/testing'; - -import { GamesService } from './games.service'; - -describe('GamesService', () => { - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [GamesService] - }); - }); - - it('should be created', inject([GamesService], (service: GamesService) => { - expect(service).toBeTruthy(); - })); -}); diff --git a/src/app/games.service.ts b/src/app/games.service.ts deleted file mode 100644 index 93f0448..0000000 --- a/src/app/games.service.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { Injectable } from '@angular/core'; -import { HttpClient, HttpHeaders } from '@angular/common/http'; -import { Observable } from 'rxjs/Observable'; -import 'rxjs/add/operator/map'; - -const httpOptions = { - headers: new HttpHeaders({ 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }) - //headers: new HttpHeaders({ 'Content-Type': 'application/json' }) -} - -const httpOptionsImage = { - headers: new HttpHeaders({ 'Content-Type': 'multipart/form-data; charset=UTF-8' }) - //headers: new HttpHeaders({ 'Content-Type': 'application/json' }) -} - -const httpOptionsPut = { - headers: new HttpHeaders({ 'Content-Type': 'application/json' }) -} - -@Injectable() -export class GamesService { - - APIURL = "http://pugludos.com/interfaceServices/api.php"; - - constructor( - private http: HttpClient - ){ } - - searchGamesByText( searchText, userToken, userId ): Observable { - return this.http.get( this.APIURL + "/games?filter[]=Title,cs," + searchText + "&filter[]=userId,eq,"+ userId + "&transform=1&transform=1&token=" + userToken ) - .map(res => { - return( - res - ); - }); - } - - getGames( queryFilters, querryPage, queryOrder, queryRecordMax, userId ): Observable { - let currentUser = JSON.parse(localStorage.getItem('currentUser')); - return this.http.get( this.APIURL + "/games?filter[]=" + queryFilters + "&filter[]=userId,eq,"+ userId + " &page="+ querryPage + "," + queryRecordMax +"&order="+ queryOrder +"&transform=1&token=" + currentUser.token ) - .map(res => { - return( - res - ); - }); - } - - postGame( gameData, userToken ): Observable { - return this.http.post( this.APIURL + "/games?token=" + userToken, gameData, httpOptions ) - .map(res => { - return( - res - ); - }); - } - - getGameById( gameId, userToken, userId ): Observable { - return this.http.get( this.APIURL + "/games/" + gameId + "?filter[]=userId,eq,"+ userId + "&token=" + userToken) - .map(res => { - return( - res - ); - }); - } - - putGameById( gameData, gameId, userToken ): Observable { - return this.http.put( this.APIURL + "/games/" + gameId + "?token=" + userToken, gameData ) - .map(res => { - return( - res - ); - }); - } - - deleteGameById( gameId, userToken ): Observable { - return this.http.delete( this.APIURL + "/games/" + gameId + "?token=" + userToken ) - .map(res => { - return( - res - ); - }); - } - - postFile(fileToUpload: File): Observable { - const endpoint = 'http://pugludos.com/imageUpload.php'; - //const endpoint = 'http://localhost/ludosdata/imageUpload.php'; - const formData: FormData = new FormData(); - - formData.append('fileToUpload', fileToUpload, fileToUpload.name); - - return this.http.post(endpoint, formData) - .map(res => { - return( - res - ); - }); - } - - - - - -} diff --git a/src/app/login/login.component.css b/src/app/login/login.component.css deleted file mode 100644 index e69de29..0000000 diff --git a/src/app/login/login.component.html b/src/app/login/login.component.html deleted file mode 100644 index 5191b17..0000000 --- a/src/app/login/login.component.html +++ /dev/null @@ -1,32 +0,0 @@ -
-
- - - - -

Login

-
-
- - - - - - - - - - - - -
-
-
\ No newline at end of file diff --git a/src/app/login/login.component.spec.ts b/src/app/login/login.component.spec.ts deleted file mode 100644 index d6d85a8..0000000 --- a/src/app/login/login.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { LoginComponent } from './login.component'; - -describe('LoginComponent', () => { - let component: LoginComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ LoginComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(LoginComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/login/login.component.ts b/src/app/login/login.component.ts deleted file mode 100644 index c69ca36..0000000 --- a/src/app/login/login.component.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { ActivatedRoute, Router } from '@angular/router'; -import { FormBuilder, FormGroup, FormControl, Validators } from '@angular/forms'; -import { RegistrationService } from '../registration.service'; - -import { AlertService, AuthenticationService } from '../_services/index'; - - -@Component({ - selector: 'app-login', - templateUrl: './login.component.html', - styleUrls: ['./login.component.css'] -}) -export class LoginComponent implements OnInit { - - form: any; - - model: any = {}; - loading = false; - returnUrl: string; - - - constructor( - private route: ActivatedRoute, - private router: Router, - private registrationService: RegistrationService, - - private authenticationService: AuthenticationService, - private alertService: AlertService - ) { } - - ngOnInit() { - - // reset login status - this.authenticationService.logout(); - - // get return url from route parameters or default to '/' - this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/'; - - this.buildForm(); - } - - buildForm(){ - const formGroup = {}; - - formGroup["userName"] = new FormControl( "", this.mapValidators({ required: true }) ); - formGroup["password"] = new FormControl( "", this.mapValidators({ required: true }) ); - - this.form = new FormGroup(formGroup); - } - - private mapValidators(validators) { - const formValidators = []; - - for( var key in validators ){ - if( key == "required" ) { - if( validators.required ){ - formValidators.push( Validators.required ); - } - } - } - return formValidators; - } - - login( form ) { - this.loading = true; - this.authenticationService.login( form ) - .subscribe( - data => { - this.router.navigate(["game-grid"]); - }, - error => { - //console.log(error) - this.alertService.error( "Bad username or password" ); - this.loading = false; - }); - } - -} diff --git a/src/app/register/register.component.css b/src/app/register/register.component.css deleted file mode 100644 index e69de29..0000000 diff --git a/src/app/register/register.component.html b/src/app/register/register.component.html deleted file mode 100644 index d996c76..0000000 --- a/src/app/register/register.component.html +++ /dev/null @@ -1,57 +0,0 @@ -
-
- - - -

Register

-
-
- - - - - - - - - - - - - - - - - - - - - - - - -
- -
- -
\ No newline at end of file diff --git a/src/app/register/register.component.spec.ts b/src/app/register/register.component.spec.ts deleted file mode 100644 index 6c19551..0000000 --- a/src/app/register/register.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { RegisterComponent } from './register.component'; - -describe('RegisterComponent', () => { - let component: RegisterComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ RegisterComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(RegisterComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/register/register.component.ts b/src/app/register/register.component.ts deleted file mode 100644 index ef59f95..0000000 --- a/src/app/register/register.component.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { ActivatedRoute, Router } from '@angular/router'; -import { FormBuilder, FormGroup, FormControl, Validators, AbstractControl } from '@angular/forms'; - -import { RegistrationService } from '../registration.service'; -import { UsernameValidator } from '../validators/username.validator' -import { EmailValidator } from '../validators/email.validator' - - -@Component({ - selector: 'app-register', - templateUrl: './register.component.html', - styleUrls: ['./register.component.css'] -}) -export class RegisterComponent implements OnInit { - - form: any; - loading = false; - - constructor( - private registrationService: RegistrationService, - private usernameValidator: UsernameValidator, - private emailValidator: EmailValidator, - public formBuilder: FormBuilder, - public router : Router - - ){ } - - ngOnInit() { - - this.buildForm(); - } - - buildForm(){ - - this.form = this.formBuilder.group({ - firstName: ['', Validators.compose([ - Validators.maxLength(50), - Validators.minLength(3), - Validators.required - ])], - lastName: ['', Validators.compose([ - Validators.maxLength(50), - Validators.minLength(3), - Validators.required - ])], - email: ['', Validators.compose([ - Validators.maxLength(100), - Validators.email, - Validators.required - ]), - this.emailValidator.checkEmail.bind(this.emailValidator) - ], - userName: ['', Validators.compose([ - Validators.maxLength(25), - Validators.minLength(5), - Validators.required - ]), - this.usernameValidator.checkUsername.bind(this.usernameValidator) - ], - password: ['', Validators.compose([ - Validators.maxLength(25), - Validators.minLength(5), - Validators.required - ])] - }); - } - - onSubmit( form ){ - this.registrationService.createNewUser( form ).subscribe( data => { - this.router.navigateByUrl("/login"); - }); - - } - - -} \ No newline at end of file diff --git a/src/app/registration.service.spec.ts b/src/app/registration.service.spec.ts deleted file mode 100644 index ec88bc1..0000000 --- a/src/app/registration.service.spec.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { TestBed, inject } from '@angular/core/testing'; - -import { RegistrationService } from './registration.service'; - -describe('RegistrationService', () => { - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [RegistrationService] - }); - }); - - it('should be created', inject([RegistrationService], (service: RegistrationService) => { - expect(service).toBeTruthy(); - })); -}); diff --git a/src/app/registration.service.ts b/src/app/registration.service.ts deleted file mode 100644 index 22a1d2a..0000000 --- a/src/app/registration.service.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { Injectable } from '@angular/core'; - -import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; -import { Observable } from 'rxjs/Observable'; -import 'rxjs/add/operator/map'; - -const httpOptions = { - headers: new HttpHeaders({ 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }) -} - -@Injectable() -export class RegistrationService { - - APIURL = "http://192.241.155.78/api.php"; - registrationUrl = "http://192.241.155.78/interfaceServices/registrationInterface.php/users/"; - loginUrl = "http://192.241.155.78/interfaceServices/loginInterface.php"; - params; - - constructor( - private http: HttpClient - ){ } - - validateUserName( userName ): Observable { - return this.http.get( this.APIURL + "/users?filter=userName,eq," + userName + "&transform=1" ) - .map(res => { - return( - res - ); - }); - } - - validateEmail( email ): Observable { - return this.http.get( this.APIURL + "/users?filter=email,eq," + email + "&transform=1" ) - .map(res => { - return( - res - ); - }); - } - - createNewUser( userData ): Observable{ - - this.params = new HttpParams({ - fromObject: userData - }); - - return this.http.post( this.registrationUrl, this.params, httpOptions ) - .map(res => { - return( - res - ); - }); - } - - loginUser( userData ): Observable{ - - this.params = new HttpParams({ - fromObject: userData - }); - - return this.http.post( this.loginUrl, this.params, httpOptions ) - .map(res => { - return( - res - ); - }); - } - - -} diff --git a/src/app/user/user.component.css b/src/app/user/user.component.css deleted file mode 100644 index 6dacbf8..0000000 --- a/src/app/user/user.component.css +++ /dev/null @@ -1,51 +0,0 @@ - -.button-center{ - margin: 2px auto; - text-align:center; - display: block; -} - -.example-fill-remaining-space { - /* This fills the remaining space, by using flexbox. - Every toolbar row uses a flexbox row layout. */ - flex: 1 1 auto; -} - -.menuButton{ - margin-right: 10px; -} - -.userButton{ - -} -.userButton img{ - width:35px; - height:35px; - border-radius: 50%; -} - -.gameSearchInput{ - margin-left:25px; - border: 0; - border-radius: 4px; - color: #555; - font-size: 16px; - font-weight: 600; - height: 50%; - line-height: 20px; - outline: none; - padding: 0 0 0 15px; - width: 80%; -} - -.app-toolbar { - position: sticky; - position: -webkit-sticky; /* For macOS/iOS Safari */ - top: 0; /* Sets the sticky toolbar to be on top */ - z-index: 1000; /* Ensure that your app's content doesn't overlap the toolbar */ -} - -.card-img-top{ - width:250px; - height:auto; -} \ No newline at end of file diff --git a/src/app/user/user.component.html b/src/app/user/user.component.html deleted file mode 100644 index 20ac5f8..0000000 --- a/src/app/user/user.component.html +++ /dev/null @@ -1,61 +0,0 @@ - - videogame_asset - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- - - - - -
-
- -
- - -
-
-
-
- -
-
-
-
-
-
- -
- -
-
\ No newline at end of file diff --git a/src/app/user/user.component.spec.ts b/src/app/user/user.component.spec.ts deleted file mode 100644 index dd3b1d7..0000000 --- a/src/app/user/user.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { UserComponent } from './user.component'; - -describe('UserComponent', () => { - let component: UserComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ UserComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(UserComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/user/user.component.ts b/src/app/user/user.component.ts deleted file mode 100644 index bceae0d..0000000 --- a/src/app/user/user.component.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { Router, ActivatedRoute } from '@angular/router'; - -@Component({ - selector: 'app-user', - templateUrl: './user.component.html', - styleUrls: ['./user.component.css'] -}) -export class UserComponent implements OnInit { - - currentUser; - - constructor( - private route: ActivatedRoute, - private router: Router - ){ - } - - ngOnInit(){ - this.currentUser = JSON.parse(localStorage.getItem('currentUser')); - buildForm(); - } - - buildForm(){ - - const formGroup = {}; - - formGroup["firstName"] = new FormControl( this.currentUser.firstName, this.mapValidators({ required: true }) ); - formGroup["lastName"] = new FormControl( this.currentUser.lastName, this.mapValidators({ required: true }) ); - formGroup["email"] = new FormControl( this.currentUser.email, this.mapValidators({ required: true }) ); - formGroup["art"] = new FormControl( this.currentUser.art, this.mapValidators({ required: false }) ); - - - this.form = new FormGroup(formGroup); - - if( this.currentUser.art.length != 0 ){ - //this.imageSample = "http://pugludos.com/community/uploads/"+this.currentUser.art+"/" + this.userData.Art + ".png"; - }else{ - - } - - } - - - isEmptyObject(obj) { - return (obj != undefined); - } - -} \ No newline at end of file diff --git a/src/app/validators/email.validator.ts b/src/app/validators/email.validator.ts deleted file mode 100644 index 13f4f0b..0000000 --- a/src/app/validators/email.validator.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Injectable } from '@angular/core'; -import { FormControl } from '@angular/forms'; -import { RegistrationService } from '../registration.service'; - -@Injectable() -export class EmailValidator { - - debouncer: any; - - constructor( - private registrationService: RegistrationService - ){ } - - checkEmail( control: FormControl ): any{ - clearTimeout(this.debouncer); - - return new Promise(resolve => { - this.debouncer = setTimeout(() => { - this.registrationService.validateEmail(control.value).subscribe((res) => { - if(res.users.length === 0){ - resolve(null); - }else{ - resolve({'emailInUse': true}); - } - }); - }, 1000); - }); - } -} \ No newline at end of file diff --git a/src/app/validators/username.validator.ts b/src/app/validators/username.validator.ts deleted file mode 100644 index e3f6756..0000000 --- a/src/app/validators/username.validator.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Injectable } from '@angular/core'; -import { FormControl } from '@angular/forms'; -import { RegistrationService } from '../registration.service'; - -@Injectable() -export class UsernameValidator { - - debouncer: any; - - constructor( - private registrationService: RegistrationService - ){ } - - checkUsername( control: FormControl ): any{ - clearTimeout(this.debouncer); - - return new Promise(resolve => { - this.debouncer = setTimeout(() => { - this.registrationService.validateUserName(control.value).subscribe((res) => { - if(res.users.length === 0){ - resolve(null); - }else{ - resolve({'usernameInUse': true}); - } - - }); - }, 1000); - }); - } -} \ No newline at end of file diff --git a/src/app/view-card/view-card.component.css b/src/app/view-card/view-card.component.css deleted file mode 100644 index 501005d..0000000 --- a/src/app/view-card/view-card.component.css +++ /dev/null @@ -1,95 +0,0 @@ -#gameImageHeaderContainer{ - overflow: hidden; -} - -.lrCard{ - width:80%; -} - -.lrContainer{ - margin-top: 1%; -} - -.card{ - height: auto; - width: 250px; - margin: 5px; -} - -.card-2x{ - width:525px; -} - -.flex-container { - display: flex; - height: auto; - flex-flow: row wrap; - align-items: center; - justify-content: center; -} - -.button-center{ - margin: 2px auto; - text-align:center; - display: block; -} - -.example-fill-remaining-space { - /* This fills the remaining space, by using flexbox. - Every toolbar row uses a flexbox row layout. */ - flex: 1 1 auto; -} - -.menuButton{ - margin-right: 10px; -} - -.userButton{ - -} -.userButton img{ - width:35px; - height:35px; - border-radius: 50%; -} - -.gameSearchInput{ - margin-left:25px; - border: 0; - border-radius: 4px; - color: #555; - font-size: 16px; - font-weight: 600; - height: 50%; - line-height: 20px; - outline: none; - padding: 0 0 0 15px; - width: 80%; -} - -.app-toolbar { - position: sticky; - position: -webkit-sticky; /* For macOS/iOS Safari */ - top: 0; /* Sets the sticky toolbar to be on top */ - z-index: 1000; /* Ensure that your app's content doesn't overlap the toolbar */ -} - -.card-img-top{ - width:250px; - height:auto; -} - - -#overlay { - position: fixed; /* Sit on top of the page content */ - /*display: none; / Hidden by default */ - width: 100%; /* Full width (cover the whole page) */ - height: 100%; /* Full height (cover the whole page) */ - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: rgba(0,0,0,0.5); /* Black background with opacity */ - z-index: 2; /* Specify a stack order in case you're using a different order for other elements */ - cursor: pointer; /* Add a pointer on hover */ -} \ No newline at end of file diff --git a/src/app/view-card/view-card.component.html b/src/app/view-card/view-card.component.html deleted file mode 100644 index 4c0dc97..0000000 --- a/src/app/view-card/view-card.component.html +++ /dev/null @@ -1,346 +0,0 @@ - - videogame_asset - - - - - - - - - - - - - - - - - - - - -
- - - -
- -
- - - -
-
-
-
- - - - - - -
-
- -
- - - - - - - -
- -
-
- -
- -
-
-
-
- -
-
-
- - -
- This field is required -
-
-
-
-
-
- -
- -
-
-
- - - - -
-
-
-
- -
- -
-
-
- - - - -
-
-
-
- -
- -
-
-
- - - - -
-
-
-
- -
- -
-
-
- - - - -
-
-
-
-
-
-
- -
-
-
- - - - sports - platformer - lightgun - fighter - rpg - strategy - adventure - racing - fps - action - simulation - card - - - -
-
-
-
- -
- -
-
-
- - - - SNES - N64 - PS1 - PS2 - GB - GBA - DS - NES - GC - PSP - 360 - WII - - - -
-
-
-
- -
- -
-
-
- - - - No - Yes - - - -
-
-
-
- -
- -
-
-
- - - - No - Yes - - - -
-
-
-
- -
- -
-
-
- - - - No - Yes - - - -
-
-
-
- -
- -
-
-
- - - - No - Yes - - - -
-
-
-
- - -
-
-
-
- -
- -
- - - -
\ No newline at end of file diff --git a/src/app/view-card/view-card.component.spec.ts b/src/app/view-card/view-card.component.spec.ts deleted file mode 100644 index 01ff2f4..0000000 --- a/src/app/view-card/view-card.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ViewCardComponent } from './view-card.component'; - -describe('ViewCardComponent', () => { - let component: ViewCardComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ ViewCardComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(ViewCardComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/view-card/view-card.component.ts b/src/app/view-card/view-card.component.ts deleted file mode 100644 index c0fad78..0000000 --- a/src/app/view-card/view-card.component.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; -import { Router } from "@angular/router"; -import { FormBuilder, FormGroup, FormControl, Validators } from '@angular/forms'; - -import { GamesService } from '../games.service'; -import { Game } from '../game'; - -@Component({ - selector: 'app-view-card', - templateUrl: './view-card.component.html', - styleUrls: ['./view-card.component.css'] -}) -export class ViewCardComponent implements OnInit { - - gid: any; - gameSubscription: any; - gameData: Game; - form: any; - fileToUpload: File = null; - imageSample = "http://lazypug.net/globalAssets/images/temp1.png"; - overlay; - currentUser; - notNewGame; - - - constructor( - private route: ActivatedRoute, - private router: Router, - private gamesService: GamesService - ){} - - ngOnInit(){ - this.gid = this.route.snapshot.paramMap.get('gid'); - this.currentUser = JSON.parse(localStorage.getItem('currentUser')); - this.overlay = false; - if( this.gid != null ){ - this.gameSubscription = this.gamesService.getGameById( this.gid, this.currentUser.token, this.currentUser.id ).subscribe( data => { - this.notNewGame = true; - this.gameData = data; - this.buildForm(); - }); - }else{ - this.notNewGame = false; - this.gameData = new Game(); - this.buildForm(); - } - } - - buildForm(){ - - const formGroup = {}; - - formGroup["Title"] = new FormControl( this.gameData.Title, this.mapValidators({ required: true }) ); - formGroup["Art"] = new FormControl( this.gameData.Art, this.mapValidators({ required: false }) ); - formGroup["Description"] = new FormControl( this.gameData.Description, this.mapValidators({ required: false }) ); - formGroup["Developer"] = new FormControl( this.gameData.Developer, this.mapValidators({ required: false }) ); - formGroup["Dumped"] = new FormControl( this.gameData.Dumped, this.mapValidators({ required: false }) ); - formGroup["Finished"] = new FormControl( this.gameData.Finished, this.mapValidators({ required: false }) ); - formGroup["Genre"] = new FormControl( this.gameData.Genre, this.mapValidators({ required: false }) ); - formGroup["Own"] = new FormControl( this.gameData.Own, this.mapValidators({ required: false }) ); - formGroup["Played"] = new FormControl( this.gameData.Played, this.mapValidators({ required: false }) ); - formGroup["Publisher"] = new FormControl( this.gameData.Publisher, this.mapValidators({ required: false }) ); - formGroup["System"] = new FormControl( this.gameData.System, this.mapValidators({ required: false }) ); - formGroup["Year"] = new FormControl( this.gameData.Year, this.mapValidators({ required: false }) ); - - this.form = new FormGroup(formGroup); - - if( this.gameData.Art.length != 0 ){ - this.imageSample = "http://pugludos.com/community/uploads/ckoch/" + this.gameData.Art + ".png"; - } - - } - - private mapValidators(validators) { - const formValidators = []; - - for( var key in validators ){ - if( key == "required" ) { - if( validators.required ){ - formValidators.push( Validators.required ); - } - } - } - return formValidators; - } - - onSubmit( form ){ - if( this.gid == null ){ - form["userId"] = this.currentUser.id; - this.gameSubscription = this.gamesService.postGame( form, this.currentUser.token ).subscribe( data => { - this.router.navigateByUrl("/game-grid"); - }); - }else{ - console.log( form ); - this.gameSubscription = this.gamesService.putGameById( form, this.gid, this.currentUser.token ).subscribe( data => { - this.router.navigateByUrl("/game-grid"); - }); - } - - } - - handleFileInput( $event ) { - this.overlay = true; - if ($event.target.files && $event.target.files[0]) { - var reader = new FileReader(); - reader.onload = (event:any) => { - this.imageSample = event.target.result; - }; - reader.readAsDataURL( $event.target.files[0] ); - } - this.fileToUpload = $event.target.files[0]; - this.uploadFileToActivity(); - } - - uploadFileToActivity() { - this.gamesService.postFile( this.fileToUpload ).subscribe(data => { - this.form.value.Art = data.imageName; - this.overlay = false; - }); - - } - - removeGame(){ - this.gameSubscription = this.gamesService.deleteGameById( this.gid, this.currentUser.token ).subscribe( data => { - this.router.navigateByUrl("/game-grid"); - }); - } - - isEmptyObject(obj) { - return (obj != undefined); - } - - logOut(){ - - } - - - -} diff --git a/src/assets/.gitkeep b/src/assets/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts deleted file mode 100644 index 3612073..0000000 --- a/src/environments/environment.prod.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const environment = { - production: true -}; diff --git a/src/environments/environment.ts b/src/environments/environment.ts deleted file mode 100644 index b7f639a..0000000 --- a/src/environments/environment.ts +++ /dev/null @@ -1,8 +0,0 @@ -// The file contents for the current environment will overwrite these during build. -// The build system defaults to the dev environment which uses `environment.ts`, but if you do -// `ng build --env=prod` then `environment.prod.ts` will be used instead. -// The list of which env maps to which file can be found in `.angular-cli.json`. - -export const environment = { - production: false -}; diff --git a/src/favicon.ico b/src/favicon.ico deleted file mode 100644 index 8081c7c..0000000 Binary files a/src/favicon.ico and /dev/null differ diff --git a/src/index.html b/src/index.html deleted file mode 100644 index 90bb386..0000000 --- a/src/index.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - LudosData - - - - - - - - - - diff --git a/src/main.ts b/src/main.ts deleted file mode 100644 index 91ec6da..0000000 --- a/src/main.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { enableProdMode } from '@angular/core'; -import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; - -import { AppModule } from './app/app.module'; -import { environment } from './environments/environment'; - -if (environment.production) { - enableProdMode(); -} - -platformBrowserDynamic().bootstrapModule(AppModule) - .catch(err => console.log(err)); diff --git a/src/polyfills.ts b/src/polyfills.ts deleted file mode 100644 index af84770..0000000 --- a/src/polyfills.ts +++ /dev/null @@ -1,79 +0,0 @@ -/** - * This file includes polyfills needed by Angular and is loaded before the app. - * You can add your own extra polyfills to this file. - * - * This file is divided into 2 sections: - * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. - * 2. Application imports. Files imported after ZoneJS that should be loaded before your main - * file. - * - * The current setup is for so-called "evergreen" browsers; the last versions of browsers that - * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), - * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. - * - * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html - */ - -/*************************************************************************************************** - * BROWSER POLYFILLS - */ - -/** IE9, IE10 and IE11 requires all of the following polyfills. **/ -// import 'core-js/es6/symbol'; -// import 'core-js/es6/object'; -// import 'core-js/es6/function'; -// import 'core-js/es6/parse-int'; -// import 'core-js/es6/parse-float'; -// import 'core-js/es6/number'; -// import 'core-js/es6/math'; -// import 'core-js/es6/string'; -// import 'core-js/es6/date'; -// import 'core-js/es6/array'; -// import 'core-js/es6/regexp'; -// import 'core-js/es6/map'; -// import 'core-js/es6/weak-map'; -// import 'core-js/es6/set'; - -/** IE10 and IE11 requires the following for NgClass support on SVG elements */ -// import 'classlist.js'; // Run `npm install --save classlist.js`. - -/** IE10 and IE11 requires the following for the Reflect API. */ -// import 'core-js/es6/reflect'; - - -/** Evergreen browsers require these. **/ -// Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove. -import 'core-js/es7/reflect'; - - -/** - * Required to support Web Animations `@angular/platform-browser/animations`. - * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation - **/ -// import 'web-animations-js'; // Run `npm install --save web-animations-js`. - -/** - * By default, zone.js will patch all possible macroTask and DomEvents - * user can disable parts of macroTask/DomEvents patch by setting following flags - */ - - // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame - // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick - // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames - - /* - * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js - * with the following flag, it will bypass `zone.js` patch for IE/Edge - */ -// (window as any).__Zone_enable_cross_context_check = true; - -/*************************************************************************************************** - * Zone JS is required by default for Angular itself. - */ -import 'zone.js/dist/zone'; // Included with Angular CLI. - - - -/*************************************************************************************************** - * APPLICATION IMPORTS - */ diff --git a/src/styles.css b/src/styles.css deleted file mode 100644 index 1102ac8..0000000 --- a/src/styles.css +++ /dev/null @@ -1,18 +0,0 @@ -/* You can add global styles to this file, and also import other style files */ -@import "~@angular/material/prebuilt-themes/indigo-pink.css"; - -.lrCard{ - min-width: 300px; - width:20%; - margin:0 auto; -} - -.fullWidth{ - width:100%; -} - -.lrContainer{ - margin-top: 10%; - justify-content: center; - align-items: center; -} \ No newline at end of file diff --git a/src/test.ts b/src/test.ts deleted file mode 100644 index 1631789..0000000 --- a/src/test.ts +++ /dev/null @@ -1,20 +0,0 @@ -// This file is required by karma.conf.js and loads recursively all the .spec and framework files - -import 'zone.js/dist/zone-testing'; -import { getTestBed } from '@angular/core/testing'; -import { - BrowserDynamicTestingModule, - platformBrowserDynamicTesting -} from '@angular/platform-browser-dynamic/testing'; - -declare const require: any; - -// First, initialize the Angular testing environment. -getTestBed().initTestEnvironment( - BrowserDynamicTestingModule, - platformBrowserDynamicTesting() -); -// Then we find all the tests. -const context = require.context('./', true, /\.spec\.ts$/); -// And load the modules. -context.keys().map(context); diff --git a/src/tsconfig.app.json b/src/tsconfig.app.json deleted file mode 100644 index 39ba8db..0000000 --- a/src/tsconfig.app.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "outDir": "../out-tsc/app", - "baseUrl": "./", - "module": "es2015", - "types": [] - }, - "exclude": [ - "test.ts", - "**/*.spec.ts" - ] -} diff --git a/src/tsconfig.spec.json b/src/tsconfig.spec.json deleted file mode 100644 index ac22a29..0000000 --- a/src/tsconfig.spec.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "outDir": "../out-tsc/spec", - "baseUrl": "./", - "module": "commonjs", - "types": [ - "jasmine", - "node" - ] - }, - "files": [ - "test.ts" - ], - "include": [ - "**/*.spec.ts", - "**/*.d.ts" - ] -} diff --git a/src/typings.d.ts b/src/typings.d.ts deleted file mode 100644 index ef5c7bd..0000000 --- a/src/typings.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -/* SystemJS module definition */ -declare var module: NodeModule; -interface NodeModule { - id: string; -} diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index a6c016b..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "compileOnSave": false, - "compilerOptions": { - "outDir": "./dist/out-tsc", - "sourceMap": true, - "declaration": false, - "moduleResolution": "node", - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "target": "es5", - "typeRoots": [ - "node_modules/@types" - ], - "lib": [ - "es2017", - "dom" - ] - } -} diff --git a/tslint.json b/tslint.json deleted file mode 100644 index 9963d6c..0000000 --- a/tslint.json +++ /dev/null @@ -1,143 +0,0 @@ -{ - "rulesDirectory": [ - "node_modules/codelyzer" - ], - "rules": { - "arrow-return-shorthand": true, - "callable-types": true, - "class-name": true, - "comment-format": [ - true, - "check-space" - ], - "curly": true, - "deprecation": { - "severity": "warn" - }, - "eofline": true, - "forin": true, - "import-blacklist": [ - true, - "rxjs", - "rxjs/Rx" - ], - "import-spacing": true, - "indent": [ - true, - "spaces" - ], - "interface-over-type-literal": true, - "label-position": true, - "max-line-length": [ - true, - 140 - ], - "member-access": false, - "member-ordering": [ - true, - { - "order": [ - "static-field", - "instance-field", - "static-method", - "instance-method" - ] - } - ], - "no-arg": true, - "no-bitwise": true, - "no-console": [ - true, - "debug", - "info", - "time", - "timeEnd", - "trace" - ], - "no-construct": true, - "no-debugger": true, - "no-duplicate-super": true, - "no-empty": false, - "no-empty-interface": true, - "no-eval": true, - "no-inferrable-types": [ - true, - "ignore-params" - ], - "no-misused-new": true, - "no-non-null-assertion": true, - "no-shadowed-variable": true, - "no-string-literal": false, - "no-string-throw": true, - "no-switch-case-fall-through": true, - "no-trailing-whitespace": true, - "no-unnecessary-initializer": true, - "no-unused-expression": true, - "no-use-before-declare": true, - "no-var-keyword": true, - "object-literal-sort-keys": false, - "one-line": [ - true, - "check-open-brace", - "check-catch", - "check-else", - "check-whitespace" - ], - "prefer-const": true, - "quotemark": [ - true, - "single" - ], - "radix": true, - "semicolon": [ - true, - "always" - ], - "triple-equals": [ - true, - "allow-null-check" - ], - "typedef-whitespace": [ - true, - { - "call-signature": "nospace", - "index-signature": "nospace", - "parameter": "nospace", - "property-declaration": "nospace", - "variable-declaration": "nospace" - } - ], - "unified-signatures": true, - "variable-name": false, - "whitespace": [ - true, - "check-branch", - "check-decl", - "check-operator", - "check-separator", - "check-type" - ], - "directive-selector": [ - true, - "attribute", - "app", - "camelCase" - ], - "component-selector": [ - true, - "element", - "app", - "kebab-case" - ], - "no-output-on-prefix": true, - "use-input-property-decorator": true, - "use-output-property-decorator": true, - "use-host-property-decorator": true, - "no-input-rename": true, - "no-output-rename": true, - "use-life-cycle-interface": true, - "use-pipe-transform-interface": true, - "component-class-suffix": true, - "directive-class-suffix": true - } -}