add login sdk

This commit is contained in:
2024-06-26 09:52:53 +02:00
parent ed63ca77d6
commit 4ecf93d31b
11 changed files with 770 additions and 54 deletions

1
.gitignore vendored
View File

@@ -3,3 +3,4 @@ node_modules
*.secret.json
apple_private_key.p8
musicplayerv2-server.zip
dist

View File

@@ -0,0 +1,6 @@
{
"token": "phafowegoväbwpb$weapvbpvfwcvfäawef39'ü0wtäqgpt5^ü62q'ẗ9wäa3g",
"name": "localhost:8082",
"client": "localhost:8081",
"backendURL": "http://localhost:8080"
}

49
backend/dist/app.js vendored
View File

@@ -1,49 +0,0 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const express_1 = __importDefault(require("express"));
const path_1 = __importDefault(require("path"));
const fs_1 = __importDefault(require("fs"));
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
const cors_1 = __importDefault(require("cors"));
if (typeof (__dirname) === 'undefined') {
__dirname = path_1.default.resolve(path_1.default.dirname(''));
}
const run = () => {
let app = (0, express_1.default)();
app.use((0, cors_1.default)({
credentials: true,
origin: true
}));
app.get('/', (request, response) => {
response.send('HELLO WORLD');
});
app.get('/getAppleMusicDevToken', (req, res) => {
// sign dev token
const privateKey = fs_1.default.readFileSync(path_1.default.join(__dirname + '/config/apple_private_key.p8')).toString();
// TODO: Remove secret
const config = JSON.parse('' + fs_1.default.readFileSync(path_1.default.join(__dirname + '/config/apple-music-api.config.secret.json')));
const now = new Date().getTime();
const tomorrow = now + 24 * 3600 * 1000;
const jwtToken = jsonwebtoken_1.default.sign({
'iss': config.teamID,
'iat': Math.floor(now / 1000),
'exp': Math.floor(tomorrow / 1000),
}, privateKey, {
algorithm: "ES256",
keyid: config.keyID
});
res.send(jwtToken);
});
app.use((request, response, next) => {
response.status(404).send('ERR_NOT_FOUND');
// response.sendFile( path.join( __dirname + '' ) )
});
const PORT = process.env.PORT || 8081;
app.listen(PORT);
};
exports.default = {
run
};

View File

@@ -16,12 +16,30 @@
"body-parser": "^1.20.2",
"cors": "^2.8.5",
"express": "^4.19.2",
"jsonwebtoken": "^9.0.2"
"jsonwebtoken": "^9.0.2",
"node-mysql": "^0.4.2",
"oauth-janishutz-client-server": "file:../../oauth/client/server/dist"
},
"devDependencies": {
"typescript": "^5.4.5"
}
},
"../../oauth/client/server/dist": {
"name": "oauth-janishutz-client-server",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@types/body-parser": "^1.19.5",
"@types/express": "^4.17.21",
"@types/express-session": "^1.18.0",
"body-parser": "^1.20.2",
"express": "^4.19.2",
"express-session": "^1.18.0"
},
"devDependencies": {
"typescript": "^5.3.3"
}
},
"node_modules/@types/body-parser": {
"version": "1.19.5",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
@@ -156,6 +174,20 @@
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
"license": "MIT"
},
"node_modules/better-js-class": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/better-js-class/-/better-js-class-0.1.3.tgz",
"integrity": "sha512-EMgtYym2RSQ75pUfOqKmCDL5EKur62Ec83aggzkJ942RM/mwskb6QDLaJJz93EwSK1dsfkPUFe+nlmxEbtBKDQ=="
},
"node_modules/bignumber.js": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz",
"integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==",
"license": "MIT",
"engines": {
"node": "*"
}
},
"node_modules/body-parser": {
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
@@ -250,6 +282,12 @@
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
"license": "MIT"
},
"node_modules/core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
"license": "MIT"
},
"node_modules/cors": {
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
@@ -263,6 +301,11 @@
"node": ">= 0.10"
}
},
"node_modules/cps": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/cps/-/cps-1.0.2.tgz",
"integrity": "sha512-kFE6vY9PBrN1sogxTxWa8ktnmfcKeR2I7+8tQgxqWndSgToerwITuprBs4FeQWEeMf+IGxo+ILixT/RK0wcIPQ=="
},
"node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@@ -577,6 +620,12 @@
"node": ">= 0.10"
}
},
"node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
"license": "MIT"
},
"node_modules/jsonwebtoken": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
@@ -731,6 +780,27 @@
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/mysql": {
"version": "2.18.1",
"resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz",
"integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==",
"license": "MIT",
"dependencies": {
"bignumber.js": "9.0.0",
"readable-stream": "2.3.7",
"safe-buffer": "5.1.2",
"sqlstring": "2.3.1"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mysql/node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"license": "MIT"
},
"node_modules/negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
@@ -740,6 +810,21 @@
"node": ">= 0.6"
}
},
"node_modules/node-mysql": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/node-mysql/-/node-mysql-0.4.2.tgz",
"integrity": "sha512-V/AVfW/d47Wsb2c8AfEu9Q8PiHUG7/3SbDJ03HFycMMrlKSXj5bNItnHgjSSA6N60/1JXmhoJYXJESSS5BYV9A==",
"dependencies": {
"better-js-class": "*",
"cps": "*",
"mysql": "*",
"underscore": "*"
}
},
"node_modules/oauth-janishutz-client-server": {
"resolved": "../../oauth/client/server/dist",
"link": true
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -785,6 +870,12 @@
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==",
"license": "MIT"
},
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"license": "MIT"
},
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -837,6 +928,27 @@
"node": ">= 0.8"
}
},
"node_modules/readable-stream": {
"version": "2.3.7",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
"license": "MIT",
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"node_modules/readable-stream/node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"license": "MIT"
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -961,6 +1073,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/sqlstring": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz",
"integrity": "sha512-ooAzh/7dxIG5+uDik1z/Rd1vli0+38izZhGzSa34FwR7IbelPWCCKSNIl8jlL/F7ERvy8CB2jNeM1E9i9mXMAQ==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
@@ -970,6 +1091,21 @@
"node": ">= 0.8"
}
},
"node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.1.0"
}
},
"node_modules/string_decoder/node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"license": "MIT"
},
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
@@ -1006,6 +1142,12 @@
"node": ">=14.17"
}
},
"node_modules/underscore": {
"version": "1.13.6",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz",
"integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==",
"license": "MIT"
},
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
@@ -1021,6 +1163,12 @@
"node": ">= 0.8"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
"node_modules/utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",

