M brackend/openapi/openapi.json => brackend/openapi/openapi.json +168 -1
@@ 7,6 7,7 @@
"servers": [{"url": "https://api.bracket.monster"}],
"components": {
"schemas": {
+ "BracketType": {"type": "string", "enum": ["RoundRobin", "SingleElimination", "DoubleElimination", "Custom"]},
"UserIDOrMe": {
"oneOf": [{"type": "integer"}, {"type": "string", "enum": ["~me"]}]
}
@@ 22,6 23,7 @@
"paths": {
"/v1/brackets": {
"post": {
+ "operationId": "bracketsCreate",
"summary": "Create a bracket",
"requestBody": {
"content": {
@@ 32,7 34,8 @@
"properties": {
"name": {"type": "string", "example": "Tournament of Ultimate Destiny"},
"players": {"type": "array", "items": {"type": "string"}, "example": ["Liam", "Emma", "Olivia", "Mason"]},
- "type": {"type": "string", "enum": ["RoundRobin", "SingleElimination", "DoubleElimination", "Custom"]},
+ "type": {"$ref": "#/components/schemas/BracketType"},
+ "use_scores": {"type": "boolean"},
"matches": {
"description": "Used for Custom brackets. A list of matches.",
"type": "array",
@@ 112,7 115,101 @@
}
},
"/v1/brackets/{bracketID}": {
+ "get": {
+ "operationId": "bracketGet",
+ "summary": "Get bracket info",
+ "parameters": [
+ {
+ "name": "bracketID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": ["id", "name", "description_md", "players", "matches", "use_scores"],
+ "properties": {
+ "id": {"type": "string"},
+ "name": {"type": "string"},
+ "description_md": {"type": "string"},
+ "players": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": ["id", "name"],
+ "properties": {
+ "id": {"type": "integer"},
+ "name": {"type": "string"}
+ }
+ }
+ },
+ "matches": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": ["id", "players", "winner", "skipped", "column", "section"],
+ "properties": {
+ "id": {"type": "integer"},
+ "players": {
+ "type": "array",
+ "minItems": 2,
+ "maxItems": 2,
+ "items": {
+ "type": "object",
+ "required": ["player", "source", "score"],
+ "properties": {
+ "player": {
+ "nullable": true,
+ "type": "object",
+ "required": ["id"],
+ "properties": {
+ "id": {"type": "integer"}
+ }
+ },
+ "source": {
+ "nullable": true,
+ "type": "object",
+ "required": ["match", "outcome"],
+ "properties": {
+ "match": {"type": "integer"},
+ "outcome": {"type": "string", "enum": ["win", "loss"]}
+ }
+ },
+ "score": {
+ "nullable": true,
+ "type": "integer"
+ }
+ }
+ }
+ },
+ "winner": {
+ "nullable": true,
+ "type": "integer"
+ },
+ "column": {"type": "integer"},
+ "section": {"type": "integer"},
+ "skipped": {"type": "boolean"}
+ }
+ }
+ },
+ "use_scores": {"type": "boolean"}
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"patch": {
+ "operationId": "bracketEdit",
"summary": "Edit bracket info",
"parameters": [
{
@@ 148,6 245,7 @@
},
"/v1/brackets/{bracketID}/your": {
"get": {
+ "operationId": "bracketYourGet",
"summary": "Check your permissions on this bracket",
"parameters": [
{
@@ 180,6 278,7 @@
},
"/v1/brackets/{bracketID}/matches/{matchID}": {
"patch": {
+ "operationId": "bracketMatchEdit",
"summary": "Update a match",
"description": "Updates scores and/or winner for a bracket match. Requires admin access for the bracket",
"parameters": [
@@ 232,6 331,7 @@
},
"/v1/users": {
"post": {
+ "operationId": "usersCreate",
"summary": "Create a user. Can either be created anonymously or with a username and password.",
"requestBody": {
"content": {
@@ 274,6 374,7 @@
},
"/v1/users/{userID}": {
"get": {
+ "operationId": "userGet",
"summary": "Get user info.",
"parameters": [
{
@@ 305,6 406,72 @@
}
},
"security": [{"bearer": []}]
+ },
+ "patch": {
+ "operationId": "userEdit",
+ "summary": "Modify user info",
+ "parameters": [
+ {
+ "name": "userID",
+ "in": "path",
+ "required": true,
+ "schema": {"$ref": "#/components/schemas/UserIDOrMe"}
+ }
+ ],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "username": {"type": "string"},
+ "password": {"type": "string"},
+ "email": {"type": "string"}
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "204": {"description": "Successfully modified."}
+ }
+ }
+ },
+ "/v1/users/{userID}/brackets": {
+ "get": {
+ "operationId": "userBracketsList",
+ "summary": "List a user's brackets",
+ "description": "List brackets created by a user. Currently only allowed for that user",
+ "parameters": [
+ {
+ "name": "userID",
+ "in": "path",
+ "required": true,
+ "schema": {"$ref": "#/components/schemas/UserIDOrMe"}
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": ["id", "name", "type"],
+ "properties": {
+ "id": {"type": "string"},
+ "name": {"type": "string"},
+ "type": {"$ref": "#/components/schemas/BracketType"}
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "security": [{"bearer": []}]
}
}
}
M brackend/src/routes/v1/brackets.rs => brackend/src/routes/v1/brackets.rs +2 -4
@@ 78,10 78,8 @@ async fn route_brackets_get_fn(
id: i32,
players: (ResponseMatchPlayerInfo, ResponseMatchPlayerInfo),
winner: Option<i32>,
- #[serde(skip_serializing_if = "Option::is_none")]
- column: Option<i32>,
- #[serde(skip_serializing_if = "Option::is_none")]
- section: Option<i32>,
+ column: i32,
+ section: i32,
skipped: bool,
}
#[derive(serde_derive::Serialize, Debug)]
M rosebush/package-lock.json => rosebush/package-lock.json +647 -0
@@ 2,6 2,44 @@
"requires": true,
"lockfileVersion": 1,
"dependencies": {
+ "@apidevtools/json-schema-ref-parser": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-8.0.0.tgz",
+ "integrity": "sha512-n4YBtwQhdpLto1BaUCyAeflizmIbaloGShsPyRtFf5qdFJxfssj+GgLavczgKJFa3Bq+3St2CKcpRJdjtB4EBw==",
+ "dev": true,
+ "requires": {
+ "@jsdevtools/ono": "^7.1.0",
+ "call-me-maybe": "^1.0.1",
+ "js-yaml": "^3.13.1"
+ }
+ },
+ "@apidevtools/openapi-schemas": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.0.3.tgz",
+ "integrity": "sha512-QoPaxGXfgqgGpK1p21FJ400z56hV681a8DOcZt3J5z0WIHgFeaIZ4+6bX5ATqmOoCpRCsH4ITEwKaOyFMz7wOA==",
+ "dev": true
+ },
+ "@apidevtools/swagger-methods": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.1.tgz",
+ "integrity": "sha512-1Vlm18XYW6Yg7uHunroXeunWz5FShPFAdxBbPy8H6niB2Elz9QQsCoYHMbcc11EL1pTxaIr9HXz2An/mHXlX1Q==",
+ "dev": true
+ },
+ "@apidevtools/swagger-parser": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-9.0.1.tgz",
+ "integrity": "sha512-Irqybg4dQrcHhZcxJc/UM4vO7Ksoj1Id5e+K94XUOzllqX1n47HEA50EKiXTCQbykxuJ4cYGIivjx/MRSTC5OA==",
+ "dev": true,
+ "requires": {
+ "@apidevtools/json-schema-ref-parser": "^8.0.0",
+ "@apidevtools/openapi-schemas": "^2.0.2",
+ "@apidevtools/swagger-methods": "^3.0.0",
+ "@jsdevtools/ono": "^7.1.0",
+ "call-me-maybe": "^1.0.1",
+ "openapi-types": "^1.3.5",
+ "z-schema": "^4.2.2"
+ }
+ },
"@babel/code-frame": {
"version": "7.8.3",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz",
@@ 1103,6 1141,12 @@
}
}
},
+ "@jsdevtools/ono": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.2.tgz",
+ "integrity": "sha512-qS/a24RA5FEoiJS9wiv6Pwg2c/kiUo3IVUQcfeM9JvsR6pM8Yx+yl/6xWYLckZCT5jpLNhslgjiA8p/XcGyMRQ==",
+ "dev": true
+ },
"@nerd-coder/webpack-node-externals": {
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/@nerd-coder/webpack-node-externals/-/webpack-node-externals-1.8.2.tgz",
@@ 1816,6 1860,29 @@
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
},
+ "better-ajv-errors": {
+ "version": "0.6.7",
+ "resolved": "https://registry.npmjs.org/better-ajv-errors/-/better-ajv-errors-0.6.7.tgz",
+ "integrity": "sha512-PYgt/sCzR4aGpyNy5+ViSQ77ognMnWq7745zM+/flYO4/Yisdtp9wDQW2IKCyVYPUxQt3E/b5GBSwfhd1LPdlg==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.0.0",
+ "@babel/runtime": "^7.0.0",
+ "chalk": "^2.4.1",
+ "core-js": "^3.2.1",
+ "json-to-ast": "^2.0.3",
+ "jsonpointer": "^4.0.1",
+ "leven": "^3.1.0"
+ },
+ "dependencies": {
+ "core-js": {
+ "version": "3.6.5",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz",
+ "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==",
+ "dev": true
+ }
+ }
+ },
"big.js": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
@@ 1997,6 2064,12 @@
"unset-value": "^1.0.0"
}
},
+ "call-me-maybe": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz",
+ "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=",
+ "dev": true
+ },
"callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ 2263,6 2336,24 @@
"shallow-clone": "^3.0.0"
}
},
+ "co": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
+ "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=",
+ "dev": true
+ },
+ "code-error-fragment": {
+ "version": "0.0.230",
+ "resolved": "https://registry.npmjs.org/code-error-fragment/-/code-error-fragment-0.0.230.tgz",
+ "integrity": "sha512-cadkfKp6932H8UkhzE/gcUqhRMNf8jHzkAN7+5Myabswaghu4xABTgPHDCjW+dBAJxj/SpkTYokpzDqY4pCzQw==",
+ "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
+ },
"collection-visit": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
@@ 2781,6 2872,12 @@
"is-symbol": "^1.0.2"
}
},
+ "es6-promise": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz",
+ "integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=",
+ "dev": true
+ },
"escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
@@ 3208,6 3305,12 @@
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
"dev": true
},
+ "fast-safe-stringify": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz",
+ "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==",
+ "dev": true
+ },
"fbjs": {
"version": "0.8.17",
"resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz",
@@ 3595,6 3698,12 @@
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
"integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw=="
},
+ "grapheme-splitter": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz",
+ "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==",
+ "dev": true
+ },
"has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@@ 3737,6 3846,12 @@
"requires-port": "^1.0.0"
}
},
+ "http2-client": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/http2-client/-/http2-client-1.3.3.tgz",
+ "integrity": "sha512-nUxLymWQ9pzkzTmir24p2RtsgruLmhje7lH3hLX1IpwvyTg77fW+1brenPPP3USAR+rQ36p5sTA/x7sjCJVkAA==",
+ "dev": true
+ },
"https-browserify": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
@@ 4213,6 4328,16 @@
"integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
"dev": true
},
+ "json-to-ast": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/json-to-ast/-/json-to-ast-2.1.0.tgz",
+ "integrity": "sha512-W9Lq347r8tA1DfMvAGn9QNcgYm4Wm7Yc+k8e6vezpMnRT+NHbtlxgNBXRVjXe9YM6eTn6+p/MKOlV/aABJcSnQ==",
+ "dev": true,
+ "requires": {
+ "code-error-fragment": "0.0.230",
+ "grapheme-splitter": "^1.0.4"
+ }
+ },
"json5": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
@@ 4221,6 4346,12 @@
"minimist": "^1.2.0"
}
},
+ "jsonpointer": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz",
+ "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=",
+ "dev": true
+ },
"jsx-ast-utils": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.2.3.tgz",
@@ 4315,6 4446,18 @@
"integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=",
"dev": true
},
+ "lodash.get": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
+ "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=",
+ "dev": true
+ },
+ "lodash.isequal": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
+ "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=",
+ "dev": true
+ },
"lodash.pick": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz",
@@ 4607,6 4750,15 @@
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz",
"integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA=="
},
+ "node-fetch-h2": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/node-fetch-h2/-/node-fetch-h2-2.3.0.tgz",
+ "integrity": "sha512-ofRW94Ab0T4AOh5Fk8t0h8OBWrmjb0SSB20xh1H8YnPV9EJ+f5AMoYSUQ2zgJ4Iq2HAK0I2l5/Nequ8YzFS3Hg==",
+ "dev": true,
+ "requires": {
+ "http2-client": "^1.2.5"
+ }
+ },
"node-libs-browser": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz",
@@ 4644,6 4796,15 @@
}
}
},
+ "node-readfiles": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/node-readfiles/-/node-readfiles-0.2.0.tgz",
+ "integrity": "sha1-271K8SE04uY1wkXvk//Pb2BnOl0=",
+ "dev": true,
+ "requires": {
+ "es6-promise": "^3.2.1"
+ }
+ },
"node-releases": {
"version": "1.1.53",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.53.tgz",
@@ 4673,6 4834,256 @@
"path-key": "^2.0.0"
}
},
+ "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
+ },
+ "oas-kit-common": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/oas-kit-common/-/oas-kit-common-1.0.8.tgz",
+ "integrity": "sha512-pJTS2+T0oGIwgjGpw7sIRU8RQMcUoKCDWFLdBqKB2BNmGpbBMH2sdqAaOXUg8OzonZHU0L7vfJu1mJFEiYDWOQ==",
+ "dev": true,
+ "requires": {
+ "fast-safe-stringify": "^2.0.7"
+ }
+ },
+ "oas-linter": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/oas-linter/-/oas-linter-3.1.2.tgz",
+ "integrity": "sha512-mv3HBG9aQz8PLGvonewIN9Y2Ra8QL6jvotRvf7NCdZ20n5vg4dO4y61UZh6s+KRDfJaU1PO+9Oxrn3EUN4Xygw==",
+ "dev": true,
+ "requires": {
+ "should": "^13.2.1",
+ "yaml": "^1.8.3"
+ }
+ },
+ "oas-resolver": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/oas-resolver/-/oas-resolver-2.3.2.tgz",
+ "integrity": "sha512-toGCUv8wyZZmUAAsw4jn+511xNpUFW2ZLp4sAZ7xpERIeosrbxBxtkVxot9kXvdUHtPjRafi5+bkJ56TwQeYSQ==",
+ "dev": true,
+ "requires": {
+ "node-fetch-h2": "^2.3.0",
+ "oas-kit-common": "^1.0.8",
+ "reftools": "^1.1.1",
+ "yaml": "^1.8.3",
+ "yargs": "^15.3.1"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
+ "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
+ "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
+ "dev": true,
+ "requires": {
+ "@types/color-name": "^1.1.1",
+ "color-convert": "^2.0.1"
+ }
+ },
+ "cliui": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
+ "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
+ "dev": true,
+ "requires": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.0",
+ "wrap-ansi": "^6.2.0"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
+ },
+ "find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "requires": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ }
+ },
+ "is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true
+ },
+ "locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "requires": {
+ "p-locate": "^4.1.0"
+ }
+ },
+ "p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "requires": {
+ "p-limit": "^2.2.0"
+ }
+ },
+ "path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true
+ },
+ "string-width": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
+ "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
+ "dev": true,
+ "requires": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
+ "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^5.0.0"
+ }
+ },
+ "wrap-ansi": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
+ "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ }
+ },
+ "yargs": {
+ "version": "15.3.1",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz",
+ "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==",
+ "dev": true,
+ "requires": {
+ "cliui": "^6.0.0",
+ "decamelize": "^1.2.0",
+ "find-up": "^4.1.0",
+ "get-caller-file": "^2.0.1",
+ "require-directory": "^2.1.1",
+ "require-main-filename": "^2.0.0",
+ "set-blocking": "^2.0.0",
+ "string-width": "^4.2.0",
+ "which-module": "^2.0.0",
+ "y18n": "^4.0.0",
+ "yargs-parser": "^18.1.1"
+ }
+ },
+ "yargs-parser": {
+ "version": "18.1.3",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
+ "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
+ "dev": true,
+ "requires": {
+ "camelcase": "^5.0.0",
+ "decamelize": "^1.2.0"
+ }
+ }
+ }
+ },
+ "oas-schema-walker": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/oas-schema-walker/-/oas-schema-walker-1.1.4.tgz",
+ "integrity": "sha512-foVDDS0RJYMfhQEDh/WdBuCzydTcsCnGo9EeD8SpWq1uW10JXiz+8SfYVDA7LO87kjmlnTRZle/2gr5qxabaEA==",
+ "dev": true
+ },
+ "oas-validator": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/oas-validator/-/oas-validator-3.4.0.tgz",
+ "integrity": "sha512-l/SxykuACi2U51osSsBXTxdsFc8Fw41xI7AsZkzgVgWJAzoEFaaNptt35WgY9C3757RUclsm6ye5GvSyYoozLQ==",
+ "dev": true,
+ "requires": {
+ "ajv": "^5.5.2",
+ "better-ajv-errors": "^0.6.7",
+ "call-me-maybe": "^1.0.1",
+ "oas-kit-common": "^1.0.7",
+ "oas-linter": "^3.1.0",
+ "oas-resolver": "^2.3.0",
+ "oas-schema-walker": "^1.1.3",
+ "reftools": "^1.1.0",
+ "should": "^13.2.1",
+ "yaml": "^1.8.3"
+ },
+ "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.0.0",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.3.0"
+ }
+ },
+ "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
+ },
+ "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
+ }
+ }
+ },
+ "oazapfts": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/oazapfts/-/oazapfts-2.2.4.tgz",
+ "integrity": "sha512-047EYWiAab853VIGWqkWFRmJ40dMs0x+6L331Gr/IBTZmykQH9jhKqyTxwjvwgzMajcjuoXZv8oFxQBckZZ4lA==",
+ "dev": true,
+ "requires": {
+ "@apidevtools/swagger-parser": "^9.0.1",
+ "lodash": "^4.17.15",
+ "swagger2openapi": "^5.4.0",
+ "typescript": "^3.8.3"
+ }
+ },
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ 4805,6 5216,12 @@
"mimic-fn": "^2.1.0"
}
},
+ "openapi-types": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-1.3.5.tgz",
+ "integrity": "sha512-11oi4zYorsgvg5yBarZplAqbpev5HkuVNPlZaPTknPDzAynq+lnJdXAmruGWP0s+dNYZS7bjM+xrTpJw7184Fg==",
+ "dev": true
+ },
"optionator": {
"version": "0.8.3",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
@@ 5395,6 5812,12 @@
}
}
},
+ "reftools": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/reftools/-/reftools-1.1.1.tgz",
+ "integrity": "sha512-7ySkzK7YpUeJP16rzJqEXTZ7IrAq/AL/p+wWejD9wdKQOe+mYYVAOB3w5ZTs2eoHfmAidwr/6PcC+q+LzPF/DQ==",
+ "dev": true
+ },
"regenerate": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz",
@@ 5806,6 6229,60 @@
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
"integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM="
},
+ "should": {
+ "version": "13.2.3",
+ "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz",
+ "integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==",
+ "dev": true,
+ "requires": {
+ "should-equal": "^2.0.0",
+ "should-format": "^3.0.3",
+ "should-type": "^1.4.0",
+ "should-type-adaptors": "^1.0.1",
+ "should-util": "^1.0.0"
+ }
+ },
+ "should-equal": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz",
+ "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==",
+ "dev": true,
+ "requires": {
+ "should-type": "^1.4.0"
+ }
+ },
+ "should-format": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz",
+ "integrity": "sha1-m/yPdPo5IFxT04w01xcwPidxJPE=",
+ "dev": true,
+ "requires": {
+ "should-type": "^1.3.0",
+ "should-type-adaptors": "^1.0.1"
+ }
+ },
+ "should-type": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz",
+ "integrity": "sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM=",
+ "dev": true
+ },
+ "should-type-adaptors": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz",
+ "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==",
+ "dev": true,
+ "requires": {
+ "should-type": "^1.3.0",
+ "should-util": "^1.0.0"
+ }
+ },
+ "should-util": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.1.tgz",
+ "integrity": "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==",
+ "dev": true
+ },
"side-channel": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.2.tgz",
@@ 6245,6 6722,152 @@
"has-flag": "^3.0.0"
}
},
+ "swagger2openapi": {
+ "version": "5.4.0",
+ "resolved": "https://registry.npmjs.org/swagger2openapi/-/swagger2openapi-5.4.0.tgz",
+ "integrity": "sha512-f5QqfXawiVijhjMtYqWZ55ESHPZFqrPC8L9idhIiuSX8O2qsa1i4MVGtCM3TQF+Smzr/6WfT/7zBuzG3aTgPAA==",
+ "dev": true,
+ "requires": {
+ "better-ajv-errors": "^0.6.1",
+ "call-me-maybe": "^1.0.1",
+ "node-fetch-h2": "^2.3.0",
+ "node-readfiles": "^0.2.0",
+ "oas-kit-common": "^1.0.7",
+ "oas-resolver": "^2.3.0",
+ "oas-schema-walker": "^1.1.3",
+ "oas-validator": "^3.4.0",
+ "reftools": "^1.1.0",
+ "yaml": "^1.8.3",
+ "yargs": "^12.0.5"
+ },
+ "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
+ },
+ "cliui": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz",
+ "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==",
+ "dev": true,
+ "requires": {
+ "string-width": "^2.1.1",
+ "strip-ansi": "^4.0.0",
+ "wrap-ansi": "^2.0.0"
+ }
+ },
+ "get-caller-file": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz",
+ "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==",
+ "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
+ },
+ "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"
+ }
+ },
+ "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"
+ }
+ },
+ "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.1",
+ "strip-ansi": "^3.0.1"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+ "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
+ "dev": true
+ },
+ "is-fullwidth-code-point": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+ "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
+ "dev": true,
+ "requires": {
+ "number-is-nan": "^1.0.0"
+ }
+ },
+ "string-width": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+ "dev": true,
+ "requires": {
+ "code-point-at": "^1.0.0",
+ "is-fullwidth-code-point": "^1.0.0",
+ "strip-ansi": "^3.0.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^2.0.0"
+ }
+ }
+ }
+ },
+ "yargs": {
+ "version": "12.0.5",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz",
+ "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==",
+ "dev": true,
+ "requires": {
+ "cliui": "^4.0.0",
+ "decamelize": "^1.2.0",
+ "find-up": "^3.0.0",
+ "get-caller-file": "^1.0.1",
+ "os-locale": "^3.0.0",
+ "require-directory": "^2.1.1",
+ "require-main-filename": "^1.0.1",
+ "set-blocking": "^2.0.0",
+ "string-width": "^2.0.0",
+ "which-module": "^2.0.0",
+ "y18n": "^3.2.1 || ^4.0.0",
+ "yargs-parser": "^11.1.1"
+ }
+ },
+ "yargs-parser": {
+ "version": "11.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz",
+ "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==",
+ "dev": true,
+ "requires": {
+ "camelcase": "^5.0.0",
+ "decamelize": "^1.2.0"
+ }
+ }
+ }
+ },
"table": {
"version": "5.4.6",
"resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz",
@@ 6622,6 7245,12 @@
"integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==",
"dev": true
},
+ "validator": {
+ "version": "12.2.0",
+ "resolved": "https://registry.npmjs.org/validator/-/validator-12.2.0.tgz",
+ "integrity": "sha512-jJfE/DW6tIK1Ek8nCfNFqt8Wb3nzMoAbocBF6/Icgg1ZFSBpObdnwVY2jQj6qUqzhx5jc71fpvBWyLGO7Xl+nQ==",
+ "dev": true
+ },
"vm-browserify": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz",
@@ 7609,6 8238,12 @@
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
},
+ "yaml": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz",
+ "integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==",
+ "dev": true
+ },
"yargs": {
"version": "13.2.4",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.4.tgz",
@@ 7635,6 8270,18 @@
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
}
+ },
+ "z-schema": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-4.2.3.tgz",
+ "integrity": "sha512-zkvK/9TC6p38IwcrbnT3ul9in1UX4cm1y/VZSs4GHKIiDCrlafc+YQBgQBUdDXLAoZHf2qvQ7gJJOo6yT1LH6A==",
+ "dev": true,
+ "requires": {
+ "commander": "^2.7.1",
+ "lodash.get": "^4.4.2",
+ "lodash.isequal": "^4.5.0",
+ "validator": "^12.0.0"
+ }
}
}
}
M rosebush/package.json => rosebush/package.json +4 -2
@@ 48,10 48,12 @@
"@typescript-eslint/parser": "^2.30.0",
"chokidar-cli": "^2.1.0",
"eslint": "^6.8.0",
- "eslint-plugin-react": "^7.19.0"
+ "eslint-plugin-react": "^7.19.0",
+ "oazapfts": "^2.2.4"
},
"scripts": {
"build": "node_modules/.bin/webpack",
- "start": "node dist/server/main.js"
+ "start": "node dist/server/main.js",
+ "gen-api": "node_modules/.bin/oazapfts ../brackend/openapi/openapi.json src/client/api.ts"
}
}
M rosebush/src/client/App.tsx => rosebush/src/client/App.tsx +5 -7
@@ 4,6 4,7 @@ import { createContext, h, Component, JSX, VNode } from "preact";
import { Text } from "preact-jsx-i18n";
import Router, { Route } from "preact-router";
+import { HttpError, userGet } from "./api";
import Helmet from "./Helmet";
import Data, { LoadState } from "./Data";
@@ 73,13 74,10 @@ export default class App extends Component<Props, State> {
console.log("hasLogin: ", hasLogin);
if(hasLogin) {
- fetch("/api/v1/users/~me")
- .then(res => {
- if(res.status === 401) return null;
- else if(res.status < 200 || res.status >= 300) {
- return res.text().then(Promise.reject.bind(Promise));
- }
- return res.json();
+ return userGet("~me")
+ .catch(err => {
+ if(err instanceof HttpError && err.status === 401) return null;
+ return Promise.reject(err);
})
.then(info => {
if(this.state.userInfo.state === "loading") {
A rosebush/src/client/api.ts => rosebush/src/client/api.ts +353 -0
@@ 0,0 1,353 @@
+/**
+ * BracketMonster API
+ * 1.0.0
+ * DO NOT MODIFY - This file has been generated using oazapfts.
+ * See https://www.npmjs.com/package/oazapfts
+ */
+export const defaults: RequestOpts = {
+ baseUrl: "https://api.bracket.monster"
+};
+export const servers = {
+ server1: "https://api.bracket.monster"
+};
+type Encoders = Array<(s: string) => string>;
+export type RequestOpts = {
+ baseUrl?: string;
+ fetch?: typeof fetch;
+ headers?: Record<string, string | undefined>;
+} & Omit<RequestInit, "body" | "headers">;
+type FetchRequestOpts = RequestOpts & {
+ body?: string | FormData;
+};
+type JsonRequestOpts = RequestOpts & {
+ body: object;
+};
+type MultipartRequestOpts = RequestOpts & {
+ body: Record<string, string | Blob | undefined | any>;
+};
+export const _ = {
+ async fetch(url: string, req?: FetchRequestOpts) {
+ const { baseUrl, headers, fetch: customFetch, ...init } = {
+ ...defaults,
+ ...req
+ };
+ const href = _.joinUrl(baseUrl, url);
+ const res = await (customFetch || fetch)(href, {
+ ...init,
+ headers: _.stripUndefined({ ...defaults.headers, ...headers })
+ });
+ if (!res.ok) {
+ throw new HttpError(res.status, res.statusText, href);
+ }
+ return res.text();
+ },
+ async fetchJson(url: string, req: FetchRequestOpts = {}) {
+ const res = await _.fetch(url, {
+ ...req,
+ headers: {
+ ...req.headers,
+ Accept: "application/json"
+ }
+ });
+ return res && JSON.parse(res);
+ },
+ json({ body, headers, ...req }: JsonRequestOpts) {
+ return {
+ ...req,
+ body: JSON.stringify(body),
+ headers: {
+ ...headers,
+ "Content-Type": "application/json"
+ }
+ };
+ },
+ form({ body, headers, ...req }: JsonRequestOpts) {
+ return {
+ ...req,
+ body: QS.form(body),
+ headers: {
+ ...headers,
+ "Content-Type": "application/x-www-form-urlencoded"
+ }
+ };
+ },
+ multipart({ body, ...req }: MultipartRequestOpts) {
+ const data = new FormData();
+ Object.entries(body).forEach(([name, value]) => {
+ data.append(name, value);
+ });
+ return {
+ ...req,
+ body: data
+ };
+ },
+ /**
+ * Deeply remove all properties with undefined values.
+ */
+ stripUndefined<T>(obj: T) {
+ return obj && JSON.parse(JSON.stringify(obj));
+ },
+ // Encode param names and values as URIComponent
+ encodeReserved: [encodeURIComponent, encodeURIComponent],
+ allowReserved: [encodeURIComponent, encodeURI],
+ /**
+ * Creates a tag-function to encode template strings with the given encoders.
+ */
+ encode(encoders: Encoders, delimiter = ",") {
+ const q = (v: any, i: number) => {
+ const encoder = encoders[i % encoders.length];
+ if (typeof v === "object") {
+ if (Array.isArray(v)) {
+ return v.map(encoder).join(delimiter);
+ }
+ const flat = Object.entries(v).reduce((flat, entry) => [...flat, ...entry], [] as any);
+ return flat.map(encoder).join(delimiter);
+ }
+ return encoder(String(v));
+ };
+ return (strings: TemplateStringsArray, ...values: any[]) => {
+ return strings.reduce((prev, s, i) => {
+ return `${prev}${s}${q(values[i] || "", i)}`;
+ }, "");
+ };
+ },
+ /**
+ * Separate array values by the given delimiter.
+ */
+ delimited(delimiter = ",") {
+ return (params: Record<string, any>, encoders = _.encodeReserved) => Object.entries(params)
+ .filter(([, value]) => value !== undefined)
+ .map(([name, value]) => _.encode(encoders, delimiter) `${name}=${value}`)
+ .join("&");
+ },
+ joinUrl(...parts: Array<string | undefined>) {
+ return parts
+ .filter(Boolean)
+ .join("/")
+ .replace(/([^:]\/)\/+/, "$1");
+ }
+};
+/**
+ * Functions to serialize query parameters in different styles.
+ */
+export const QS = {
+ /**
+ * Join params using an ampersand and prepends a questionmark if not empty.
+ */
+ query(...params: string[]) {
+ const s = params.join("&");
+ return s && `?${s}`;
+ },
+ /**
+ * Serializes nested objects according to the `deepObject` style specified in
+ * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#style-values
+ */
+ deep(params: Record<string, any>, [k, v] = _.encodeReserved): string {
+ const qk = _.encode([s => s, k]);
+ const qv = _.encode([s => s, v]);
+ // don't add index to arrays
+ // https://github.com/expressjs/body-parser/issues/289
+ const visit = (obj: any, prefix = ""): string => Object.entries(obj)
+ .filter(([, v]) => v !== undefined)
+ .map(([prop, v]) => {
+ const index = Array.isArray(obj) ? "" : prop;
+ const key = prefix ? qk `${prefix}[${index}]` : prop;
+ if (typeof v === "object") {
+ return visit(v, key);
+ }
+ return qv `${key}=${v}`;
+ })
+ .join("&");
+ return visit(params);
+ },
+ /**
+ * Property values of type array or object generate separate parameters
+ * for each value of the array, or key-value-pair of the map.
+ * For other types of properties this property has no effect.
+ * See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#encoding-object
+ */
+ explode(params: Record<string, any>, encoders = _.encodeReserved): string {
+ const q = _.encode(encoders);
+ return Object.entries(params)
+ .filter(([, value]) => value !== undefined)
+ .map(([name, value]) => {
+ if (Array.isArray(value)) {
+ return value.map(v => q `${name}=${v}`).join("&");
+ }
+ if (typeof value === "object") {
+ return QS.explode(value, encoders);
+ }
+ return q `${name}=${value}`;
+ })
+ .join("&");
+ },
+ form: _.delimited(),
+ pipe: _.delimited("|"),
+ space: _.delimited("%20")
+};
+export class HttpError extends Error {
+ status: number;
+ constructor(status: number, message: string, url: string) {
+ super(`${url} - ${message} (${status})`);
+ this.status = status;
+ }
+}
+export type ApiResult<Fn> = Fn extends (...args: any) => Promise<infer T> ? T : never;
+export type BracketType = "RoundRobin" | "SingleElimination" | "DoubleElimination" | "Custom";
+export type UserIDOrMe = number | ("~me");
+/**
+ * Create a bracket
+ */
+export async function bracketsCreate(body: {
+ name: string;
+ players: string[];
+ "type": BracketType;
+ use_scores?: boolean;
+ matches?: {
+ players: {
+ "type": "player" | "winner" | "loser";
+ index?: number;
+ "from"?: number;
+ }[];
+ section?: number;
+ skip_if?: {
+ "type": "winner";
+ "from": number;
+ "is": number;
+ };
+ }[];
+}, opts?: RequestOpts) {
+ return await _.fetchJson("/v1/brackets", _.json({
+ ...opts,
+ method: "POST",
+ body
+ })) as {
+ id: string;
+ };
+}
+/**
+ * Get bracket info
+ */
+export async function bracketGet(bracketId: string, opts?: RequestOpts) {
+ return await _.fetchJson(`/v1/brackets/${bracketId}`, {
+ ...opts
+ }) as {
+ id: string;
+ name: string;
+ description_md: string;
+ players: {
+ id: number;
+ name: string;
+ }[];
+ matches: {
+ id: number;
+ players: {
+ player: {
+ id: number;
+ } | null;
+ source: {
+ match: number;
+ outcome: "win" | "loss";
+ } | null;
+ score: number | null;
+ }[];
+ winner: number | null;
+ column: number;
+ section: number;
+ skipped: boolean;
+ }[];
+ use_scores: boolean;
+ };
+}
+/**
+ * Edit bracket info
+ */
+export async function bracketEdit(bracketId: string, body: {
+ name?: string;
+ description_md?: string;
+}, opts?: RequestOpts) {
+ return await _.fetch(`/v1/brackets/${bracketId}`, _.json({
+ ...opts,
+ method: "PATCH",
+ body
+ }));
+}
+/**
+ * Check your permissions on this bracket
+ */
+export async function bracketYourGet(bracketId: string, opts?: RequestOpts) {
+ return await _.fetchJson(`/v1/brackets/${bracketId}/your`, {
+ ...opts
+ }) as {
+ isAdmin: boolean;
+ };
+}
+/**
+ * Update a match
+ */
+export async function bracketMatchEdit(bracketId: string, matchId: number, body: {
+ scores?: number[];
+ winner?: number | null;
+}, opts?: RequestOpts) {
+ return await _.fetch(`/v1/brackets/${bracketId}/matches/${matchId}`, _.json({
+ ...opts,
+ method: "PATCH",
+ body
+ }));
+}
+/**
+ * Create a user. Can either be created anonymously or with a username and password.
+ */
+export async function usersCreate(body: {
+ username?: string;
+ password?: string;
+}, opts?: RequestOpts) {
+ return await _.fetchJson("/v1/users", _.json({
+ ...opts,
+ method: "POST",
+ body
+ })) as {
+ token: string;
+ user: {
+ id: number;
+ username: string | null;
+ };
+ };
+}
+/**
+ * Get user info.
+ */
+export async function userGet(userId: UserIDOrMe, opts?: RequestOpts) {
+ return await _.fetchJson(`/v1/users/${userId}`, {
+ ...opts
+ }) as {
+ id: number;
+ username: string | null;
+ };
+}
+/**
+ * Modify user info
+ */
+export async function userEdit(userId: UserIDOrMe, body: {
+ username?: string;
+ password?: string;
+ email?: string;
+}, opts?: RequestOpts) {
+ return await _.fetch(`/v1/users/${userId}`, _.json({
+ ...opts,
+ method: "PATCH",
+ body
+ }));
+}
+/**
+ * List a user's brackets
+ */
+export async function userBracketsList(userId: UserIDOrMe, opts?: RequestOpts) {
+ return await _.fetchJson(`/v1/users/${userId}/brackets`, {
+ ...opts
+ }) as {
+ id: string;
+ name: string;
+ "type": BracketType;
+ }[];
+}
D rosebush/src/client/fetchWithError.ts => rosebush/src/client/fetchWithError.ts +0 -10
@@ 1,10 0,0 @@
-export default function fetchWithError(fetch: typeof window.fetch, input: RequestInfo, init?: RequestInit): Promise<Response> {
- return fetch(input, init)
- .then(res => {
- if(res.status < 200 || res.status >= 300) {
- return res.text()
- .then(text => Promise.reject(new Error(text)));
- }
- return res;
- });
-}
M rosebush/src/client/index.tsx => rosebush/src/client/index.tsx +3 -0
@@ 6,6 6,7 @@ if(process.env.NODE_ENV !== "production") {
require("preact/debug");
}
+import { defaults as apiDefaults } from "./api";
import App, { UserInfo } from "./App";
import LanguageHandler from "./LanguageHandler";
import Loading from "./Loading";
@@ 14,6 15,8 @@ import routes from "./routes";
import "./main.scss";
+apiDefaults.baseUrl = "/api";
+
const root = document.getElementById("root")!;
console.log(root);
M rosebush/src/client/pages/bracket.tsx => rosebush/src/client/pages/bracket.tsx +14 -19
@@ 6,11 6,11 @@ import { Text } from "preact-jsx-i18n";
import { Radio, RadioGroup } from "preact-radio-group";
import { SteppedLineTo } from "react-lineto";
+import { bracketEdit, bracketGet, bracketMatchEdit, bracketYourGet } from "../api";
import Data, { LoadState } from "../Data";
import Helmet from "../Helmet";
import Loading from "../Loading";
import { CancelButton, Modal, ModalContainer } from "../modals";
-import fetchWithError from "../fetchWithError";
import { addData } from "../preload";
import { BracketID } from "../types";
@@ 21,10 21,7 @@ interface PlayerInfo {
name: string;
}
-enum PlayerOutcome {
- Win = "win",
- Loss = "loss",
-}
+type PlayerOutcome = "win" | "loss";
export interface MatchInfo {
id: number;
@@ 160,21 157,21 @@ export default class BracketPage extends Component<PageProps, PageState> {
addData<PageProps, PageState, BracketPage>(
BracketPage,
(props, fetch) => ({
- yourInfo: fetchWithError(fetch, "/api/v1/brackets/" + props.id + "/your")
- .then(res => res.json()),
+ yourInfo: bracketYourGet(props.id, {fetch}),
// preload bracket only on server, since we fetch it from the socket anyway
bracket: (
typeof window !== "undefined" ?
undefined :
Promise.all([
- fetchWithError(fetch, "/api/v1/brackets/" + props.id)
- .then(res => res.json()),
+ bracketGet(props.id, {fetch}),
import("../markdown"),
])
.then(([info, mdMod]) => {
- info.description_html = mdMod.default.render(info.description_md);
- return info;
+ return {
+ ...info,
+ description_html: mdMod.default.render(info.description_md),
+ };
})
),
}),
@@ 268,7 265,7 @@ class BracketView extends Component<BracketViewProps, BracketViewState> {
})}
{typeof window !== "undefined" && bracket.matches.map(match => {
return match.players.map((matchPlayer, idx) => {
- if(matchPlayer.source && matchPlayer.source.outcome === PlayerOutcome.Win) {
+ if(matchPlayer.source && matchPlayer.source.outcome === "win") {
return <SteppedLineTo from={"matchMagic_" + matchPlayer.source.match} to={"matchMagic_" + match.id + "_player_" + idx} orientation="h" delay fromAnchor="right" toAnchor="left" borderColor="gray" within={"bracketMagic_" + bracket.id} />;
}
});
@@ 415,12 412,10 @@ export class MatchEditDialog extends Component<MatchEditProps, MatchEditState> {
evt.preventDefault();
this.setState({submitting: true}, () => {
- fetch(
- "/api/v1/brackets/" + this.props.bracket.id + "/matches/" + this.props.match.id,
- {
- method: "PATCH",
- body: JSON.stringify({scores: this.props.bracket.use_scores ? this.state.scores : undefined, winner: this.state.winner}),
- },
+ bracketMatchEdit(
+ this.props.bracket.id,
+ this.props.match.id,
+ {scores: this.props.bracket.use_scores ? this.state.scores : undefined, winner: this.state.winner},
)
.then(() => {
this.modal.current.dismiss();
@@ 481,7 476,7 @@ export class BracketInfoEditDialog extends Component<BracketInfoEditProps, Brack
const body = {name: this.state.name, description_md: this.state.description};
this.setState({submitting: true}, () => {
- fetch("/api/v1/brackets/" + this.props.bracket.id, {method: "PATCH", body: JSON.stringify(body)})
+ bracketEdit(this.props.bracket.id, body)
.then(() => this.modal.current.dismiss())
.catch(console.error)
.then(() => this.setState({submitting: false}));
M rosebush/src/client/pages/dialog/bracketInfoEdit.tsx => rosebush/src/client/pages/dialog/bracketInfoEdit.tsx +2 -3
@@ 1,8 1,8 @@
import { bind } from "decko";
import { Component, h } from "preact";
+import { bracketGet } from "../../api";
import Data, { LoadState } from "../../Data";
-import fetchWithError from "../../fetchWithError";
import { FakeModalContainer } from "../../modals";
import { addData } from "../../preload";
@@ 37,7 37,6 @@ export default class BracketInfoEditPage extends Component<PageProps, PageState>
addData<PageProps, PageState, BracketInfoEditPage>(
BracketInfoEditPage,
(props, fetch) => ({
- bracket: fetchWithError(fetch, "/api/v1/brackets/" + props.bracketID)
- .then(res => res.json()),
+ bracket: bracketGet(props.bracketID, {fetch}),
}),
);
M rosebush/src/client/pages/dialog/matchEdit.tsx => rosebush/src/client/pages/dialog/matchEdit.tsx +2 -3
@@ 2,8 2,8 @@ import { bind } from "decko";
import { Component, h } from "preact";
import { Text } from "preact-jsx-i18n";
+import { bracketGet } from "../../api";
import Data, { LoadState } from "../../Data";
-import fetchWithError from "../../fetchWithError";
import { FakeModalContainer } from "../../modals";
import { addData } from "../../preload";
@@ 55,7 55,6 @@ export default class BracketInfoEditPage extends Component<PageProps, PageState>
addData<PageProps, PageState, BracketInfoEditPage>(
BracketInfoEditPage,
(props, fetch) => ({
- bracket: fetchWithError(fetch, "/api/v1/brackets/" + props.bracketID)
- .then(res => res.json()),
+ bracket: bracketGet(props.bracketID, {fetch}),
}),
);
M rosebush/src/client/pages/myBrackets.tsx => rosebush/src/client/pages/myBrackets.tsx +2 -2
@@ 1,9 1,9 @@
import { Component, JSX, h } from "preact";
import { Text } from "preact-jsx-i18n";
+import { userBracketsList } from "../api";
import Data, { LoadState } from "../Data";
import Helmet from "../Helmet";
-import fetchWithError from "../fetchWithError";
import { addData } from "../preload";
import { BracketID } from "../types";
@@ 43,6 43,6 @@ export default class MyBracketsPage extends Component<{}, PageState> {
addData<{}, PageState, MyBracketsPage>(
MyBracketsPage,
(_, fetch) => ({
- myBrackets: fetchWithError(fetch, "/api/v1/users/~me/brackets").then(res => res.json()),
+ myBrackets: userBracketsList("~me", {fetch}),
}),
);
M rosebush/src/client/pages/newBracket.tsx => rosebush/src/client/pages/newBracket.tsx +4 -5
@@ 5,6 5,7 @@ import { Text } from "preact-jsx-i18n";
import { route } from "preact-router";
import { shuffle } from "@pacote/shuffle";
+import { bracketsCreate, usersCreate } from "../api";
import { AppContext, AppContextContent } from "../App";
import { COOKIE_AGE } from "../constants";
@@ 138,8 139,7 @@ class PageContent extends Component<Props, State> {
(
(app.userInfo.state === "done" && app.userInfo.value !== null) ?
Promise.resolve() :
- fetch("/api/v1/users", {method: "POST"})
- .then(res => res.json())
+ usersCreate({})
.then(({user, token}) => {
document.cookie = "rosebushToken=" + token + ";path=/;max-age=" + COOKIE_AGE;
app.setUserInfo(user);
@@ 152,14 152,13 @@ class PageContent extends Component<Props, State> {
players = shuffle(players);
}
- return fetch("/api/v1/brackets", {method: "POST", body: JSON.stringify({
+ return bracketsCreate({
name: form.bracketName.value,
players,
type: form.bracketType.value,
use_scores: this.state.useScores,
- })});
+ });
})
- .then(res => res.json())
.then(({id}) => {
route("/bracket/" + id);
})
M rosebush/src/client/pages/register.tsx => rosebush/src/client/pages/register.tsx +3 -3
@@ 3,6 3,7 @@ import { useContext } from "preact/hooks";
import { Text } from "preact-jsx-i18n";
import { route } from "preact-router";
+import { usersCreate, userEdit } from "../api";
import { AppContext } from "../App";
import { COOKIE_AGE } from "../constants";
import Data from "../Data";
@@ 73,8 74,7 @@ export default class RegisterPage extends Component<Props, State> {
(
app.userInfo.value === null ?
- fetch("/api/v1/users", {method: "POST", body: JSON.stringify(data)})
- .then(res => res.json())
+ usersCreate(data)
.then(({token, user}) => {
document.cookie = "rosebushToken=" + token + ";path=/;max-age=" + COOKIE_AGE;
@@ 84,7 84,7 @@ export default class RegisterPage extends Component<Props, State> {
route(this.props.continue);
}
}) :
- fetch("/api/v1/users/~me", {method: "PATCH", body: JSON.stringify(data)})
+ userEdit("~me", data)
.then(() => undefined)
)
.then(() => this.setState({success: true}))
M rosebush/src/server/index.tsx => rosebush/src/server/index.tsx +3 -0
@@ 15,6 15,7 @@ import * as serveStatic from "serve-static";
import * as querystring from "querystring";
import { shuffle } from "@pacote/shuffle";
+import { defaults as apiDefaults } from "../client/api";
import App, { UserInfo } from "../client/App";
import { COOKIE_AGE } from "../client/constants";
import Data, { LoadState } from "../client/Data";
@@ 22,6 23,8 @@ import Helmet, { rewindClear } from "../client/Helmet";
import { PreloadedDataContext } from "../client/preload";
import routes from "../client/routes";
+apiDefaults.baseUrl = "/api";
+
const API_HOST = process.env.API_HOST || "http://localhost:5000";
const SOCKET_HOST = process.env.SOCKET_HOST || "ws://localhost:5555";