mirror of
https://github.com/janishutz/MusicPlayerV2.git
synced 2026-01-12 00:48:24 +00:00
Prepare for rewrite
This commit is contained in:
@@ -1,43 +0,0 @@
|
||||
{
|
||||
"name": "musicplayer-v2-backend",
|
||||
"version": "1.0.0",
|
||||
"description": "The backend for MusicPlayerV2",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/simplePCBuilding/MusicPlayerV2.git"
|
||||
},
|
||||
"author": "Janis Hutz",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"bugs": {
|
||||
"url": "https://github.com/simplePCBuilding/MusicPlayerV2/issues"
|
||||
},
|
||||
"homepage": "https://github.com/simplePCBuilding/MusicPlayerV2#readme",
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.29.0",
|
||||
"@stylistic/eslint-plugin": "^5.0.0",
|
||||
"@types/express-session": "^1.18.0",
|
||||
"eslint-plugin-vue": "^10.2.0",
|
||||
"typescript": "^5.4.5",
|
||||
"typescript-eslint": "^8.35.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@janishutz/login-sdk-server": "^1.2.0",
|
||||
"@janishutz/login-sdk-server-stubs": "^1.0.0",
|
||||
"@janishutz/store-sdk": "^1.1.0",
|
||||
"@types/body-parser": "^1.19.5",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/jsonwebtoken": "^9.0.6",
|
||||
"body-parser": "^1.20.2",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.19.2",
|
||||
"express-session": "^1.18.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"node-mysql": "^0.4.2",
|
||||
"socket.io": "^4.7.5"
|
||||
}
|
||||
}
|
||||
@@ -1,279 +0,0 @@
|
||||
import express from 'express';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import cors from 'cors';
|
||||
import {
|
||||
createServer
|
||||
} from 'node:http';
|
||||
import crypto from 'node:crypto';
|
||||
import {
|
||||
SocketData
|
||||
} from './definitions';
|
||||
|
||||
|
||||
// ┌ ┐
|
||||
// │ Handle FOSS vs paid version │
|
||||
// └ ┘
|
||||
const isFossVersion = false;
|
||||
|
||||
import storeSDK from '@janishutz/store-sdk';
|
||||
import sdk from '@janishutz/login-sdk-server';
|
||||
import sse from './sse';
|
||||
import socket from './socket';
|
||||
import logger from './logger';
|
||||
|
||||
// const isFossVersion = true;
|
||||
//
|
||||
// import storeSDK from './sdk/store-sdk-stub';
|
||||
// import sdk from '@janishutz/login-sdk-server-stubs';
|
||||
|
||||
|
||||
const corsOpts: cors.CorsOptions = {
|
||||
'credentials': true,
|
||||
'origin': ( origin, cb ) => {
|
||||
if ( isFossVersion ) cb( null, true );
|
||||
else cb( null, origin === 'https://music.janishutz.com' );
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const run = () => {
|
||||
const app = express();
|
||||
const httpServer = createServer( app );
|
||||
|
||||
if ( !isFossVersion ) {
|
||||
logger.info( '[ APP ] Starting in non-FOSS version' );
|
||||
|
||||
const storeConfig = JSON.parse( fs.readFileSync( path.join(
|
||||
__dirname,
|
||||
'/config/store-sdk.config.secret.json'
|
||||
) ).toString() );
|
||||
|
||||
storeSDK.configure( storeConfig );
|
||||
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
const sdkConfig = JSON.parse( fs.readFileSync( path.join(
|
||||
__dirname,
|
||||
'/config/sdk.config.secret.json'
|
||||
) ).toString() );
|
||||
|
||||
// Load id.janishutz.com SDK and allow signing in
|
||||
sdk.setUp(
|
||||
{
|
||||
'prod': false,
|
||||
'service': {
|
||||
'serviceID': 'jh-music',
|
||||
'serviceToken': sdkConfig[ 'token' ]
|
||||
},
|
||||
'user-agent': sdkConfig[ 'ua' ],
|
||||
'sessionType': 'memory',
|
||||
'frontendURL': 'https://music.janishutz.com',
|
||||
'corsWhitelist': [ 'https://music.janishutz.com' ],
|
||||
'recheckTimeout': 300 * 1000,
|
||||
'advancedVerification': 'sdk',
|
||||
},
|
||||
app,
|
||||
() => {
|
||||
return new Promise( resolve => {
|
||||
resolve( true );
|
||||
} );
|
||||
},
|
||||
() => {
|
||||
return new Promise( resolve => {
|
||||
resolve( true );
|
||||
} );
|
||||
},
|
||||
() => {
|
||||
return new Promise( resolve => {
|
||||
resolve( true );
|
||||
} );
|
||||
},
|
||||
() => {
|
||||
return new Promise( resolve => {
|
||||
resolve( true );
|
||||
} );
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Configuration of SSE or WebSocket
|
||||
*/
|
||||
const socketData: SocketData = {};
|
||||
|
||||
sse.useSSE( app, socketData, corsOpts, sdk.getSessionID, sdk.getSignedIn );
|
||||
socket.useWebSocket( httpServer, socketData );
|
||||
|
||||
|
||||
/*
|
||||
GENERAL ROUTES
|
||||
*/
|
||||
app.get( '/', ( _request: express.Request, response: express.Response ) => {
|
||||
response.send( 'Please visit <a href="https://music.janishutz.com">https://music.janishutz.com</a> to use this service' );
|
||||
} );
|
||||
|
||||
|
||||
|
||||
app.get(
|
||||
'/createRoomToken',
|
||||
cors( corsOpts ),
|
||||
sdk.loginCheck(),
|
||||
( request: express.Request, response: express.Response ) => {
|
||||
// eslint-disable-next-line no-constant-binary-expression
|
||||
const roomName = String( request.query.roomName ) ?? '';
|
||||
|
||||
if ( !socketData[ roomName ] ) {
|
||||
const roomToken = crypto.randomUUID();
|
||||
|
||||
socketData[ roomName ] = {
|
||||
'playbackStart': 0,
|
||||
'playbackStatus': false,
|
||||
'playlist': [],
|
||||
'playlistIndex': 0,
|
||||
'roomName': roomName,
|
||||
'roomToken': roomToken,
|
||||
'ownerUID': sdk.getUID( request ),
|
||||
'useAntiTamper': request.query.useAntiTamper === 'true'
|
||||
? true : false,
|
||||
};
|
||||
logger.debug( `Created room "${ roomName }"` );
|
||||
response.send( roomToken );
|
||||
} else {
|
||||
if (
|
||||
socketData[ roomName ].ownerUID
|
||||
=== sdk.getUID( request )
|
||||
) {
|
||||
response.send( socketData[ roomName ].roomToken );
|
||||
} else {
|
||||
response.status( 409 ).send( 'ERR_CONFLICT' );
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
app.get(
|
||||
'/getAppleMusicDevToken',
|
||||
cors( corsOpts ),
|
||||
sdk.loginCheck(),
|
||||
( req, res ) => {
|
||||
checkIfOwned( req )
|
||||
.then( owned => {
|
||||
if ( owned ) {
|
||||
// sign dev token
|
||||
const privateKey = fs.readFileSync( path.join(
|
||||
__dirname,
|
||||
'/config/apple_private_key.p8'
|
||||
) ).toString();
|
||||
const config = JSON.parse( fs.readFileSync( path.join(
|
||||
__dirname,
|
||||
'/config/apple-music-api.config.secret.json'
|
||||
) ).toString() );
|
||||
const now = new Date().getTime();
|
||||
const tomorrow = now + ( 24 * 3600 * 1000 );
|
||||
const jwtToken = jwt.sign( {
|
||||
'iss': config.teamID,
|
||||
'iat': Math.floor( now / 1000 ),
|
||||
'exp': Math.floor( tomorrow / 1000 ),
|
||||
}, privateKey, {
|
||||
'algorithm': 'ES256',
|
||||
'keyid': config.keyID
|
||||
} );
|
||||
|
||||
res.send( jwtToken );
|
||||
} else {
|
||||
res.status( 402 ).send( 'ERR_NOT_OWNED' );
|
||||
}
|
||||
} )
|
||||
.catch( e => {
|
||||
if ( e === 'ERR_NOT_OWNED' ) {
|
||||
res.status( 402 ).send( e );
|
||||
} else if ( e === 'ERR_AUTH_REQUIRED' ) {
|
||||
res.status( 401 ).send( e );
|
||||
} else {
|
||||
res.send( 500 ).send( e );
|
||||
}
|
||||
} );
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
const ownedCache = {};
|
||||
|
||||
const checkIfOwned = ( request: express.Request ): Promise<boolean> => {
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
const uid = sdk.getUID( request );
|
||||
|
||||
if ( ownedCache[ uid ] ) {
|
||||
resolve( ownedCache[ uid ] );
|
||||
} else {
|
||||
storeSDK.getSubscriptions( uid )
|
||||
.then( stat => {
|
||||
logger.log( 'Subscription check was successful' );
|
||||
const now = new Date().getTime();
|
||||
|
||||
for ( const sub in stat ) {
|
||||
if ( stat[ sub ].expires - now > 0
|
||||
&& (
|
||||
stat[ sub ].id
|
||||
=== 'com.janishutz.MusicPlayer.subscription'
|
||||
|| stat[ sub ].id
|
||||
=== 'com.janishutz.MusicPlayer.subscription-month'
|
||||
)
|
||||
) {
|
||||
ownedCache[ uid ] = true;
|
||||
resolve( true );
|
||||
}
|
||||
}
|
||||
|
||||
ownedCache[ uid ] = false;
|
||||
resolve( false );
|
||||
} )
|
||||
.catch( e => {
|
||||
logger.error( 'Subscription check unsuccessful with error', e );
|
||||
reject( 'ERR_NOT_OWNED' );
|
||||
} );
|
||||
}
|
||||
} );
|
||||
};
|
||||
|
||||
app.get(
|
||||
'/checkUserStatus',
|
||||
cors( corsOpts ),
|
||||
sdk.loginCheck(),
|
||||
( request: express.Request, response: express.Response ) => {
|
||||
checkIfOwned( request )
|
||||
.then( owned => {
|
||||
if ( owned ) {
|
||||
response.send( 'ok' );
|
||||
} else {
|
||||
response.status( 402 ).send( 'ERR_NOT_OWNED' );
|
||||
}
|
||||
} )
|
||||
.catch( e => {
|
||||
if ( e === 'ERR_NOT_OWNED' ) {
|
||||
response.status( 402 ).send( e );
|
||||
} else if ( e === 'ERR_AUTH_REQUIRED' ) {
|
||||
response.status( 401 ).send( e );
|
||||
} else {
|
||||
response.send( 500 ).send( e );
|
||||
}
|
||||
} );
|
||||
}
|
||||
);
|
||||
|
||||
app.use( ( request: express.Request, response: express.Response ) => {
|
||||
response.status( 404 ).send( 'ERR_NOT_FOUND: ' + request.path );
|
||||
} );
|
||||
|
||||
|
||||
const PORT = process.env.PORT || 8082;
|
||||
|
||||
httpServer.listen( PORT );
|
||||
};
|
||||
|
||||
export default {
|
||||
run
|
||||
};
|
||||
|
||||
23
backend/src/definitions.d.ts
vendored
23
backend/src/definitions.d.ts
vendored
@@ -1,23 +0,0 @@
|
||||
export interface Room {
|
||||
'playbackStatus': boolean;
|
||||
'playbackStart': number;
|
||||
'playlist': Song[];
|
||||
'playlistIndex': number;
|
||||
'roomName': string;
|
||||
'roomToken': string;
|
||||
'ownerUID': string;
|
||||
'useAntiTamper': boolean;
|
||||
}
|
||||
|
||||
export interface Song {
|
||||
'title': string;
|
||||
'artist': string;
|
||||
'duration': number;
|
||||
'cover': string;
|
||||
'additionalInfo'?: string;
|
||||
}
|
||||
|
||||
export interface SocketData {
|
||||
[key: string]: Room;
|
||||
}
|
||||
|
||||
@@ -1,178 +0,0 @@
|
||||
import http from 'node:http';
|
||||
import {
|
||||
Server
|
||||
} from 'socket.io';
|
||||
import type {
|
||||
SocketData,
|
||||
Song
|
||||
} from './definitions';
|
||||
|
||||
const useWebSocket = ( httpServer: http.Server, socketData: SocketData ) => {
|
||||
// Websocket for events
|
||||
const io = new Server( httpServer, {
|
||||
'cors': {
|
||||
'origin': true,
|
||||
'credentials': true,
|
||||
}
|
||||
} );
|
||||
|
||||
io.on( 'connection', socket => {
|
||||
socket.on( 'create-room', ( room: {
|
||||
'name': string,
|
||||
'token': string
|
||||
}, cb: ( res: {
|
||||
'status': boolean,
|
||||
'msg': string
|
||||
} ) => void ) => {
|
||||
if ( socketData[ room.name ] ) {
|
||||
if ( room.token === socketData[ room.name ].roomToken ) {
|
||||
socket.join( room.name );
|
||||
cb( {
|
||||
'status': true,
|
||||
'msg': 'ADDED_TO_ROOM'
|
||||
} );
|
||||
} else {
|
||||
cb( {
|
||||
'status': false,
|
||||
'msg': 'ERR_TOKEN_INVALID'
|
||||
} );
|
||||
}
|
||||
} else {
|
||||
cb( {
|
||||
'status': false,
|
||||
'msg': 'ERR_NAME_INVALID'
|
||||
} );
|
||||
}
|
||||
} );
|
||||
|
||||
socket.on( 'delete-room', ( room: {
|
||||
'name': string,
|
||||
'token': string
|
||||
}, cb: ( res: {
|
||||
'status': boolean,
|
||||
'msg': string
|
||||
} ) => void ) => {
|
||||
if ( socketData[ room.name ] ) {
|
||||
if ( room.token === socketData[ room.name ].roomToken ) {
|
||||
socket.leave( room.name );
|
||||
socket.to( room.name ).emit( 'delete-share', room.name );
|
||||
socketData[ room.name ] = undefined;
|
||||
cb( {
|
||||
'status': true,
|
||||
'msg': 'ROOM_DELETED'
|
||||
} );
|
||||
} else {
|
||||
cb( {
|
||||
'status': false,
|
||||
'msg': 'ERR_TOKEN_INVALID'
|
||||
} );
|
||||
}
|
||||
} else {
|
||||
cb( {
|
||||
'status': false,
|
||||
'msg': 'ERR_NAME_INVALID'
|
||||
} );
|
||||
}
|
||||
} );
|
||||
|
||||
socket.on( 'join-room', ( room: string, cb: ( res: {
|
||||
'status': boolean,
|
||||
'msg': string,
|
||||
'data'?: {
|
||||
'playbackStatus': boolean,
|
||||
'playbackStart': number,
|
||||
'playlist': Song[],
|
||||
'playlistIndex': number,
|
||||
'useAntiTamper': boolean
|
||||
}
|
||||
} ) => void ) => {
|
||||
if ( socketData[ room ] ) {
|
||||
socket.join( room );
|
||||
cb( {
|
||||
'data': {
|
||||
'playbackStart': socketData[ room ].playbackStart,
|
||||
'playbackStatus': socketData[ room ].playbackStatus,
|
||||
'playlist': socketData[ room ].playlist,
|
||||
'playlistIndex': socketData[ room ].playlistIndex,
|
||||
'useAntiTamper': socketData[ room ].useAntiTamper,
|
||||
},
|
||||
'msg': 'STATUS_OK',
|
||||
'status': true,
|
||||
} );
|
||||
} else {
|
||||
cb( {
|
||||
'msg': 'ERR_NO_ROOM_WITH_THIS_ID',
|
||||
'status': false,
|
||||
} );
|
||||
socket.disconnect();
|
||||
}
|
||||
} );
|
||||
|
||||
socket.on( 'tampering', ( data: {
|
||||
'msg': string,
|
||||
'roomName': string
|
||||
} ) => {
|
||||
if ( data.roomName ) {
|
||||
socket.to( data.roomName ).emit( 'tampering-msg', data.msg );
|
||||
}
|
||||
} );
|
||||
|
||||
socket.on( 'playlist-update', ( data: {
|
||||
'roomName': string,
|
||||
'roomToken': string,
|
||||
'data': Song[]
|
||||
} ) => {
|
||||
if ( socketData[ data.roomName ] ) {
|
||||
if ( socketData[ data.roomName ].roomToken === data.roomToken ) {
|
||||
if ( socketData[ data.roomName ].playlist !== data.data ) {
|
||||
socketData[ data.roomName ].playlist = data.data;
|
||||
io.to( data.roomName ).emit( 'playlist', data.data );
|
||||
}
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
socket.on( 'playback-update', ( data: {
|
||||
'roomName': string,
|
||||
'roomToken': string,
|
||||
'data': boolean
|
||||
} ) => {
|
||||
if ( socketData[ data.roomName ] ) {
|
||||
if ( socketData[ data.roomName ].roomToken === data.roomToken ) {
|
||||
socketData[ data.roomName ].playbackStatus = data.data;
|
||||
io.to( data.roomName ).emit( 'playback', data.data );
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
socket.on( 'playlist-index-update', ( data: {
|
||||
'roomName': string,
|
||||
'roomToken': string,
|
||||
'data': number
|
||||
} ) => {
|
||||
if ( socketData[ data.roomName ] ) {
|
||||
if ( socketData[ data.roomName ].roomToken === data.roomToken ) {
|
||||
socketData[ data.roomName ].playlistIndex = data.data;
|
||||
io.to( data.roomName ).emit( 'playlist-index', data.data );
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
socket.on( 'playback-start-update', ( data: {
|
||||
'roomName': string,
|
||||
'roomToken': string,
|
||||
'data': number
|
||||
} ) => {
|
||||
if ( socketData[ data.roomName ] ) {
|
||||
if ( socketData[ data.roomName ].roomToken === data.roomToken ) {
|
||||
socketData[ data.roomName ].playbackStart = data.data;
|
||||
io.to( data.roomName ).emit( 'playback-start', data.data );
|
||||
}
|
||||
}
|
||||
} );
|
||||
} );
|
||||
};
|
||||
|
||||
export default {
|
||||
useWebSocket
|
||||
};
|
||||
@@ -1,286 +0,0 @@
|
||||
import cors from 'cors';
|
||||
import express from 'express';
|
||||
import bodyParser from 'body-parser';
|
||||
import {
|
||||
SocketData
|
||||
} from './definitions';
|
||||
import logger from './logger';
|
||||
|
||||
const useSSE = (
|
||||
app: express.Application,
|
||||
socketData: SocketData,
|
||||
corsOpts: cors.CorsOptions,
|
||||
getSessionID: ( request: express.Request ) => string,
|
||||
getSignedIn: ( request: express.Request ) => boolean
|
||||
) => {
|
||||
/*
|
||||
ROUTES FOR SERVER SENT EVENTS VERSION
|
||||
*/
|
||||
// Connected clients have their session ID as key
|
||||
interface SocketClientList {
|
||||
[key: string]: SocketClient;
|
||||
}
|
||||
|
||||
interface SocketClient {
|
||||
'response': express.Response;
|
||||
'room': string;
|
||||
}
|
||||
|
||||
interface ClientReferenceList {
|
||||
/**
|
||||
* Find all clients connected to one room
|
||||
*/
|
||||
[key: string]: string[];
|
||||
}
|
||||
|
||||
const importantClients: SocketClientList = {};
|
||||
const connectedClients: SocketClientList = {};
|
||||
const clientReference: ClientReferenceList = {};
|
||||
|
||||
app.get(
|
||||
'/socket/connection',
|
||||
cors( corsOpts ),
|
||||
( request: express.Request, response: express.Response ) => {
|
||||
if ( request.query.room ) {
|
||||
if ( socketData[ String( request.query.room ) ] ) {
|
||||
response.writeHead( 200, {
|
||||
'Content-Type': 'text/event-stream',
|
||||
'Cache-Control': 'no-cache',
|
||||
'Connection': 'keep-alive',
|
||||
} );
|
||||
response.status( 200 );
|
||||
response.flushHeaders();
|
||||
response.write( `data: ${ JSON.stringify( {
|
||||
'type': 'basics',
|
||||
'data': socketData[ String( request.query.room ) ]
|
||||
} ) }\n\n` );
|
||||
const sid = getSessionID( request );
|
||||
|
||||
if ( getSignedIn( request ) ) {
|
||||
importantClients[ sid ] = {
|
||||
'response': response,
|
||||
'room': String( request.query.room )
|
||||
};
|
||||
}
|
||||
|
||||
connectedClients[ sid ] = {
|
||||
'response': response,
|
||||
'room': String( request.query.room )
|
||||
};
|
||||
|
||||
if ( !clientReference[ String( request.query.room ) ] ) {
|
||||
clientReference[ String( request.query.room ) ] = [];
|
||||
}
|
||||
|
||||
if ( !clientReference[ String( request.query.room ) ]
|
||||
.includes( sid ) ) {
|
||||
clientReference[ String( request.query.room ) ].push( sid );
|
||||
}
|
||||
|
||||
request.on( 'close', () => {
|
||||
try {
|
||||
importantClients[ sid ] = undefined;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
} catch ( e ) { /* empty */ }
|
||||
|
||||
const cl = clientReference[ String( request.query.room ) ];
|
||||
|
||||
for ( const c in cl ) {
|
||||
if ( cl[ c ] === sid ) {
|
||||
try {
|
||||
cl.splice( parseInt( c ), 1 );
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
} catch ( _ ) { /* empty */ }
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
connectedClients[ sid ] = undefined;
|
||||
} );
|
||||
} else {
|
||||
response.status( 404 ).send( 'ERR_ROOM_NOT_FOUND' );
|
||||
}
|
||||
} else {
|
||||
response.status( 404 ).send( 'ERR_NO_ROOM_SPECIFIED' );
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
app.get(
|
||||
'/socket/getData',
|
||||
cors( corsOpts ),
|
||||
( request: express.Request, response: express.Response ) => {
|
||||
if ( request.query.room ) {
|
||||
response.send( socketData[ String( request.query.room ) ] );
|
||||
} else {
|
||||
response.status( 400 ).send( 'ERR_NO_ROOM_SPECIFIED' );
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
app.get(
|
||||
'/socket/joinRoom',
|
||||
cors( corsOpts ),
|
||||
( request: express.Request, response: express.Response ) => {
|
||||
if ( request.query.room ) {
|
||||
if ( socketData[ String( request.query.room ) ] ) {
|
||||
logger.debug( `Room "${ request.query.room }" was joined` );
|
||||
response.send( 'ok' );
|
||||
} else {
|
||||
response.status( 404 ).send( 'ERR_ROOM_NOT_FOUND' );
|
||||
}
|
||||
} else {
|
||||
response.status( 400 ).send( 'ERR_NO_ROOM_SPECIFIED' );
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
app.options( '/socket/update', cors( corsOpts ) );
|
||||
app.post(
|
||||
'/socket/update',
|
||||
cors( corsOpts ),
|
||||
bodyParser.json(),
|
||||
( request: express.Request, response: express.Response ) => {
|
||||
if ( socketData[ request.body.roomName ] ) {
|
||||
if ( request.body.event === 'tampering' ) {
|
||||
logger.debug( `Room "${
|
||||
request.query.roomName }" has new event: Tampering` );
|
||||
|
||||
const clients = clientReference[ request.body.roomName ];
|
||||
|
||||
if ( !clients ) {
|
||||
response.send( 'ERR_CANNOT_SEND' );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for ( const client in clients ) {
|
||||
if ( importantClients[ clients[ client ] ] ) {
|
||||
importantClients[ clients[ client ] ]
|
||||
.response.write( 'data: ' + JSON.stringify( {
|
||||
'type': 'tampering-msg',
|
||||
'data': true
|
||||
} ) + '\n\n' );
|
||||
}
|
||||
}
|
||||
|
||||
response.send( 'ok' );
|
||||
} else {
|
||||
if (
|
||||
socketData[ request.body.roomName ].roomToken
|
||||
=== request.body.roomToken
|
||||
) {
|
||||
let send = false;
|
||||
let update = '';
|
||||
|
||||
if ( request.body.event === 'playback-start-update' ) {
|
||||
send = true;
|
||||
update = 'playback-start';
|
||||
socketData[ request.body.roomName ]
|
||||
.playbackStart = request.body.data;
|
||||
} else if ( request.body.event === 'playback-update' ) {
|
||||
send = true;
|
||||
update = 'playback';
|
||||
socketData[ request.body.roomName ]
|
||||
.playbackStatus = request.body.data;
|
||||
} else if ( request.body.event === 'playlist-update' ) {
|
||||
send = true;
|
||||
update = 'playlist';
|
||||
socketData[ request.body.roomName ]
|
||||
.playlist = request.body.data;
|
||||
} else if ( request.body.event === 'playlist-index-update' ) {
|
||||
send = true;
|
||||
update = 'playlist-index';
|
||||
socketData[ request.body.roomName ]
|
||||
.playlistIndex = request.body.data;
|
||||
}
|
||||
|
||||
logger.debug( `Room "${
|
||||
request.query.roomName }" has new event: ${ update }` );
|
||||
|
||||
if ( send ) {
|
||||
const clients = clientReference[ request.body.roomName ];
|
||||
|
||||
if ( !clients ) {
|
||||
response.send( 'ERR_CANNOT_SEND' );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for ( const client in clients ) {
|
||||
if ( connectedClients[ clients[ client ] ] ) {
|
||||
connectedClients[ clients[ client ] ]
|
||||
.response.write( 'data: ' + JSON.stringify( {
|
||||
'type': update,
|
||||
'data': request.body.data
|
||||
} ) + '\n\n' );
|
||||
}
|
||||
}
|
||||
|
||||
response.send( 'ok' );
|
||||
} else {
|
||||
response.status( 404 ).send( 'ERR_CANNOT_SEND' );
|
||||
}
|
||||
} else {
|
||||
response.status( 403 ).send( 'ERR_UNAUTHORIZED' );
|
||||
}
|
||||
}
|
||||
} else {
|
||||
response.status( 400 ).send( 'ERR_WRONG_REQUEST' );
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
|
||||
app.options( '/socket/deleteRoom', cors( corsOpts ) );
|
||||
app.post(
|
||||
'/socket/deleteRoom',
|
||||
cors( corsOpts ),
|
||||
bodyParser.json(),
|
||||
( request: express.Request, response: express.Response ) => {
|
||||
if ( request.body.roomName ) {
|
||||
if ( socketData[ request.body.roomName ] ) {
|
||||
if (
|
||||
socketData[ request.body.roomName ].roomToken
|
||||
=== request.body.roomToken
|
||||
) {
|
||||
logger.debug( `Room "${
|
||||
request.query.roomName }" was deleted` );
|
||||
socketData[ request.body.roomName ] = undefined;
|
||||
const clients = clientReference[ request.body.roomName ];
|
||||
|
||||
if ( !clients ) {
|
||||
response.send( 'ok' );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for ( const client in clients ) {
|
||||
if ( connectedClients[ clients[ client ] ] ) {
|
||||
connectedClients[ clients[ client ] ]
|
||||
.response.write( 'data: ' + JSON.stringify( {
|
||||
'type': 'delete-share',
|
||||
'data': true
|
||||
} ) + '\n\n' );
|
||||
}
|
||||
}
|
||||
|
||||
response.send( 'ok' );
|
||||
} else {
|
||||
response.send( 403 ).send( 'ERR_UNAUTHORIZED' );
|
||||
}
|
||||
} else {
|
||||
response.status( 404 ).send( 'ERR_ROOM_NOT_FOUND' );
|
||||
}
|
||||
} else {
|
||||
response.status( 400 ).send( 'ERR_NO_ROOM_NAME' );
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default {
|
||||
useSSE
|
||||
};
|
||||
@@ -1,386 +0,0 @@
|
||||
/*
|
||||
* libreevent - db.js
|
||||
*
|
||||
* Created by Janis Hutz 03/26/2023, Licensed under the GPL V3 License
|
||||
* https://janishutz.com, development@janishutz.com
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import * as sqlDB from './mysqldb.js';
|
||||
import logger from '../logger.js';
|
||||
|
||||
declare let __dirname: string | undefined;
|
||||
|
||||
if ( typeof __dirname === 'undefined' ) {
|
||||
__dirname = path.resolve( path.dirname( '' ) );
|
||||
} else {
|
||||
__dirname = __dirname + '/../';
|
||||
}
|
||||
|
||||
const dbRef = {
|
||||
'user': 'music_users',
|
||||
'users': 'music_users',
|
||||
};
|
||||
const dbh = new sqlDB.SQLDB();
|
||||
|
||||
dbh.connect();
|
||||
|
||||
/**
|
||||
* Initialize database (create tables, etc)
|
||||
* @returns {undefined}
|
||||
*/
|
||||
const initDB = (): undefined => {
|
||||
( async () => {
|
||||
logger.info( '[ DB ] Setting up...' );
|
||||
dbh.setupDB();
|
||||
logger.info( '[ DB ] Setting up complete!' );
|
||||
} )();
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve data from the database
|
||||
* @param {string} db The name of the database
|
||||
* @param {string} column The name of the column of the data-table in which to search for the searchQuery
|
||||
* @param {string} searchQuery The query for the selected column
|
||||
* @returns {Promise<object>} Returns a promise that resolves to an object containing the results.
|
||||
*/
|
||||
const getDataSimple = ( db: string, column: string, searchQuery: string ): Promise<object> => {
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
dbh.query( {
|
||||
'command': 'getFilteredData',
|
||||
'property': column,
|
||||
'searchQuery': searchQuery
|
||||
}, dbRef[ db ] ).then( data => {
|
||||
resolve( data );
|
||||
} )
|
||||
.catch( error => {
|
||||
reject( error );
|
||||
} );
|
||||
} );
|
||||
};
|
||||
|
||||
/**
|
||||
* Use the SQL LeftJoin function to obtain data from DB.
|
||||
* @param {string} db DB name to get data from
|
||||
* @param {string} column The column in the DB in which to search for the searchQuery
|
||||
* @param {string} searchQuery The data to look for in the selected column
|
||||
* @param {string} secondTable The second table on which to perform the left join function
|
||||
* @param {object} columns The columns to return, list of objects: { 'db': TABLE NAME, 'column': COLUMN NAME })
|
||||
* @param {string} nameOfMatchingParam Which properties should be matched to get the data, e.g. order.user_id=users.id
|
||||
* @returns {Promise<Object | Error>} Returns all records from the db and all matching data specified with the matchingParam from the secondTable.
|
||||
*/
|
||||
const getDataWithLeftJoinFunction = ( db: string, column: string, searchQuery: string, secondTable: string, columns: object, nameOfMatchingParam: string ): Promise<object> => {
|
||||
/*
|
||||
LeftJoin (Select values in first table and return all corresponding values of second table):
|
||||
- operation.property (the column to search for the value),
|
||||
- operation.searchQuery (the value to search for [will be sanitised by method])
|
||||
- operation.columns (The columns of both tables to be selected, list of objects: { 'db': TABLE NAME, 'column': COLUMN NAME })
|
||||
- operation.secondTable (The second table to perform Join operation with)
|
||||
- operation.matchingParam (Which properties should be matched to get the data, e.g. order.user_id=users.id)
|
||||
*/
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
const settings = {
|
||||
'command': 'LeftJoin',
|
||||
'property': column,
|
||||
'searchQuery': searchQuery,
|
||||
'selection': '',
|
||||
'secondTable': dbRef[ secondTable ],
|
||||
'matchingParam': dbRef[ db ] + '.' + nameOfMatchingParam + '=' + dbRef[ secondTable ] + '.' + nameOfMatchingParam,
|
||||
};
|
||||
|
||||
for ( const el in columns ) {
|
||||
settings.selection += dbRef[ columns[ el ].db ] + '.' + columns[ el ].column + ',';
|
||||
}
|
||||
|
||||
settings.selection = settings.selection.slice( 0, settings.selection.length - 1 );
|
||||
dbh.query( settings, dbRef[ db ] ).then( data => {
|
||||
resolve( data );
|
||||
} )
|
||||
.catch( error => {
|
||||
reject( error );
|
||||
} );
|
||||
} );
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all data from the selected database
|
||||
* @param {string} db The database of which all data should be retrieved
|
||||
* @returns {Promise<object>} Returns an object containing all data
|
||||
*/
|
||||
const getData = ( db: string ): Promise<object> => {
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
dbh.query( {
|
||||
'command': 'getAllData'
|
||||
}, dbRef[ db ] ).then( data => {
|
||||
resolve( data );
|
||||
} )
|
||||
.catch( error => {
|
||||
reject( error );
|
||||
} );
|
||||
} );
|
||||
};
|
||||
|
||||
/**
|
||||
* Write data to the database
|
||||
* @param {string} db The database in which the data should be written
|
||||
* @param {string} column The column in which to search for the data
|
||||
* @param {string} searchQuery The query with which to search
|
||||
* @param {string} data The data to write. Also include the column & searchQuery parameters, if they also need to be added
|
||||
* @returns {Promise<object>} Returns a promise that resolves to the interaction module return.
|
||||
*/
|
||||
const writeDataSimple = ( db: string, column: string, searchQuery: string, data: any ): Promise<object> => {
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
dbh.query( {
|
||||
'command': 'checkDataAvailability',
|
||||
'property': column,
|
||||
'searchQuery': searchQuery
|
||||
}, dbRef[ db ] ).then( res => {
|
||||
if ( res.length > 0 ) {
|
||||
dbh.query( {
|
||||
'command': 'updateData',
|
||||
'property': column,
|
||||
'searchQuery': searchQuery,
|
||||
'newValues': data
|
||||
}, dbRef[ db ] ).then( dat => {
|
||||
resolve( dat );
|
||||
} )
|
||||
.catch( error => {
|
||||
reject( error );
|
||||
} );
|
||||
} else {
|
||||
dbh.query( {
|
||||
'command': 'addData',
|
||||
'data': data
|
||||
}, dbRef[ db ] ).then( dat => {
|
||||
resolve( dat );
|
||||
} )
|
||||
.catch( error => {
|
||||
reject( error );
|
||||
} );
|
||||
}
|
||||
} )
|
||||
.catch( error => {
|
||||
reject( error );
|
||||
} );
|
||||
} );
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete data from the database
|
||||
* @param {string} db The database from which the data should be deleted
|
||||
* @param {string} column The column in which to search for the data
|
||||
* @param {string} searchQuery The query with which to search
|
||||
* @returns {Promise<object>} Returns a promise that resolves to the interaction module return.
|
||||
*/
|
||||
const deleteDataSimple = ( db: string, column: string, searchQuery: string ): Promise<object> => {
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
dbh.query( {
|
||||
'command': 'deleteData',
|
||||
'property': column,
|
||||
'searchQuery': searchQuery
|
||||
}, dbRef[ db ] ).then( dat => {
|
||||
resolve( dat );
|
||||
} )
|
||||
.catch( error => {
|
||||
reject( error );
|
||||
} );
|
||||
} );
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the data is available in the database.
|
||||
* @param {string} db The database in which to check
|
||||
* @param {string} column The column in which to search for the data
|
||||
* @param {string} searchQuery The query with which to search
|
||||
* @returns {Promise<boolean>} Returns a promise that resolves to a boolean (true = is available)
|
||||
*/
|
||||
const checkDataAvailability = ( db: string, column: string, searchQuery: string ): Promise<boolean> => {
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
dbh.query( {
|
||||
'command': 'checkDataAvailability',
|
||||
'property': column,
|
||||
'searchQuery': searchQuery
|
||||
}, dbRef[ db ] ).then( res => {
|
||||
if ( res.length > 0 ) {
|
||||
resolve( true );
|
||||
} else {
|
||||
resolve( false );
|
||||
}
|
||||
} )
|
||||
.catch( error => {
|
||||
reject( error );
|
||||
} );
|
||||
} );
|
||||
};
|
||||
|
||||
/**
|
||||
* Load multiple JSON files at once
|
||||
* @param {Array<string>} files The files which to load
|
||||
* @returns {Promise<object>} Returns the data from all files
|
||||
*/
|
||||
const getJSONDataBatch = async ( files: Array<string> ): Promise<object> => {
|
||||
const allFiles = {};
|
||||
|
||||
for ( const file in files ) {
|
||||
try {
|
||||
allFiles[ files[ file ] ] = await getJSONData( files[ file ] );
|
||||
} catch ( err ) {
|
||||
allFiles[ files[ file ] ] = 'ERROR: ' + err;
|
||||
}
|
||||
}
|
||||
|
||||
return allFiles;
|
||||
};
|
||||
|
||||
/**
|
||||
* Load all data from a JSON file
|
||||
* @param {string} file The file to load (just file name, file must be in "/data/" folder, no file extension)
|
||||
* @returns {Promise<object>} The data that was loaded
|
||||
*/
|
||||
const getJSONData = ( file: string ): Promise<object> => {
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
fs.readFile( path.join( __dirname + '/' + file + '.json' ), ( error, data ) => {
|
||||
if ( error ) {
|
||||
reject( 'Error occurred: Error trace: ' + error );
|
||||
} else {
|
||||
if ( data.byteLength > 0 ) {
|
||||
resolve( JSON.parse( data.toString() ) ?? {} );
|
||||
} else {
|
||||
resolve( { } );
|
||||
}
|
||||
}
|
||||
} );
|
||||
} );
|
||||
};
|
||||
|
||||
/**
|
||||
* Load some data from a JSON file
|
||||
* @param {string} file The file to load (just file name, file must be in "/data/" folder, no file extension)
|
||||
* @param {string} identifier The identifier of the element which should be loaded
|
||||
* @returns {Promise<object>} The data that was loaded
|
||||
*/
|
||||
const getJSONDataSimple = ( file: string, identifier: string ): Promise<object> => {
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
fs.readFile( path.join( __dirname + '/' + file + '.json' ), ( error, data ) => {
|
||||
if ( error ) {
|
||||
reject( 'Error occurred: Error trace: ' + error );
|
||||
} else {
|
||||
if ( data.byteLength > 0 ) {
|
||||
resolve( JSON.parse( data.toString() )[ identifier ] ?? {} );
|
||||
} else {
|
||||
resolve( { } );
|
||||
}
|
||||
}
|
||||
} );
|
||||
} );
|
||||
};
|
||||
|
||||
/**
|
||||
* Get JSON data, but synchronous (blocking)
|
||||
* @param {string} file The file to be loaded (path relative to root)
|
||||
* @returns {object} Returns the JSON file
|
||||
*/
|
||||
const getJSONDataSync = ( file: string ): object => {
|
||||
return JSON.parse( fs.readFileSync( path.join( __dirname + '/' + file ) ).toString() );
|
||||
};
|
||||
|
||||
/**
|
||||
* Description
|
||||
* @param {any} db:string
|
||||
* @param {any} identifier:string
|
||||
* @param {any} values:any
|
||||
* @returns {any}
|
||||
*/
|
||||
const writeJSONDataSimple = ( db: string, identifier: string, values: any ) => {
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
fs.readFile( path.join( __dirname + '/../../data/' + db + '.json' ), ( error, data ) => {
|
||||
if ( error ) {
|
||||
reject( 'Error occurred: Error trace: ' + error );
|
||||
} else {
|
||||
let dat = {};
|
||||
|
||||
if ( data.byteLength > 0 ) {
|
||||
dat = JSON.parse( data.toString() ) ?? {};
|
||||
}
|
||||
|
||||
dat[ identifier ] = values;
|
||||
fs.writeFile( path.join( __dirname + '/../../data/' + db + '.json' ), JSON.stringify( dat ), error => {
|
||||
if ( error ) {
|
||||
reject( 'Error occurred: Error trace: ' + error );
|
||||
}
|
||||
|
||||
resolve( true );
|
||||
} );
|
||||
}
|
||||
} );
|
||||
} );
|
||||
};
|
||||
|
||||
/**
|
||||
* Write data to a JSON file
|
||||
* @param {string} db The database to write into
|
||||
* @param {object} data The data to write
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
const writeJSONData = ( db: string, data: object ): Promise<boolean> => {
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
fs.writeFile( path.join( __dirname + '/../../data/' + db + '.json' ), JSON.stringify( data ), error => {
|
||||
if ( error ) {
|
||||
reject( 'Error occurred: Error trace: ' + error );
|
||||
} else {
|
||||
resolve( true );
|
||||
}
|
||||
} );
|
||||
} );
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete data from a JSON file
|
||||
* @param {string} db The file to delete from (just filename, has to be in "/data/" folder, no file extension)
|
||||
* @param {string} identifier The identifier of the element to delete
|
||||
* @returns {Promise<boolean>} Returns a promise that resolves to a boolean
|
||||
*/
|
||||
const deleteJSONDataSimple = ( db: string, identifier: string ): Promise<boolean> => {
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
fs.readFile( path.join( __dirname + '/../../data/' + db + '.json' ), ( error, data ) => {
|
||||
if ( error ) {
|
||||
reject( 'Error occurred: Error trace: ' + error );
|
||||
} else {
|
||||
let dat = {};
|
||||
|
||||
if ( data.byteLength > 0 ) {
|
||||
dat = JSON.parse( data.toString() ) ?? {};
|
||||
}
|
||||
|
||||
delete dat[ identifier ];
|
||||
fs.writeFile( path.join( __dirname + '/../../data/' + db + '.json' ), JSON.stringify( dat ), error => {
|
||||
if ( error ) {
|
||||
reject( 'Error occurred: Error trace: ' + error );
|
||||
}
|
||||
|
||||
resolve( true );
|
||||
} );
|
||||
}
|
||||
} );
|
||||
} );
|
||||
};
|
||||
|
||||
export default {
|
||||
initDB,
|
||||
checkDataAvailability,
|
||||
deleteDataSimple,
|
||||
deleteJSONDataSimple,
|
||||
getData,
|
||||
getDataSimple,
|
||||
getDataWithLeftJoinFunction,
|
||||
getJSONData,
|
||||
getJSONDataBatch,
|
||||
getJSONDataSimple,
|
||||
getJSONDataSync,
|
||||
writeDataSimple,
|
||||
writeJSONData,
|
||||
writeJSONDataSimple
|
||||
};
|
||||
@@ -1,221 +0,0 @@
|
||||
/*
|
||||
* libreevent - mysqldb.js
|
||||
*
|
||||
* Created by Janis Hutz 07/12/2023, Licensed under the GPL V3 License
|
||||
* https://janishutz.com, development@janishutz.com
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
import mysql from 'mysql';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import logger from '../logger';
|
||||
|
||||
declare let __dirname: string | undefined;
|
||||
|
||||
if ( typeof __dirname === 'undefined' ) {
|
||||
__dirname = path.resolve( path.dirname( '' ) );
|
||||
} else {
|
||||
__dirname = __dirname + '/../';
|
||||
}
|
||||
|
||||
// If the connection does not work for you, you will need to add your ip
|
||||
// to the whitelist of the database
|
||||
|
||||
class SQLConfig {
|
||||
|
||||
command: string;
|
||||
|
||||
property?: string;
|
||||
|
||||
searchQuery?: string;
|
||||
|
||||
selection?: string;
|
||||
|
||||
query?: string;
|
||||
|
||||
newValues?: object;
|
||||
|
||||
secondTable?: string;
|
||||
|
||||
matchingParam?: string;
|
||||
|
||||
data?: object;
|
||||
|
||||
}
|
||||
|
||||
class SQLDB {
|
||||
|
||||
sqlConnection: mysql.Connection;
|
||||
|
||||
isRecovering: boolean;
|
||||
|
||||
config: object;
|
||||
|
||||
constructor () {
|
||||
this.config = JSON.parse( '' + fs.readFileSync( path.join( __dirname + '/config/db.config.secret.json' ) ) );
|
||||
this.sqlConnection = mysql.createConnection( this.config );
|
||||
this.isRecovering = false;
|
||||
}
|
||||
|
||||
connect () {
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
const self = this;
|
||||
|
||||
if ( this.isRecovering ) {
|
||||
logger.info( '[ SQL ] Attempting to recover from critical error' );
|
||||
this.sqlConnection = mysql.createConnection( this.config );
|
||||
this.isRecovering = false;
|
||||
}
|
||||
|
||||
this.sqlConnection.connect( err => {
|
||||
if ( err ) {
|
||||
logger.error( '[ SQL ]: An error ocurred whilst connecting: ' + err.stack );
|
||||
reject( err );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info( '[ SQL ] Connected to database successfully' );
|
||||
self.sqlConnection.on( 'error', err => {
|
||||
if ( err.code === 'ECONNRESET' ) {
|
||||
self.isRecovering = true;
|
||||
setTimeout( () => {
|
||||
self.disconnect();
|
||||
self.connect();
|
||||
}, 1000 );
|
||||
} else {
|
||||
logger.error( err );
|
||||
}
|
||||
} );
|
||||
resolve( 'connection' );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
disconnect ( ) {
|
||||
this.sqlConnection.end();
|
||||
}
|
||||
|
||||
async setupDB () {
|
||||
this.sqlConnection.query( 'SELECT @@default_storage_engine;', ( error, results ) => {
|
||||
if ( error ) throw error;
|
||||
|
||||
if ( results[ 0 ][ '@@default_storage_engine' ] !== 'InnoDB' ) throw 'DB HAS TO USE InnoDB!';
|
||||
} );
|
||||
this.sqlConnection.query( 'CREATE TABLE music_users ( account_id INT ( 10 ) NOT NULL AUTO_INCREMENT, email TINYTEXT NOT NULL, uid TINYTEXT, lang TINYTEXT, username TINYTEXT, settings VARCHAR( 5000 ), PRIMARY KEY ( account_id ) ) ENGINE=INNODB;', error => {
|
||||
if ( error ) if ( error.code !== 'ER_TABLE_EXISTS_ERROR' ) throw error;
|
||||
|
||||
return 'DONE';
|
||||
} );
|
||||
}
|
||||
|
||||
query ( operation: SQLConfig, table: string ): Promise<Array<object>> {
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
/*
|
||||
Possible operation.command values (all need the table argument of the method call):
|
||||
- getAllData: no additional instructions needed
|
||||
|
||||
- getFilteredData:
|
||||
- operation.property (the column to search for the value),
|
||||
- operation.searchQuery (the value to search for [will be sanitised by method])
|
||||
|
||||
- InnerJoin (Select values that match in both tables):
|
||||
- operation.property (the column to search for the value),
|
||||
- operation.searchQuery (the value to search for [will be sanitised by method])
|
||||
- operation.selection (The columns of both tables to be selected, e.g. users.name, orders.id)
|
||||
- operation.secondTable (The second table to perform Join operation with)
|
||||
- operation.matchingParam (Which properties should be matched to get the data, e.g. order.user_id=users.id)
|
||||
|
||||
- LeftJoin (Select values in first table and return all corresponding values of second table):
|
||||
- operation.property (the column to search for the value),
|
||||
- operation.searchQuery (the value to search for [will be sanitised by method])
|
||||
- operation.selection (The columns of both tables to be selected, e.g. users.name, orders.id)
|
||||
- operation.secondTable (The second table to perform Join operation with)
|
||||
- operation.matchingParam (Which properties should be matched to get the data, e.g. order.user_id=users.id)
|
||||
|
||||
- RightJoin (Select values in second table and return all corresponding values of first table):
|
||||
- operation.property (the column to search for the value),
|
||||
- operation.searchQuery (the value to search for [will be sanitised by method])
|
||||
- operation.selection (The columns of both tables to be selected, e.g. users.name, orders.id)
|
||||
- operation.secondTable (The second table to perform Join operation with)
|
||||
- operation.matchingParam (Which properties should be matched to get the data, e.g. order.user_id=users.id)
|
||||
|
||||
- addData:
|
||||
- operation.data (key-value pair with all data as values and column to insert into as key)
|
||||
|
||||
- deleteData:
|
||||
- operation.property (the column to search for the value)
|
||||
- operation.searchQuery (the value to search for [will be sanitised by method])
|
||||
|
||||
- updateData:
|
||||
- operation.newValues (a object with keys being the column and value being the value to be inserted into that column, values are being
|
||||
sanitised by the function)
|
||||
- operation.property (the column to search for the value),
|
||||
- operation.searchQuery (the value to search for [will be sanitised by method])
|
||||
|
||||
- checkDataAvailability:
|
||||
- operation.property (the column to search for the value),
|
||||
- operation.searchQuery (the value to search for [will be sanitised by method])
|
||||
|
||||
- fullCustomCommand:
|
||||
- operation.query (the SQL instruction to be executed) --> NOTE: This command will not be sanitised, so use only with proper sanitisation!
|
||||
*/
|
||||
let command = '';
|
||||
|
||||
if ( operation.command === 'getAllData' ) {
|
||||
command = 'SELECT * FROM ' + table;
|
||||
} else if ( operation.command === 'getFilteredData' || operation.command === 'checkDataAvailability' ) {
|
||||
command = 'SELECT * FROM ' + table + ' WHERE ' + operation.property + ' = ' + this.sqlConnection.escape( operation.searchQuery );
|
||||
} else if ( operation.command === 'fullCustomCommand' ) {
|
||||
command = operation.query;
|
||||
} else if ( operation.command === 'addData' ) {
|
||||
let keys = '';
|
||||
let values = '';
|
||||
|
||||
for ( const key in operation.data ) {
|
||||
keys += String( key ) + ', ';
|
||||
values += this.sqlConnection.escape( String( operation.data[ key ] ) ) + ', ';
|
||||
}
|
||||
|
||||
command = 'INSERT INTO ' + table + ' (' + keys.slice( 0, keys.length - 2 ) + ') VALUES (' + values.slice( 0, values.length - 2 ) + ');';
|
||||
} else if ( operation.command === 'updateData' ) {
|
||||
if ( !operation.property || !operation.searchQuery ) reject( 'Refusing to run destructive command: Missing Constraints' );
|
||||
else {
|
||||
command = 'UPDATE ' + table + ' SET ';
|
||||
let updatedValues = '';
|
||||
|
||||
for ( const value in operation.newValues ) {
|
||||
updatedValues += value + ' = ' + this.sqlConnection.escape( String( operation.newValues[ value ] ) ) + ', ';
|
||||
}
|
||||
|
||||
command += updatedValues.slice( 0, updatedValues.length - 2 );
|
||||
command += ' WHERE ' + operation.property + ' = ' + this.sqlConnection.escape( operation.searchQuery );
|
||||
}
|
||||
} else if ( operation.command === 'deleteData' ) {
|
||||
if ( !operation.property || !operation.searchQuery ) reject( 'Refusing to run destructive command: Missing Constraints' );
|
||||
else {
|
||||
command = 'DELETE FROM ' + table + ' WHERE ' + operation.property + ' = ' + this.sqlConnection.escape( operation.searchQuery );
|
||||
}
|
||||
} else if ( operation.command === 'InnerJoin' ) {
|
||||
command = 'SELECT ' + operation.selection + ' FROM ' + table + ' INNER JOIN ' + operation.secondTable + ' ON ' + operation.matchingParam + ' WHERE ' + operation.property + ' = ' + this.sqlConnection.escape( operation.searchQuery );
|
||||
} else if ( operation.command === 'LeftJoin' ) {
|
||||
command = 'SELECT ' + operation.selection + ' FROM ' + table + ' LEFT JOIN ' + operation.secondTable + ' ON ' + operation.matchingParam + ' WHERE ' + operation.property + ' = ' + this.sqlConnection.escape( operation.searchQuery );
|
||||
} else if ( operation.command === 'RightJoin' ) {
|
||||
command = 'SELECT ' + operation.selection + ' FROM ' + table + ' RIGHT JOIN ' + operation.secondTable + ' ON ' + operation.matchingParam + ' WHERE ' + operation.property + ' = ' + this.sqlConnection.escape( operation.searchQuery );
|
||||
}
|
||||
|
||||
this.sqlConnection.query( command, ( error, results ) => {
|
||||
if ( error ) reject( error );
|
||||
|
||||
resolve( results );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export {
|
||||
SQLConfig, SQLDB
|
||||
};
|
||||
@@ -1,12 +0,0 @@
|
||||
import db from './db.js';
|
||||
// import hash from '../security/hash.js';
|
||||
|
||||
db.initDB();
|
||||
// setTimeout( () => {
|
||||
// console.log( 'Setting up admin account' );
|
||||
// hash.hashPassword( 'test' ).then( hash => {
|
||||
// db.writeDataSimple( 'admin', 'email', 'info@janishutz.com', { email: 'info@janishutz.com', pass: hash, two_fa: 'enhanced' } );
|
||||
// console.log( 'Complete!' );
|
||||
// } );
|
||||
// }, 5000 );
|
||||
|
||||
Reference in New Issue
Block a user