mirror of
https://github.com/janishutz/MusicPlayerV2.git
synced 2025-11-25 04:54:23 +00:00
Integrate new account backend
This commit is contained in:
@@ -1,2 +1,2 @@
|
||||
const app = require( './dist/app.js' ).default;
|
||||
app.run();
|
||||
app.run();
|
||||
|
||||
7245
backend/package-lock.json
generated
7245
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,42 +1,43 @@
|
||||
{
|
||||
"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": {
|
||||
"@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",
|
||||
"oauth-janishutz-client-server": "file:../../oauth/client/server/dist",
|
||||
"socket.io": "^4.7.5",
|
||||
"store.janishutz.com-sdk": "file:../../store/sdk/dist"
|
||||
}
|
||||
"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.0.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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,48 +3,58 @@ import db from './storage/db';
|
||||
|
||||
const createUser = ( uid: string, username: string, email: string ): Promise<boolean> => {
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
db.writeDataSimple( 'users', 'uid', uid, { 'uid': uid, 'username': username, 'email': email } ).then( () => {
|
||||
db.writeDataSimple( 'users', 'uid', uid, {
|
||||
'uid': uid,
|
||||
'username': username,
|
||||
'email': email
|
||||
} ).then( () => {
|
||||
resolve( true );
|
||||
} ).catch( err => {
|
||||
reject( err );
|
||||
} );
|
||||
} )
|
||||
.catch( err => {
|
||||
reject( err );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
};
|
||||
|
||||
const saveUserData = ( uid: string, data: object ): Promise<boolean> => {
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
db.writeDataSimple( 'users', 'uid', uid, { 'data': data } ).then( () => {
|
||||
db.writeDataSimple( 'users', 'uid', uid, {
|
||||
'data': data
|
||||
} ).then( () => {
|
||||
resolve( true );
|
||||
} ).catch( err => {
|
||||
reject( err );
|
||||
} );
|
||||
} )
|
||||
.catch( err => {
|
||||
reject( err );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
};
|
||||
|
||||
const checkUser = ( uid: string ): Promise<boolean> => {
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
db.checkDataAvailability( 'users', 'uid', uid ).then( res => {
|
||||
resolve( res );
|
||||
} ).catch( err => {
|
||||
reject( err );
|
||||
} )
|
||||
.catch( err => {
|
||||
reject( err );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const getUserData = ( uid: string ): Promise<object> => {
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
db.getDataSimple( 'users', 'uid', uid ).then( data => {
|
||||
resolve( data );
|
||||
} ).catch( err => {
|
||||
reject( err );
|
||||
} );
|
||||
} )
|
||||
.catch( err => {
|
||||
reject( err );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
createUser,
|
||||
saveUserData,
|
||||
checkUser,
|
||||
getUserData
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3,8 +3,6 @@ import path from 'path';
|
||||
import fs from 'fs';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import cors from 'cors';
|
||||
import account from './account';
|
||||
import sdk from 'oauth-janishutz-client-server';
|
||||
import {
|
||||
createServer
|
||||
} from 'node:http';
|
||||
@@ -15,26 +13,19 @@ import crypto from 'node:crypto';
|
||||
import type {
|
||||
Room, Song
|
||||
} from './definitions';
|
||||
import storeSDK from 'store.janishutz.com-sdk';
|
||||
import bodyParser from 'body-parser';
|
||||
|
||||
|
||||
// ┌ ┐
|
||||
// │ Handle FOSS vs paid version │
|
||||
// └ ┘
|
||||
const isFossVersion = true;
|
||||
|
||||
declare let __dirname: string | undefined;
|
||||
import storeSDK from '@janishutz/store-sdk';
|
||||
import sdk from '@janishutz/login-sdk-server';
|
||||
// import storeSDK from './sdk/store-sdk-stub';
|
||||
// import sdk from '@janishutz/login-sdk-server-stubs';
|
||||
|
||||
if ( typeof __dirname === 'undefined' ) {
|
||||
__dirname = path.resolve( path.dirname( '' ) );
|
||||
}
|
||||
|
||||
// TODO: Change config file, as well as in main.ts, index.html, oauth, if deploying there
|
||||
// const sdkConfig = JSON.parse( fs.readFileSync( path.join(
|
||||
// __dirname,
|
||||
// '/config/sdk.config.testing.json'
|
||||
// ) ).toString() );
|
||||
const sdkConfig = JSON.parse( fs.readFileSync( path.join(
|
||||
__dirname,
|
||||
'/config/sdk.config.secret.json'
|
||||
) ).toString() );
|
||||
|
||||
const run = () => {
|
||||
const app = express();
|
||||
@@ -45,10 +36,6 @@ const run = () => {
|
||||
} ) );
|
||||
|
||||
if ( !isFossVersion ) {
|
||||
// storeSDK.configure( JSON.parse( fs.readFileSync( path.join(
|
||||
// __dirname,
|
||||
// '/config/store-sdk.config.testing.json'
|
||||
// ) ).toString() ) );
|
||||
storeSDK.configure( JSON.parse( fs.readFileSync( path.join(
|
||||
__dirname,
|
||||
'/config/store-sdk.config.secret.json'
|
||||
@@ -58,26 +45,49 @@ const run = () => {
|
||||
const httpServer = createServer( app );
|
||||
|
||||
if ( !isFossVersion ) {
|
||||
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.routes( app, ( uid: string ) => {
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
account.checkUser( uid ).then( stat => {
|
||||
resolve( stat );
|
||||
} )
|
||||
.catch( e => {
|
||||
reject( e );
|
||||
} );
|
||||
} );
|
||||
}, ( uid: string, email: string, username: string ) => {
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
account.createUser( uid, username, email ).then( stat => {
|
||||
resolve( stat );
|
||||
} )
|
||||
.catch( e => {
|
||||
reject( e );
|
||||
} );
|
||||
} );
|
||||
}, sdkConfig );
|
||||
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 );
|
||||
} );
|
||||
},
|
||||
sdkConfig
|
||||
);
|
||||
}
|
||||
|
||||
// Websocket for events
|
||||
@@ -291,7 +301,7 @@ const run = () => {
|
||||
} ) }\n\n` );
|
||||
const sid = sdk.getSessionID( request );
|
||||
|
||||
if ( sdk.checkAuth( request ) ) {
|
||||
if ( sdk.getSignedIn( request ) ) {
|
||||
importantClients[ sid ] = {
|
||||
'response': response,
|
||||
'room': String( request.query.room )
|
||||
@@ -488,38 +498,35 @@ const run = () => {
|
||||
|
||||
app.get(
|
||||
'/createRoomToken',
|
||||
sdk.loginCheck(),
|
||||
( request: express.Request, response: express.Response ) => {
|
||||
if ( sdk.checkAuth( request ) ) {
|
||||
// eslint-disable-next-line no-constant-binary-expression
|
||||
const roomName = String( request.query.roomName ) ?? '';
|
||||
// eslint-disable-next-line no-constant-binary-expression
|
||||
const roomName = String( request.query.roomName ) ?? '';
|
||||
|
||||
if ( !socketData[ roomName ] ) {
|
||||
const roomToken = crypto.randomUUID();
|
||||
if ( !socketData[ roomName ] ) {
|
||||
const roomToken = crypto.randomUUID();
|
||||
|
||||
socketData[ roomName ] = {
|
||||
'playbackStart': 0,
|
||||
'playbackStatus': false,
|
||||
'playlist': [],
|
||||
'playlistIndex': 0,
|
||||
'roomName': roomName,
|
||||
'roomToken': roomToken,
|
||||
'ownerUID': sdk.getUserData( request ).uid,
|
||||
'useAntiTamper': request.query.useAntiTamper === 'true'
|
||||
? true : false,
|
||||
};
|
||||
response.send( roomToken );
|
||||
} else {
|
||||
if (
|
||||
socketData[ roomName ].ownerUID
|
||||
=== sdk.getUserData( request ).uid
|
||||
) {
|
||||
response.send( socketData[ roomName ].roomToken );
|
||||
} else {
|
||||
response.status( 409 ).send( 'ERR_CONFLICT' );
|
||||
}
|
||||
}
|
||||
socketData[ roomName ] = {
|
||||
'playbackStart': 0,
|
||||
'playbackStatus': false,
|
||||
'playlist': [],
|
||||
'playlistIndex': 0,
|
||||
'roomName': roomName,
|
||||
'roomToken': roomToken,
|
||||
'ownerUID': sdk.getUID( request ),
|
||||
'useAntiTamper': request.query.useAntiTamper === 'true'
|
||||
? true : false,
|
||||
};
|
||||
response.send( roomToken );
|
||||
} else {
|
||||
response.status( 403 ).send( 'ERR_FORBIDDEN' );
|
||||
if (
|
||||
socketData[ roomName ].ownerUID
|
||||
=== sdk.getUID( request )
|
||||
) {
|
||||
response.send( socketData[ roomName ].roomToken );
|
||||
} else {
|
||||
response.status( 409 ).send( 'ERR_CONFLICT' );
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -569,46 +576,43 @@ const run = () => {
|
||||
|
||||
const checkIfOwned = ( request: express.Request ): Promise<boolean> => {
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
if ( sdk.checkAuth( request ) ) {
|
||||
const userData = sdk.getUserData( request );
|
||||
const uid = sdk.getUID( request );
|
||||
|
||||
if ( ownedCache[ userData.uid ] ) {
|
||||
resolve( ownedCache[ userData.uid ] );
|
||||
} else {
|
||||
storeSDK.getSubscriptions( userData.uid )
|
||||
.then( stat => {
|
||||
const now = new Date().getTime();
|
||||
if ( ownedCache[ uid ] ) {
|
||||
resolve( ownedCache[ uid ] );
|
||||
} else {
|
||||
storeSDK.getSubscriptions( uid )
|
||||
.then( stat => {
|
||||
const now = new Date().getTime();
|
||||
|
||||
for ( const sub in stat ) {
|
||||
if ( stat[ sub ].expires - now > 0
|
||||
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[ userData.uid ] = true;
|
||||
resolve( true );
|
||||
}
|
||||
) {
|
||||
ownedCache[ uid ] = true;
|
||||
resolve( true );
|
||||
}
|
||||
}
|
||||
|
||||
ownedCache[ userData.uid ] = false;
|
||||
resolve( false );
|
||||
} )
|
||||
.catch( e => {
|
||||
console.error( e );
|
||||
reject( 'ERR_NOT_OWNED' );
|
||||
} );
|
||||
}
|
||||
} else {
|
||||
reject( 'ERR_AUTH_REQUIRED' );
|
||||
ownedCache[ uid ] = false;
|
||||
resolve( false );
|
||||
} )
|
||||
.catch( e => {
|
||||
console.error( e );
|
||||
reject( 'ERR_NOT_OWNED' );
|
||||
} );
|
||||
}
|
||||
} );
|
||||
};
|
||||
|
||||
app.get(
|
||||
'/checkUserStatus',
|
||||
sdk.loginCheck(),
|
||||
( request: express.Request, response: express.Response ) => {
|
||||
checkIfOwned( request )
|
||||
.then( owned => {
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
import express from 'express';
|
||||
import expressSession from 'express-session';
|
||||
import crypto from 'node:crypto';
|
||||
|
||||
// TODO: Use also express-session to make it work with getSessionID and session referencing
|
||||
const checkAuth = ( request: express.Request ) => {
|
||||
return true;
|
||||
}
|
||||
|
||||
export interface AuthSDKConfig {
|
||||
token: string;
|
||||
name: string;
|
||||
client: string;
|
||||
backendURL: string;
|
||||
failReturnURL: string;
|
||||
useSecureCookie?: boolean;
|
||||
}
|
||||
|
||||
declare module 'express-session' {
|
||||
interface SessionData {
|
||||
isAuth: boolean;
|
||||
uid: string;
|
||||
username: string;
|
||||
email: string;
|
||||
additionalData: object;
|
||||
}
|
||||
}
|
||||
|
||||
const getUserData = ( request: express.Request ) => {
|
||||
if ( !request.session.uid ) {
|
||||
request.session.uid = crypto.randomUUID();
|
||||
request.session.username = 'FOSS-Version';
|
||||
request.session.email = 'example@example.com';
|
||||
}
|
||||
return { 'email': request.session.email, 'username': request.session.username, 'uid': request.session.uid, 'id': request.session.id };
|
||||
}
|
||||
|
||||
export default {
|
||||
checkAuth,
|
||||
getUserData
|
||||
}
|
||||
@@ -1,11 +1,15 @@
|
||||
const getSubscriptions = ( uid: string ) => {
|
||||
return [ {
|
||||
'id': 'com.janishutz.MusicPlayer.subscription',
|
||||
'expires': new Date().getTime() + 200000,
|
||||
'status': true
|
||||
} ];
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const getSubscriptions = ( _uid: string ) => {
|
||||
return [
|
||||
{
|
||||
'id': 'com.janishutz.MusicPlayer.subscription',
|
||||
'expires': new Date().getTime() + 200000,
|
||||
'status': true
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
export default {
|
||||
getSubscriptions,
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -11,20 +11,20 @@ import path from 'path';
|
||||
import fs from 'fs';
|
||||
import * as sqlDB from './mysqldb.js';
|
||||
|
||||
declare let __dirname: string | undefined
|
||||
if ( typeof( __dirname ) === 'undefined' ) {
|
||||
declare let __dirname: string | undefined;
|
||||
|
||||
if ( typeof __dirname === 'undefined' ) {
|
||||
__dirname = path.resolve( path.dirname( '' ) );
|
||||
} else {
|
||||
__dirname = __dirname + '/../';
|
||||
}
|
||||
|
||||
const dbRef = {
|
||||
'user': 'music_users',
|
||||
const dbRef = {
|
||||
'user': 'music_users',
|
||||
'users': 'music_users',
|
||||
};
|
||||
const dbh = new sqlDB.SQLDB();
|
||||
|
||||
|
||||
let dbh = new sqlDB.SQLDB();
|
||||
dbh.connect();
|
||||
|
||||
/**
|
||||
@@ -32,7 +32,7 @@ dbh.connect();
|
||||
* @returns {undefined}
|
||||
*/
|
||||
const initDB = (): undefined => {
|
||||
( async() => {
|
||||
( async () => {
|
||||
console.log( '[ DB ] Setting up...' );
|
||||
dbh.setupDB();
|
||||
console.log( '[ DB ] Setting up complete!' );
|
||||
@@ -48,11 +48,16 @@ const initDB = (): undefined => {
|
||||
*/
|
||||
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 => {
|
||||
dbh.query( {
|
||||
'command': 'getFilteredData',
|
||||
'property': column,
|
||||
'searchQuery': searchQuery
|
||||
}, dbRef[ db ] ).then( data => {
|
||||
resolve( data );
|
||||
} ).catch( error => {
|
||||
reject( error );
|
||||
} );
|
||||
} )
|
||||
.catch( error => {
|
||||
reject( error );
|
||||
} );
|
||||
} );
|
||||
};
|
||||
|
||||
@@ -66,34 +71,36 @@ const getDataSimple = ( db: string, column: string, searchQuery: string ): Promi
|
||||
* @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),
|
||||
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 ) => {
|
||||
let settings = {
|
||||
'command': 'LeftJoin',
|
||||
'property': column,
|
||||
'searchQuery': searchQuery,
|
||||
const settings = {
|
||||
'command': 'LeftJoin',
|
||||
'property': column,
|
||||
'searchQuery': searchQuery,
|
||||
'selection': '',
|
||||
'secondTable': dbRef[ secondTable ],
|
||||
'secondTable': dbRef[ secondTable ],
|
||||
'matchingParam': dbRef[ db ] + '.' + nameOfMatchingParam + '=' + dbRef[ secondTable ] + '.' + nameOfMatchingParam,
|
||||
}
|
||||
for ( let el in columns ) {
|
||||
};
|
||||
|
||||
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 );
|
||||
} );
|
||||
} )
|
||||
.catch( error => {
|
||||
reject( error );
|
||||
} );
|
||||
} );
|
||||
};
|
||||
|
||||
@@ -102,13 +109,16 @@ const getDataWithLeftJoinFunction = ( db: string, column: string, searchQuery: s
|
||||
* @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> => {
|
||||
const getData = ( db: string ): Promise<object> => {
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
dbh.query( { 'command': 'getAllData' }, dbRef[ db ] ).then( data => {
|
||||
dbh.query( {
|
||||
'command': 'getAllData'
|
||||
}, dbRef[ db ] ).then( data => {
|
||||
resolve( data );
|
||||
} ).catch( error => {
|
||||
reject( error );
|
||||
} );
|
||||
} )
|
||||
.catch( error => {
|
||||
reject( error );
|
||||
} );
|
||||
} );
|
||||
};
|
||||
|
||||
@@ -120,25 +130,40 @@ const getData = ( db: string ): Promise<Object> => {
|
||||
* @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> => {
|
||||
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 => {
|
||||
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 => {
|
||||
dbh.query( {
|
||||
'command': 'updateData',
|
||||
'property': column,
|
||||
'searchQuery': searchQuery,
|
||||
'newValues': data
|
||||
}, dbRef[ db ] ).then( dat => {
|
||||
resolve( dat );
|
||||
} ).catch( error => {
|
||||
reject( error );
|
||||
} );
|
||||
} )
|
||||
.catch( error => {
|
||||
reject( error );
|
||||
} );
|
||||
} else {
|
||||
dbh.query( { 'command': 'addData', 'data': data }, dbRef[ db ] ).then( dat => {
|
||||
dbh.query( {
|
||||
'command': 'addData',
|
||||
'data': data
|
||||
}, dbRef[ db ] ).then( dat => {
|
||||
resolve( dat );
|
||||
} ).catch( error => {
|
||||
reject( error );
|
||||
} );
|
||||
} )
|
||||
.catch( error => {
|
||||
reject( error );
|
||||
} );
|
||||
}
|
||||
} ).catch( error => {
|
||||
reject( error );
|
||||
} );
|
||||
} )
|
||||
.catch( error => {
|
||||
reject( error );
|
||||
} );
|
||||
} );
|
||||
};
|
||||
|
||||
@@ -151,11 +176,16 @@ const writeDataSimple = ( db: string, column: string, searchQuery: string, data:
|
||||
*/
|
||||
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 => {
|
||||
dbh.query( {
|
||||
'command': 'deleteData',
|
||||
'property': column,
|
||||
'searchQuery': searchQuery
|
||||
}, dbRef[ db ] ).then( dat => {
|
||||
resolve( dat );
|
||||
} ).catch( error => {
|
||||
reject( error );
|
||||
} );
|
||||
} )
|
||||
.catch( error => {
|
||||
reject( error );
|
||||
} );
|
||||
} );
|
||||
};
|
||||
|
||||
@@ -168,15 +198,20 @@ const deleteDataSimple = ( db: string, column: string, searchQuery: string ): Pr
|
||||
*/
|
||||
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 => {
|
||||
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 );
|
||||
} );
|
||||
} )
|
||||
.catch( error => {
|
||||
reject( error );
|
||||
} );
|
||||
} );
|
||||
};
|
||||
|
||||
@@ -186,16 +221,18 @@ const checkDataAvailability = ( db: string, column: string, searchQuery: string
|
||||
* @returns {Promise<object>} Returns the data from all files
|
||||
*/
|
||||
const getJSONDataBatch = async ( files: Array<string> ): Promise<object> => {
|
||||
let allFiles = {};
|
||||
for ( let file in files ) {
|
||||
const allFiles = {};
|
||||
|
||||
for ( const file in files ) {
|
||||
try {
|
||||
allFiles[ files[ file ] ] = await getJSONData( files[ file ] );
|
||||
} catch( err ) {
|
||||
} catch ( err ) {
|
||||
allFiles[ files[ file ] ] = 'ERROR: ' + err;
|
||||
}
|
||||
}
|
||||
|
||||
return allFiles;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Load all data from a JSON file
|
||||
@@ -245,7 +282,7 @@ const getJSONDataSimple = ( file: string, identifier: string ): Promise<object>
|
||||
* @param {string} file The file to be loaded (path relative to root)
|
||||
* @returns {object} Returns the JSON file
|
||||
*/
|
||||
const getJSONDataSync = ( file: string ): Object => {
|
||||
const getJSONDataSync = ( file: string ): object => {
|
||||
return JSON.parse( fs.readFileSync( path.join( __dirname + '/' + file ) ).toString() );
|
||||
};
|
||||
|
||||
@@ -263,14 +300,17 @@ const writeJSONDataSimple = ( db: string, identifier: string, values: any ) => {
|
||||
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 ) => {
|
||||
fs.writeFile( path.join( __dirname + '/../../data/' + db + '.json' ), JSON.stringify( dat ), error => {
|
||||
if ( error ) {
|
||||
reject( 'Error occurred: Error trace: ' + error );
|
||||
}
|
||||
|
||||
resolve( true );
|
||||
} );
|
||||
}
|
||||
@@ -286,7 +326,7 @@ const writeJSONDataSimple = ( db: string, identifier: string, values: any ) => {
|
||||
*/
|
||||
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 ) => {
|
||||
fs.writeFile( path.join( __dirname + '/../../data/' + db + '.json' ), JSON.stringify( data ), error => {
|
||||
if ( error ) {
|
||||
reject( 'Error occurred: Error trace: ' + error );
|
||||
} else {
|
||||
@@ -309,14 +349,17 @@ const deleteJSONDataSimple = ( db: string, identifier: string ): Promise<boolean
|
||||
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 ) => {
|
||||
fs.writeFile( path.join( __dirname + '/../../data/' + db + '.json' ), JSON.stringify( dat ), error => {
|
||||
if ( error ) {
|
||||
reject( 'Error occurred: Error trace: ' + error );
|
||||
}
|
||||
|
||||
resolve( true );
|
||||
} );
|
||||
}
|
||||
@@ -324,7 +367,19 @@ const deleteJSONDataSimple = ( db: string, identifier: string ): Promise<boolean
|
||||
} );
|
||||
};
|
||||
|
||||
export default { initDB, checkDataAvailability, deleteDataSimple, deleteJSONDataSimple, getData,
|
||||
getDataSimple, getDataWithLeftJoinFunction, getJSONData, getJSONDataBatch, getJSONDataSimple,
|
||||
getJSONDataSync, writeDataSimple, writeJSONData, writeJSONDataSimple
|
||||
export default {
|
||||
initDB,
|
||||
checkDataAvailability,
|
||||
deleteDataSimple,
|
||||
deleteJSONDataSimple,
|
||||
getData,
|
||||
getDataSimple,
|
||||
getDataWithLeftJoinFunction,
|
||||
getJSONData,
|
||||
getJSONDataBatch,
|
||||
getJSONDataSimple,
|
||||
getJSONDataSync,
|
||||
writeDataSimple,
|
||||
writeJSONData,
|
||||
writeJSONDataSimple
|
||||
};
|
||||
|
||||
@@ -11,8 +11,9 @@ import mysql from 'mysql';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
declare let __dirname: string | undefined
|
||||
if ( typeof( __dirname ) === 'undefined' ) {
|
||||
declare let __dirname: string | undefined;
|
||||
|
||||
if ( typeof __dirname === 'undefined' ) {
|
||||
__dirname = path.resolve( path.dirname( '' ) );
|
||||
} else {
|
||||
__dirname = __dirname + '/../';
|
||||
@@ -22,21 +23,35 @@ if ( typeof( __dirname ) === 'undefined' ) {
|
||||
// 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 );
|
||||
@@ -46,19 +61,23 @@ class SQLDB {
|
||||
connect () {
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
const self = this;
|
||||
|
||||
if ( this.isRecovering ) {
|
||||
console.log( '[ SQL ] Attempting to recover from critical error' );
|
||||
this.sqlConnection = mysql.createConnection( this.config );
|
||||
this.isRecovering = false;
|
||||
}
|
||||
this.sqlConnection.connect( ( err ) => {
|
||||
|
||||
this.sqlConnection.connect( err => {
|
||||
if ( err ) {
|
||||
console.error( '[ SQL ]: An error ocurred whilst connecting: ' + err.stack );
|
||||
reject( err );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
console.log( '[ SQL ] Connected to database successfully' );
|
||||
self.sqlConnection.on( 'error', ( err ) => {
|
||||
self.sqlConnection.on( 'error', err => {
|
||||
if ( err.code === 'ECONNRESET' ) {
|
||||
self.isRecovering = true;
|
||||
setTimeout( () => {
|
||||
@@ -81,22 +100,24 @@ class SQLDB {
|
||||
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 ) => {
|
||||
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>> {
|
||||
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),
|
||||
- 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):
|
||||
@@ -106,15 +127,15 @@ class SQLDB {
|
||||
- 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),
|
||||
- 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),
|
||||
- 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)
|
||||
@@ -122,25 +143,26 @@ class SQLDB {
|
||||
|
||||
- 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.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.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' ) {
|
||||
@@ -150,19 +172,23 @@ class SQLDB {
|
||||
} else if ( operation.command === 'addData' ) {
|
||||
let keys = '';
|
||||
let values = '';
|
||||
for ( let key in operation.data ) {
|
||||
|
||||
for ( const key in operation.data ) {
|
||||
keys += String( key ) + ', ';
|
||||
values += this.sqlConnection.escape( String( operation.data[ 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 ) + ');';
|
||||
|
||||
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 ( let value in operation.newValues ) {
|
||||
|
||||
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 );
|
||||
}
|
||||
@@ -178,12 +204,17 @@ class SQLDB {
|
||||
} 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 };
|
||||
export {
|
||||
SQLConfig, SQLDB
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user