2023-01-18 03:05:49 +01:00
/ *
2023-03-30 16:51:15 +02:00
Spacebar : A FOSS re - implementation and extension of the Discord . com backend .
2023-03-30 17:15:42 +02:00
Copyright ( C ) 2023 Spacebar and Spacebar Contributors
2024-08-21 19:08:25 +02:00
2023-01-18 03:05:49 +01:00
This program is free software : you can redistribute it and / or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
2024-08-21 19:08:25 +02:00
2023-01-18 03:05:49 +01:00
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU Affero General Public License for more details .
2024-08-21 19:08:25 +02:00
2023-01-18 03:05:49 +01:00
You should have received a copy of the GNU Affero General Public License
along with this program . If not , see < https : //www.gnu.org/licenses/>.
* /
2023-01-06 09:30:03 +01: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" ) ;
2023-03-24 23:14:47 +01:00
const schemas = JSON . parse ( fs . readFileSync ( SchemaPath , { encoding : "utf8" } ) ) ;
2024-08-21 19:08:25 +02:00
2023-03-24 23:14:47 +01:00
let specification = {
openapi : "3.1.0" ,
info : {
2023-04-13 21:48:27 +02:00
title : "Spacebar Server" ,
2023-03-24 23:14:47 +01:00
description :
2024-08-21 19:08:25 +02:00
"Spacebar 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." ,
2023-03-24 23:14:47 +01:00
license : {
name : "AGPLV3" ,
url : "https://www.gnu.org/licenses/agpl-3.0.en.html" ,
} ,
version : "1.0.0" ,
} ,
externalDocs : {
2023-04-13 21:48:27 +02:00
description : "Spacebar Docs" ,
url : "https://docs.spacebar.chat" ,
2023-03-24 23:14:47 +01:00
} ,
servers : [
{
2023-04-13 21:48:27 +02:00
url : "https://old.server.spacebar.chat/api/" ,
description : "Official Spacebar Instance" ,
2023-03-24 23:14:47 +01:00
} ,
] ,
components : {
securitySchemes : {
bearer : {
type : "http" ,
scheme : "bearer" ,
description : "Bearer/Bot prefixes are not required." ,
bearerFormat : "JWT" ,
in : "header" ,
} ,
} ,
} ,
tags : [ ] ,
paths : { } ,
} ;
2023-01-06 09:30:03 +01:00
2024-08-21 19:08:25 +02:00
const schemaRegEx = new RegExp ( /^[\w.]+$/ ) ;
2023-01-06 09:30:03 +01:00
function combineSchemas ( schemas ) {
2024-08-21 19:08:25 +02:00
let definitions = { } ;
2023-01-06 09:30:03 +01:00
for ( const name in schemas ) {
definitions = {
... definitions ,
... schemas [ name ] . definitions ,
[ name ] : {
... schemas [ name ] ,
definitions : undefined ,
$schema : undefined ,
} ,
} ;
}
for ( const key in definitions ) {
2024-08-21 19:08:25 +02:00
if ( ! schemaRegEx . test ( key ) ) {
console . error ( ` Invalid schema name: ${ key } ` ) ;
2023-03-24 23:14:47 +01:00
continue ;
}
2023-01-06 09:30:03 +01:00
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 ] ;
}
2024-08-21 19:19:33 +02:00
function apiRoutes ( missingRoutes ) {
2023-01-06 09:30:03 +01:00
const routes = getRouteDescriptions ( ) ;
2023-03-24 23:14:47 +01:00
// 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 } ) ) ;
2023-01-06 09:30:03 +01:00
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 ;
2023-03-24 23:14:47 +01:00
obj [ "x-fires-event" ] = route . event ;
2023-01-06 09:30:03 +01:00
if (
! NO _AUTHORIZATION _ROUTES . some ( ( x ) => {
2024-08-24 08:46:04 +02:00
if ( typeof x === "string" )
return ( method . toUpperCase ( ) + " " + path ) . startsWith ( x ) ;
2024-08-24 08:43:22 +02:00
return x . test ( method . toUpperCase ( ) + " " + path ) ;
2023-01-06 09:30:03 +01:00
} )
) {
2023-03-23 18:16:58 +01:00
obj . security = [ { bearer : [ ] } ] ;
2023-01-06 09:30:03 +01:00
}
2023-03-24 23:14:47 +01:00
if ( route . description ) obj . description = route . description ;
if ( route . summary ) obj . summary = route . summary ;
2023-03-25 21:09:04 +01:00
if ( route . deprecated ) obj . deprecated = route . deprecated ;
2023-03-24 23:14:47 +01:00
if ( route . requestBody ) {
2023-01-06 09:30:03 +01:00
obj . requestBody = {
required : true ,
content : {
"application/json" : {
2023-03-24 23:14:47 +01:00
schema : {
$ref : ` #/components/schemas/ ${ route . requestBody } ` ,
} ,
2023-01-06 09:30:03 +01:00
} ,
} ,
2024-08-21 19:08:25 +02:00
} ;
2023-01-06 09:30:03 +01:00
}
2023-03-23 15:40:37 +01:00
if ( route . responses ) {
2024-08-21 19:08:25 +02:00
obj . responses = { } ;
2023-03-23 15:40:37 +01:00
for ( const [ k , v ] of Object . entries ( route . responses ) ) {
2024-08-21 19:08:25 +02:00
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" ,
} ;
2023-03-23 15:40:37 +01:00
}
2023-03-24 23:28:32 +01:00
} else {
obj . responses = {
default : {
description : "No description available" ,
} ,
} ;
2023-01-06 09:30:03 +01:00
}
2023-03-24 23:14:47 +01:00
// handles path parameters
2023-01-06 09:30:03 +01:00
if ( p . includes ( ":" ) ) {
obj . parameters = p . match ( /:\w+/g ) ? . map ( ( x ) => ( {
name : x . replace ( ":" , "" ) ,
in : "path" ,
required : true ,
schema : { type : "string" } ,
description : x . replace ( ":" , "" ) ,
} ) ) ;
}
2023-03-24 23:14:47 +01:00
2023-03-24 23:57:44 +01:00
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 ] ;
}
2023-01-06 09:30:03 +01:00
obj . tags = [ ... ( obj . tags || [ ] ) , getTag ( p ) ] . unique ( ) ;
2024-08-21 19:19:33 +02:00
if ( missingRoutes . additional . includes ( path . replace ( /\/$/ , "" ) ) ) {
obj [ "x-badges" ] = [
{
label : "Spacebar-only" ,
color : "red" ,
} ,
] ;
}
2023-04-14 07:17:30 +02:00
specification . paths [ path ] = Object . assign (
specification . paths [ path ] || { } ,
{
[ method ] : obj ,
} ,
) ;
2023-01-06 09:30:03 +01:00
} ) ;
}
2024-08-21 19:19:33 +02:00
async function main ( ) {
2023-03-24 23:14:47 +01:00
console . log ( "Generating OpenAPI Specification..." ) ;
2024-08-21 19:19:33 +02:00
const routesRes = await fetch (
"https://github.com/spacebarchat/missing-routes/raw/main/missing.json" ,
{
headers : {
Accept : "application/json" ,
} ,
} ,
) ;
const missingRoutes = await routesRes . json ( ) ;
2023-01-06 09:30:03 +01:00
combineSchemas ( schemas ) ;
2024-08-21 19:19:33 +02:00
apiRoutes ( missingRoutes ) ;
2023-01-06 09:30:03 +01:00
fs . writeFileSync (
openapiPath ,
JSON . stringify ( specification , null , 4 )
. replaceAll ( "#/definitions" , "#/components/schemas" )
. replaceAll ( "bigint" , "number" ) ,
) ;
}
main ( ) ;