-
-
-
-
-
-
- Album: {{ photo.album_name }}
- Taken on: {{ photo.date_taken }}
-
-
-
-
-
- Nothing currently in the queue...
- Visit protu.be to add some tunes!
-
-
+
-
-
+
+
+
+
+ Nothing currently in the queue...
+ Visit protu.be to add some tunes!
@@ -96,7 +61,6 @@
diff --git a/deployment.yml b/deployment.yml
new file mode 100644
index 0000000..3fa09ea
--- /dev/null
+++ b/deployment.yml
@@ -0,0 +1,38 @@
+version: "3.1"
+
+services:
+ mysql:
+ image: "mysql:8.0"
+ restart: always
+ environment:
+ MYSQL_ROOT_PASSWORD: "thisisareallystrongpassword123"
+ MYSQL_DATABASE: "protube_production"
+ MYSQL_USER: "protube_mainuser"
+ MYSQL_PASSWORD: "hetan9t84anne"
+
+ nginx-proxy:
+ image: "jc21/nginx-proxy-manager:latest"
+ restart: always
+ ports:
+ - "80:80"
+ - "81:81"
+ - "443:443"
+ volumes:
+ - ./data:/data
+ - ./letsencrypt:/etc/letsencrypt
+
+ protube:
+ image: "hyttioaoac/protube:latest"
+ restart: "always"
+ pull_policy: "always"
+ volumes:
+ - ./protube/.env:/xxxx/.env
+ - ./protube/logs:/xxxx/logs
+
+ phpmyadmin:
+ image: phpmyadmin
+ restart: always
+ ports:
+ - 8080:80
+ environment:
+ - PMA_HOST=mysql
diff --git a/server/.env.example b/server/.env.example
index 03cfa9f..a6358f7 100644
--- a/server/.env.example
+++ b/server/.env.example
@@ -22,10 +22,8 @@ SSL_CERT_FILE=./ssl/selfsigned.crt
# Make sure this corresponds to the host of the web app
CORS_ORIGIN=https://localhost:3001
-# API key to access the Proto website's API (make sure this is set correctly on the website instance)
-LARAVEL_API_KEY=SECRET_KEY
-# API key to allow the Proto website instance to access the ProTube API
-PROTUBE_API_SECRET=321
+# API key to allow the Proto website and ProTube to use aech others API
+PROTUBE_SHARED_SECRET=321
# Endpoint for OAuth2 authentication/API (Should be the URL of the local Proto website instance)
LARAVEL_ENDPOINT=http://localhost:8080
diff --git a/server/docker-compose.yml b/server/docker-compose.yml
index 24cc4be..6d027f8 100644
--- a/server/docker-compose.yml
+++ b/server/docker-compose.yml
@@ -12,3 +12,14 @@ services:
MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
ports:
- "${DATABASE_PORT}:3306"
+
+ phpmyadmin:
+ container_name: "${DOCKER_CONTAINER_NAME}-pma"
+ image: phpmyadmin
+ ports:
+ - "3002:80"
+ environment:
+ - "PMA_HOST=mysql"
+ - "PMA_USER=${DATABASE_USER}"
+ - "PMA_PASSWORD=${DATABASE_PASSWORD}"
+ - "PMA_PORT=3002"
diff --git a/server/modules/AuthService.js b/server/modules/AuthService.js
index 847de67..1f65a2f 100644
--- a/server/modules/AuthService.js
+++ b/server/modules/AuthService.js
@@ -14,7 +14,6 @@ passport.use(
tokenURL: tokenURL,
clientID: process.env.OAUTH_CLIENT_ID,
clientSecret: process.env.OAUTH_CLIENT_SECRET,
- //callbackURL: "http://localhost:3000/api/auth/example/callback"
},
async function (accessToken, refreshToken, profile, done) {
let response = await fetch(
@@ -30,13 +29,14 @@ passport.use(
await User.upsert({
id: userData.id,
name: userData.name,
- admin: +userData.admin,
+ admin_from: +userData.admin_from,
+ admin_until: +userData.admin_until,
refresh_token: refreshToken,
access_token: accessToken,
});
return done(null, {
id: userData.id,
- admin: userData.admin,
+ admin_until: userData.admin_until,
name: userData.name,
});
}
diff --git a/server/modules/DataBase.js b/server/modules/DataBase.js
index 1afdf7b..7c7d858 100644
--- a/server/modules/DataBase.js
+++ b/server/modules/DataBase.js
@@ -38,11 +38,13 @@ this.sequelize
class User extends Model {
isAdmin() {
- return this.admin;
+ return (
+ this.admin_from < getCurrentUnix() && this.admin_until > getCurrentUnix()
+ );
}
hasValidRemote() {
- return this.admin || this.valid_remote_until > getCurrentUnix();
+ return this.isAdmin() || this.valid_remote_until > getCurrentUnix();
}
setValidRemote() {
@@ -73,7 +75,8 @@ User.init(
primaryKey: true,
},
name: DataTypes.TEXT,
- admin: DataTypes.BOOLEAN,
+ admin_from: DataTypes.INTEGER,
+ admin_until: DataTypes.INTEGER,
valid_remote_until: {
type: DataTypes.INTEGER,
defaultValue: 0,
@@ -106,7 +109,7 @@ exports.Session = this.sequelize.define("sessions", {
data: DataTypes.TEXT,
});
-//syncing tables
+//syncing tables and drop them in production upon (re)start
this.sequelize
.sync({ force: process.env.NODE_ENV === "production" })
.then(() => {
diff --git a/server/modules/Middlewares.js b/server/modules/Middlewares.js
index 2a24fd1..7a4c1c5 100644
--- a/server/modules/Middlewares.js
+++ b/server/modules/Middlewares.js
@@ -45,7 +45,7 @@ exports.socketCheckAuthenticated = (socket, next) => {
exports.socketCheckAdminAuthenticated = (socket, next) => {
if (socket.request.isAuthenticated()) {
- if (socket.request.user.admin) return next();
+ if (socket.request.user.isAdmin()) return next();
else return next(new Error("forbidden"));
}
// accept localhost connections also to be admin (local client)
@@ -103,7 +103,7 @@ exports.screenCodeCheck = async (socket, next) => {
};
exports.checkBearerToken = (req, res, next) => {
- if (req.token === process.env.PROTUBE_API_SECRET) return next();
+ if (req.token === process.env.PROTUBE_SHARED_SECRET) return next();
return res.status(401).json({
success: false,
message: "Not Authorized for this API",
diff --git a/server/modules/PlaybackManager.js b/server/modules/PlaybackManager.js
index 242f890..0ba4728 100644
--- a/server/modules/PlaybackManager.js
+++ b/server/modules/PlaybackManager.js
@@ -9,7 +9,7 @@ let playerType = enums.TYPES.VIDEO;
let selectedRadioStation = {};
let playbackInterval;
-let volume = 75;
+let volume = 35;
let timestamp = 0;
exports.getCurrentVideoTimestamp = () => timestamp;
diff --git a/server/modules/ScreenSettings.js b/server/modules/ScreenSettings.js
new file mode 100644
index 0000000..000e1df
--- /dev/null
+++ b/server/modules/ScreenSettings.js
@@ -0,0 +1,21 @@
+let showQueue = true;
+let showPhotos = true;
+
+exports.getCurrentSetting = () => {
+ return {
+ showQueue: showQueue,
+ showPhotos: showPhotos,
+ };
+};
+
+exports.toggleQueueVisibility = () => {
+ showQueue = !showQueue;
+ eventBus.emit("queue-photos-visibility-changed", this.getCurrentSetting());
+ return enums.SUCCESS;
+};
+
+exports.togglePhotosVisibility = () => {
+ showPhotos = !showPhotos;
+ eventBus.emit("queue-photos-visibility-changed", this.getCurrentSetting());
+ return enums.SUCCESS;
+};
diff --git a/server/modules/api_endpoints/ProtubeApi.js b/server/modules/api_endpoints/ProtubeApi.js
index 03f2ff7..f4414e0 100644
--- a/server/modules/api_endpoints/ProtubeApi.js
+++ b/server/modules/api_endpoints/ProtubeApi.js
@@ -1,6 +1,7 @@
const express = require("express");
const bearerToken = require("express-bearer-token");
const { checkBearerToken } = require("../Middlewares");
+const fetch = require("node-fetch");
const { playSound } = require("../socket_endpoints/SoundBoard");
const { playNextVideo, getPlayerMode } = require("../PlaybackManager");
const { User } = require("../DataBase");
@@ -11,51 +12,29 @@ this.protubeApi.use(bearerToken());
this.protubeApi.use(checkBearerToken);
// Endpoint to update the admin status of a user id
-this.protubeApi.post("/updateadmin", async function (req, res) {
+this.protubeApi.post("/updateadmin/:userID", async function (req, res) {
+ const userID = parseInt(req.params.userID);
+
logger.apiInfo(
- `Attempt from ${req.hostname} to update the admin status of a user`
+ `Attempt from ${req.hostname} to update the admin status of user ${userID}`
);
- // Check if the required data is present and parse it
- if (!req.body?.user_id || !req.body?.admin) {
- logger.apiInfo("Request had incomplete body");
- return res.send({ success: enums.FAIL, message: "Incomplete body" });
- }
- const userID = parseInt(req.body.user_id);
- const isAdmin = parseInt(req.body.admin) === 1;
-
// finding and updating the users admin status in the database
const user = await User.findByPk(userID);
-
if (!user) {
logger.apiInfo("User is not found!");
return res.send({ success: enums.FAIL, message: "User not found!" });
}
- user.admin = isAdmin;
- await user.save();
- logger.apiInfo(`User ${userID}'s new admin status: ${isAdmin}`);
-
- if (isAdmin) return res.send({ success: enums.SUCCESS });
-
- // disconnecting admin screen and remote sockets if present
- const sockets = await io.of("/socket/remote/admin").fetchSockets();
- const screenSockets = await io.of("/socket/screen/admin").fetchSockets();
-
- for (const sock of sockets) {
- await sock.request.user.reload();
- if (sock.request.user.id === userID) {
- logger.apiInfo(`Disconnected admin remote socket`);
- sock.disconnect(true);
- }
- }
-
- for (const sock of screenSockets) {
- await sock.request.user.reload();
- if (sock.request.user.id === userID) {
- logger.apiInfo(`Disconnected admin screen socket`);
- sock.disconnect(true);
- }
- }
-
+ // Fetch the new userdata and update the user
+ fetch(`${process.env.LARAVEL_ENDPOINT}/api/protube/userdetails`, {
+ headers: {
+ Authorization: "Bearer " + user.access_token,
+ },
+ }).then(async (response) => {
+ const userData = await response.json();
+ user.admin_from = +userData.admin_from;
+ user.admin_until = +userData.admin_until;
+ await user.save();
+ });
return res.send({ success: enums.SUCCESS });
});
diff --git a/server/modules/socket_endpoints/AdminRemote.js b/server/modules/socket_endpoints/AdminRemote.js
index 1867c34..8383651 100644
--- a/server/modules/socket_endpoints/AdminRemote.js
+++ b/server/modules/socket_endpoints/AdminRemote.js
@@ -13,6 +13,7 @@ const {
const { adminResetScreenCode } = require("../ScreenCode");
const radio = require("../RadioStations");
const queueManager = require("../QueueManager");
+const screenSettings = require("../ScreenSettings");
endpoint.use(socketCheckAdminAuthenticated);
@@ -40,6 +41,7 @@ endpoint.on("connection", (socket) => {
volume: getVolume(),
playerMode: getPlayerMode(),
playerType: getPlayerType(),
+ screenSettings: screenSettings.getCurrentSetting(),
});
});
@@ -97,6 +99,24 @@ endpoint.on("connection", (socket) => {
}
});
+ socket.on("toggle-photos-visibility", (callback) => {
+ logger.adminInfo(`${socket.id} Requested to change the screensetting`);
+ try {
+ callback({ success: screenSettings.togglePhotosVisibility() });
+ } catch (e) {
+ callback(e.getInfo());
+ }
+ });
+
+ socket.on("toggle-queue-visibility", (callback) => {
+ logger.adminInfo(`${socket.id} Requested to change the screensetting`);
+ try {
+ callback({ success: screenSettings.toggleQueueVisibility() });
+ } catch (e) {
+ callback(e.getInfo());
+ }
+ });
+
// change the screen's volume
socket.on("set-new-volume", (volume, callback) => {
logger.adminInfo(
@@ -121,10 +141,15 @@ eventBus.on("queue-update", () => {
});
});
+eventBus.on("queue-photos-visibility-changed", (newSetting) => {
+ endpoint.emit("queue-photos-visibility-changed", newSetting);
+});
+
function updateAdminPanels() {
endpoint.emit("update-admin-panel", {
volume: getVolume(),
playerMode: getPlayerMode(),
playerType: getPlayerType(),
+ screenSettings: screenSettings.getCurrentSetting(),
});
}
diff --git a/server/modules/socket_endpoints/RemoteSocket.js b/server/modules/socket_endpoints/RemoteSocket.js
index 2dca179..d2ba8c4 100644
--- a/server/modules/socket_endpoints/RemoteSocket.js
+++ b/server/modules/socket_endpoints/RemoteSocket.js
@@ -19,7 +19,7 @@ endpoint.on("connection", (socket) => {
const result = await youtube.search(
request.query,
request.continuationToken,
- socket.request.user.admin,
+ socket.request.user.isAdmin(),
queueManager.getQueue()
);
callback(result);
@@ -33,7 +33,7 @@ endpoint.on("connection", (socket) => {
try {
const videos = await youtube.getVideosInPlaylist(
playlistId,
- socket.request.user.admin
+ socket.request.user.isAdmin()
);
videos.forEach((video) => (video.user = formatUser(socket)));
callback(queueManager.addAllFair(videos));
@@ -44,7 +44,10 @@ endpoint.on("connection", (socket) => {
socket.on("fetch-then-add-video", async (videoId, callback) => {
try {
- const video = await youtube.getVideo(videoId, socket.request.user.admin);
+ const video = await youtube.getVideo(
+ videoId,
+ socket.request.user.isAdmin()
+ );
video.user = formatUser(socket);
callback({ success: queueManager.addFair(video) });
@@ -62,7 +65,7 @@ endpoint.on("connection", (socket) => {
success: queueManager.removeVideos(
videoIDs,
socket.request.session.passport.user.id,
- socket.request.user.admin
+ socket.request.user.isAdmin()
),
});
} catch (e) {
@@ -82,7 +85,7 @@ function formatUser(socket) {
return {
name: socket.request.user.name,
id: socket.request.user.id,
- admin: socket.request.user.admin,
+ admin: socket.request.user.isAdmin(),
};
}
diff --git a/server/modules/socket_endpoints/ScreenSocket.js b/server/modules/socket_endpoints/ScreenSocket.js
index c688993..08a02f4 100644
--- a/server/modules/socket_endpoints/ScreenSocket.js
+++ b/server/modules/socket_endpoints/ScreenSocket.js
@@ -2,6 +2,7 @@ const fetch = require("node-fetch");
const endpoint = io.of("/socket/screen");
const queueManager = require("../QueueManager");
const playbackManager = require("../PlaybackManager");
+const { getCurrentSetting } = require("../ScreenSettings");
let newPhotoInterval = null;
let photo = null;
@@ -13,6 +14,8 @@ endpoint.on("connection", (socket) => {
if (!photo) emitNewPhoto();
socket.emit("photo-update", photo);
+ socket.emit("queue-photos-visibility-changed", getCurrentSetting());
+
socket.emit("queue-update", {
queue: queueManager.getQueue(),
duration: queueManager.getTotalDurationFormatted(),
@@ -22,6 +25,10 @@ endpoint.on("connection", (socket) => {
logger.screenInfo(`Disconnected socket: ${socket.id}`);
});
+ socket.on("get-photo", (callback) => {
+ callback(photo);
+ });
+
socket.emit("player-update", {
playerType: playbackManager.getPlayerType(),
playerMode: playbackManager.getPlayerMode(),
@@ -65,6 +72,10 @@ eventBus.on("new-video-timestamp", (timestamp) => {
endpoint.emit("new-video-timestamp", timestamp);
});
+eventBus.on("queue-photos-visibility-changed", (newSetting) => {
+ endpoint.emit("queue-photos-visibility-changed", newSetting);
+});
+
function emitNewPhoto() {
if (newPhotoInterval === null) {
newPhotoInterval = setInterval(emitNewPhoto, 10000);