View File

@@ -27,6 +27,8 @@
"body-parser": "^1.20.2",
"cors": "^2.8.5",
"express": "^4.19.2",
"jsonwebtoken": "^9.0.2"
"jsonwebtoken": "^9.0.2",
"node-mysql": "^0.4.2",
"oauth-janishutz-client-server": "file:../../oauth/client/server/dist"
}
}

50
backend/src/account.ts Normal file
View File

@@ -0,0 +1,50 @@
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( () => {
resolve( true );
} ).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( () => {
resolve( true );
} ).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 );
} )
} );
}
const getUserData = ( uid: string ): Promise<object> => {
return new Promise( ( resolve, reject ) => {
db.getDataSimple( 'users', 'uid', uid ).then( data => {
resolve( data );
} ).catch( err => {
reject( err );
} );
} );
}
export default {
createUser,
saveUserData,
checkUser,
getUserData
}

View File

@@ -1,15 +1,20 @@
import express from 'express';
import path from 'path';
import fs from 'fs';
import bodyParser from 'body-parser';
import jwt from 'jsonwebtoken';
import cors from 'cors';
import account from './account';
import sdk from 'oauth-janishutz-client-server';
declare let __dirname: string | undefined
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' ) ) );
// const sdkConfig: AuthSDKConfig = JSON.parse( '' + fs.readFileSync( path.join( __dirname + '/config/sdk.config.secret.json' ) ) );
const run = () => {
let app = express();
app.use( cors( {
@@ -17,8 +22,29 @@ const run = () => {
origin: true
} ) );
// 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 );
app.get( '/', ( request, response ) => {
response.send( 'HELLO WORLD' );
response.send( 'Please visit <a href="https://music.janishutz.com">https://music.janishutz.com</a> to use this service' );
} );

330
backend/src/storage/db.ts Normal file
View File

@@ -0,0 +1,330 @@
/*
* 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';
declare let __dirname: string | undefined
if ( typeof( __dirname ) === 'undefined' ) {
__dirname = path.resolve( path.dirname( '' ) );
} else {
__dirname = __dirname + '/../';
}
const dbRef = {
'user': 'jh_store_users',
'users': 'jh_store_users',
};
let dbh = new sqlDB.SQLDB();
dbh.connect();
/**
* Initialize database (create tables, etc)
* @returns {undefined}
*/
const initDB = (): undefined => {
( async() => {
console.log( '[ DB ] Setting up...' );
dbh.setupDB();
console.log( '[ 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 ) => {
let settings = {
'command': 'LeftJoin',
'property': column,
'searchQuery': searchQuery,
'selection': '',
'secondTable': dbRef[ secondTable ],
'matchingParam': dbRef[ db ] + '.' + nameOfMatchingParam + '=' + dbRef[ secondTable ] + '.' + nameOfMatchingParam,
}
for ( let 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> => {
let allFiles = {};
for ( let 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
};

View File

@@ -0,0 +1,190 @@
/*
* 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';
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 ) {
console.log( '[ SQL ] Attempting to recover from critical error' );
this.sqlConnection = mysql.createConnection( this.config );
this.isRecovering = false;
}
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 ) => {
if ( err.code === 'ECONNRESET' ) {
console.error( '[ SQL ] Reconnecting to database, because connection was reset!' );
self.isRecovering = true;
setTimeout( () => {
self.disconnect();
self.connect();
}, 1000 );
} else {
console.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 jh_store_users ( account_id INT ( 10 ) NOT NULL AUTO_INCREMENT, email TINYTEXT NOT NULL, data VARCHAR( 55000 ), uid TINYTEXT, lang TINYTEXT, username TINYTEXT, stripe_user_id 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 ( let 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 ( let 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 };

View File

@@ -0,0 +1,12 @@
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 );