server list
This commit is contained in:
parent
f01c616a44
commit
46802f995d
10 changed files with 221 additions and 8 deletions
|
@ -25,6 +25,7 @@ export { logger, app, db, PORT, SESSION_LIFETIME }
|
|||
import('./routes/internal/ws'),
|
||||
import('./routes/root'),
|
||||
import('./routes/login'),
|
||||
import('./routes/dash/servers'),
|
||||
]);
|
||||
logger.done('All routes and middlewares loaded');
|
||||
})();
|
||||
|
|
21
api/src/routes/dash/servers.ts
Normal file
21
api/src/routes/dash/servers.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { app } from '../..';
|
||||
import { Request, Response } from 'express';
|
||||
import { isAuthenticated } from '../../utils';
|
||||
import { botReq } from '../internal/ws';
|
||||
|
||||
type Server = { id: string, perms: 0|1|2, name: string, iconURL?: string, bannerURL?: string }
|
||||
|
||||
app.get('/dash/servers', async (req: Request, res: Response) => {
|
||||
const user = await isAuthenticated(req, res, true);
|
||||
if (!user) return;
|
||||
|
||||
const response = await botReq('getUserServers', { user });
|
||||
if (!response.success) {
|
||||
return res.status(response.statusCode ?? 500).send({ error: response.error });
|
||||
}
|
||||
|
||||
if (!response.servers) return res.status(404).send({ error: 'Not found' });
|
||||
|
||||
const servers: Server[] = response.servers;
|
||||
res.send({ servers });
|
||||
});
|
|
@ -1,4 +1,4 @@
|
|||
import { Request } from "express";
|
||||
import { Request, Response } from "express";
|
||||
import { FindOneResult } from "monk";
|
||||
import { db } from ".";
|
||||
|
||||
|
@ -15,13 +15,16 @@ class Session {
|
|||
* @param req
|
||||
* @returns false if not authenticated, otherwise the (Revolt) user ID
|
||||
*/
|
||||
async function isAuthenticated(req: Request): Promise<string|false> {
|
||||
async function isAuthenticated(req: Request, res?: Response, send401?: boolean): Promise<string|false> {
|
||||
const user = req.header('x-auth-user');
|
||||
const token = req.header('x-auth-token');
|
||||
|
||||
if (!user || !token) return false;
|
||||
|
||||
const info = await getSessionInfo(user, token);
|
||||
if (res && send401 && !info.valid) {
|
||||
res.status(401).send({ error: 'Unauthorized' });
|
||||
}
|
||||
return info.valid ? user : false;
|
||||
}
|
||||
|
||||
|
|
54
bot/src/bot/modules/api/servers.ts
Normal file
54
bot/src/bot/modules/api/servers.ts
Normal file
|
@ -0,0 +1,54 @@
|
|||
import { User } from 'revolt.js/dist/maps/Users';
|
||||
import { client } from '../../..';
|
||||
import { getPermissionLevel, isBotManager } from '../../util';
|
||||
import { wsEvents, WSResponse } from '../api_communication';
|
||||
|
||||
type ReqData = { user: string }
|
||||
|
||||
wsEvents.on('req:getUserServers', async (data: ReqData, cb: (data: WSResponse) => void) => {
|
||||
try {
|
||||
let user: User;
|
||||
try {
|
||||
user = client.users.get(data.user) || await client.users.fetch(data.user);
|
||||
} catch(e) {
|
||||
cb({ success: false, error: 'The requested user could not be found', statusCode: 404 });
|
||||
return;
|
||||
}
|
||||
|
||||
const mutuals = await user.fetchMutual();
|
||||
|
||||
type ServerResponse = { id: string, perms: 0|1|2, name: string, iconURL?: string, bannerURL?: string }
|
||||
|
||||
const promises: Promise<ServerResponse>[] = [];
|
||||
|
||||
for (const sid of mutuals.servers) {
|
||||
promises.push(new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const server = client.servers.get(sid);
|
||||
if (!server) return reject('Server not found');
|
||||
const perms = await getPermissionLevel(user, server);
|
||||
resolve({
|
||||
id: sid,
|
||||
perms,
|
||||
name: server.name,
|
||||
bannerURL: server.generateBannerURL(),
|
||||
iconURL: server.generateIconURL({}),
|
||||
});
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
reject(`${e}`);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
cb({
|
||||
success: true,
|
||||
servers: (await Promise.allSettled(promises)).map(
|
||||
p => p.status == 'fulfilled' ? p.value : undefined
|
||||
),
|
||||
});
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
cb({ success: false, error: `${e}` });
|
||||
}
|
||||
});
|
|
@ -9,7 +9,6 @@ import { client as bot } from '../..';
|
|||
import { EventEmitter } from "events";
|
||||
import { parseUser } from "../util";
|
||||
import PendingLogin from "../../struct/PendingLogin";
|
||||
import { LogLevel } from "log75";
|
||||
import { ulid } from "ulid";
|
||||
|
||||
const wsEvents = new EventEmitter();
|
||||
|
@ -17,6 +16,8 @@ const { API_WS_URL, API_WS_TOKEN } = process.env;
|
|||
const wsQueue: { [key: string]: string }[] = [];
|
||||
let client: ws|undefined = undefined;
|
||||
|
||||
type WSResponse = { success: false, error: string, statusCode?: number } | { success: true, [key: string]: any }
|
||||
|
||||
if (!API_WS_URL || !API_WS_TOKEN)
|
||||
logger.info("$API_WS_URL or $API_WS_TOKEN not found.");
|
||||
else {
|
||||
|
@ -92,7 +93,7 @@ wsEvents.on('req:test', (data: any, res: (data: any) => void) => {
|
|||
res({ received: data });
|
||||
});
|
||||
|
||||
wsEvents.on('req:requestLogin', async (data: any, cb: (data: any) => void) => {
|
||||
wsEvents.on('req:requestLogin', async (data: any, cb: (data: WSResponse) => void) => {
|
||||
try {
|
||||
const user = await parseUser(data.user);
|
||||
if (!user)
|
||||
|
@ -126,8 +127,10 @@ wsEvents.on('req:requestLogin', async (data: any, cb: (data: any) => void) => {
|
|||
cb({ success: true, uid: user._id, nonce, code });
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
cb({ success: false, error: e });
|
||||
cb({ success: false, error: `${e}` });
|
||||
}
|
||||
});
|
||||
|
||||
export { wsEvents, wsSend }
|
||||
export { wsEvents, wsSend, WSResponse }
|
||||
|
||||
import('./api/servers');
|
||||
|
|
|
@ -99,6 +99,22 @@ async function checkSudoPermission(message: Message): Promise<boolean> {
|
|||
return true;
|
||||
}
|
||||
}
|
||||
async function getPermissionLevel(user: User|Member, server: Server): Promise<0|1|2> {
|
||||
if (isSudo(user instanceof User ? user : (user.user || await client.users.fetch(user._id.user)))) return 2;
|
||||
|
||||
const member = user instanceof User ? await server.fetchMember(user) : user;
|
||||
if (user instanceof Member) user = user.user!;
|
||||
|
||||
if (hasPerm(member, 'ManageServer')) return 2;
|
||||
if (hasPerm(member, 'KickMembers')) return 1;
|
||||
|
||||
const config = (await client.db.get('servers').findOne({ id: server._id }) || {}) as ServerConfig;
|
||||
|
||||
if (config.botManagers?.includes(user._id)) return 2;
|
||||
if (config.moderators?.includes(user._id)) return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
function hasPerm(member: Member, perm: keyof typeof ServerPermission): boolean {
|
||||
let p = ServerPermission[perm];
|
||||
|
@ -292,6 +308,7 @@ export {
|
|||
getOwnMemberInServer,
|
||||
isModerator,
|
||||
isBotManager,
|
||||
getPermissionLevel,
|
||||
parseUser,
|
||||
parseUserOrId,
|
||||
storeInfraction,
|
||||
|
|
|
@ -4,6 +4,8 @@ import './App.css';
|
|||
import '@revoltchat/ui/src/styles/dark.css';
|
||||
import '@revoltchat/ui/src/styles/common.css';
|
||||
import RequireAuth from './components/RequireAuth';
|
||||
import DashboardHome from './pages/DashboardHome';
|
||||
import ServerDashboard from './pages/ServerDashboard';
|
||||
|
||||
const API_URL = 'http://localhost:9000';
|
||||
|
||||
|
@ -12,7 +14,8 @@ function App() {
|
|||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path='/' element={<Home />} />
|
||||
<Route path='/dashboard' element={<RequireAuth><a>among us</a></RequireAuth>} />
|
||||
<Route path='/dashboard' element={<RequireAuth><DashboardHome /></RequireAuth>} />
|
||||
<Route path='/dashboard/:serverid' element={<RequireAuth><ServerDashboard /></RequireAuth>} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
);
|
||||
|
|
81
web/src/pages/DashboardHome.tsx
Normal file
81
web/src/pages/DashboardHome.tsx
Normal file
|
@ -0,0 +1,81 @@
|
|||
import axios from 'axios';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { FunctionComponent, useCallback, useEffect, useState } from "react";
|
||||
import { Button } from '@revoltchat/ui/lib/components/atoms/inputs/Button';
|
||||
import { H1 } from '@revoltchat/ui/lib/components/atoms/heading/H1';
|
||||
import { H2 } from '@revoltchat/ui/lib/components/atoms/heading/H2';
|
||||
import { API_URL } from "../App";
|
||||
import { getAuthHeaders } from "../utils";
|
||||
|
||||
type Server = { id: string, perms: 0|1|2, name: string, iconURL?: string, bannerURL?: string }
|
||||
|
||||
function permissionName(p: number) {
|
||||
switch(p) {
|
||||
case 0: return 'User';
|
||||
case 1: return 'Moderator';
|
||||
case 2: return 'Manager';
|
||||
default: return 'Unknown';
|
||||
}
|
||||
}
|
||||
|
||||
const Dashboard: FunctionComponent = () => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [servers, setServers] = useState([] as Server[]);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const loadServers = useCallback(async () => {
|
||||
try {
|
||||
const res = await axios.get(API_URL + '/dash/servers', { headers: await getAuthHeaders() });
|
||||
setServers(res.data.servers);
|
||||
setLoading(false);
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => { loadServers() }, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<H1>dashbord</H1>
|
||||
<br/>
|
||||
<p hidden={!loading}>loading</p>
|
||||
{
|
||||
servers.map(server => <div className="server-card" style={{ paddingTop: '10px' }} key={server.id}>
|
||||
<img
|
||||
src={server.iconURL ?? 'https://dl.insrt.uk/projects/revolt/emotes/trol.png'}
|
||||
width={48}
|
||||
height={48}
|
||||
style={{
|
||||
float: 'left',
|
||||
marginLeft: '8px',
|
||||
marginRight: '12px',
|
||||
borderRadius: "50%",
|
||||
}}
|
||||
/>
|
||||
<div style={{
|
||||
float: 'left',
|
||||
maxWidth: '240px',
|
||||
textOverflow: 'clip',
|
||||
overflow: 'clip',
|
||||
whiteSpace: 'nowrap',
|
||||
}}>
|
||||
<H2>{server.name} ({permissionName(server.perms)})</H2>
|
||||
<code style={{ color: 'var(--foreground)' }}>{server.id}</code>
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
style={{ position: 'relative', top: '8px', left: '12px' }}
|
||||
onClick={() => {
|
||||
navigate(`/dashboard/${server.id}`);
|
||||
}}
|
||||
>Open</Button>
|
||||
</div>
|
||||
<div style={{ clear: 'both' }} />
|
||||
</div>)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Dashboard;
|
22
web/src/pages/ServerDashboard.tsx
Normal file
22
web/src/pages/ServerDashboard.tsx
Normal file
|
@ -0,0 +1,22 @@
|
|||
import localforage from "localforage";
|
||||
import axios from 'axios';
|
||||
import { FunctionComponent, useCallback, useEffect, useState } from "react";
|
||||
import { Button } from '@revoltchat/ui/lib/components/atoms/inputs/Button';
|
||||
import { InputBox } from '@revoltchat/ui/lib/components/atoms/inputs/InputBox';
|
||||
import { H1 } from '@revoltchat/ui/lib/components/atoms/heading/H1';
|
||||
import { H2 } from '@revoltchat/ui/lib/components/atoms/heading/H2';
|
||||
import { API_URL } from "../App";
|
||||
import { getAuthHeaders } from "../utils";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
type Server = { id: string, perms: 0|1|2, name: string, iconURL?: string, bannerURL?: string }
|
||||
|
||||
const ServerDashboard: FunctionComponent = () => {
|
||||
const { serverid } = useParams();
|
||||
|
||||
return (
|
||||
<a>sus</a>
|
||||
);
|
||||
}
|
||||
|
||||
export default ServerDashboard;
|
|
@ -2,6 +2,14 @@ import axios from "axios";
|
|||
import localforage from "localforage";
|
||||
import { API_URL } from "./App";
|
||||
|
||||
async function getAuthHeaders() {
|
||||
const auth: any = await localforage.getItem('auth');
|
||||
return {
|
||||
'x-auth-user': auth.user,
|
||||
'x-auth-token': auth.token,
|
||||
}
|
||||
}
|
||||
|
||||
async function getAuth(): Promise<false|{ user: string, token: string }> {
|
||||
const auth: any = await localforage.getItem('auth');
|
||||
if (!auth) return false;
|
||||
|
@ -19,4 +27,4 @@ async function getAuth(): Promise<false|{ user: string, token: string }> {
|
|||
} catch(e) { return false } // todo: dont assume we're logged out if death
|
||||
}
|
||||
|
||||
export { getAuth }
|
||||
export { getAuth, getAuthHeaders }
|
Loading…
Reference in a new issue