feat: swagger-ui, work in progress

This commit is contained in:
Bobby 2022-09-14 03:18:30 +07:00
parent 93dc820368
commit d4e41e3586
No known key found for this signature in database
GPG Key ID: 941839794CBF5A09
27 changed files with 3138 additions and 17 deletions

View File

@ -93,7 +93,7 @@ module.exports = {
as any changes in said directory will be detected live.
You may even add or remove pages while lolisafe is running.
*/
pages: ['home', 'auth', 'dashboard', 'faq'],
pages: ['home', 'auth', 'dashboard', 'faq', 'apidocs'],
/*
This will load public/libs/cookieconsent/cookieconsent.min.{css,js} on homepage (configured from home.js).

View File

@ -8,8 +8,8 @@ const utils = require('./utilsController')
const ClientError = require('./utils/ClientError')
const config = require('./../config')
// Don't forget to update min/max length of text inputs in auth.njk
// when changing these values.
// Don't forget to update min/max length of text inputs in views/auth.njk,
// and OpenAPI spec in routes/api.js when changing these values.
const self = {
user: {
min: 4,

View File

@ -48,7 +48,9 @@ self.higher = (user, target) => {
self.mapPermissions = user => {
const map = {}
Object.keys(self.permissions).forEach(group => {
map[group] = self.is(user, group)
if (self.is(user, group)) {
map[group] = self.is(user, group)
}
})
return map
}

View File

@ -140,9 +140,17 @@ gulp.task('build:js', () => {
.pipe(gulp.dest(dist))
})
gulp.task('build', gulp.parallel('build:sass', 'build:css', 'build:fontello', 'build:js'))
gulp.task('build:openapi-spec', cb => {
exec('node ./scripts/build-openapi-spec.js', (error, stdout, stderr) => {
if (stdout) process.stdout.write(stdout)
if (stderr) process.stderr.write(stderr)
cb(error)
})
})
/** TASKS: VERSION STRINGS */
gulp.task('build', gulp.parallel('build:sass', 'build:css', 'build:fontello', 'build:js', 'build:openapi-spec'))
/** TASKS: EXEC */
gulp.task('exec:bump-versions', cb => {
exec('node ./scripts/bump-versions.js 1', (error, stdout, stderr) => {
@ -192,6 +200,7 @@ gulp.task('nodemon', cb => {
watch: [
'controllers/',
'routes/',
'scripts/build-openapi-spec.js',
'views/_globals.njk',
'views/_layout.njk',
'views/album.njk',
@ -200,6 +209,7 @@ gulp.task('nodemon', cb => {
'lolisafe.js'
],
ext: 'js',
tasks: ['build:openapi-spec'],
done: cb
})
})

View File

@ -86,7 +86,8 @@
"postcss": "~8.4.16",
"postcss-preset-env": "~7.8.1",
"stylelint": "~14.11.0",
"stylelint-config-standard-scss": "~5.0.0"
"stylelint-config-standard-scss": "~5.0.0",
"swagger-jsdoc": "~6.2.5"
},
"volta": {
"node": "16.17.0",

Binary file not shown.

After

Width:  |  Height:  |  Size: 665 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 628 B

View File

@ -0,0 +1,16 @@
html {
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
body {
margin: 0;
background: #fafafa;
}

View File

@ -0,0 +1,79 @@
<!doctype html>
<html lang="en-US">
<head>
<title>Swagger UI: OAuth2 Redirect</title>
</head>
<body>
<script>
'use strict';
function run () {
var oauth2 = window.opener.swaggerUIRedirectOauth2;
var sentState = oauth2.state;
var redirectUrl = oauth2.redirectUrl;
var isValid, qp, arr;
if (/code|token|error/.test(window.location.hash)) {
qp = window.location.hash.substring(1).replace('?', '&');
} else {
qp = location.search.substring(1);
}
arr = qp.split("&");
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';});
qp = qp ? JSON.parse('{' + arr.join() + '}',
function (key, value) {
return key === "" ? value : decodeURIComponent(value);
}
) : {};
isValid = qp.state === sentState;
if ((
oauth2.auth.schema.get("flow") === "accessCode" ||
oauth2.auth.schema.get("flow") === "authorizationCode" ||
oauth2.auth.schema.get("flow") === "authorization_code"
) && !oauth2.auth.code) {
if (!isValid) {
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "warning",
message: "Authorization may be unsafe, passed state was changed in server. The passed state wasn't returned from auth server."
});
}
if (qp.code) {
delete oauth2.state;
oauth2.auth.code = qp.code;
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
} else {
let oauthErrorMsg;
if (qp.error) {
oauthErrorMsg = "["+qp.error+"]: " +
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
(qp.error_uri ? "More info: "+qp.error_uri : "");
}
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "error",
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server."
});
}
} else {
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
}
window.close();
}
if (document.readyState !== 'loading') {
run();
} else {
document.addEventListener('DOMContentLoaded', function () {
run();
});
}
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

877
public/openapi.json Normal file
View File

@ -0,0 +1,877 @@
{
"openapi": "3.0.0",
"info": {
"title": "lolisafe",
"version": "3.0.0"
},
"paths": {
"/api/check": {
"get": {
"summary": "Get server's partial config",
"description": "This only contains the necessary config to customize the homepage uploader's behavior.",
"tags": [
"General"
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"private",
"enableUserAccounts",
"maxSize",
"chunkSize",
"fileIdentifierLength",
"stripTags"
],
"properties": {
"private": {
"type": "boolean"
},
"enableUserAccounts": {
"type": "boolean"
},
"maxSize": {
"type": "string"
},
"chunkSize": {
"type": "object"
},
"fileIdentifierLength": {
"type": "object",
"properties": {
"min": {
"type": "number"
},
"max": {
"type": "number"
},
"default": {
"type": "number"
},
"force": {
"type": "boolean"
}
}
},
"stripTags": {
"oneOf": [
{
"type": "object"
},
{
"type": "boolean"
}
]
},
"temporaryUploadAges": {
"type": "array",
"items": {
"type": "number"
}
},
"defaultTemporaryUploadAge": {
"type": "number"
},
"version": {
"type": "string"
}
}
}
}
}
},
"500": {
"$ref": "#/components/responses/ServerError"
}
}
}
},
"/api/login": {
"post": {
"summary": "Verify user credentials and get token",
"tags": [
"Auth"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UsernamePassword"
},
"example": {
"username": "",
"password": ""
}
}
}
},
"responses": {
"200": {
"$ref": "#/components/responses/AuthSuccessful"
},
"400": {
"$ref": "#/components/responses/BadRequest"
},
"403": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"success": false,
"description": "Wrong credentials."
}
}
}
},
"500": {
"$ref": "#/components/responses/ServerError"
}
}
}
},
"/api/register": {
"post": {
"summary": "Register a user",
"tags": [
"Auth"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UsernamePassword"
},
"example": {
"username": "",
"password": ""
}
}
}
},
"responses": {
"200": {
"$ref": "#/components/responses/AuthSuccessful"
},
"400": {
"$ref": "#/components/responses/BadRequest"
},
"403": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"success": false,
"description": "Registration is currently disabled."
}
}
}
},
"500": {
"$ref": "#/components/responses/ServerError"
}
}
}
},
"/api/password/change": {
"post": {
"summary": "Change user's password",
"tags": [
"Auth"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"password"
],
"properties": {
"password": {
"$ref": "#/components/schemas/Password"
}
}
},
"example": {
"password": ""
}
}
}
},
"responses": {
"200": {
"$ref": "#/components/responses/Success"
},
"400": {
"$ref": "#/components/responses/BadRequest"
},
"403": {
"$ref": "#/components/responses/Unauthorized"
},
"500": {
"$ref": "#/components/responses/ServerError"
}
},
"security": [
{
"token": []
}
]
}
},
"/api/users": {
"get": {
"summary": "Get users at page 1",
"description": "Requires admin permission.",
"tags": [
"Users",
"Administration"
],
"responses": {
"200": {
"$ref": "#/components/responses/UsersResponse"
},
"403": {
"$ref": "#/components/responses/Unauthorized"
},
"500": {
"$ref": "#/components/responses/ServerError"
}
},
"security": [
{
"token": []
}
]
}
},
"/api/users/{page}": {
"get": {
"summary": "Get users at page N",
"description": "Requires admin permission. This allows negative value to navigate backwards.",
"tags": [
"Users",
"Administration"
],
"parameters": [
{
"in": "path",
"name": "page",
"schema": {
"type": "number"
},
"required": true,
"description": "Page of users to get."
}
],
"responses": {
"200": {
"$ref": "#/components/responses/UsersResponse"
},
"403": {
"$ref": "#/components/responses/Unauthorized"
},
"500": {
"$ref": "#/components/responses/ServerError"
}
},
"security": [
{
"token": []
}
]
}
},
"/api/users/create": {
"post": {
"summary": "Create a new user",
"description": "Requires admin permission. If password is omitted, server will generate a random one.",
"tags": [
"Users",
"Administration"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"username",
"group"
],
"properties": {
"username": {
"$ref": "#/components/schemas/Username"
},
"password": {
"$ref": "#/components/schemas/Password"
},
"group": {
"$ref": "#/components/schemas/Usergroup"
}
}
},
"example": {
"username": "",
"password": "",
"group": "user"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"success",
"username",
"password",
"group"
],
"properties": {
"success": {
"type": "boolean"
},
"username": {
"$ref": "#/components/schemas/Username"
},
"password": {
"$ref": "#/components/schemas/Password"
},
"group": {
"$ref": "#/components/schemas/Usergroup"
}
}
}
}
}
},
"403": {
"$ref": "#/components/responses/Unauthorized"
},
"500": {
"$ref": "#/components/responses/ServerError"
}
},
"security": [
{
"token": []
}
]
}
},
"/api/users/delete": {
"post": {
"summary": "Delete a user",
"description": "Requires admin permission.",
"tags": [
"Users",
"Administration"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"id"
],
"properties": {
"id": {
"$ref": "#/components/schemas/UserID"
},
"purge": {
"description": "Whether to purge the user's uploaded files as well.",
"type": "boolean"
}
}
},
"example": {
"id": 69420,
"purge": false
}
}
}
},
"responses": {
"200": {
"$ref": "#/components/responses/Success"
},
"403": {
"$ref": "#/components/responses/Unauthorized"
},
"500": {
"$ref": "#/components/responses/ServerError"
}
},
"security": [
{
"token": []
}
]
}
},
"/api/users/disable": {
"post": {
"summary": "Disable a user",
"description": "Requires admin permission.",
"tags": [
"Users",
"Administration"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"id"
],
"properties": {
"id": {
"$ref": "#/components/schemas/UserID"
}
}
},
"example": {
"id": 69420
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"success",
"update"
],
"properties": {
"success": {
"type": "boolean"
},
"update": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"description": "Whether user is enabled or not.",
"type": "boolean"
}
}
}
}
},
"example": {
"success": true,
"update": {
"enabled": false
}
}
}
}
},
"403": {
"$ref": "#/components/responses/Unauthorized"
},
"500": {
"$ref": "#/components/responses/ServerError"
}
},
"security": [
{
"token": []
}
]
}
},
"/api/users/edit": {
"post": {
"summary": "Edit a user",
"description": "Requires admin permission.",
"tags": [
"Users",
"Administration"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"id"
],
"properties": {
"id": {
"$ref": "#/components/schemas/UserID"
},
"username": {
"$ref": "#/components/schemas/Username"
},
"group": {
"$ref": "#/components/schemas/Usergroup"
},
"enabled": {
"description": "Whether to enable or disable user.",
"type": "boolean"
},
"resetPassword": {
"description": "Whether to reset user's password with a randomly generated one.",
"type": "boolean"
}
}
},
"example": {
"id": 69420,
"username": "",
"group": "user",
"enabled": true,
"resetPassword": false
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"success",
"update"
],
"properties": {
"success": {
"type": "boolean"
},
"update": {
"type": "object",
"properties": {
"username": {
"$ref": "#/components/schemas/Username"
},
"enabled": {
"description": "Whether user is enabled or not.",
"type": "boolean"
},
"permission": {
"description": "Numeric permission according to selected usergroup.",
"type": "number"
},
"password": {
"$ref": "#/components/schemas/Password"
}
}
}
}
}
}
}
},
"403": {
"$ref": "#/components/responses/Unauthorized"
},
"500": {
"$ref": "#/components/responses/ServerError"
}
},
"security": [
{
"token": []
}
]
}
}
},
"components": {
"schemas": {
"Username": {
"type": "string",
"minLength": 4,
"maxLength": 32
},
"Password": {
"type": "string",
"format": "password",
"minLength": 6,
"maxLength": 64
},
"Result": {
"type": "object",
"required": [
"success"
],
"properties": {
"success": {
"type": "boolean"
}
}
},
"Error": {
"type": "object",
"required": [
"success",
"description"
],
"properties": {
"success": {
"type": "boolean"
},
"description": {
"type": "string"
},
"code": {
"description": "Helper error code. Currently very sparsely used.",
"type": "number"
}
}
},
"UsernamePassword": {
"type": "object",
"required": [
"username",
"password"
],
"properties": {
"username": {
"$ref": "#/components/schemas/Username"
},
"password": {
"$ref": "#/components/schemas/Password"
}
}
},
"UserID": {
"description": "User's numerical ID in database.",
"type": "number"
},
"User": {
"type": "object",
"required": [
"id",
"username",
"enabled",
"timestamp",
"registration",
"groups",
"uploads",
"usage"
],
"properties": {
"id": {
"$ref": "#/components/schemas/UserID"
},
"username": {
"$ref": "#/components/schemas/Username"
},
"enabled": {
"description": "Whether user is enabled or not.",
"type": "boolean",
"nullable": true
},
"timestamp": {
"description": "Timestamp of user's last token update.",
"type": "number",
"nullable": true
},
"registration": {
"description": "Timestamp of user's registration date.",
"type": "number",
"nullable": true
},
"groups": {
"description": "Usergroup(s) information.",
"type": "object"
},
"uploads": {
"description": "Amount of uploads associated to this user.",
"type": "number"
},
"usage": {
"description": "Amount of disk used by this user in bytes.",
"type": "number"
}
}
},
"Usergroup": {
"description": "Usergroup name.",
"type": "string"
}
},
"responses": {
"Success": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Result"
},
"example": {
"success": true
}
}
}
},
"BadRequest": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"success": false,
"description": "Bad request."
}
}
}
},
"Unauthorized": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"success": false,
"description": "Invalid token.",
"code": 10001
}
}
}
},
"ServerError": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"success": false,
"description": "An unexpected error occurred. Try again?"
}
}
}
},
"AuthSuccessful": {
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"success",
"token"
],
"properties": {
"success": {
"type": "boolean"
},
"token": {
"description": "Token that can be used for user authentication in other API routes.",
"type": "string"
}
}
},
"example": {
"success": true,
"token": "YOUR_TOKEN_HERE"
}
}
}
},
"UsersResponse": {
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"success",
"users",
"usersPerPage",
"count"
],
"properties": {
"success": {
"type": "boolean"
},
"users": {
"description": "Array of users.",
"type": "array",
"items": {
"$ref": "#/components/schemas/User"
}
},
"usersPerPage": {
"description": "Users per page for pagination.",
"type": "number"
},
"count": {
"description": "Total users in database.",
"type": "number"
}
}
}
}
}
}
},
"securitySchemes": {
"token": {
"type": "apiKey",
"name": "token",
"in": "header"
}
}
},
"tags": [
{
"name": "General",
"description": "General routes"
},
{
"name": "Uploads",
"description": "Uploads routes"
},
{
"name": "Auth",
"description": "Auth routes"
},
{
"name": "Users",
"description": "Users routes"
},
{
"name": "Album",
"description": "Albums routes"
},
{
"name": "Moderation",
"description": "Moderation routes"
},
{
"name": "Administration",
"description": "Administration routes"
}
]
}

