v1.6.2/admin dashboard

This commit is contained in:
2020-08-15 11:45:50 -04:00
parent 56cd151a04
commit 43dc657582
25 changed files with 15498 additions and 10 deletions
+23
View File
@@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
+14429
View File
File diff suppressed because it is too large Load Diff
+34
View File
@@ -0,0 +1,34 @@
{
"name": "watch-party-admin",
"version": "1.6.2",
"private": true,
"dependencies": {
"@ant-design/icons": "^4.2.2",
"antd": "^4.5.4",
"firebase": "^7.18.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-scripts": "3.4.3"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

+17
View File
@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Watch Party Admin Dashboard" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>Watch Party | Admin</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

+25
View File
@@ -0,0 +1,25 @@
{
"short_name": "WP | Admin",
"name": "Watch Party Admin Dashboard",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#c0f300",
"background_color": "#000000"
}
+24
View File
@@ -0,0 +1,24 @@
import firebase from 'firebase/app';
import 'firebase/auth';
const API = 'http://localhost:5000/watch-party-crx/us-central1/admin';
const fetcher = async ({ endpoint, method, bodyData }) => {
const idToken = await firebase.auth().currentUser.getIdToken();
const body = JSON.stringify(bodyData);
return fetch(`${API}/${endpoint}`, {
method,
headers: {
Authorization: `Token ${idToken}:${firebase.auth().currentUser.uid}`,
'Access-Control-Allow-Origin': '*',
},
body,
});
};
export const getPartyDashboardData = () => fetcher({ endpoint: 'partyDashboard', method: 'GET' });
export const getServerDashboardData = () => fetcher({ endpoint: 'serverDashboard', method: 'GET' });
export const postUpdateVersionActive = (bodyData) =>
fetcher({ endpoint: 'updateVersionActive', method: 'POST', bodyData });
export const postUpdateVideoSelectors = (bodyData) =>
fetcher({ endpoint: 'updateVideoSelectors', method: 'POST', bodyData });
+28
View File
@@ -0,0 +1,28 @@
import React from 'react';
import { Component } from 'react';
import Login from './Login';
import Dashboard from './Dashboard';
import firebase from 'firebase/app';
import 'firebase/auth';
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
auth: false,
};
}
componentDidMount() {
firebase.auth().onAuthStateChanged((authUser) => {
if (authUser && !authUser.isAnonymous) {
this.setState({ auth: true });
} else {
this.setState({ auth: false });
}
});
}
render() {
const { auth } = this.state;
return <div className="app-wrapper">{auth ? <Dashboard /> : <Login />}</div>;
}
}
+515
View File
@@ -0,0 +1,515 @@
import React, { Component } from 'react';
import firebase from 'firebase/app';
import 'firebase/auth';
import 'firebase/firestore';
import {
Button,
Card,
Table,
Tabs,
Tag,
Switch,
Input,
notification,
Divider,
Row,
Col,
PageHeader,
} from 'antd';
import {
getPartyDashboardData,
getServerDashboardData,
postUpdateVersionActive,
postUpdateVideoSelectors,
} from './Api';
import {
CheckCircleTwoTone,
CloseCircleTwoTone,
ReloadOutlined,
LoadingOutlined,
EnterOutlined,
DeleteOutlined,
PlusCircleFilled,
SaveFilled,
SaveOutlined,
LogoutOutlined,
SendOutlined,
PlusCircleOutlined,
} from '@ant-design/icons';
const PLAYER_SOURCES = [
{ text: 'Netflix', value: 'netflix.' },
{ text: 'Amazon', value: 'amazon.' },
{ text: 'Hulu', value: 'hulu.' },
{ text: 'Disney+', value: 'disneyplus.' },
{ text: 'YouTube', value: 'youtube.' },
{ text: 'HBO Now', value: 'hbonow.' },
{ text: 'HBO Max', value: 'hbomax.' },
{ text: 'Peacock', value: 'peacocktv.' },
{ text: 'Google Play', value: 'play.google.' },
{ text: 'Crunchyroll', value: 'crunchyroll.' },
];
const partiesColumns = [
{
title: 'Party Name',
dataIndex: 'id',
sorter: (a, b) => (a.id.toLowerCase() < b.id.toLowerCase() ? -1 : 1),
},
{
title: 'Source',
dataIndex: 'source',
sorter: (a, b) => (a.source.toLowerCase() < b.source.toLowerCase() ? -1 : 1),
filters: PLAYER_SOURCES,
onFilter: (v, r) => r.source === v,
render: (a) => PLAYER_SOURCES.filter((p) => p.value === a)[0].text,
},
{
title: 'Created At',
dataIndex: 'createTime',
defaultSortOrder: 'descend',
sorter: (a, b) => Date.parse(a.createTime) - Date.parse(b.createTime),
render: (a) => new Date(Date.parse(a)).toLocaleString(),
},
{
title: 'Updated At',
dataIndex: 'updateTime',
sorter: (a, b) => Date.parse(a.updateTime) - Date.parse(b.updateTime),
render: (a) => {
const minutes = (Date.now() - Date.parse(a)) / 60000;
const dS = new Date(a).toLocaleString();
return (
<Tag color={minutes <= 5 ? 'success' : minutes <= 15 ? 'processing' : 'default'}>{dS}</Tag>
);
},
},
{
title: 'On Call',
dataIndex: 'onCall',
filters: [
{
text: 'On Call',
value: true,
},
{
text: 'Not On Call',
value: false,
},
],
render: (a) =>
a ? (
<CheckCircleTwoTone color="#c0f300" id="call-icon" />
) : (
<CloseCircleTwoTone id="call-icon" />
),
onFilter: (v, r) => r.onCall === v,
},
];
export default class Dashboard extends Component {
constructor(props) {
super(props);
this.state = {
partiesOverview: null,
activeTab: '1',
refreshing: true,
selectedParties: [],
nameSelectors: {},
selectors: {},
versions: [],
};
this.serverStatusListener = null;
}
componentDidMount() {
this.getPartiesOverview();
this.getVersionsOverview();
}
getPartiesOverview = async () => {
getPartyDashboardData()
.then((res) => res.json())
.then((res) => {
console.log(res);
this.setState({ partiesOverview: res.partyData, refreshing: false });
});
};
getVersionsOverview = () => {
getServerDashboardData()
.then((res) => res.json())
.then((res) => this.setState({ ...res.serverData, refreshing: false }));
};
refreshData = () => {
this.setState({ refreshing: true });
const { activeTab } = this.state;
switch (activeTab) {
case '1':
this.getPartiesOverview();
break;
case '2':
case '3':
case '4':
this.getVersionsOverview();
break;
}
};
tabChange = (activeTab) => {
this.setState({ activeTab });
};
updateVersionMessage = (message, version) => {
this.setState({ refreshing: true });
const { versions } = this.state;
const versionData = versions.filter((v) => v.version.split('.').join('') === version)[0];
versionData.message = message;
this.updateVersion(version, versionData);
};
updateVersionActive = (active, version) => {
this.setState({ refreshing: true });
const { versions } = this.state;
const versionData = versions.filter((v) => v.version.split('.').join('') === version)[0];
versionData.active = active;
this.updateVersion(version, versionData);
};
updateVersion = (version, versionData) => {
postUpdateVersionActive({ version, versionData })
.then(() => this.refreshData())
.catch((err) =>
notification.error({
description: err.message,
message: 'Unable To Update',
})
);
};
viewPartyDetails(party) {
console.log(party);
}
getSourceName(source) {
return PLAYER_SOURCES.filter((p) => p.value === source)[0].text;
}
handleVideoSelectorChange = (e, s) => {
const { selectors } = this.state;
selectors[s] = e.target.value;
this.setState({ selectors });
};
updateVideoSelector = () => {
this.setState({ refreshing: true });
const { selectors } = this.state;
postUpdateVideoSelectors({ selectors })
.then(() => this.refreshData())
.catch((err) => {
notification.error({
description: err.message,
message: 'Unable To Update',
});
this.setState({ refreshing: false });
});
};
handleVersionMessageChange = (e, v) => {
const { versions } = this.state;
versions.filter((vs) => vs.version === v)[0].message = e.target.value;
this.setState({ versions });
};
handleAlertTitleChange = (value, version, index) => {
const { versions } = this.state;
versions.filter((vs) => vs.version === version)[0].alerts[index].title = value;
this.setState({ versions });
};
handleAlertDescriptionChange = (value, version, index) => {
const { versions } = this.state;
versions.filter((vs) => vs.version === version)[0].alerts[index].description = value;
this.setState({ versions });
};
handleAlertLinkChange = (value, version, index) => {
const { versions } = this.state;
versions.filter((vs) => vs.version === version)[0].alerts[index].link = value;
this.setState({ versions });
};
handleAlertLinkTextChange = (value, version, index) => {
const { versions } = this.state;
versions.filter((vs) => vs.version === version)[0].alerts[index].linkText = value;
this.setState({ versions });
};
deleteAlert = (version, index) => {
const { versions } = this.state;
versions.filter((vs) => vs.version === version)[0].alerts.splice(index, 1);
if (
versions.filter((vs) => vs.version === version)[0].importantAlert === index ||
versions.filter((vs) => vs.version === version)[0].alerts.length < index
) {
versions.filter((vs) => vs.version === version)[0].importantAlert = undefined;
}
this.setState({ versions });
};
addEmptyAlert = (version) => {
const { versions } = this.state;
versions
.filter((vs) => vs.version === version)[0]
.alerts.push({
title: '',
description: '',
link: '',
linkText: '',
});
this.setState({ versions });
};
saveAlerts = (version) => {
this.setState({ refreshing: true });
const { versions } = this.state;
const versionData = versions.filter((v) => v.version === version)[0];
if (versionData.alerts.length > 0) this.updateVersion(version.split('.').join(''), versionData);
else {
notification.error({
message: 'Missing Alerts',
description: 'Must have atleast one alert',
});
this.setState({ refreshing: false });
}
};
changeImportantAlert = (active, version, index) => {
const { versions } = this.state;
if (active === false) {
versions.filter((vs) => vs.version === version)[0].importantAlert = undefined;
} else {
versions.filter((vs) => vs.version === version)[0].importantAlert = index;
}
this.setState({ versions });
};
getAddMessageText = () => {
const { activeTab } = this.state;
switch (activeTab) {
case '1':
return 'Send Message';
case '2':
return 'Add Version';
case '3':
return 'Add Video Selector';
case '4':
return 'Add Name Selector';
}
};
render() {
const {
partiesOverview,
activeTab,
refreshing,
selectedParties,
versions,
nameSelectors,
selectors,
} = this.state;
const versionsColumns = [
{
title: 'Version',
dataIndex: 'version',
defaultSortOrder: 'descend',
sorter: (a, b) =>
parseInt(a.version.split('.').join('')) - parseInt(b.version.split('.').join('')),
},
{
title: 'Active',
dataIndex: 'active',
filters: [
{
text: 'Active',
value: true,
},
{
text: 'Inactive',
value: false,
},
],
onFilter: (v, r) => r.active === v,
render: (a, r) => (
<Switch
disabled={refreshing}
onChange={(c) => this.updateVersionActive(c, r.version.split('.').join(''))}
checked={a}
/>
),
},
{
title: 'Inactive Message',
dataIndex: 'message',
render: (a, r) => (
<Input.Search
disabled={refreshing}
onSearch={(v) => this.updateVersionMessage(v, r.version.split('.').join(''))}
value={a}
onChange={(e) => this.handleVersionMessageChange(e, r.version)}
enterButton={<EnterOutlined />}
/>
),
},
];
return (
<div className="dashboard-wrapper">
<Card>
<PageHeader
extra={[
<Button
key="3"
loading={refreshing}
disabled={refreshing}
onClick={this.refreshData}
shape="circle"
icon={refreshing ? <LoadingOutlined /> : <ReloadOutlined />}
/>,
<Button
key="2"
icon={activeTab === '1' ? <SendOutlined /> : <PlusCircleOutlined />}
disabled={activeTab === '1' && selectedParties.length === 0}
>
{this.getAddMessageText()}
</Button>,
<Button
onClick={() => firebase.auth().signOut()}
key="1"
type="primary"
danger
icon={<LogoutOutlined />}
>
Logout
</Button>,
]}
/>
<Tabs activeKey={activeTab} onChange={this.tabChange}>
<Tabs.TabPane tab="Parties" key="1">
<Table
rowSelection={{
selectedRowKeys: selectedParties,
onChange: (selectedParties) => this.setState({ selectedParties }),
}}
loading={partiesOverview === null || refreshing}
pagination={{
position: ['bottomCenter'],
total: partiesOverview ? partiesOverview.length : 0,
showTotal: (t, r) => `${r[0]} to ${r[1]} of ${t}`,
defaultPageSize: 10,
showSizeChanger: true,
pageSizeOptions: [10, 25, 50],
}}
rowKey={(p) => p.id}
onRow={(r) => ({
onClick: () => this.viewPartyDetails(r),
})}
dataSource={partiesOverview}
columns={partiesColumns}
/>
</Tabs.TabPane>
<Tabs.TabPane tab="Versions" key="2">
<Table
loading={refreshing}
pagination={{ position: ['bottomCenter'] }}
rowKey={(p) => p.version}
dataSource={versions}
columns={versionsColumns}
expandable={{
rowExpandable: (r) => r.alerts,
expandedRowClassName: () => 'alerts-wrapper',
expandedRowRender: (r) => (
<Row>
{r.alerts.map((a, i) => (
<Col key={`${r.version}-alerts-${i}`}>
<Card
actions={[
<Button
onClick={() => this.deleteAlert(r.version, i)}
danger
icon={<DeleteOutlined />}
shape="circle"
color="danger"
/>,
<Switch
onClick={(active) =>
this.changeImportantAlert(active, r.version, i)
}
checked={r.importantAlert && r.importantAlert === i}
/>,
]}
>
<Input
placeholder="title"
value={a.title}
onChange={(e) =>
this.handleAlertTitleChange(e.target.value, r.version, i)
}
/>
<Input.TextArea
placeholder="Description"
autoSize={{ minRows: 2, maxRows: 2 }}
value={a.description}
onChange={(e) =>
this.handleAlertDescriptionChange(e.target.value, r.version, i)
}
/>
<Divider orientation="left">Link</Divider>
<Input.Group>
<Input
value={a.linkText}
placeholder="Link Text"
onChange={(e) =>
this.handleAlertLinkTextChange(e.target.value, r.version, i)
}
/>
<Input
value={a.link}
placeholder="Link"
onChange={(e) =>
this.handleAlertLinkChange(e.target.value, r.version, i)
}
/>
</Input.Group>
</Card>
</Col>
))}
<Col>
<Card
onClick={() => this.addEmptyAlert(r.version)}
className="add-alert-card"
>
<PlusCircleFilled />
</Card>
</Col>
<Col>
<Card onClick={() => this.saveAlerts(r.version)} className="add-alert-card">
<SaveFilled />
</Card>
</Col>
</Row>
),
}}
/>
</Tabs.TabPane>
<Tabs.TabPane tab="Video Selectors" key="3">
{Object.keys(selectors)
.sort()
.map((source) => (
<Input
key={`video-selector-${source}`}
className="video-selector-input"
addonBefore={this.getSourceName(source)}
value={selectors[source]}
onChange={(e) => this.handleVideoSelectorChange(e, source)}
/>
))}
<Button
block
type="primary"
icon={<SaveOutlined />}
className="save-video-selector"
onClick={this.updateVideoSelector}
loading={refreshing}
>
Save Video Selectors
</Button>
</Tabs.TabPane>
<Tabs.TabPane tab="Name Selectors" key="4">
name selectors
</Tabs.TabPane>
</Tabs>
</Card>
</div>
);
}
}
+62
View File
@@ -0,0 +1,62 @@
import React, { Component } from 'react';
import { Card, Space, Input, Button, notification } from 'antd';
import { UnlockFilled } from '@ant-design/icons';
import firebase from 'firebase/app';
import 'firebase/auth';
export default class Login extends Component {
constructor(props) {
super(props);
this.state = {
email: '',
password: '',
loading: false,
};
}
login = async () => {
const { email, password } = this.state;
this.setState({ loading: true });
await firebase.auth().setPersistence(firebase.auth.Auth.Persistence.SESSION);
firebase
.auth()
.signInWithEmailAndPassword(email, password)
.catch((err) => {
this.setState({ loading: false });
notification.error({
message: 'Failed Login',
description: err.message,
placement: 'bottomRight',
});
});
};
render() {
const { loading, email, password } = this.state;
return (
<div className="login-card-wrapper">
<Card
title="Login"
actions={[
<Button onClick={this.login} icon={<UnlockFilled key="login" />} loading={loading}>
LOGIN
</Button>,
]}
>
<Space direction="vertical">
<Input
value={email}
onChange={(e) => this.setState({ email: e.target.value })}
placeholder="E-Mail"
type="email"
/>
<Input.Password
onPressEnter={this.login}
value={password}
onChange={(e) => this.setState({ password: e.target.value })}
placeholder="Password"
/>
</Space>
</Card>
</div>
);
}
}
+83
View File
@@ -0,0 +1,83 @@
@import '~antd/dist/antd.css';
@import '~antd/dist/antd.dark.css' screen and (prefers-color-scheme: dark);
#root,
#root .app-wrapper {
width: 100%;
height: 100%;
}
.login-card-wrapper {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
.login-card-wrapper .ant-card {
border-radius: 26px;
transition: all ease-in-out 0.3s;
}
.login-card-wrapper .ant-card:hover {
filter: drop-shadow(0px 0px 50px rgba(170, 170, 170, 0.2));
}
.login-card-wrapper .ant-card-actions {
border-radius: 26px;
border-top-right-radius: 0px;
border-top-left-radius: 0px;
}
.dashboard-wrapper .ant-card {
margin: 35px 70px;
border-radius: 26px;
filter: drop-shadow(0px 0px 5px rgba(221, 221, 221, 0.438));
}
.ant-table-content {
overflow-x: auto;
}
.video-selector-input {
margin: 10px 0;
}
.alerts-wrapper {
max-height: 300px !important;
overflow: hidden !important;
}
.alerts-wrapper .ant-card {
margin: 5px 2px !important;
filter: none !important;
border-radius: initial !important;
}
.alerts-wrapper .ant-card-body input,
.ant-card-body textarea {
margin: 10px auto;
}
.alerts-wrapper .add-alert-card {
width: calc(400px - 2px);
height: calc(400px - 5px);
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
.alerts-wrapper .add-alert-card svg {
width: 64px;
height: 64px;
}
.save-video-selector {
margin-top: 10px;
}
span#call-icon svg {
width: 25px;
height: 25px;
}
+20
View File
@@ -0,0 +1,20 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.jsx';
import firebase from 'firebase/app';
import './index.css';
const firebaseConfig = {
apiKey: 'AIzaSyCJLuYpzXLDRmu2ttzFuSGmYcpFxqToWFU',
authDomain: 'watch-party-crx.firebaseapp.com',
databaseURL: 'https://watch-party-crx.firebaseio.com',
projectId: 'watch-party-crx',
storageBucket: 'watch-party-crx.appspot.com',
messagingSenderId: '712242707190',
appId: '1:712242707190:web:65fa7f772e30da364b0ad7',
measurementId: 'G-EZS4PEVG4T',
};
firebase.initializeApp(firebaseConfig);
ReactDOM.render(<App />, document.getElementById('root'));
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

+13
View File
@@ -0,0 +1,13 @@
const superAuth = require('./superAuth');
const partyDashboard = require('./partyDashboard');
const serverDashboard = require('./serverDashboard');
const updateVersionActive = require('./updateVersionActive');
const updateVideoSelectors = require('./updateVideoSelectors');
module.exports = {
updateVideoSelectors,
updateVersionActive,
serverDashboard,
partyDashboard,
superAuth,
};
+55
View File
@@ -0,0 +1,55 @@
/* eslint-disable promise/no-nesting */
const admin = require('firebase-admin');
const Logger = require('../Logger');
const mLogger = new Logger('admin', 'partyDashboard');
module.exports = async (req, res) => {
mLogger.start();
var partyData = await admin
.firestore()
.collection('rooms')
.get()
.then((roomSnapshot) => {
const docData = [];
roomSnapshot.docs.forEach((doc) => {
docData.push({
id: doc.id,
createTime: doc.createTime.toDate().toUTCString(),
updateTime: doc.updateTime.toDate().toUTCString(),
source: doc.data().player.source,
onCall: doc.data().hasOwnProperty('conference'),
showName: doc.data().player.showName || '',
time: doc.data().player.time || 0,
status: doc.data().player.status || '',
});
});
return docData;
})
.catch((err) => {
mLogger.error('FAILED TO RESOLVE PARTIES', err);
res.status(500).send({ message: 'Unable to get all parties', error: err });
});
await partyData.forEach((party) => {
const messageCount = admin
.firestore()
.collection('rooms')
.doc(party.id)
.collection('messages')
.get()
.then((snap) => snap.size);
const userCount = admin
.firestore()
.collection('rooms')
.doc(party.id)
.collection('users')
.get()
.then((snap) => snap.size);
party.messageCount = messageCount || 0;
party.userCount = userCount || 0;
});
mLogger.success('GOT ALL PARTIES', { parties: partyData.length });
return res.status(200).send({ partyData });
};
+30
View File
@@ -0,0 +1,30 @@
const admin = require('firebase-admin');
const Logger = require('../Logger');
const mLogger = new Logger('admin', 'serverDashboard');
module.exports = async (req, res) => {
mLogger.start();
const serverData = await admin
.firestore()
.collection('server')
.doc('status')
.get()
.then((snap) => {
const status = snap.data();
const { nameSelectors, selectors } = status;
delete status.nameSelectors;
delete status.selectors;
const versions = Object.keys(status).map((v) =>
Object.assign({ version: v.split('').join('.') }, status[v])
);
return { nameSelectors, selectors, versions };
})
.catch((err) => {
mLogger.error('FAILED TO RESOLVE SERVER', err);
res.status(500).send({ message: 'Unable to get all parties', error: err });
});
mLogger.success('GOT ALL SERVER STATUS');
return res.status(200).send({ serverData });
};
+54
View File
@@ -0,0 +1,54 @@
const admin = require('firebase-admin');
const Logger = require('../Logger');
const mLogger = new Logger('admin', 'superAuth');
module.exports = (req, res, next) => {
mLogger.start();
res.set('Access-Control-Allow-Origin', ['*']);
if (req.method === 'OPTIONS') {
mLogger.success('OPTIONS CALL', { URL: req.url });
res.status(200).send();
return next();
}
if (req.headers.authorization) {
try {
const token = req.headers.authorization.split(' ')[1];
const reqToken = token.split(':')[0];
const reqUid = token.split(':')[1];
admin
.auth()
.verifyIdToken(reqToken)
.then((user) => {
if (user.uid === 'cgEJNxHVFxTeCCeWnAiNyalaFax1' && user.uid === reqUid) {
if (req.method === 'POST') {
const bodydata = JSON.parse(req.body);
bodydata.uid = user.uid;
req.body = JSON.stringify(bodydata);
}
if (req.method === 'GET' || req.method === 'DELETE' || req.method === 'PUT') {
req.body = JSON.stringify({ uid: user.uid });
}
mLogger.success('AUTHORIZED', { URL: req.url, UID: reqUid });
return next();
} else {
mLogger.success('NOT AUTHORIZED', { URL: req.url });
return res.status(401).send({ message: 'Unauthorized' });
}
})
.catch((err) => {
mLogger.error('AUTH VALIDATE FIREBASE ERROR', {
URL: req.url,
Error: err,
});
return res.status(500).send({ message: 'Unable to verify user token' });
});
} catch (err) {
mLogger.error('AUTH VALIDATE ERROR', { URL: req.url, Error: err });
return res.status(401).send({ message: 'Something went wrong when authorizing' });
}
} else {
mLogger.error('NO AUTH TOKEN', { URL: req.url });
res.status(401).send({ message: 'Got no user token' });
}
return 0;
};
+41
View File
@@ -0,0 +1,41 @@
const admin = require('firebase-admin');
const Logger = require('../Logger');
const mLogger = new Logger('admin', 'updateVersionActive');
module.exports = async (req, res) => {
mLogger.start();
const { version, versionData } = JSON.parse(req.body);
if (version && versionData) {
delete versionData.version;
if (versionData.importantAlert === null || versionData.importantAlert === undefined) {
await admin
.firestore()
.collection('server')
.doc('status')
.update({
[`${version}.importantUpdate`]: admin.firestore.FieldValue.delete(),
})
.catch((err) => {
mLogger.error('FAILED TO DELETE IMPORTANT ALERT', err);
res.status(500).send({ message: 'FAILED TO DELETE IMPORTANT ALERT', error: err });
});
delete versionData.importantAlert;
}
await admin
.firestore()
.collection('server')
.doc('status')
.update({
[version]: versionData,
})
.catch((err) => {
mLogger.error('FAILED TO UPDATE', err);
res.status(500).send({ message: 'FAILED TO UPDATE', error: err });
});
mLogger.success('UPDATE VERSION ACTIVE STATUS', Object.assign(versionData, versionData));
res.status(200).send({ message: 'Updated version active' });
} else {
mLogger.error('VALUES NOT RECEIVED');
res.status(500).send({ message: 'Values not received' });
}
};
+26
View File
@@ -0,0 +1,26 @@
const admin = require('firebase-admin');
const Logger = require('../Logger');
const mLogger = new Logger('admin', 'updateVideoSelectors');
module.exports = async (req, res) => {
mLogger.start();
const { selectors } = JSON.parse(req.body);
if (selectors) {
await admin
.firestore()
.collection('server')
.doc('status')
.update({
selectors,
})
.catch((err) => {
mLogger.error('FAILED TO UPDATE', err);
res.status(500).send({ message: 'FAILED TO UPDATE', error: err });
});
mLogger.success('UPDATED SELECTORS');
res.status(200).send({ message: 'Updated video selectors' });
} else {
mLogger.error('VALUES NOT RECEIVED');
res.status(500).send({ message: 'Values not received' });
}
};
+11
View File
@@ -1,5 +1,6 @@
const FunctionModules = require('./modules/index');
const Webhooks = require('./webhooks/index');
const AdminModules = require('./admin/index');
const express = require('express');
const cors = require('cors');
@@ -26,7 +27,17 @@ app.delete('/leave/:rid/users/:uid', FunctionModules.leaveRoom);
app.delete('/delete/:rid', FunctionModules.deleteRoom);
app.put('/updateAdminOnly/:rid/:adminOnly', FunctionModules.updateAdminOnly);
// ADMIN
const admin = express();
admin.use(cors({ origin: ['http://localhost:3000', 'https://admin.chromewatchparty.com/*'] }));
admin.use(AdminModules.superAuth);
admin.get('/partyDashboard', AdminModules.partyDashboard);
admin.get('/serverDashboard', AdminModules.serverDashboard);
admin.post('/updateVersionActive', AdminModules.updateVersionActive);
admin.post('/updateVideoSelectors', AdminModules.updateVideoSelectors);
module.exports = {
v1: app,
webhooks,
admin,
};
+2
View File
@@ -14,3 +14,5 @@ admin.initializeApp(FIREBASE_OPTIONS);
exports.widgets = functions.https.onRequest(REST.v1);
// WEBHOOKS
exports.webhooks = functions.https.onRequest(REST.webhooks);
// ADMIN
exports.admin = functions.https.onRequest(REST.admin);
+6 -10
View File
@@ -1,4 +1,4 @@
const admin = require("firebase-admin");
const admin = require('firebase-admin');
const timeDiff = (ds1, ds2) => {
const d1 = new Date(ds1);
@@ -15,7 +15,7 @@ const cleanUser = async (pT, sT) => {
.listUsers(1000, pT)
.then((listUsersResult) => {
listUsersResult.users.forEach((userRecord) => {
if (timeDiff(userRecord.metadata.lastSignInTime, sT) >= 24 * 7) {
if (timeDiff(userRecord.metadata.lastRefreshTime, sT) >= 24) {
usersToDelete.push(userRecord.uid);
}
});
@@ -25,19 +25,17 @@ const cleanUser = async (pT, sT) => {
return;
})
.catch((error) => {
console.log("Error listing users:", error);
console.log('Error listing users:', error);
});
return usersToDelete;
};
module.exports = async (req, res) => {
console.info(`[START]\tWEBHOOKS/User Clean Up`);
res.status(200).send({ message: "User Clean Up Started" });
res.status(200).send({ message: 'User Clean Up Started' });
const startedTime = new Date();
const usersToDelete = await cleanUser(undefined, startedTime);
console.info(
`[PROGRESS]\tWEBHOOKS/User Clean Up\t# OF USERS ${usersToDelete.length}`
);
console.info(`[PROGRESS]\tWEBHOOKS/User Clean Up\t# OF USERS ${usersToDelete.length}`);
admin
.auth()
.deleteUsers(usersToDelete)
@@ -46,7 +44,5 @@ module.exports = async (req, res) => {
`[ERROR]\tWEBHOOKS/User Clean Up\t# OF USERS ${usersToDelete.length},\tERROR:${err}`
)
);
console.info(
`[DONE]\tWEBHOOKS/User Clean Up\t# OF USERS ${usersToDelete.length}`
);
console.info(`[DONE]\tWEBHOOKS/User Clean Up\t# OF USERS ${usersToDelete.length}`);
};