2024-09-05 00:21:51 -07:00
require ( "module-alias/register" ) ;
const getRouteDescriptions = require ( "./util/getRouteDescriptions" ) ;
const path = require ( "path" ) ;
const fs = require ( "fs" ) ;
const {
NO _AUTHORIZATION _ROUTES ,
} = require ( "../dist/api/middlewares/Authentication" ) ;
require ( "missing-native-js-functions" ) ;
const openapiPath = path . join ( _ _dirname , ".." , "assets" , "openapi.json" ) ;
const SchemaPath = path . join ( _ _dirname , ".." , "assets" , "schemas.json" ) ;
const schemas = JSON . parse ( fs . readFileSync ( SchemaPath , { encoding : "utf8" } ) ) ;
let specification = {
openapi : "3.1.0" ,
info : {
title : "Valkyrie Server" ,
description :
"Valkyrie is a Discord.com server implementation and extension, with the goal of complete feature parity with Discord.com, all while adding some additional goodies, security, privacy, and configuration options." ,
license : {
name : "Apache-2.0" ,
2024-09-06 21:08:38 -07:00
url : "http://www.apache.org/licenses/LICENSE-2.0.html" ,
2024-09-05 00:21:51 -07:00
} ,
version : "1.0.0" ,
} ,
externalDocs : {
description : "Valkyrie Docs" ,
url : "https://docs.valkyriecoms.com" ,
} ,
servers : [
{
url : "https://old.server.valkyriecoms.com/api/" ,
description : "Official Valkyrie Instance" ,
} ,
] ,
components : {
securitySchemes : {
bearer : {
type : "http" ,
scheme : "bearer" ,
description : "Bearer/Bot prefixes are not required." ,
bearerFormat : "JWT" ,
in : "header" ,
} ,
} ,
} ,
tags : [ ] ,
paths : { } ,
} ;
const schemaRegEx = new RegExp ( /^[\w.]+$/ ) ;
function combineSchemas ( schemas ) {
let definitions = { } ;
for ( const name in schemas ) {
definitions = {
... definitions ,
... schemas [ name ] . definitions ,
[ name ] : {
... schemas [ name ] ,
definitions : undefined ,
$schema : undefined ,
} ,
} ;
}
for ( const key in definitions ) {
if ( ! schemaRegEx . test ( key ) ) {
console . error ( ` Invalid schema name: ${ key } ` ) ;
continue ;
}
specification . components = specification . components || { } ;
specification . components . schemas =
specification . components . schemas || { } ;
specification . components . schemas [ key ] = definitions [ key ] ;
delete definitions [ key ] . additionalProperties ;
delete definitions [ key ] . $schema ;
const definition = definitions [ key ] ;
if ( typeof definition . properties === "object" ) {
for ( const property of Object . values ( definition . properties ) ) {
if ( Array . isArray ( property . type ) ) {
if ( property . type . includes ( "null" ) ) {
property . type = property . type . find ( ( x ) => x !== "null" ) ;
property . nullable = true ;
}
}
}
}
}
return definitions ;
}
function getTag ( key ) {
return key . match ( /\/([\w-]+)/ ) [ 1 ] ;
}
function apiRoutes ( missingRoutes ) {
const routes = getRouteDescriptions ( ) ;
// populate tags
const tags = Array . from ( routes . keys ( ) )
. map ( ( x ) => getTag ( x ) )
. sort ( ( a , b ) => a . localeCompare ( b ) ) ;
specification . tags = tags . unique ( ) . map ( ( x ) => ( { name : x } ) ) ;
routes . forEach ( ( route , pathAndMethod ) => {
const [ p , method ] = pathAndMethod . split ( "|" ) ;
const path = p . replace ( /:(\w+)/g , "{$1}" ) ;
let obj = specification . paths [ path ] ? . [ method ] || { } ;
obj [ "x-right-required" ] = route . right ;
obj [ "x-permission-required" ] = route . permission ;
obj [ "x-fires-event" ] = route . event ;
if (
! NO _AUTHORIZATION _ROUTES . some ( ( x ) => {
if ( typeof x === "string" )
return ( method . toUpperCase ( ) + " " + path ) . startsWith ( x ) ;
return x . test ( method . toUpperCase ( ) + " " + path ) ;
} )
) {
obj . security = [ { bearer : [ ] } ] ;
}
if ( route . description ) obj . description = route . description ;
if ( route . summary ) obj . summary = route . summary ;
if ( route . deprecated ) obj . deprecated = route . deprecated ;
if ( route . requestBody ) {
obj . requestBody = {
required : true ,
content : {
"application/json" : {
schema : {
$ref : ` #/components/schemas/ ${ route . requestBody } ` ,
} ,
} ,
} ,
} ;
}
if ( route . responses ) {
obj . responses = { } ;
for ( const [ k , v ] of Object . entries ( route . responses ) ) {
if ( v . body )
obj . responses [ k ] = {
description : obj ? . responses ? . [ k ] ? . description || "" ,
content : {
"application/json" : {
schema : {
$ref : ` #/components/schemas/ ${ v . body } ` ,
} ,
} ,
} ,
} ;
else
obj . responses [ k ] = {
description :
obj ? . responses ? . [ k ] ? . description ||
"No description available" ,
} ;
}
} else {
obj . responses = {
default : {
description : "No description available" ,
} ,
} ;
}
// handles path parameters
if ( p . includes ( ":" ) ) {
obj . parameters = p . match ( /:\w+/g ) ? . map ( ( x ) => ( {
name : x . replace ( ":" , "" ) ,
in : "path" ,
required : true ,
schema : { type : "string" } ,
description : x . replace ( ":" , "" ) ,
} ) ) ;
}
if ( route . query ) {
// map to array
const query = Object . entries ( route . query ) . map ( ( [ k , v ] ) => ( {
name : k ,
in : "query" ,
required : v . required ,
schema : { type : v . type } ,
description : v . description ,
} ) ) ;
obj . parameters = [ ... ( obj . parameters || [ ] ) , ... query ] ;
}
obj . tags = [ ... ( obj . tags || [ ] ) , getTag ( p ) ] . unique ( ) ;
if ( missingRoutes . additional . includes ( path . replace ( /\/$/ , "" ) ) ) {
obj [ "x-badges" ] = [
{
label : "Valkyrie-only" ,
color : "red" ,
} ,
] ;
}
specification . paths [ path ] = Object . assign (
specification . paths [ path ] || { } ,
{
[ method ] : obj ,
} ,
) ;
} ) ;
}
async function main ( ) {
console . log ( "Generating OpenAPI Specification..." ) ;
const routesRes = await fetch (
"https://toastielab.dev/ValkyrieChat/missing-routes/raw/branch/main/missing.json" ,
{
headers : {
Accept : "application/json" ,
} ,
} ,
) ;
const missingRoutes = await routesRes . json ( ) ;
combineSchemas ( schemas ) ;
apiRoutes ( missingRoutes ) ;
fs . writeFileSync (
openapiPath ,
JSON . stringify ( specification , null , 4 )
. replaceAll ( "#/definitions" , "#/components/schemas" )
. replaceAll ( "bigint" , "number" ) ,
) ;
}
main ( ) ;