View File

@ -7,6 +7,150 @@ const upload = require('./../controllers/uploadController')
const utils = require('./../controllers/utilsController')
const config = require('./../config')
/**
* @openapi
* components:
* schemas:
* Username:
* type: string
* minLength: 4
* maxLength: 32
* Password:
* type: string
* format: password
* minLength: 6
* maxLength: 64
* Result:
* type: object
* required:
* - success
* properties:
* success:
* type: boolean
* Error:
* type: object
* required:
* - success
* - description
* properties:
* success:
* type: boolean
* description:
* type: string
* code:
* description: Helper error code. Currently very sparsely used.
* type: number
* responses:
* Success:
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Result"
* example:
* success: true
* BadRequest:
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Error"
* example:
* success: false
* description: Bad request.
* Unauthorized:
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Error"
* example:
* success: false
* description: Invalid token.
* code: 10001
* ServerError:
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Error"
* example:
* success: false
* description: An unexpected error occurred. Try again?
* securitySchemes:
* token:
* type: apiKey
* name: token
* in: header
* tags:
* - name: General
* description: General routes
* - name: Uploads
* description: Uploads routes
* - name: Auth
* description: Auth routes
* - name: Users
* description: Users routes
* - name: Album
* description: Albums routes
* - name: Moderation
* description: Moderation routes
* - name: Administration
* description: Administration routes
*/
/**
* @openapi
* /api/check:
* get:
* summary: Get server's partial config
* description: This only contains the necessary config to customize the homepage uploader's behavior.
* tags:
* - General
* responses:
* 200:
* content:
* application/json:
* schema:
* type: object
* required:
* - private
* - enableUserAccounts
* - maxSize
* - chunkSize
* - fileIdentifierLength
* - stripTags
* properties:
* private:
* type: boolean
* enableUserAccounts:
* type: boolean
* maxSize:
* type: string
* chunkSize:
* type: object
* fileIdentifierLength:
* type: object
* properties:
* min:
* type: number
* max:
* type: number
* default:
* type: number
* force:
* type: boolean
* stripTags:
* oneOf:
* - type: object
* - type: boolean
* temporaryUploadAges:
* type: array
* items:
* type: number
* defaultTemporaryUploadAge:
* type: number
* version:
* type: string
* 500:
* $ref: "#/components/responses/ServerError"
*/
routes.get('/check', async (req, res) => {
const obj = {
private: config.private,
@ -23,22 +167,488 @@ routes.get('/check', async (req, res) => {
if (utils.clientVersion) {
obj.version = utils.clientVersion
}
return res.json(obj)
})
/** ./controllers/authController.js */
/**
* @openapi
* components:
* schemas:
* UsernamePassword:
* type: object
* required:
* - username
* - password
* properties:
* username:
* $ref: "#/components/schemas/Username"
* password:
* $ref: "#/components/schemas/Password"
* responses:
* AuthSuccessful:
* content:
* application/json:
* schema:
* type: object
* required:
* - success
* - token
* properties:
* success:
* type: boolean
* token:
* description: Token that can be used for user authentication in other API routes.
* type: string
* example:
* success: true
* token: YOUR_TOKEN_HERE
*/
/**
* @openapi
* /api/login:
* post:
* summary: Verify user credentials and get token
* tags:
* - Auth
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/UsernamePassword"
* example:
* username: ""
* password: ""
* responses:
* 200:
* $ref: "#/components/responses/AuthSuccessful"
* 400:
* $ref: "#/components/responses/BadRequest"
* 403:
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Error"
* example:
* success: false
* description: Wrong credentials.
* 500:
* $ref: "#/components/responses/ServerError"
*/
routes.post('/login', utils.assertJSON, auth.verify)
/**
* @openapi
* /api/register:
* post:
* summary: Register a user
* tags:
* - Auth
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/UsernamePassword"
* example:
* username: ""
* password: ""
* responses:
* 200:
* $ref: "#/components/responses/AuthSuccessful"
* 400:
* $ref: "#/components/responses/BadRequest"
* 403:
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Error"
* example:
* success: false
* description: Registration is currently disabled.
* 500:
* $ref: "#/components/responses/ServerError"
*/
routes.post('/register', utils.assertJSON, auth.register)
/**
* @openapi
* /api/password/change:
* post:
* summary: Change user's password
* tags:
* - Auth
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - password
* properties:
* password:
* $ref: "#/components/schemas/Password"
* example:
* password: ""
* responses:
* 200:
* $ref: "#/components/responses/Success"
* 400:
* $ref: "#/components/responses/BadRequest"
* 403:
* $ref: "#/components/responses/Unauthorized"
* 500:
* $ref: "#/components/responses/ServerError"
* security:
* - token: []
*/
routes.post('/password/change', [auth.requireUser, utils.assertJSON], auth.changePassword)
/**
* @openapi
* components:
* schemas:
* UserID:
* description: User's numerical ID in database.
* type: number
* User:
* type: object
* required:
* - id
* - username
* - enabled
* - timestamp
* - registration
* - groups
* - uploads
* - usage
* properties:
* id:
* $ref: "#/components/schemas/UserID"
* username:
* $ref: "#/components/schemas/Username"
* enabled:
* description: Whether user is enabled or not.
* type: boolean
* nullable: true
* timestamp:
* description: Timestamp of user's last token update.
* type: number
* nullable: true
* registration:
* description: Timestamp of user's registration date.
* type: number
* nullable: true
* groups:
* description: Usergroup(s) information.
* type: object
* uploads:
* description: Amount of uploads associated to this user.
* type: number
* usage:
* description: Amount of disk used by this user in bytes.
* type: number
* Usergroup:
* description: Usergroup name.
* type: string
* responses:
* UsersResponse:
* content:
* application/json:
* schema:
* type: object
* required:
* - success
* - users
* - usersPerPage
* - count
* properties:
* success:
* type: boolean
* users:
* description: Array of users.
* type: array
* items:
* $ref: "#/components/schemas/User"
* usersPerPage:
* description: Users per page for pagination.
* type: number
* count:
* description: Total users in database.
* type: number
*/
/**
* @openapi
* /api/users:
* get:
* summary: Get users at page 1
* description: Requires admin permission.
* tags:
* - Users
* - Administration
* responses:
* 200:
* $ref: "#/components/responses/UsersResponse"
* 403:
* $ref: "#/components/responses/Unauthorized"
* 500:
* $ref: "#/components/responses/ServerError"
* security:
* - token: []
*/
routes.get('/users', auth.requireUser, auth.listUsers)
/**
* @openapi
* /api/users/{page}:
* get:
* summary: Get users at page N
* description: Requires admin permission. This allows negative value to navigate backwards.
* tags:
* - Users
* - Administration
* parameters:
* - in: path
* name: page
* schema:
* type: number
* required: true
* description: Page of users to get.
* responses:
* 200:
* $ref: "#/components/responses/UsersResponse"
* 403:
* $ref: "#/components/responses/Unauthorized"
* 500:
* $ref: "#/components/responses/ServerError"
* security:
* - token: []
*/
routes.get('/users/:page', auth.requireUser, auth.listUsers)
/**
* @openapi
* /api/users/create:
* post:
* summary: Create a new user
* description: Requires admin permission. If password is omitted, server will generate a random one.
* tags:
* - Users
* - Administration
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - username
* - group
* properties:
* username:
* $ref: "#/components/schemas/Username"
* password:
* $ref: "#/components/schemas/Password"
* group:
* $ref: "#/components/schemas/Usergroup"
* example:
* username: ""
* password: ""
* group: user
* responses:
* 200:
* content:
* application/json:
* schema:
* type: object
* required:
* - success
* - username
* - password
* - group
* properties:
* success:
* type: boolean
* username:
* $ref: "#/components/schemas/Username"
* password:
* $ref: "#/components/schemas/Password"
* group:
* $ref: "#/components/schemas/Usergroup"
* 403:
* $ref: "#/components/responses/Unauthorized"
* 500:
* $ref: "#/components/responses/ServerError"
* security:
* - token: []
*/
routes.post('/users/create', [auth.requireUser, utils.assertJSON], auth.createUser)
/**
* @openapi
* /api/users/delete:
* post:
* summary: Delete a user
* description: Requires admin permission.
* tags:
* - Users
* - Administration
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - id
* properties:
* id:
* $ref: "#/components/schemas/UserID"
* purge:
* description: Whether to purge the user's uploaded files as well.
* type: boolean
* example:
* id: 69420
* purge: false
* responses:
* 200:
* $ref: "#/components/responses/Success"
* 403:
* $ref: "#/components/responses/Unauthorized"
* 500:
* $ref: "#/components/responses/ServerError"
* security:
* - token: []
*/
routes.post('/users/delete', [auth.requireUser, utils.assertJSON], auth.deleteUser)
/**
* @openapi
* /api/users/disable:
* post:
* summary: Disable a user
* description: Requires admin permission.
* tags:
* - Users
* - Administration
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - id
* properties:
* id:
* $ref: "#/components/schemas/UserID"
* example:
* id: 69420
* responses:
* 200:
* content:
* application/json:
* schema:
* type: object
* required:
* - success
* - update
* properties:
* success:
* type: boolean
* update:
* type: object
* required:
* - enabled
* properties:
* enabled:
* description: Whether user is enabled or not.
* type: boolean
* example:
* success: true
* update:
* enabled: false
* 403:
* $ref: "#/components/responses/Unauthorized"
* 500:
* $ref: "#/components/responses/ServerError"
* security:
* - token: []
*/
routes.post('/users/disable', [auth.requireUser, utils.assertJSON], auth.disableUser)
/**
* @openapi
* /api/users/edit:
* post:
* summary: Edit a user
* description: Requires admin permission.
* tags:
* - Users
* - Administration
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - id
* properties:
* id:
* $ref: "#/components/schemas/UserID"
* username:
* $ref: "#/components/schemas/Username"
* group:
* $ref: "#/components/schemas/Usergroup"
* enabled:
* description: Whether to enable or disable user.
* type: boolean
* resetPassword:
* description: Whether to reset user's password with a randomly generated one.
* type: boolean
* example:
* id: 69420
* username: ""
* group: user
* enabled: true
* resetPassword: false
* responses:
* 200:
* content:
* application/json:
* schema:
* type: object
* required:
* - success
* - update
* properties:
* success:
* type: boolean
* update:
* type: object
* properties:
* username:
* $ref: "#/components/schemas/Username"
* enabled:
* description: Whether user is enabled or not.
* type: boolean
* permission:
* description: Numeric permission according to selected usergroup.
* type: number
* password:
* $ref: "#/components/schemas/Password"
* 403:
* $ref: "#/components/responses/Unauthorized"
* 500:
* $ref: "#/components/responses/ServerError"
* security:
* - token: []
*/
routes.post('/users/edit', [auth.requireUser, utils.assertJSON], auth.editUser)
/** ./controllers/uploadController.js */

View File

@ -0,0 +1,54 @@
const { promisify } = require('util')
const fs = require('fs')
const path = require('path')
const swaggerJsdoc = require('swagger-jsdoc')
const self = {
dest: './public/openapi.json',
options: {
failOnErrors: true,
definition: {
openapi: '3.0.0',
info: {
title: 'lolisafe',
version: '3.0.0'
}
},
apis: ['./routes/*.js']
},
// This is a parallel of utilsController.js->stripIndents().
// Added here so that this script won't have to import the said controller.
stripIndents: string => {
if (!string) return
const result = string.replace(/^[^\S\n]+/gm, '')
const match = result.match(/^[^\S\n]*(?=\S)/gm)
const indent = match && Math.min(...match.map(el => el.length))
if (indent) {
const regexp = new RegExp(`^.{${indent}}`, 'gm')
return result.replace(regexp, '')
}
return result
},
access: promisify(fs.access),
writeFile: promisify(fs.writeFile)
}
;(async () => {
const openapiSpecification = swaggerJsdoc(self.options)
// logger.inspect(openapiSpecification)
const file = path.resolve(self.dest)
// Stringify OpenAPI specification
const stringified = JSON.stringify(openapiSpecification, null, 2) + '\n'
// Write to file
await self.writeFile(file, stringified)
// console.log(`Successfully written OpenAPI specification to ${self.dest}.`)
})()
.then(() => process.exit(0))
.catch(error => {
console.error(error)
process.exit(1)
})

1266
src/css/apidocs.scss Normal file

File diff suppressed because it is too large Load Diff

25
src/js/apidocs.js Normal file
View File

@ -0,0 +1,25 @@
/* global SwaggerUIBundle, SwaggerUIStandalonePreset */
window.addEventListener('DOMContentLoaded', () => {
const mainScript = document.querySelector('#mainScript')
const name = mainScript && mainScript.dataset.name
? mainScript.dataset.name
: 'lolisafe'
window.ui = SwaggerUIBundle({
urls: [
// Expects openapi.json to be publicly accessible via GET /openapi.json route
{ url: 'openapi.json', name }
],
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: 'StandaloneLayout'
})
})

46
views/apidocs.njk Normal file
View File

@ -0,0 +1,46 @@
{%- import '_globals.njk' as globals with context -%}
{# Set root domain here to inherit values from config file #}
{%- set root = utils.conf.homeDomain or utils.conf.domain -%}
{% set metaTitle = "API Docs" %}
{% set metaUrl = '/api-docs' %}
{%- set title -%}
{%- if metaTitle -%}
{{ metaTitle + ' | ' + globals.name }}
{%- else -%}
{{ globals.name + ' ' + globals.motto }}
{%- endif %}
{%- endset -%}
{%- set url = root + (metaUrl or '') -%}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>{{ title }}</title>
<!-- Stylesheets -->
<link rel="stylesheet" type="text/css" href="libs/swagger-ui/swagger-ui.css{{ versions[3] }}">
<link rel="stylesheet" type="text/css" href="libs/swagger-ui/index.css{{ versions[3] }}">
<link rel="stylesheet" type="text/css" href="css/apidocs.css{{ versions[1] }}">
<!-- Icons, configs, etcetera -->
<link rel="icon" type="image/png" href="{{ root }}/libs/swagger-ui/favicon-32x32.png{{ versions[2] }}" sizes="32x32">
<link rel="icon" type="image/png" href="{{ root }}/libs/swagger-ui/favicon-16x16.png{{ versions[2] }}" sizes="16x16">
</head>
<body>
<!-- Swagger UI container -->
<div id="swagger-ui"></div>
<!-- Scripts -->
<script src="libs/swagger-ui/swagger-ui-bundle.js{{ versions[3] }}" charset="utf-8"></script>
<script src="libs/swagger-ui/swagger-ui-standalone-preset.js{{ versions[3] }}" charset="utf-8"></script>
{# We assign an ID for this so that the script can find out its own data tags #}
<script id="mainScript" src="js/apidocs.js{{ versions[1] }}" data-name="{{ name }}" charset="utf-8"></script>
</body>
</html>

132
yarn.lock
View File

@ -2,6 +2,38 @@
# yarn lockfile v1
"@apidevtools/json-schema-ref-parser@^9.0.6":
version "9.0.9"
resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz#d720f9256e3609621280584f2b47ae165359268b"
integrity sha512-GBD2Le9w2+lVFoc4vswGI/TjkNIZSVp7+9xPf+X3uidBfWnAeUWmquteSyt0+VCrhNMWj/FTABISQrD3Z/YA+w==
dependencies:
"@jsdevtools/ono" "^7.1.3"
"@types/json-schema" "^7.0.6"
call-me-maybe "^1.0.1"
js-yaml "^4.1.0"
"@apidevtools/openapi-schemas@^2.0.4":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz#9fa08017fb59d80538812f03fc7cac5992caaa17"
integrity sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==
"@apidevtools/swagger-methods@^3.0.2":
version "3.0.2"
resolved "https://registry.yarnpkg.com/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz#b789a362e055b0340d04712eafe7027ddc1ac267"
integrity sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==
"@apidevtools/swagger-parser@10.0.2":
version "10.0.2"
resolved "https://registry.yarnpkg.com/@apidevtools/swagger-parser/-/swagger-parser-10.0.2.tgz#f4145afb7c3a3bafe0376f003b5c3bdeae17a952"
integrity sha512-JFxcEyp8RlNHgBCE98nwuTkZT6eNFPc1aosWV6wPcQph72TSEEu1k3baJD4/x1qznU+JiDdz8F5pTwabZh+Dhg==
dependencies:
"@apidevtools/json-schema-ref-parser" "^9.0.6"
"@apidevtools/openapi-schemas" "^2.0.4"
"@apidevtools/swagger-methods" "^3.0.2"
"@jsdevtools/ono" "^7.1.3"
call-me-maybe "^1.0.1"
z-schema "^4.2.3"
"@babel/code-frame@^7.0.0":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a"
@ -251,6 +283,11 @@
"@jridgewell/resolve-uri" "^3.0.3"
"@jridgewell/sourcemap-codec" "^1.4.10"
"@jsdevtools/ono@^7.1.3":
version "7.1.3"
resolved "https://registry.yarnpkg.com/@jsdevtools/ono/-/ono-7.1.3.tgz#9df03bbd7c696a5c58885c34aa06da41c8543796"
integrity sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==
"@mapbox/node-pre-gyp@^1.0.0":
version "1.0.9"
resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.9.tgz#09a8781a3a036151cdebbe8719d6f8b25d4058bc"
@ -372,7 +409,7 @@
"@types/qs" "*"
"@types/serve-static" "*"
"@types/json-schema@*":
"@types/json-schema@*", "@types/json-schema@^7.0.6":
version "7.0.11"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==
@ -1068,6 +1105,11 @@ call-bind@^1.0.0, call-bind@^1.0.2:
function-bind "^1.1.1"
get-intrinsic "^1.0.2"
call-me-maybe@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b"
integrity sha512-wCyFsDQkKPwwF8BDwOiWNx/9K45L/hvggQiDbve+viMNMQnWhrlYIuBk09offfwCRtCO9P6XwUttufzU11WCVw==
callsites@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
@ -1308,7 +1350,12 @@ colors@^1.2.1:
resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78"
integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==
commander@^2.20.0:
commander@6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.0.tgz#b990bfb8ac030aedc6d11bc04d1488ffef56db75"
integrity sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==
commander@^2.20.0, commander@^2.7.1:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
@ -1704,6 +1751,13 @@ dir-glob@^3.0.1:
dependencies:
path-type "^4.0.0"
doctrine@3.0.0, doctrine@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961"
integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==
dependencies:
esutils "^2.0.2"
doctrine@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d"
@ -1711,13 +1765,6 @@ doctrine@^2.1.0:
dependencies:
esutils "^2.0.2"
doctrine@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961"
integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==
dependencies:
esutils "^2.0.2"
dom-serializer@^1.0.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30"
@ -2643,6 +2690,18 @@ glob-watcher@^5.0.3:
normalize-path "^3.0.0"
object.defaults "^1.1.0"
glob@7.1.6:
version "7.1.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^3.0.4"
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@^7.1.1, glob@^7.1.3:
version "7.2.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
@ -3669,6 +3728,16 @@ lodash.clonedeep@^4.3.2:
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
integrity sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==
lodash.get@^4.4.2:
version "4.4.2"
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==
lodash.isequal@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==
lodash.memoize@4.1.2, lodash.memoize@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
@ -3679,6 +3748,11 @@ lodash.merge@^4.6.2:
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
lodash.mergewith@^4.6.2:
version "4.6.2"
resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz#617121f89ac55f59047c7aec1ccd6654c6590f55"
integrity sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==
lodash.truncate@^4.4.2:
version "4.4.2"
resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193"
@ -5941,6 +6015,25 @@ svgo@^2.7.0:
picocolors "^1.0.0"
stable "^0.1.8"
swagger-jsdoc@~6.2.5:
version "6.2.5"
resolved "https://registry.yarnpkg.com/swagger-jsdoc/-/swagger-jsdoc-6.2.5.tgz#65bffa142276436b2b131255f59a6b55384a0e8e"
integrity sha512-l+cdsKS2y+QDhrH1TJSUiE0y9XKuf5xaGSatjf0hR/wjTlMpO8WfubBK9d/nASdbHPMtj9iJZLBH2ogBEhL7Sw==
dependencies:
commander "6.2.0"
doctrine "3.0.0"
glob "7.1.6"
lodash.mergewith "^4.6.2"
swagger-parser "10.0.2"
yaml "2.0.0-1"
swagger-parser@10.0.2:
version "10.0.2"
resolved "https://registry.yarnpkg.com/swagger-parser/-/swagger-parser-10.0.2.tgz#d7f18faa09c9c145e938977c9bd6c3435998b667"
integrity sha512-9jHkHM+QXyLGFLk1DkXBwV+4HyNm0Za3b8/zk/+mjr8jgOSiqm3FOTHBSDsBjtn9scdL+8eWcHdupp2NLM8tDw==
dependencies:
"@apidevtools/swagger-parser" "10.0.2"
systeminformation@~5.12.6:
version "5.12.6"
resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-5.12.6.tgz#b75d7aaf9f5da32439fc633d2be9eb741691d200"
@ -6336,6 +6429,11 @@ validate-npm-package-license@^3.0.1:
spdx-correct "^3.0.0"
spdx-expression-parse "^3.0.0"
validator@^13.6.0:
version "13.7.0"
resolved "https://registry.yarnpkg.com/validator/-/validator-13.7.0.tgz#4f9658ba13ba8f3d82ee881d3516489ea85c0857"
integrity sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==
value-or-function@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/value-or-function/-/value-or-function-3.0.0.tgz#1c243a50b595c1be54a754bfece8563b9ff8d813"
@ -6492,6 +6590,11 @@ yallist@^4.0.0:
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
yaml@2.0.0-1:
version "2.0.0-1"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.0.0-1.tgz#8c3029b3ee2028306d5bcf396980623115ff8d18"
integrity sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==
yaml@^1.10.0, yaml@^1.10.2:
version "1.10.2"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
@ -6538,3 +6641,14 @@ yocto-queue@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
z-schema@^4.2.3:
version "4.2.4"
resolved "https://registry.yarnpkg.com/z-schema/-/z-schema-4.2.4.tgz#73102a49512179b12a8ec50b1daa676b984da6e4"
integrity sha512-YvBeW5RGNeNzKOUJs3rTL4+9rpcvHXt5I051FJbOcitV8bl40pEfcG0Q+dWSwS0/BIYrMZ/9HHoqLllMkFhD0w==
dependencies:
lodash.get "^4.4.2"
lodash.isequal "^4.5.0"
validator "^13.6.0"
optionalDependencies:
commander "^2.7.1"