Merge branch 'develop' of github.com:thecodingmachine/workadventure into develop
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
import { BaseHttpController } from "./BaseHttpController";
|
||||
import * as fs from "fs";
|
||||
import { ADMIN_URL } from "../Enum/EnvironmentVariable";
|
||||
import SwaggerGenerator from "../Services/SwaggerGenerator";
|
||||
|
||||
export class SwaggerController extends BaseHttpController {
|
||||
routes() {
|
||||
this.app.get("/openapi", (req, res) => {
|
||||
this.app.get("/openapi/pusher", (req, res) => {
|
||||
// Let's load the module dynamically (it may not exist in prod because part of the -dev packages)
|
||||
const swaggerJsdoc = require("swagger-jsdoc");
|
||||
const options = {
|
||||
@@ -20,6 +22,43 @@ export class SwaggerController extends BaseHttpController {
|
||||
res.json(swaggerJsdoc(options));
|
||||
});
|
||||
|
||||
this.app.get("/openapi/admin", (req, res) => {
|
||||
// Let's load the module dynamically (it may not exist in prod because part of the -dev packages)
|
||||
const swaggerJsdoc = require("swagger-jsdoc");
|
||||
const options = {
|
||||
swaggerDefinition: {
|
||||
swagger: "2.0",
|
||||
//openapi: "3.0.0",
|
||||
info: {
|
||||
title: "WorkAdventure Pusher",
|
||||
version: "1.0.0",
|
||||
description:
|
||||
"This is a documentation about the endpoints called by the pusher. \n You can find out more about WorkAdventure on [github](https://github.com/thecodingmachine/workadventure).",
|
||||
contact: {
|
||||
email: "hello@workadventu.re",
|
||||
},
|
||||
},
|
||||
host: "pusher." + ADMIN_URL.replace("//", ""),
|
||||
tags: [
|
||||
{
|
||||
name: "AdminAPI",
|
||||
description: "Access to end points of the admin from the pusher",
|
||||
},
|
||||
],
|
||||
securityDefinitions: {
|
||||
Bearer: {
|
||||
type: "apiKey",
|
||||
name: "Authorization",
|
||||
in: "header",
|
||||
},
|
||||
},
|
||||
...SwaggerGenerator.definitions(),
|
||||
},
|
||||
apis: ["./src/Services/*.ts"],
|
||||
};
|
||||
res.json(swaggerJsdoc(options));
|
||||
});
|
||||
|
||||
// Create a LiveDirectory instance to virtualize directory with our assets
|
||||
// @ts-ignore
|
||||
const LiveDirectory = require("live-directory");
|
||||
@@ -39,8 +78,16 @@ export class SwaggerController extends BaseHttpController {
|
||||
if (err) {
|
||||
return response.status(500).send(err.message);
|
||||
}
|
||||
const result = data.replace(/https:\/\/petstore\.swagger\.io\/v2\/swagger.json/g, "/openapi");
|
||||
|
||||
const urls = [
|
||||
{ url: "/openapi/pusher", name: "Front -> Pusher" },
|
||||
{ url: "/openapi/admin", name: "Pusher <- Admin" },
|
||||
];
|
||||
|
||||
const result = data.replace(
|
||||
/url: "https:\/\/petstore\.swagger\.io\/v2\/swagger.json"/g,
|
||||
`urls: ${JSON.stringify(urls)}, "urls.primaryName": "Pusher <- Admin"`
|
||||
);
|
||||
response.send(result);
|
||||
|
||||
return;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { z } from "zod";
|
||||
import { extendApi } from "@anatine/zod-openapi";
|
||||
|
||||
export const isBanBannedAdminMessageInterface = z.object({
|
||||
type: z.enum(["ban", "banned"]),
|
||||
@@ -8,7 +9,7 @@ export const isBanBannedAdminMessageInterface = z.object({
|
||||
|
||||
export const isUserMessageAdminMessageInterface = z.object({
|
||||
event: z.enum(["user-message"]),
|
||||
message: isBanBannedAdminMessageInterface,
|
||||
message: extendApi(isBanBannedAdminMessageInterface, { $ref: "#/definitions/BanBannedAdminMessageInterface" }),
|
||||
world: z.string(),
|
||||
jwt: z.string(),
|
||||
});
|
||||
|
||||
+267
-12
@@ -9,6 +9,7 @@ import qs from "qs";
|
||||
import { AdminInterface } from "./AdminInterface";
|
||||
import { AuthTokenData, jwtTokenManager } from "./JWTTokenManager";
|
||||
import { InvalidTokenError } from "../Controller/InvalidTokenError";
|
||||
import { extendApi } from "@anatine/zod-openapi";
|
||||
|
||||
export interface AdminBannedData {
|
||||
is_banned: boolean;
|
||||
@@ -16,15 +17,31 @@ export interface AdminBannedData {
|
||||
}
|
||||
|
||||
export const isFetchMemberDataByUuidResponse = z.object({
|
||||
email: z.string(),
|
||||
userUuid: z.string(),
|
||||
tags: z.array(z.string()),
|
||||
visitCardUrl: z.nullable(z.string()),
|
||||
textures: z.array(isWokaDetail),
|
||||
messages: z.array(z.unknown()),
|
||||
// @ts-ignore
|
||||
email: extendApi(z.string(), {
|
||||
description: "The email of the fetched user, it can be an email, an uuid or undefined.",
|
||||
example: "example@workadventu.re",
|
||||
}),
|
||||
userUuid: extendApi(z.string(), {
|
||||
description: "The uuid of the fetched user, it can be an email, an uuid or undefined.",
|
||||
example: "998ce839-3dea-4698-8b41-ebbdf7688ad9",
|
||||
}),
|
||||
tags: extendApi(z.array(z.string()), {
|
||||
description: "List of tags related to the user fetched.",
|
||||
example: ["editor"],
|
||||
}),
|
||||
visitCardUrl: extendApi(z.nullable(z.string()), {
|
||||
description: "URL of the visitCard of the user fetched.",
|
||||
example: "https://mycompany.com/contact/me",
|
||||
}),
|
||||
textures: extendApi(z.array(isWokaDetail), { $ref: "#/definitions/WokaDetail" }),
|
||||
messages: extendApi(z.array(z.unknown()), { description: "List of user's messages." }),
|
||||
|
||||
anonymous: z.optional(z.boolean()),
|
||||
userRoomToken: z.optional(z.string()),
|
||||
anonymous: extendApi(z.optional(z.boolean()), {
|
||||
description: "Whether the user if logged as anonymous or not",
|
||||
example: false,
|
||||
}),
|
||||
userRoomToken: extendApi(z.optional(z.string()), { description: "", example: "" }),
|
||||
});
|
||||
|
||||
export type FetchMemberDataByUuidResponse = z.infer<typeof isFetchMemberDataByUuidResponse>;
|
||||
@@ -69,6 +86,47 @@ class AdminApi implements AdminInterface {
|
||||
userId,
|
||||
};
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /api/map:
|
||||
* get:
|
||||
* tags: ["AdminAPI"]
|
||||
* description: Returns a map mapping map name to file name of the map
|
||||
* security:
|
||||
* - Bearer: []
|
||||
* produces:
|
||||
* - "application/json"
|
||||
* parameters:
|
||||
* - name: "playUri"
|
||||
* in: "query"
|
||||
* description: "The full URL of WorkAdventure"
|
||||
* required: true
|
||||
* type: "string"
|
||||
* example: "http://play.workadventure.localhost/@/teamSlug/worldSLug/roomSlug"
|
||||
* - name: "userId"
|
||||
* in: "query"
|
||||
* description: "The identifier of the current user \n It can be undefined or an uuid or an email"
|
||||
* type: "string"
|
||||
* example: "998ce839-3dea-4698-8b41-ebbdf7688ad9"
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The details of the member
|
||||
* schema:
|
||||
* $ref: "#/definitions/MapDetailsData"
|
||||
* 401:
|
||||
* description: Error while retrieving the data because you are not authorized
|
||||
* schema:
|
||||
* $ref: '#/definitions/ErrorApiRedirectData'
|
||||
* 403:
|
||||
* description: Error while retrieving the data because you are not authorized
|
||||
* schema:
|
||||
* $ref: '#/definitions/ErrorApiUnauthorizedData'
|
||||
* 404:
|
||||
* description: Error while retrieving the data
|
||||
* schema:
|
||||
* $ref: '#/definitions/ErrorApiErrorData'
|
||||
*
|
||||
*/
|
||||
const res = await Axios.get<unknown, AxiosResponse<unknown>>(ADMIN_API_URL + "/api/map", {
|
||||
headers: { Authorization: `${ADMIN_API_TOKEN}`, "Accept-Language": locale ?? "en" },
|
||||
params,
|
||||
@@ -99,6 +157,58 @@ class AdminApi implements AdminInterface {
|
||||
characterLayers: string[],
|
||||
locale?: string
|
||||
): Promise<FetchMemberDataByUuidResponse> {
|
||||
/**
|
||||
* @openapi
|
||||
* /api/room/access:
|
||||
* get:
|
||||
* tags: ["AdminAPI"]
|
||||
* description: Returns member's informations if he can access this room
|
||||
* security:
|
||||
* - Bearer: []
|
||||
* produces:
|
||||
* - "application/json"
|
||||
* parameters:
|
||||
* - name: "userIdentifier"
|
||||
* in: "query"
|
||||
* description: "The identifier of the current user \n It can be undefined or an uuid or an email"
|
||||
* type: "string"
|
||||
* example: "998ce839-3dea-4698-8b41-ebbdf7688ad9"
|
||||
* - name: "playUri"
|
||||
* in: "query"
|
||||
* description: "The full URL of WorkAdventure"
|
||||
* required: true
|
||||
* type: "string"
|
||||
* example: "http://play.workadventure.localhost/@/teamSlug/worldSLug/roomSlug"
|
||||
* - name: "ipAddress"
|
||||
* in: "query"
|
||||
* required: true
|
||||
* type: "string"
|
||||
* example: "127.0.0.1"
|
||||
* - name: "characterLayers"
|
||||
* in: "query"
|
||||
* type: "array"
|
||||
* items:
|
||||
* type: string
|
||||
* example: ["male1"]
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The details of the member
|
||||
* schema:
|
||||
* $ref: "#/definitions/FetchMemberDataByUuidResponse"
|
||||
* 401:
|
||||
* description: Error while retrieving the data because you are not authorized
|
||||
* schema:
|
||||
* $ref: '#/definitions/ErrorApiRedirectData'
|
||||
* 403:
|
||||
* description: Error while retrieving the data because you are not authorized
|
||||
* schema:
|
||||
* $ref: '#/definitions/ErrorApiUnauthorizedData'
|
||||
* 404:
|
||||
* description: Error while retrieving the data
|
||||
* schema:
|
||||
* $ref: '#/definitions/ErrorApiErrorData'
|
||||
*
|
||||
*/
|
||||
const res = await Axios.get<unknown, AxiosResponse<unknown>>(ADMIN_API_URL + "/api/room/access", {
|
||||
params: {
|
||||
userIdentifier,
|
||||
@@ -130,6 +240,42 @@ class AdminApi implements AdminInterface {
|
||||
playUri: string | null,
|
||||
locale?: string
|
||||
): Promise<AdminApiData> {
|
||||
/**
|
||||
* @openapi
|
||||
* /api/login-url/{organizationMemberToken}:
|
||||
* get:
|
||||
* tags: ["AdminAPI"]
|
||||
* description: Returns a member from the token
|
||||
* security:
|
||||
* - Bearer: []
|
||||
* produces:
|
||||
* - "application/json"
|
||||
* parameters:
|
||||
* - name: "organizationMemberToken"
|
||||
* in: "path"
|
||||
* description: "The token of member in the organization"
|
||||
* type: "string"
|
||||
* - name: "playUri"
|
||||
* in: "query"
|
||||
* description: "The full URL of WorkAdventure"
|
||||
* required: true
|
||||
* type: "string"
|
||||
* example: "http://play.workadventure.localhost/@/teamSlug/worldSLug/roomSlug"
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The details of the member
|
||||
* schema:
|
||||
* $ref: "#/definitions/AdminApiData"
|
||||
* 401:
|
||||
* description: Error while retrieving the data because you are not authorized
|
||||
* schema:
|
||||
* $ref: '#/definitions/ErrorApiRedirectData'
|
||||
* 404:
|
||||
* description: Error while retrieving the data
|
||||
* schema:
|
||||
* $ref: '#/definitions/ErrorApiErrorData'
|
||||
*
|
||||
*/
|
||||
//todo: this call can fail if the corresponding world is not activated or if the token is invalid. Handle that case.
|
||||
const res = await Axios.get(ADMIN_API_URL + "/api/login-url/" + organizationMemberToken, {
|
||||
params: { playUri },
|
||||
@@ -154,6 +300,41 @@ class AdminApi implements AdminInterface {
|
||||
reportWorldSlug: string,
|
||||
locale?: string
|
||||
) {
|
||||
/**
|
||||
* @openapi
|
||||
* /api/report:
|
||||
* post:
|
||||
* tags: ["AdminAPI"]
|
||||
* description: Report one user with a comment
|
||||
* security:
|
||||
* - Bearer: []
|
||||
* produces:
|
||||
* - "application/json"
|
||||
* parameters:
|
||||
* - name: "reportedUserUuid"
|
||||
* in: "query"
|
||||
* description: "The identifier of the reported user \n It can be an uuid or an email"
|
||||
* type: "string"
|
||||
* example: "998ce839-3dea-4698-8b41-ebbdf7688ad9"
|
||||
* - name: "reportedUserComment"
|
||||
* in: "query"
|
||||
* description: "The comment of the report"
|
||||
* required: true
|
||||
* type: "string"
|
||||
* - name: "reporterUserUuid"
|
||||
* in: "query"
|
||||
* description: "The identifier of the reporter user \n It can be an uuid or an email"
|
||||
* type: "string"
|
||||
* example: "998ce839-3dea-4698-8b41-ebbdf7688ad8"
|
||||
* - name: "reportWorldSlug"
|
||||
* in: "query"
|
||||
* description: "The slug of the world where the report is made"
|
||||
* type: "string"
|
||||
* example: "/@/teamSlug/worldSlug/roomSlug"
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The report has been successfully saved
|
||||
*/
|
||||
return Axios.post(
|
||||
`${ADMIN_API_URL}/api/report`,
|
||||
{
|
||||
@@ -174,6 +355,53 @@ class AdminApi implements AdminInterface {
|
||||
roomUrl: string,
|
||||
locale?: string
|
||||
): Promise<AdminBannedData> {
|
||||
/**
|
||||
* @openapi
|
||||
* /api/ban:
|
||||
* get:
|
||||
* tags: ["AdminAPI"]
|
||||
* description: Check if user is banned or not
|
||||
* security:
|
||||
* - Bearer: []
|
||||
* produces:
|
||||
* - "application/json"
|
||||
* parameters:
|
||||
* - name: "ipAddress"
|
||||
* in: "query"
|
||||
* type: "string"
|
||||
* required: true
|
||||
* example: "127.0.0.1"
|
||||
* - name: "token"
|
||||
* in: "query"
|
||||
* description: "The uuid of the user \n It can be an uuid or an email"
|
||||
* type: "string"
|
||||
* required: true
|
||||
* example: "998ce839-3dea-4698-8b41-ebbdf7688ad8"
|
||||
* - name: "roomUrl"
|
||||
* in: "query"
|
||||
* description: "The slug of the world where to check if the user is banned"
|
||||
* type: "string"
|
||||
* required: true
|
||||
* example: "/@/teamSlug/worldSlug/roomSlug"
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The user is banned or not
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: array
|
||||
* required:
|
||||
* - is_banned
|
||||
* properties:
|
||||
* is_banned:
|
||||
* type: boolean
|
||||
* description: Whether the user is banned or not
|
||||
* example: true
|
||||
* 404:
|
||||
* description: Error while retrieving the data
|
||||
* schema:
|
||||
* $ref: '#/definitions/ErrorApiErrorData'
|
||||
*/
|
||||
//todo: this call can fail if the corresponding world is not activated or if the token is invalid. Handle that case.
|
||||
return Axios.get(
|
||||
ADMIN_API_URL +
|
||||
@@ -191,6 +419,37 @@ class AdminApi implements AdminInterface {
|
||||
}
|
||||
|
||||
async getUrlRoomsFromSameWorld(roomUrl: string, locale?: string): Promise<string[]> {
|
||||
/**
|
||||
* @openapi
|
||||
* /api/room/sameWorld:
|
||||
* get:
|
||||
* tags: ["AdminAPI"]
|
||||
* description: Get all URLs of the rooms from the world specified
|
||||
* security:
|
||||
* - Bearer: []
|
||||
* produces:
|
||||
* - "application/json"
|
||||
* parameters:
|
||||
* - name: "roomUrl"
|
||||
* in: "query"
|
||||
* description: "The slug of the room"
|
||||
* type: "string"
|
||||
* required: true
|
||||
* example: "/@/teamSlug/worldSlug/roomSlug"
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The list of URL of the rooms from the same world
|
||||
* schema:
|
||||
* type: array
|
||||
* items:
|
||||
* type: string
|
||||
* description: URL of a room
|
||||
* example: "http://example.com/@/teamSlug/worldSlug/room2Slug"
|
||||
* 404:
|
||||
* description: Error while retrieving the data
|
||||
* schema:
|
||||
* $ref: '#/definitions/ErrorApiErrorData'
|
||||
*/
|
||||
return Axios.get(ADMIN_API_URL + "/api/room/sameWorld" + "?roomUrl=" + encodeURIComponent(roomUrl), {
|
||||
headers: { Authorization: `${ADMIN_API_TOKEN}`, "Accept-Language": locale ?? "en" },
|
||||
}).then((data) => {
|
||||
@@ -204,10 +463,6 @@ class AdminApi implements AdminInterface {
|
||||
}
|
||||
return `${OPID_PROFILE_SCREEN_PROVIDER}?accessToken=${accessToken}`;
|
||||
}
|
||||
|
||||
async logoutOauth(token: string): Promise<void> {
|
||||
await Axios.get(ADMIN_API_URL + `/oauth/logout?token=${token}`);
|
||||
}
|
||||
}
|
||||
|
||||
export const adminApi = new AdminApi();
|
||||
|
||||
@@ -74,9 +74,4 @@ export interface AdminInterface {
|
||||
* @return string
|
||||
*/
|
||||
getProfileUrl(accessToken: string): string;
|
||||
|
||||
/**
|
||||
* @param token
|
||||
*/
|
||||
logoutOauth(token: string): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -87,10 +87,6 @@ class LocalAdmin implements AdminInterface {
|
||||
new Error("No admin backoffice set!");
|
||||
return "";
|
||||
}
|
||||
|
||||
async logoutOauth(token: string): Promise<void> {
|
||||
return Promise.reject(new Error("No admin backoffice set!"));
|
||||
}
|
||||
}
|
||||
|
||||
export const localAdmin = new LocalAdmin();
|
||||
|
||||
@@ -658,7 +658,7 @@ export class SocketManager implements ZoneEventListener {
|
||||
public emitErrorScreenMessage(client: compressors.WebSocket, errorApi: ErrorApiData) {
|
||||
const errorMessage = new ErrorScreenMessage();
|
||||
errorMessage.setType(errorApi.type);
|
||||
if (errorApi.type == "retry" || errorApi.type == "error") {
|
||||
if (errorApi.type == "retry" || errorApi.type == "error" || errorApi.type == "unauthorized") {
|
||||
errorMessage.setCode(new StringValue().setValue(errorApi.code));
|
||||
errorMessage.setTitle(new StringValue().setValue(errorApi.title));
|
||||
errorMessage.setSubtitle(new StringValue().setValue(errorApi.subtitle));
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { generateSchema } from "@anatine/zod-openapi";
|
||||
import { isAdminApiData } from "../Messages/JsonMessages/AdminApiData";
|
||||
import {
|
||||
isErrorApiErrorData,
|
||||
isErrorApiRedirectData,
|
||||
isErrorApiRetryData,
|
||||
isErrorApiUnauthorizedData,
|
||||
} from "../Messages/JsonMessages/ErrorApiData";
|
||||
import { isMapDetailsData } from "../Messages/JsonMessages/MapDetailsData";
|
||||
import { isFetchMemberDataByUuidResponse } from "./AdminApi";
|
||||
import { isWokaDetail } from "../Messages/JsonMessages/PlayerTextures";
|
||||
|
||||
class SwaggerGenerator {
|
||||
definitions() {
|
||||
return {
|
||||
definitions: {
|
||||
AdminApiData: generateSchema(isAdminApiData),
|
||||
//BanBannedAdminMessageInterface: generateSchema(isBanBannedAdminMessageInterface),
|
||||
ErrorApiErrorData: generateSchema(isErrorApiErrorData),
|
||||
ErrorApiRedirectData: generateSchema(isErrorApiRedirectData),
|
||||
ErrorApiRetryData: generateSchema(isErrorApiRetryData),
|
||||
ErrorApiUnauthorizedData: generateSchema(isErrorApiUnauthorizedData),
|
||||
FetchMemberDataByUuidResponse: generateSchema(isFetchMemberDataByUuidResponse),
|
||||
//ListenRoomsMessageInterface: generateSchema(isListenRoomsMessageInterface),
|
||||
MapDetailsData: generateSchema(isMapDetailsData),
|
||||
//RegisterData: generateSchema(isRegisterData),
|
||||
//RoomRedirect: generateSchema(isRoomRedirect),
|
||||
//UserMessageAdminMessageInterface: generateSchema(isUserMessageAdminMessageInterface),
|
||||
WokaDetail: generateSchema(isWokaDetail),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default new SwaggerGenerator();
|
||||
Reference in New Issue
Block a user