diff --git a/Dockerfile b/Dockerfile index efca3de..ea7d751 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,6 +4,8 @@ COPY package.json . RUN npm install COPY index.js . COPY src src +RUN mkdir databases +COPY databases/*.sql databases/ COPY entrypoint.sh . EXPOSE 8080 CMD ./entrypoint.sh \ No newline at end of file diff --git a/databases/_upgrade_private_1.sql b/databases/_upgrade_private_1.sql new file mode 100644 index 0000000..c4de7e9 --- /dev/null +++ b/databases/_upgrade_private_1.sql @@ -0,0 +1,8 @@ +BEGIN TRANSACTION; + +/* for testing the db upgrade, don't remove because it looks empty */ + +/* Add version to config */ +INSERT INTO config (key, value) VALUES("version", 1); + +COMMIT; \ No newline at end of file diff --git a/databases/_upgrade_sponsorTimes_2.sql b/databases/_upgrade_sponsorTimes_2.sql new file mode 100644 index 0000000..f6816bc --- /dev/null +++ b/databases/_upgrade_sponsorTimes_2.sql @@ -0,0 +1,13 @@ +BEGIN TRANSACTION; + +/* Add new table: noSegments */ +CREATE TABLE "noSegments" ( + "videoID" TEXT NOT NULL, + "userID" TEXT NOT NULL, + "category" TEXT NOT NULL +); + +/* Add version to config */ +UPDATE config SET value = 2 WHERE key = 'version'; + +COMMIT; \ No newline at end of file diff --git a/databases/_upgrade_sponsorTimes_3.sql b/databases/_upgrade_sponsorTimes_3.sql new file mode 100644 index 0000000..5480ec2 --- /dev/null +++ b/databases/_upgrade_sponsorTimes_3.sql @@ -0,0 +1,13 @@ +BEGIN TRANSACTION; + +/* hash upgrade test sha256('vid') = '1ff838dc6ca9680d88455341118157d59a055fe6d0e3870f9c002847bebe4663' +/* Add hash field */ +ALTER TABLE sponsorTimes ADD hashedVideoID TEXT NOT NULL default ""; +UPDATE sponsorTimes SET hashedVideoID = sha256(videoID); + +CREATE INDEX IF NOT EXISTS sponsorTimes_hashedVideoID on sponsorTimes(hashedVideoID); + +/* Bump version in config */ +UPDATE config SET value = 3 WHERE key = "version"; + +COMMIT; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index d314594..0878451 100644 --- a/package-lock.json +++ b/package-lock.json @@ -68,6 +68,11 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, + "asap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/asap/-/asap-1.0.0.tgz", + "integrity": "sha1-sqRdpf36ILBJb8N2jMJ8EvqRan0=" + }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -96,12 +101,39 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==" }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, + "barrage": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/barrage/-/barrage-1.1.0.tgz", + "integrity": "sha1-b1OEdlxGP3cYAS8WDczwM/xvw5Q=", + "requires": { + "promise": "^6.0.0" + }, + "dependencies": { + "promise": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-6.1.0.tgz", + "integrity": "sha1-LOcp9rlLRcJoka0GAsXJDgTG7vY=", + "requires": { + "asap": "~1.0.0" + } + } + } + }, "base64url": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/base64url/-/base64url-1.0.6.tgz", @@ -128,6 +160,11 @@ "tar": "^4.4.10" } }, + "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==" + }, "binary-extensions": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", @@ -269,6 +306,11 @@ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, "bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", @@ -471,6 +513,11 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, + "core-js": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", + "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==" + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -559,6 +606,11 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, + "denque": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz", + "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==" + }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -877,6 +929,11 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, + "get-port": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz", + "integrity": "sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw=" + }, "get-stdin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", @@ -1744,6 +1801,56 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "mysql": { + "version": "2.18.1", + "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz", + "integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==", + "requires": { + "bignumber.js": "9.0.0", + "readable-stream": "2.3.7", + "safe-buffer": "5.1.2", + "sqlstring": "2.3.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "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==" + }, + "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==", + "requires": { + "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" + } + }, + "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==" + }, + "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==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, "negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", @@ -2005,6 +2112,21 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "requires": { + "asap": "~2.0.3" + }, + "dependencies": { + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + } + } + }, "proxy-addr": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", @@ -2089,6 +2211,40 @@ "picomatch": "^2.0.4" } }, + "redis": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/redis/-/redis-3.0.2.tgz", + "integrity": "sha512-PNhLCrjU6vKVuMOyFu7oSP296mwBkcE6lrAjruBYG5LgdSqtRBoVQIylrMyVZD/lkF24RSNNatzvYag6HRBHjQ==", + "requires": { + "denque": "^1.4.1", + "redis-commands": "^1.5.0", + "redis-errors": "^1.2.0", + "redis-parser": "^3.0.0" + } + }, + "redis-commands": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.6.0.tgz", + "integrity": "sha512-2jnZ0IkjZxvguITjFTrGiLyzQZcTvaw8DAaCXxZq/dsHXz7KfMQ3OUJy7Tz9vnRtZRVz6VRCPDvruvU8Ts44wQ==" + }, + "redis-errors": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", + "integrity": "sha1-62LSrbFeTq9GEMBK/hUpOEJQq60=" + }, + "redis-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", + "integrity": "sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=", + "requires": { + "redis-errors": "^1.0.0" + } + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + }, "registry-auth-token": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz", @@ -2277,6 +2433,11 @@ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, + "sqlstring": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", + "integrity": "sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A=" + }, "sshpk": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", @@ -2414,6 +2575,75 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" }, + "sync-mysql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sync-mysql/-/sync-mysql-3.0.1.tgz", + "integrity": "sha1-B6ArGSk04EzZDtU+lvCfgbFM68g=", + "requires": { + "babel-runtime": "^6.18.0", + "concat-stream": "^1.6.0", + "sync-rpc": "^1.1.1", + "then-mysql": "^1.1.1" + }, + "dependencies": { + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "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==" + }, + "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==", + "requires": { + "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" + } + }, + "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==" + }, + "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==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "sync-rpc": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/sync-rpc/-/sync-rpc-1.3.6.tgz", + "integrity": "sha512-J8jTXuZzRlvU7HemDgHi3pGnh/rkoqR/OZSjhTyyZrEkkYQbk7Z33AXp37mkPfPpfdOuj7Ex3H/TJM1z48uPQw==", + "requires": { + "get-port": "^3.1.0" + } + }, "tar": { "version": "4.4.13", "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", @@ -2437,6 +2667,16 @@ "execa": "^0.7.0" } }, + "then-mysql": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/then-mysql/-/then-mysql-1.1.1.tgz", + "integrity": "sha1-p1Q2zGXyvm37t3+HIg6YKuDjYR0=", + "requires": { + "barrage": "^1.1.0", + "mysql": "^2.10.0", + "promise": "^7.1.1" + } + }, "timed-out": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", diff --git a/package.json b/package.json index ffd1a63..4a44856 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "express": "^4.17.1", "http": "0.0.0", "iso8601-duration": "^1.2.0", + "redis": "^3.0.2", "sync-mysql": "^3.0.1", "uuid": "^3.3.2", "youtube-api": "^2.0.10", diff --git a/src/app.js b/src/app.js index c6ab3b5..787f32e 100644 --- a/src/app.js +++ b/src/app.js @@ -2,6 +2,7 @@ var express = require('express'); // Create a service (the app object is just a callback). var app = express(); var config = require('./config.js'); +var redis = require('./utils/redis.js'); // Middleware var corsMiddleware = require('./middleware/cors.js'); @@ -11,6 +12,7 @@ const userCounter = require('./middleware/userCounter.js'); // Routes var getSkipSegments = require('./routes/getSkipSegments.js').endpoint; var postSkipSegments = require('./routes/postSkipSegments.js'); +var getSkipSegmentsByHash = require('./routes/getSkipSegmentsByHash.js'); var voteOnSponsorTime = require('./routes/voteOnSponsorTime.js'); var viewedVideoSponsorTime = require('./routes/viewedVideoSponsorTime.js'); var setUsername = require('./routes/setUsername.js'); @@ -23,6 +25,8 @@ var getTopUsers = require('./routes/getTopUsers.js'); var getTotalStats = require('./routes/getTotalStats.js'); var getDaysSavedFormatted = require('./routes/getDaysSavedFormatted.js'); var getUserInfo = require('./routes/getUserInfo.js'); +var postNoSegments = require('./routes/postNoSegments.js'); +var getIsUserVIP = require('./routes/getIsUserVIP.js'); // Old Routes var oldGetVideoSponsorTimes = require('./routes/oldGetVideoSponsorTimes.js'); @@ -52,6 +56,9 @@ app.post('/api/postVideoSponsorTimes', oldSubmitSponsorTimes); app.get('/api/skipSegments', getSkipSegments); app.post('/api/skipSegments', postSkipSegments); +// add the privacy protecting skip segments functions +app.get('/api/skipSegments/:prefix', getSkipSegmentsByHash); + //voting endpoint app.get('/api/voteOnSponsorTime', voteOnSponsorTime.endpoint); app.post('/api/voteOnSponsorTime', voteOnSponsorTime.endpoint); @@ -92,6 +99,13 @@ app.get('/api/getUserInfo', getUserInfo); //send out a formatted time saved total app.get('/api/getDaysSavedFormatted', getDaysSavedFormatted); +//submit video containing no segments +app.post('/api/noSegments', postNoSegments); + +//get if user is a vip +app.get('/api/isUserVIP', getIsUserVIP); + + app.get('/database.db', function (req, res) { res.sendFile("./databases/sponsorTimes.db", { root: "./" }); }); diff --git a/src/config.js b/src/config.js index 1bc3721..d85b930 100644 --- a/src/config.js +++ b/src/config.js @@ -1,6 +1,6 @@ -var fs = require('fs'); -var config = undefined; +const fs = require('fs'); +let config = {}; // Check to see if launched in test mode if (process.env.npm_lifecycle_script === 'node test.js') { @@ -9,4 +9,27 @@ if (process.env.npm_lifecycle_script === 'node test.js') { config = JSON.parse(fs.readFileSync('config.json')); } -module.exports = config; \ No newline at end of file +addDefaults(config, { + "port": 80, + "behindProxy": "X-Forwarded-For", + "db": "./databases/sponsorTimes.db", + "privateDB": "./databases/private.db", + "createDatabaseIfNotExist": true, + "schemaFolder": "./databases", + "dbSchema": "./databases/_sponsorTimes.db.sql", + "privateDBSchema": "./databases/_private.db.sql", + "readOnly": false, + "webhooks": [], + "categoryList": ["sponsor", "intro", "outro", "interaction", "selfpromo", "music_offtopic"] +}) + +module.exports = config; + +// Add defaults +function addDefaults(config, defaults) { + for (const key in defaults) { + if(!config.hasOwnProperty(key)) { + config[key] = defaults[key]; + } + } +}; \ No newline at end of file diff --git a/src/databases/Mysql.js b/src/databases/Mysql.js index 8f3d8ba..a6b2850 100644 --- a/src/databases/Mysql.js +++ b/src/databases/Mysql.js @@ -1,5 +1,4 @@ var MysqlInterface = require('sync-mysql'); -var config = require('../config.js'); const logger = require('../utils/logger.js'); class Mysql { diff --git a/src/databases/databases.js b/src/databases/databases.js index 1a1b448..a6f38ab 100644 --- a/src/databases/databases.js +++ b/src/databases/databases.js @@ -3,7 +3,9 @@ var Sqlite3 = require('better-sqlite3'); var fs = require('fs'); var path = require('path'); var Sqlite = require('./Sqlite.js') -var Mysql = require('./Mysql.js') +var Mysql = require('./Mysql.js'); +const logger = require('../utils/logger.js'); +const getHash = require('../utils/getHash.js'); let options = { readonly: config.readOnly, @@ -33,6 +35,10 @@ if (config.mysql) { } if (!config.readOnly) { + db.function("sha256", (string) => { + return getHash(string, 1); + }); + // Upgrade database if required ugradeDB(db, "sponsorTimes"); ugradeDB(privateDB, "private") @@ -60,12 +66,16 @@ if (config.mysql) { let versionCodeInfo = db.prepare("SELECT value FROM config WHERE key = ?").get("version"); let versionCode = versionCodeInfo ? versionCodeInfo.value : 0; - let path = config.schemaFolder + "/_upgrade_" + prefix + "_" + (versionCode + 1) + ".sql"; + let path = config.schemaFolder + "/_upgrade_" + prefix + "_" + (parseInt(versionCode) + 1) + ".sql"; + logger.debug('db update: trying ' + path); while (fs.existsSync(path)) { + logger.debug('db update: updating ' + path); db.exec(fs.readFileSync(path).toString()); versionCode = db.prepare("SELECT value FROM config WHERE key = ?").get("version").value; - path = config.schemaFolder + "/_upgrade_" + prefix + "_" + (versionCode + 1) + ".sql"; + path = config.schemaFolder + "/_upgrade_" + prefix + "_" + (parseInt(versionCode) + 1) + ".sql"; + logger.debug('db update: trying ' + path); } + logger.debug('db update: no file ' + path); } } \ No newline at end of file diff --git a/src/routes/getIsUserVIP.js b/src/routes/getIsUserVIP.js new file mode 100644 index 0000000..63efd02 --- /dev/null +++ b/src/routes/getIsUserVIP.js @@ -0,0 +1,31 @@ +var db = require('../databases/databases.js').db; + +var getHash = require('../utils/getHash.js'); +const logger = require('../utils/logger.js'); +const isUserVIP = require('../utils/isUserVIP.js'); + +module.exports = (req, res) => { + let userID = req.query.userID; + + if (userID == undefined) { + //invalid request + res.sendStatus(400); + return; + } + + //hash the userID + userID = getHash(userID); + + try { + let vipState = isUserVIP(userID); + res.status(200).json({ + hashedUserID: userID, + vip: vipState + }); + } catch (err) { + logger.error(err); + res.sendStatus(500); + + return; + } +} diff --git a/src/routes/getSkipSegments.js b/src/routes/getSkipSegments.js index a003a6c..81ea763 100644 --- a/src/routes/getSkipSegments.js +++ b/src/routes/getSkipSegments.js @@ -1,4 +1,3 @@ -var fs = require('fs'); var config = require('../config.js'); var databases = require('../databases/databases.js'); @@ -9,45 +8,101 @@ var logger = require('../utils/logger.js'); var getHash = require('../utils/getHash.js'); var getIP = require('../utils/getIP.js'); +function cleanGetSegments(req, videoID, categories) { + let userHashedIP, shadowHiddenSegments; + + let segments = []; + + try { + for (const category of categories) { + const categorySegments = db + .prepare( + 'all', + 'SELECT startTime, endTime, votes, UUID, shadowHidden FROM sponsorTimes WHERE videoID = ? and category = ? ORDER BY startTime', + [videoID, category] + ) + .filter(segment => { + if (segment.votes < -1) { + return false; //too untrustworthy, just ignore it + } + + //check if shadowHidden + //this means it is hidden to everyone but the original ip that submitted it + if (segment.shadowHidden != 1) { + return true; + } + + if (shadowHiddenSegments === undefined) { + shadowHiddenSegments = privateDB.prepare('all', 'SELECT hashedIP FROM sponsorTimes WHERE videoID = ?', [videoID]); + } + + //if this isn't their ip, don't send it to them + return shadowHiddenSegments.some(shadowHiddenSegment => { + if (userHashedIP === undefined) { + //hash the IP only if it's strictly necessary + userHashedIP = getHash(getIP(req) + config.globalSalt); + } + return shadowHiddenSegment.hashedIP === userHashedIP; + }); + }); + + chooseSegments(categorySegments).forEach(chosenSegment => { + segments.push({ + category, + segment: [chosenSegment.startTime, chosenSegment.endTime], + UUID: chosenSegment.UUID, + }); + }); + } + + return segments; + } catch (err) { + if (err) { + logger.error(err); + return null; + } + } +} + //gets a weighted random choice from the choices array based on their `votes` property. //amountOfChoices specifies the maximum amount of choices to return, 1 or more. //choices are unique function getWeightedRandomChoice(choices, amountOfChoices) { - //trivial case: no need to go through the whole process - if (amountOfChoices >= choices.length) { - return choices; - } - - //assign a weight to each choice - let totalWeight = 0; - choices = choices.map(choice => { - //The 3 makes -2 the minimum votes before being ignored completely - //https://www.desmos.com/calculator/c1duhfrmts - //this can be changed if this system increases in popularity. - const weight = Math.exp((choice.votes + 3), 0.85); - totalWeight += weight; - - return { ...choice, weight }; - }); - - //iterate and find amountOfChoices choices - const chosen = []; - while (amountOfChoices-- > 0) { - //weighted random draw of one element of choices - const randomNumber = Math.random() * totalWeight; - let stackWeight = choices[0].weight; - let i = 0; - while (stackWeight < randomNumber) { - stackWeight += choices[++i].weight; + //trivial case: no need to go through the whole process + if (amountOfChoices >= choices.length) { + return choices; } - //add it to the chosen ones and remove it from the choices before the next iteration - chosen.push(choices[i]); - totalWeight -= choices[i].weight; - choices.splice(i, 1); - } + //assign a weight to each choice + let totalWeight = 0; + choices = choices.map(choice => { + //The 3 makes -2 the minimum votes before being ignored completely + //https://www.desmos.com/calculator/c1duhfrmts + //this can be changed if this system increases in popularity. + const weight = Math.exp((choice.votes + 3), 0.85); + totalWeight += weight; - return chosen; + return { ...choice, weight }; + }); + + //iterate and find amountOfChoices choices + const chosen = []; + while (amountOfChoices-- > 0) { + //weighted random draw of one element of choices + const randomNumber = Math.random() * totalWeight; + let stackWeight = choices[0].weight; + let i = 0; + while (stackWeight < randomNumber) { + stackWeight += choices[++i].weight; + } + + //add it to the chosen ones and remove it from the choices before the next iteration + chosen.push(choices[i]); + totalWeight -= choices[i].weight; + choices.splice(i, 1); + } + + return chosen; } //This function will find segments that are contained inside of eachother, called similar segments @@ -55,34 +110,34 @@ function getWeightedRandomChoice(choices, amountOfChoices) { //This allows new less voted items to still sometimes appear to give them a chance at getting votes. //Segments with less than -1 votes are already ignored before this function is called function chooseSegments(segments) { - //Create groups of segments that are similar to eachother - //Segments must be sorted by their startTime so that we can build groups chronologically: - //1. As long as the segments' startTime fall inside the currentGroup, we keep adding them to that group - //2. If a segment starts after the end of the currentGroup (> cursor), no other segment will ever fall - // inside that group (because they're sorted) so we can create a new one - const similarSegmentsGroups = []; - let currentGroup; - let cursor = -1; //-1 to make sure that, even if the 1st segment starts at 0, a new group is created - segments.forEach(segment => { - if (segment.startTime > cursor) { - currentGroup = { segments: [], votes: 0 }; - similarSegmentsGroups.push(currentGroup); - } + //Create groups of segments that are similar to eachother + //Segments must be sorted by their startTime so that we can build groups chronologically: + //1. As long as the segments' startTime fall inside the currentGroup, we keep adding them to that group + //2. If a segment starts after the end of the currentGroup (> cursor), no other segment will ever fall + // inside that group (because they're sorted) so we can create a new one + const similarSegmentsGroups = []; + let currentGroup; + let cursor = -1; //-1 to make sure that, even if the 1st segment starts at 0, a new group is created + segments.forEach(segment => { + if (segment.startTime > cursor) { + currentGroup = { segments: [], votes: 0 }; + similarSegmentsGroups.push(currentGroup); + } - currentGroup.segments.push(segment); - //only if it is a positive vote, otherwise it is probably just a sponsor time with slightly wrong time - if (segment.votes > 0) { - currentGroup.votes += segment.votes; - } + currentGroup.segments.push(segment); + //only if it is a positive vote, otherwise it is probably just a sponsor time with slightly wrong time + if (segment.votes > 0) { + currentGroup.votes += segment.votes; + } - cursor = Math.max(cursor, segment.endTime); - }); + cursor = Math.max(cursor, segment.endTime); + }); - //if there are too many groups, find the best 8 - return getWeightedRandomChoice(similarSegmentsGroups, 8).map( - //randomly choose 1 good segment per group and return them - group => getWeightedRandomChoice(group.segments, 1)[0] - ); + //if there are too many groups, find the best 8 + return getWeightedRandomChoice(similarSegmentsGroups, 8).map( + //randomly choose 1 good segment per group and return them + group => getWeightedRandomChoice(group.segments, 1)[0] + ); } /** @@ -96,91 +151,39 @@ function chooseSegments(segments) { * @returns */ function handleGetSegments(req, res) { - const videoID = req.query.videoID; - // Default to sponsor - // If using params instead of JSON, only one category can be pulled - const categories = req.query.categories - ? JSON.parse(req.query.categories) - : req.query.category - ? [req.query.category] - : ['sponsor']; + const videoID = req.query.videoID; + // Default to sponsor + // If using params instead of JSON, only one category can be pulled + const categories = req.query.categories + ? JSON.parse(req.query.categories) + : req.query.category + ? [req.query.category] + : ['sponsor']; - /** - * @type {Array<{ - * segment: number[], - * category: string, - * UUID: string - * }> - * } - */ - const segments = []; + let segments = cleanGetSegments(req, videoID, categories); - let userHashedIP, shadowHiddenSegments; - - try { - for (const category of categories) { - const categorySegments = db - .prepare( - 'all', - 'SELECT startTime, endTime, votes, UUID, shadowHidden FROM sponsorTimes WHERE videoID = ? and category = ? ORDER BY startTime', - [videoID, category] - ) - .filter(segment => { - if (segment.votes < -1) { - return false; //too untrustworthy, just ignore it - } - - //check if shadowHidden - //this means it is hidden to everyone but the original ip that submitted it - if (segment.shadowHidden != 1) { - return true; - } - - if (shadowHiddenSegments === undefined) { - shadowHiddenSegments = privateDB.prepare('all', 'SELECT hashedIP FROM sponsorTimes WHERE videoID = ?', [videoID]); - } - - //if this isn't their ip, don't send it to them - return shadowHiddenSegments.some(shadowHiddenSegment => { - if (userHashedIP === undefined) { - //hash the IP only if it's strictly necessary - userHashedIP = getHash(getIP(req) + config.globalSalt); - } - return shadowHiddenSegment.hashedIP === userHashedIP; - }); - }); - - chooseSegments(categorySegments).forEach(chosenSegment => { - segments.push({ - category, - segment: [chosenSegment.startTime, chosenSegment.endTime], - UUID: chosenSegment.UUID, - }); - }); + if (segments === null || segments === undefined) { + res.sendStatus(500); + return false; } if (segments.length == 0) { - res.sendStatus(404); - return false; + res.sendStatus(404); + return false; } return segments; - } catch (error) { - logger.error(error); - res.sendStatus(500); - - return false; - } } module.exports = { - handleGetSegments, - endpoint: function (req, res) { - let segments = handleGetSegments(req, res); + handleGetSegments, + cleanGetSegments, + endpoint: function (req, res) { + let segments = handleGetSegments(req, res); - if (segments) { - //send result - res.send(segments); - } - }, + if (segments) { + //send result + res.send(segments); + } + }, }; diff --git a/src/routes/getSkipSegmentsByHash.js b/src/routes/getSkipSegmentsByHash.js new file mode 100644 index 0000000..23a9167 --- /dev/null +++ b/src/routes/getSkipSegmentsByHash.js @@ -0,0 +1,37 @@ +const hashPrefixTester = require('../utils/hashPrefixTester.js'); +const getSegments = require('./getSkipSegments.js').cleanGetSegments; + +const databases = require('../databases/databases.js'); +const logger = require('../utils/logger.js'); +const db = databases.db; + +module.exports = async function (req, res) { + let hashPrefix = req.params.prefix; + if (!hashPrefixTester(req.params.prefix)) { + res.status(400).send("Hash prefix does not match format requirements."); // Exit early on faulty prefix + return; + } + + const categories = req.query.categories + ? JSON.parse(req.query.categories) + : req.query.category + ? [req.query.category] + : ['sponsor']; + + // Get all video id's that match hash prefix + const videoIds = db.prepare('all', 'SELECT DISTINCT videoId, hashedVideoID from sponsorTimes WHERE hashedVideoID LIKE ?', [hashPrefix+'%']); + if (videoIds.length === 0) { + res.sendStatus(404); + return; + } + + let segments = videoIds.map((video) => { + return { + videoID: video.videoID, + hash: video.hashedVideoID, + segments: getSegments(req, video.videoID, categories) + }; + }); + + res.status(200).json(segments); +} \ No newline at end of file diff --git a/src/routes/postNoSegments.js b/src/routes/postNoSegments.js new file mode 100644 index 0000000..cdbfce9 --- /dev/null +++ b/src/routes/postNoSegments.js @@ -0,0 +1,74 @@ +const db = require('../databases/databases.js').db; +const getHash = require('../utils/getHash.js'); +const isUserVIP = require('../utils/isUserVIP.js'); +const logger = require('../utils/logger.js'); + +module.exports = (req, res) => { + // Collect user input data + let videoID = req.body.videoID; + let userID = req.body.userID; + let categories = req.body.categories; + + // Check input data is valid + if (!videoID + || !userID + || !categories + || !Array.isArray(categories) + || categories.length === 0 + ) { + res.status(400).json({ + message: 'Bad Format' + }); + return; + } + + // Check if user is VIP + userID = getHash(userID); + let userIsVIP = isUserVIP(userID); + + if (!userIsVIP) { + res.status(403).json({ + message: 'Must be a VIP to mark videos.' + }); + return; + } + + // Get existing no segment markers + let noSegmentList = db.prepare('all', 'SELECT category from noSegments where videoID = ?', [videoID]); + if (!noSegmentList || noSegmentList.length === 0) { + noSegmentList = []; + } else { + noSegmentList = noSegmentList.map((obj) => { + return obj.category; + }); + } + + // get user categories not already submitted that match accepted format + let categoriesToMark = categories.filter((category) => { + return !!category.match(/^[_a-zA-Z]+$/); + }).filter((category) => { + return noSegmentList.indexOf(category) === -1; + }); + + // remove any duplicates + categoriesToMark = categoriesToMark.filter((category, index) => { + return categoriesToMark.indexOf(category) === index; + }); + + // create database entry + categoriesToMark.forEach((category) => { + try { + db.prepare('run', "INSERT INTO noSegments (videoID, userID, category) VALUES(?, ?, ?)", [videoID, userID, category]); + } catch (err) { + logger.error("Error submitting 'noSegment' marker for category '" + category + "' for video '" + videoID + "'"); + logger.error(err); + res.status(500).json({ + message: "Internal Server Error: Could not write marker to the database." + }); + } + }); + + res.status(200).json({ + submitted: categoriesToMark + }); +}; \ No newline at end of file diff --git a/src/routes/postSkipSegments.js b/src/routes/postSkipSegments.js index ae3822a..820e0cf 100644 --- a/src/routes/postSkipSegments.js +++ b/src/routes/postSkipSegments.js @@ -49,10 +49,7 @@ function sendWebhooks(userID, videoID, UUID, segmentInfo) { if (config.youtubeAPIKey !== null) { let userSubmissionCountRow = db.prepare('get', "SELECT count(*) as submissionCount FROM sponsorTimes WHERE userID = ?", [userID]); - YouTubeAPI.videos.list({ - part: "snippet", - id: videoID - }, function (err, data) { + YouTubeAPI.listVideos(videoID, "snippet", (err, data) => { if (err || data.items.length === 0) { err && logger.error(err); return; @@ -156,10 +153,7 @@ async function autoModerateSubmission(submission) { // Get the video information from the youtube API if (config.youtubeAPIKey !== null) { let {err, data} = await new Promise((resolve, reject) => { - YouTubeAPI.videos.list({ - part: "contentDetails,snippet", - id: submission.videoID - }, (err, data) => resolve({err, data})); + YouTubeAPI.listVideos(submission.videoID, "contentDetails,snippet", (err, data) => resolve({err, data})); }); if (err) { @@ -224,7 +218,6 @@ async function autoModerateSubmission(submission) { } } } - } else { logger.debug("Skipped YouTube API"); @@ -238,9 +231,9 @@ function proxySubmission(req) { request.post(config.proxySubmission + '/api/skipSegments?userID='+req.query.userID+'&videoID='+req.query.videoID, {json: req.body}, (err, result) => { if (config.mode === 'development') { if (!err) { - logger.error('Proxy Submission: ' + result.statusCode + ' ('+result.body+')'); + logger.debug('Proxy Submission: ' + result.statusCode + ' ('+result.body+')'); } else { - logger.debug("Proxy Submission: Failed to make call"); + logger.error("Proxy Submission: Failed to make call"); } } }); @@ -277,6 +270,8 @@ module.exports = async function postSkipSegments(req, res) { //hash the ip 5000 times so no one can get it from the database let hashedIP = getHash(getIP(req) + config.globalSalt); + let noSegmentList = db.prepare('all', 'SELECT category from noSegments where videoID = ?', [videoID]).map((list) => { return list.category }); + //check if this user is on the vip list let isVIP = db.prepare("get", "SELECT count(*) as userCount FROM vipUsers WHERE userID = ?", [userID]).userCount > 0; @@ -295,6 +290,18 @@ module.exports = async function postSkipSegments(req, res) { return; } + // Reject segemnt if it's in the no segments list + if (noSegmentList.indexOf(segments[i].category) !== -1) { + // TODO: Do something about the fradulent submission + logger.warn("Caught a no-segment submission. userID: '" + userID + "', videoID: '" + videoID + "', category: '" + segments[i].category + "'"); + res.status(403).send( + "Request rejected by auto moderator: This video has been reported as not containing any segments with the category '" + + segments[i].category + "'. If you believe this is incorrect, contact someone on Discord." + ); + return; + } + + let startTime = parseFloat(segments[i].segment[0]); let endTime = parseFloat(segments[i].segment[1]); @@ -394,11 +401,13 @@ module.exports = async function postSkipSegments(req, res) { segmentInfo.segment[1] + segmentInfo.category + userID, 1); try { - db.prepare('run', "INSERT INTO sponsorTimes " + - "(videoID, startTime, endTime, votes, UUID, userID, timeSubmitted, views, category, shadowHidden)" + - "VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [videoID, segmentInfo.segment[0], - segmentInfo.segment[1], startingVotes, UUID, userID, timeSubmitted, 0, segmentInfo.category, shadowBanned]); - + db.prepare('run', "INSERT INTO sponsorTimes " + + "(videoID, startTime, endTime, votes, UUID, userID, timeSubmitted, views, category, shadowHidden, hashedVideoID)" + + "VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ + videoID, segmentInfo.segment[0], segmentInfo.segment[1], startingVotes, UUID, userID, timeSubmitted, 0, segmentInfo.category, shadowBanned, getHash(videoID, 1) + ] + ); + //add to private db as well privateDB.prepare('run', "INSERT INTO sponsorTimes VALUES(?, ?, ?)", [videoID, hashedIP, timeSubmitted]); } catch (err) { diff --git a/src/routes/voteOnSponsorTime.js b/src/routes/voteOnSponsorTime.js index 7fe4f18..e74dd3e 100644 --- a/src/routes/voteOnSponsorTime.js +++ b/src/routes/voteOnSponsorTime.js @@ -1,11 +1,10 @@ -var fs = require('fs'); var config = require('../config.js'); var getHash = require('../utils/getHash.js'); var getIP = require('../utils/getIP.js'); var getFormattedTime = require('../utils/getFormattedTime.js'); var isUserTrustworthy = require('../utils/isUserTrustworthy.js'); -const {getVoteAuthor, getVoteAuthorRaw, dispatchEvent} = require('../utils/webhookUtils.js'); +const { getVoteAuthor, getVoteAuthorRaw, dispatchEvent } = require('../utils/webhookUtils.js'); var databases = require('../databases/databases.js'); var db = databases.db; @@ -49,10 +48,7 @@ function sendWebhooks(voteData) { } if (config.youtubeAPIKey !== null) { - YouTubeAPI.videos.list({ - part: "snippet", - id: submissionInfoRow.videoID - }, function (err, data) { + YouTubeAPI.listVideos(submissionInfoRow.videoID, "snippet", (err, data) => { if (err || data.items.length === 0) { err && logger.error(err); return; @@ -375,8 +371,6 @@ async function voteOnSponsorTime(req, res) { } module.exports = { - voteOnSponsorTime, - endpoint: function (req, res) { - voteOnSponsorTime(req, res); - }, - }; + voteOnSponsorTime, + endpoint: voteOnSponsorTime +}; diff --git a/src/utils/hashPrefixTester.js b/src/utils/hashPrefixTester.js new file mode 100644 index 0000000..f5aec61 --- /dev/null +++ b/src/utils/hashPrefixTester.js @@ -0,0 +1,10 @@ +const config = require('../config.js'); + +const minimumPrefix = config.minimumPrefix || '3'; +const maximumPrefix = config.maximumPrefix || '32'; // Half the hash. + +const prefixChecker = new RegExp('^[\\da-f]{' + minimumPrefix + ',' + maximumPrefix + '}$', 'i'); + +module.exports = (prefix) => { + return prefixChecker.test(prefix); +}; \ No newline at end of file diff --git a/src/utils/isUserVIP.js b/src/utils/isUserVIP.js new file mode 100644 index 0000000..eeb25dc --- /dev/null +++ b/src/utils/isUserVIP.js @@ -0,0 +1,8 @@ +const databases = require('../databases/databases.js'); +const db = databases.db; + +module.exports = (userID) => { + return db.prepare('get', "SELECT count(*) as userCount FROM vipUsers WHERE userID = ?", [userID]).userCount > 0; +} + + diff --git a/src/utils/logger.js b/src/utils/logger.js index 731b5cf..66ebb03 100644 --- a/src/utils/logger.js +++ b/src/utils/logger.js @@ -46,7 +46,7 @@ if (config.mode === 'development') { settings.INFO = true; settings.DEBUG = true; } else if (config.mode === 'test') { - settings.WARN = false; + settings.WARN = false; } function log(level, string) { diff --git a/src/utils/redis.js b/src/utils/redis.js new file mode 100644 index 0000000..b278b6f --- /dev/null +++ b/src/utils/redis.js @@ -0,0 +1,18 @@ +const config = require('../config.js'); +const logger = require('./logger.js'); + +if (config.redis) { + const redis = require('redis'); + logger.info('Connected to redis'); + const client = redis.createClient(config.redis); + module.exports = client; +} else { + module.exports = { + get: (key, callback) => { + callback(false); + }, + set: (key, value, callback) => { + callback(false); + } + }; +} diff --git a/src/utils/youtubeAPI.js b/src/utils/youtubeAPI.js index c28f882..a053eb1 100644 --- a/src/utils/youtubeAPI.js +++ b/src/utils/youtubeAPI.js @@ -2,6 +2,8 @@ var config = require('../config.js'); // YouTube API const YouTubeAPI = require("youtube-api"); +const redis = require('./redis.js'); +const logger = require('./logger.js'); var exportObject; // If in test mode, return a mocked youtube object @@ -14,6 +16,41 @@ if (config.mode === "test") { key: config.youtubeAPIKey }); exportObject = YouTubeAPI; + + // YouTubeAPI.videos.list wrapper with cacheing + exportObject.listVideos = (videoID, part, callback) => { + let redisKey = "youtube.video." + videoID + "." + part; + redis.get(redisKey, (getErr, result) => { + if (getErr || !result) { + logger.debug("redis: no cache for video information: " + videoID); + YouTubeAPI.videos.list({ + part, + id: videoID + }, (ytErr, data) => { + if (!ytErr) { + // Only set cache if data returned + if (data.items.length > 0) { + redis.set(redisKey, JSON.stringify(data), (setErr) => { + if(setErr) { + logger.warn(setErr); + } else { + logger.debug("redis: video information cache set for: " + videoID); + } + callback(false, data); // don't fail + }); + } else { + callback(false, data); // don't fail + } + } else { + callback(ytErr, data) + } + }); + } else { + logger.debug("redis: fetched video information from cache: " + videoID); + callback(getErr, JSON.parse(result)); + } + }); + }; } module.exports = exportObject; \ No newline at end of file diff --git a/test.js b/test.js index 45ace90..f4e54ff 100644 --- a/test.js +++ b/test.js @@ -9,6 +9,7 @@ if (fs.existsSync(config.privateDB)) fs.unlinkSync(config.privateDB); var createServer = require('./src/app.js'); var createMockServer = require('./test/mocks.js'); +const logger = require('./src/utils/logger.js'); // Instantiate a Mocha instance. var mocha = new Mocha(); @@ -27,11 +28,11 @@ fs.readdirSync(testDir).filter(function(file) { }); var mockServer = createMockServer(() => { - console.log("Started mock HTTP Server"); + logger.info("Started mock HTTP Server"); var server = createServer(() => { - console.log("Started main HTTP server"); + logger.info("Started main HTTP server"); // Run the tests. - mocha.run(function(failures) { + mocha.run((failures) => { mockServer.close(); server.close(); process.exitCode = failures ? 1 : 0; // exit with non-zero status if there were failures diff --git a/test/cases/dbUpgrade.js b/test/cases/dbUpgrade.js new file mode 100644 index 0000000..8228d27 --- /dev/null +++ b/test/cases/dbUpgrade.js @@ -0,0 +1,12 @@ +const databases = require('../../src/databases/databases.js'); +const db = databases.db; +const privateDB = databases.privateDB; + +describe('dbUpgrade', () => { + it('Should update the database version when starting the application', (done) => { + let dbVersion = db.prepare('get', 'SELECT key, value FROM config where key = ?', ['version']).value; + let privateVersion = privateDB.prepare('get', 'SELECT key, value FROM config where key = ?', ['version']).value; + if (dbVersion >= 1 && privateVersion >= 1) done(); + else done('Versions are not at least 1. db is ' + dbVersion + ', private is ' + privateVersion); + }); +}); \ No newline at end of file diff --git a/test/cases/getHash.js b/test/cases/getHash.js index 15d7cc5..896076d 100644 --- a/test/cases/getHash.js +++ b/test/cases/getHash.js @@ -13,6 +13,10 @@ describe('getHash', () => { assert.equal(getHash("test"), "2f327ef967ade1ebf4319163f7debbda9cc17bb0c8c834b00b30ca1cf1c256ee"); }); + it ('Should be able to output the same has the DB upgrade script will output', () => { + assert.equal(getHash("vid", 1), "1ff838dc6ca9680d88455341118157d59a055fe6d0e3870f9c002847bebe4663"); + }); + it ('Should take a variable number of passes', () => { assert.equal(getHash("test", 1), "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"); assert.equal(getHash("test", 2), "7b3d979ca8330a94fa7e9e1b466d8b99e0bcdea1ec90596c0dcc8d7ef6b4300c"); diff --git a/test/cases/getIsUserVIP.js b/test/cases/getIsUserVIP.js new file mode 100644 index 0000000..1bbfbfe --- /dev/null +++ b/test/cases/getIsUserVIP.js @@ -0,0 +1,57 @@ +var request = require('request'); +var utils = require('../utils.js'); +var db = require('../../src/databases/databases.js').db; +var getHash = require('../../src/utils/getHash.js'); + +describe('getIsUserVIP', () => { + before(() => { + db.exec("INSERT INTO vipUsers (userID) VALUES ('" + getHash("supertestman") + "')"); + }); + + it('Should be able to get a 200', (done) => { + request.get(utils.getbaseURL() + + "/api/isUserVIP?userID=supertestman", null, + (err, res, body) => { + if (err) done("couldn't call endpoint"); + else if (res.statusCode !== 200) done("non 200: " + res.statusCode); + else done(); // pass + }); + }); + + + it('Should get a 400 if no userID', (done) => { + request.get(utils.getbaseURL() + + "/api/isUserVIP", null, + (err, res, body) => { + if (err) done("couldn't call endpoint"); + else if (res.statusCode !== 400) done("non 400: " + res.statusCode); + else done(); // pass + }); + }); + + it('Should say a VIP is a VIP', (done) => { + request.get(utils.getbaseURL() + + "/api/isUserVIP?userID=supertestman", null, + (err, res, body) => { + if (err) done("couldn't call endpoint"); + else if (res.statusCode !== 200) done("non 200: " + res.statusCode); + else { + if (JSON.parse(body).vip === true) done(); // pass + else done("Result was non-vip when should have been vip"); + } + }); + }); + + it('Should say a normal user is not a VIP', (done) => { + request.get(utils.getbaseURL() + + "/api/isUserVIP?userID=regulartestman", null, + (err, res, body) => { + if (err) done("couldn't call endpoint"); + else if (res.statusCode !== 200) done("non 200: " + res.statusCode); + else { + if (JSON.parse(body).vip === false) done(); // pass + else done("Result was vip when should have been non-vip"); + } + }); + }); +}); \ No newline at end of file diff --git a/test/cases/getSavedTimeForUser.js b/test/cases/getSavedTimeForUser.js index 5df8a48..242204d 100644 --- a/test/cases/getSavedTimeForUser.js +++ b/test/cases/getSavedTimeForUser.js @@ -5,8 +5,8 @@ var getHash = require('../../src/utils/getHash.js'); describe('getSavedTimeForUser', () => { before(() => { - let startOfQuery = "INSERT INTO sponsorTimes (videoID, startTime, endTime, votes, UUID, userID, timeSubmitted, views, category, shadowHidden) VALUES"; - db.exec(startOfQuery + "('getSavedTimeForUser', 1, 11, 2, 'abc1239999', '" + getHash("testman") + "', 0, 50, 'sponsor', 0)"); + let startOfQuery = "INSERT INTO sponsorTimes (videoID, startTime, endTime, votes, UUID, userID, timeSubmitted, views, category, shadowHidden, hashedVideoID) VALUES"; + db.exec(startOfQuery + "('getSavedTimeForUser', 1, 11, 2, 'abc1239999', '" + getHash("testman") + "', 0, 50, 'sponsor', 0, '" + getHash('getSavedTimeForUser', 0) + "')"); }); it('Should be able to get a 200', (done) => { diff --git a/test/cases/getSegmentsByHash.js b/test/cases/getSegmentsByHash.js new file mode 100644 index 0000000..2704b61 --- /dev/null +++ b/test/cases/getSegmentsByHash.js @@ -0,0 +1,189 @@ +var request = require('request'); +var db = require('../../src/databases/databases.js').db; +var utils = require('../utils.js'); +var getHash = require('../../src/utils/getHash.js'); + +describe('getSegmentsByHash', () => { + before(() => { + let startOfQuery = "INSERT INTO sponsorTimes (videoID, startTime, endTime, votes, UUID, userID, timeSubmitted, views, category, shadowHidden, hashedVideoID) VALUES"; + db.exec(startOfQuery + "('getSegmentsByHash-0', 1, 10, 2, 'getSegmentsByHash-0-0', 'testman', 0, 50, 'sponsor', 0, '" + getHash('getSegmentsByHash-0', 1) + "')"); // hash = fdaff4dee1043451faa7398324fb63d8618ebcd11bddfe0491c488db12c6c910 + db.exec(startOfQuery + "('getSegmentsByHash-0', 20, 30, 2, 'getSegmentsByHash-0-1', 'testman', 100, 150, 'intro', 0, '" + getHash('getSegmentsByHash-0', 1) + "')"); // hash = fdaff4dee1043451faa7398324fb63d8618ebcd11bddfe0491c488db12c6c910 + db.exec(startOfQuery + "('getSegmentsByHash-noMatchHash', 40, 50, 2, 'getSegmentsByHash-noMatchHash', 'testman', 0, 50, 'sponsor', 0, 'fdaffnoMatchHash')"); // hash = fdaff4dee1043451faa7398324fb63d8618ebcd11bddfe0491c488db12c6c910 + db.exec(startOfQuery + "('getSegmentsByHash-1', 60, 70, 2, 'getSegmentsByHash-1', 'testman', 0, 50, 'sponsor', 0, '" + getHash('getSegmentsByHash-1', 1) + "')"); // hash = 3272fa85ee0927f6073ef6f07ad5f3146047c1abba794cfa364d65ab9921692b + }); + + it('Should be able to get a 200', (done) => { + request.get(utils.getbaseURL() + + '/api/skipSegments/3272f?categories=["sponsor", "intro"]', null, + (err, res, body) => { + if (err) done("Couldn't call endpoint"); + else if (res.statusCode !== 200) done("non 200 status code, was " + res.statusCode); + else { + done(); + } // pass + }); + }); + + it('Should be able to get a 200 with empty segments for video but no matching categories', (done) => { + request.get(utils.getbaseURL() + + '/api/skipSegments/3272f?categories=["shilling"]', null, + (err, res, body) => { + if (err) done("Couldn't call endpoint"); + else if (res.statusCode !== 200) done("non 200 status code, was " + res.statusCode); + else { + if (JSON.parse(body) && JSON.parse(body).length > 0 && JSON.parse(body)[0].segments.length === 0) { + done(); // pass + } else { + done("response had segments"); + } + } + }); + }); + + it('Should be able to get a 404 if no videos', (done) => { + request.get(utils.getbaseURL() + + '/api/skipSegments/11111?categories=["shilling"]', null, + (err, res, body) => { + if (err) done("Couldn't call endpoint"); + else if (res.statusCode !== 404) done("non 404 status code, was " + res.statusCode); + else { + done(); // pass + } + }); + }); + + it('Should return 400 prefix too short', (done) => { + request.get(utils.getbaseURL() + + '/api/skipSegments/11?categories=["shilling"]', null, + (err, res, body) => { + if (err) done("Couldn't call endpoint"); + else if (res.statusCode !== 400) done("non 400 status code, was " + res.statusCode); + else { + done(); // pass + } + }); + }); + + it('Should return 400 prefix too long', (done) => { + let prefix = new Array(50).join('1'); + if (prefix.length <= 32) { // default value, config can change this + done('failed to generate a long enough string for the test ' + prefix.length); + return; + } + + request.get(utils.getbaseURL() + + '/api/skipSegments/'+prefix+'?categories=["shilling"]', null, + (err, res, body) => { + if (err) done("Couldn't call endpoint"); + else if (res.statusCode !== 400) done("non 400 status code, was " + res.statusCode); + else { + done(); // pass + } + }); + }); + + it('Should not return 400 prefix in range', (done) => { + request.get(utils.getbaseURL() + + '/api/skipSegments/11111?categories=["shilling"]', null, + (err, res, body) => { + if (err) done("Couldn't call endpoint"); + else if (res.statusCode === 400) done("prefix length 5 gave 400 " + res.statusCode); + else { + done(); // pass + } + }); + }); + + it('Should return 404 for no hash', (done) => { + request.get(utils.getbaseURL() + + '/api/skipSegments/?categories=["shilling"]', null, + (err, res, body) => { + if (err) done("Couldn't call endpoint"); + else if (res.statusCode !== 404) done("expected 404, got " + res.statusCode); + else { + done(); // pass + } + }); + }); + + it('Should return 500 for bad format categories', (done) => { // should probably be 400 + request.get(utils.getbaseURL() + + '/api/skipSegments/?categories=shilling', null, + (err, res, body) => { + if (err) done("Couldn't call endpoint"); + else if (res.statusCode !== 500) done("expected 500 got " + res.statusCode); + else { + done(); // pass + } + }); + }); + + it('Should be able to get multiple videos', (done) => { + request.get(utils.getbaseURL() + + '/api/skipSegments/fdaf?categories=["sponsor","intro"]', null, + (err, res, body) => { + if (err) done("Couldn't call endpoint"); + else if (res.statusCode !== 200) done("non 200 status code, was " + res.statusCode); + else { + body = JSON.parse(body); + if (body.length !== 2) done("expected 2 video, got " + body.length); + else if (body[0].segments.length !== 2) done("expected 2 segments for first video, got " + body[0].segments.length); + else if (body[1].segments.length !== 1) done("expected 1 segment for second video, got " + body[1].segments.length); + else done(); + } + }); + }); + + it('Should be able to get 200 for no categories (default sponsor)', (done) => { + request.get(utils.getbaseURL() + + '/api/skipSegments/fdaf', null, + (err, res, body) => { + if (err) done("Couldn't call endpoint"); + else if (res.statusCode !== 200) done("non 200 status code, was " + res.statusCode); + else { + body = JSON.parse(body); + if (body.length !== 2) done("expected 2 videos, got " + body.length); + else if (body[0].segments.length !== 1) done("expected 1 segments for first video, got " + body[0].segments.length); + else if (body[1].segments.length !== 1) done("expected 1 segments for second video, got " + body[1].segments.length); + else if (body[0].segments[0].category !== 'sponsor' || body[1].segments[0].category !== 'sponsor') done("both segments are not sponsor"); + else done(); + } + }); + }); + + it('Should be able to post a segment and get it using endpoint', (done) => { + let testID = 'abc123goodVideo'; + request.post(utils.getbaseURL() + + "/api/postVideoSponsorTimes", { + json: { + userID: "test", + videoID: testID, + segments: [{ + segment: [13, 17], + category: "sponsor" + }] + } + }, + (err, res, body) => { + if (err) done('(post) ' + err); + else if (res.statusCode === 200) { + request.get(utils.getbaseURL() + + '/api/skipSegments/'+getHash(testID, 1).substring(0,3), null, + (err, res, body) => { + if (err) done("(get) Couldn't call endpoint"); + else if (res.statusCode !== 200) done("(get) non 200 status code, was " + res.statusCode); + else { + body = JSON.parse(body); + if (body.length !== 1) done("(get) expected 1 video, got " + body.length); + else if (body[0].segments.length !== 1) done("(get) expected 1 segments for first video, got " + body[0].segments.length); + else if (body[0].segments[0].category !== 'sponsor') done("(get) segment should be sponsor, was "+body[0].segments[0].category); + else done(); + } + }); + } else { + done("(post) non 200 status code, was " + res.statusCode); + } + } + ); + }); +}); \ No newline at end of file diff --git a/test/cases/getSkipSegments.js b/test/cases/getSkipSegments.js index 5c15c63..0314028 100644 --- a/test/cases/getSkipSegments.js +++ b/test/cases/getSkipSegments.js @@ -1,31 +1,19 @@ var request = require('request'); var db = require('../../src/databases/databases.js').db; var utils = require('../utils.js'); +var getHash = require('../../src/utils/getHash.js'); -/* - *CREATE TABLE IF NOT EXISTS "sponsorTimes" ( - "videoID" TEXT NOT NULL, - "startTime" REAL NOT NULL, - "endTime" REAL NOT NULL, - "votes" INTEGER NOT NULL, - "UUID" TEXT NOT NULL UNIQUE, - "userID" TEXT NOT NULL, - "timeSubmitted" INTEGER NOT NULL, - "views" INTEGER NOT NULL, - "shadowHidden" INTEGER NOT NULL -); - */ describe('getSkipSegments', () => { before(() => { - let startOfQuery = "INSERT INTO sponsorTimes (videoID, startTime, endTime, votes, UUID, userID, timeSubmitted, views, category, shadowHidden) VALUES"; - db.exec(startOfQuery + "('testtesttest', 1, 11, 2, '1-uuid-0', 'testman', 0, 50, 'sponsor', 0)"); - db.exec(startOfQuery + "('testtesttest', 20, 33, 2, '1-uuid-2', 'testman', 0, 50, 'intro', 0)"); - db.exec(startOfQuery + "('testtesttest,test', 1, 11, 2, '1-uuid-1', 'testman', 0, 50, 'sponsor', 0)"); - db.exec(startOfQuery + "('test3', 1, 11, 2, '1-uuid-4', 'testman', 0, 50, 'sponsor', 0)"); - db.exec(startOfQuery + "('test3', 7, 22, -3, '1-uuid-5', 'testman', 0, 50, 'sponsor', 0)"); - db.exec(startOfQuery + "('multiple', 1, 11, 2, '1-uuid-6', 'testman', 0, 50, 'intro', 0)"); - db.exec(startOfQuery + "('multiple', 20, 33, 2, '1-uuid-7', 'testman', 0, 50, 'intro', 0)"); + let startOfQuery = "INSERT INTO sponsorTimes (videoID, startTime, endTime, votes, UUID, userID, timeSubmitted, views, category, shadowHidden, hashedVideoID) VALUES"; + db.exec(startOfQuery + "('testtesttest', 1, 11, 2, '1-uuid-0', 'testman', 0, 50, 'sponsor', 0, '" + getHash('testtesttest', 0) + "')"); + db.exec(startOfQuery + "('testtesttest', 20, 33, 2, '1-uuid-2', 'testman', 0, 50, 'intro', 0, '" + getHash('testtesttest', 0) + "')"); + db.exec(startOfQuery + "('testtesttest,test', 1, 11, 2, '1-uuid-1', 'testman', 0, 50, 'sponsor', 0, '" + getHash('testtesttest,test', 0) + "')"); + db.exec(startOfQuery + "('test3', 1, 11, 2, '1-uuid-4', 'testman', 0, 50, 'sponsor', 0, '" + getHash('test3', 0) + "')"); + db.exec(startOfQuery + "('test3', 7, 22, -3, '1-uuid-5', 'testman', 0, 50, 'sponsor', 0, '" + getHash('test3', 0) + "')"); + db.exec(startOfQuery + "('multiple', 1, 11, 2, '1-uuid-6', 'testman', 0, 50, 'intro', 0, '" + getHash('multiple', 0) + "')"); + db.exec(startOfQuery + "('multiple', 20, 33, 2, '1-uuid-7', 'testman', 0, 50, 'intro', 0, '" + getHash('multiple', 0) + "')"); }); diff --git a/test/cases/noSegmentRecords.js b/test/cases/noSegmentRecords.js new file mode 100644 index 0000000..f922d12 --- /dev/null +++ b/test/cases/noSegmentRecords.js @@ -0,0 +1,408 @@ +var request = require('request'); + +var utils = require('../utils.js'); +const getHash = require('../../src/utils/getHash.js'); + +var databases = require('../../src/databases/databases.js'); +const logger = require('../../src/utils/logger.js'); +var db = databases.db; + +describe('noSegmentRecords', () => { + before(() => { + db.exec("INSERT INTO vipUsers (userID) VALUES ('" + getHash("VIPUser-noSegments") + "')"); + + db.exec("INSERT INTO noSegments (userID, videoID, category) VALUES ('" + getHash("VIPUser-noSegments") + "', 'no-segments-video-id', 'sponsor')"); + db.exec("INSERT INTO noSegments (userID, videoID, category) VALUES ('" + getHash("VIPUser-noSegments") + "', 'no-segments-video-id', 'intro')"); + + db.exec("INSERT INTO noSegments (userID, videoID, category) VALUES ('" + getHash("VIPUser-noSegments") + "', 'no-segments-video-id-1', 'sponsor')"); + db.exec("INSERT INTO noSegments (userID, videoID, category) VALUES ('" + getHash("VIPUser-noSegments") + "', 'no-segments-video-id-1', 'intro')"); + db.exec("INSERT INTO noSegments (userID, videoID, category) VALUES ('" + getHash("VIPUser-noSegments") + "', 'noSubmitVideo', 'sponsor')"); + }); + + it('Should update the database version when starting the application', (done) => { + let version = db.prepare('get', 'SELECT key, value FROM config where key = ?', ['version']).value; + if (version > 1) done(); + else done('Version isn\'t greater than 1. Version is ' + version); + }); + + it('Should be able to submit categories not in video (http response)', (done) => { + let json = { + videoID: 'no-segments-video-id', + userID: 'VIPUser-noSegments', + categories: [ + 'outro', + 'shilling', + 'shilling', + 'shil ling', + '', + 'intro' + ] + }; + + let expected = { + submitted: [ + 'outro', + 'shilling' + ] + }; + + request.post(utils.getbaseURL() + + "/api/noSegments", {json}, + (err, res, body) => { + if (err) done(err); + else if (res.statusCode === 200) { + if (JSON.stringify(body) === JSON.stringify(expected)) { + done(); + } else { + done("Incorrect response: expected " + JSON.stringify(expected) + " got " + JSON.stringify(body)); + } + } else { + console.log(body); + done("Status code was " + res.statusCode); + } + }); + }); + + it('Should be able to submit categories not in video (sql check)', (done) => { + let json = { + videoID: 'no-segments-video-id-1', + userID: 'VIPUser-noSegments', + categories: [ + 'outro', + 'shilling', + 'shilling', + 'shil ling', + '', + 'intro' + ] + }; + + request.post(utils.getbaseURL() + + "/api/noSegments", {json}, + (err, res, body) => { + if (err) done(err); + else if (res.statusCode === 200) { + let result = db.prepare('all', 'SELECT * FROM noSegments WHERE videoID = ?', ['no-segments-video-id-1']); + if (result.length !== 4) { + console.log(result); + done("Expected 4 entrys in db, got " + result.length); + } else { + done(); + } + } else { + console.log(body); + done("Status code was " + res.statusCode); + } + }); + }); + + it('Should be able to submit categories with _ in the category', (done) => { + let json = { + videoID: 'underscore', + userID: 'VIPUser-noSegments', + categories: [ + 'word_word', + ] + }; + + request.post(utils.getbaseURL() + + "/api/noSegments", {json}, + (err, res, body) => { + if (err) done(err); + else if (res.statusCode === 200) { + let result = db.prepare('all', 'SELECT * FROM noSegments WHERE videoID = ?', ['underscore']); + if (result.length !== 1) { + console.log(result); + done("Expected 1 entrys in db, got " + result.length); + } else { + done(); + } + } else { + console.log(body); + done("Status code was " + res.statusCode); + } + }); + }); + + it('Should be able to submit categories with upper and lower case in the category', (done) => { + let json = { + videoID: 'bothCases', + userID: 'VIPUser-noSegments', + categories: [ + 'wordWord', + ] + }; + + request.post(utils.getbaseURL() + + "/api/noSegments", {json}, + (err, res, body) => { + if (err) done(err); + else if (res.statusCode === 200) { + let result = db.prepare('all', 'SELECT * FROM noSegments WHERE videoID = ?', ['bothCases']); + if (result.length !== 1) { + console.log(result); + done("Expected 1 entrys in db, got " + result.length); + } else { + done(); + } + } else { + console.log(body); + done("Status code was " + res.statusCode); + } + }); + }); + + it('Should not be able to submit categories with $ in the category', (done) => { + let json = { + videoID: 'specialChar', + userID: 'VIPUser-noSegments', + categories: [ + 'word&word', + ] + }; + + request.post(utils.getbaseURL() + + "/api/noSegments", {json}, + (err, res, body) => { + if (err) done(err); + else if (res.statusCode === 200) { + let result = db.prepare('all', 'SELECT * FROM noSegments WHERE videoID = ?', ['specialChar']); + if (result.length !== 0) { + console.log(result); + done("Expected 0 entrys in db, got " + result.length); + } else { + done(); + } + } else { + console.log(body); + done("Status code was " + res.statusCode); + } + }); + }); + + it('Should return 400 for missing params', (done) => { + request.post(utils.getbaseURL() + + "/api/noSegments", {json: {}}, + (err, res, body) => { + if (err) done(err); + else if (res.statusCode === 400) { + done() + } else { + done("Status code was " + res.statusCode); + } + }); + }); + + it('Should return 400 for no categories', (done) => { + let json = { + videoID: 'test', + userID: 'test', + categories: [] + }; + + request.post(utils.getbaseURL() + + "/api/noSegments", {json}, + (err, res, body) => { + if (err) done(err); + else if (res.statusCode === 400) { + done() + } else { + done("Status code was " + res.statusCode); + } + }); + }); + + it('Should return 400 for no userID', (done) => { + let json = { + videoID: 'test', + userID: null, + categories: ['sponsor'] + }; + + request.post(utils.getbaseURL() + + "/api/noSegments", {json}, + (err, res, body) => { + if (err) done(err); + else if (res.statusCode === 400) { + done() + } else { + done("Status code was " + res.statusCode); + } + }); + }); + + it('Should return 400 for no videoID', (done) => { + let json = { + videoID: null, + userID: 'test', + categories: ['sponsor'] + }; + + request.post(utils.getbaseURL() + + "/api/noSegments", {json}, + (err, res, body) => { + if (err) done(err); + else if (res.statusCode === 400) { + done() + } else { + done("Status code was " + res.statusCode); + } + }); + }); + + it('Should return 400 object categories)', (done) => { + let json = { + videoID: 'test', + userID: 'test', + categories: {} + }; + + request.post(utils.getbaseURL() + + "/api/noSegments", {json}, + (err, res, body) => { + if (err) done(err); + else if (res.statusCode === 400) { + done() + } else { + done("Status code was " + res.statusCode); + } + }); + }); + + it('Should return 400 bad format categories', (done) => { + let json = { + videoID: 'test', + userID: 'test', + categories: 'sponsor' + }; + + request.post(utils.getbaseURL() + + "/api/noSegments", {json}, + (err, res, body) => { + if (err) done(err); + else if (res.statusCode === 400) { + done() + } else { + done("Status code was " + res.statusCode); + } + }); + }); + + it('Should return 403 if user is not VIP', (done) => { + let json = { + videoID: 'test', + userID: 'test', + categories: [ + 'sponsor' + ] + }; + + request.post(utils.getbaseURL() + + "/api/noSegments", {json}, + (err, res, body) => { + if (err) done(err); + else if (res.statusCode === 403) { + done(); + } else { + done("Status code was " + res.statusCode); + } + }); + }); + + /* + * Submission tests in this file do not check database records, only status codes. + * To test the submission code properly see ./test/cases/postSkipSegments.js + */ + + it('Should not be able to submit a segment to a video with a no-segment record (single submission)', (done) => { + request.post(utils.getbaseURL() + + "/api/postVideoSponsorTimes", { + json: { + userID: "testman42", + videoID: "noSubmitVideo", + segments: [{ + segment: [20, 40], + category: "sponsor" + }] + } + }, + (err, res, body) => { + if (err) done(err); + else if (res.statusCode === 403) { + done() + } else { + done("Status code was " + res.statusCode); + } + }); + }); + + it('Should not be able to submit segments to a video where any of the submissions with a no-segment record', (done) => { + request.post(utils.getbaseURL() + + "/api/postVideoSponsorTimes", { + json: { + userID: "testman42", + videoID: "noSubmitVideo", + segments: [{ + segment: [20, 40], + category: "sponsor" + },{ + segment: [50, 60], + category: "intro" + }] + } + }, + (err, res, body) => { + if (err) done(err); + else if (res.statusCode === 403) { + done() + } else { + done("Status code was " + res.statusCode); + } + }); + }); + + + it('Should be able to submit a segment to a video with a different no-segment record', (done) => { + request.post(utils.getbaseURL() + + "/api/postVideoSponsorTimes", { + json: { + userID: "testman42", + videoID: "noSubmitVideo", + segments: [{ + segment: [20, 40], + category: "intro" + }] + } + }, + (err, res, body) => { + if (err) done(err); + else if (res.statusCode === 200) { + done() + } else { + done("Status code was " + res.statusCode); + } + }); + }); + + it('Should be able to submit a segment to a video with no no-segment records', (done) => { + request.post(utils.getbaseURL() + + "/api/postVideoSponsorTimes", { + json: { + userID: "testman42", + videoID: "normalVideo", + segments: [{ + segment: [20, 40], + category: "intro" + }] + } + }, + (err, res, body) => { + if (err) done(err); + else if (res.statusCode === 200) { + done() + } else { + done("Status code was " + res.statusCode); + } + }); + }); +}); \ No newline at end of file diff --git a/test/cases/oldGetSponsorTime.js b/test/cases/oldGetSponsorTime.js index 569b109..d4b9a88 100644 --- a/test/cases/oldGetSponsorTime.js +++ b/test/cases/oldGetSponsorTime.js @@ -1,6 +1,7 @@ var request = require('request'); var db = require('../../src/databases/databases.js').db; var utils = require('../utils.js'); +var getHash = require('../../src/utils/getHash.js'); /* @@ -19,9 +20,9 @@ var utils = require('../utils.js'); describe('getVideoSponsorTime (Old get method)', () => { before(() => { - let startOfQuery = "INSERT INTO sponsorTimes (videoID, startTime, endTime, votes, UUID, userID, timeSubmitted, views, category, shadowHidden) VALUES"; - db.exec(startOfQuery + "('old-testtesttest', 1, 11, 2, 'uuid-0', 'testman', 0, 50, 'sponsor', 0)"); - db.exec(startOfQuery + "('old-testtesttest,test', 1, 11, 2, 'uuid-1', 'testman', 0, 50, 'sponsor', 0)"); + let startOfQuery = "INSERT INTO sponsorTimes (videoID, startTime, endTime, votes, UUID, userID, timeSubmitted, views, category, shadowHidden, hashedVideoID) VALUES"; + db.exec(startOfQuery + "('old-testtesttest', 1, 11, 2, 'uuid-0', 'testman', 0, 50, 'sponsor', 0, '" + getHash('old-testtesttest', 1) + "')"); + db.exec(startOfQuery + "('old-testtesttest,test', 1, 11, 2, 'uuid-1', 'testman', 0, 50, 'sponsor', 0, '" + getHash('old-testtesttest,test', 1) + "')"); }); it('Should be able to get a time', (done) => { diff --git a/test/cases/voteOnSponsorTime.js b/test/cases/voteOnSponsorTime.js index 615eefb..2e94a0b 100644 --- a/test/cases/voteOnSponsorTime.js +++ b/test/cases/voteOnSponsorTime.js @@ -5,25 +5,26 @@ const getHash = require('../../src/utils/getHash.js'); describe('voteOnSponsorTime', () => { before(() => { - let startOfQuery = "INSERT INTO sponsorTimes (videoID, startTime, endTime, votes, UUID, userID, timeSubmitted, views, category, shadowHidden) VALUES"; - db.exec(startOfQuery + "('vote-testtesttest', 1, 11, 2, 'vote-uuid-0', 'testman', 0, 50, 'sponsor', 0)"); - db.exec(startOfQuery + "('vote-testtesttest2', 1, 11, 2, 'vote-uuid-1', 'testman', 0, 50, 'sponsor', 0)"); - db.exec(startOfQuery + "('vote-testtesttest2', 1, 11, 10, 'vote-uuid-1.5', 'testman', 0, 50, 'outro', 0)"); - db.exec(startOfQuery + "('vote-testtesttest2', 1, 11, 10, 'vote-uuid-1.6', 'testman', 0, 50, 'interaction', 0)"); - db.exec(startOfQuery + "('vote-testtesttest3', 20, 33, 10, 'vote-uuid-2', 'testman', 0, 50, 'intro', 0)"); - db.exec(startOfQuery + "('vote-testtesttest,test', 1, 11, 100, 'vote-uuid-3', 'testman', 0, 50, 'sponsor', 0)"); - db.exec(startOfQuery + "('vote-test3', 1, 11, 2, 'vote-uuid-4', 'testman', 0, 50, 'sponsor', 0)"); - db.exec(startOfQuery + "('vote-test3', 7, 22, -3, 'vote-uuid-5', 'testman', 0, 50, 'intro', 0)"); - db.exec(startOfQuery + "('vote-multiple', 1, 11, 2, 'vote-uuid-6', 'testman', 0, 50, 'intro', 0)"); - db.exec(startOfQuery + "('vote-multiple', 20, 33, 2, 'vote-uuid-7', 'testman', 0, 50, 'intro', 0)"); - db.exec(startOfQuery + "('voter-submitter', 1, 11, 2, 'vote-uuid-8', '" + getHash("randomID") + "', 0, 50, 'sponsor', 0)"); - db.exec(startOfQuery + "('voter-submitter2', 1, 11, 2, 'vote-uuid-9', '" + getHash("randomID2") + "', 0, 50, 'sponsor', 0)"); - db.exec(startOfQuery + "('voter-submitter2', 1, 11, 2, 'vote-uuid-10', '" + getHash("randomID3") + "', 0, 50, 'sponsor', 0)"); - db.exec(startOfQuery + "('voter-submitter2', 1, 11, 2, 'vote-uuid-11', '" + getHash("randomID4") + "', 0, 50, 'sponsor', 0)"); - db.exec(startOfQuery + "('own-submission-video', 1, 11, 500, 'own-submission-uuid', '"+ getHash('own-submission-id') +"', 0, 50, 'sponsor', 0)"); - db.exec(startOfQuery + "('not-own-submission-video', 1, 11, 500, 'not-own-submission-uuid', '"+ getHash('somebody-else-id') +"', 0, 50, 'sponsor', 0)"); - db.exec(startOfQuery + "('incorrect-category', 1, 11, 500, 'incorrect-category', '"+ getHash('somebody-else-id') +"', 0, 50, 'sponsor', 0)"); - db.exec(startOfQuery + "('incorrect-category-change', 1, 11, 500, 'incorrect-category-change', '"+ getHash('somebody-else-id') +"', 0, 50, 'sponsor', 0)"); + let startOfQuery = "INSERT INTO sponsorTimes (videoID, startTime, endTime, votes, UUID, userID, timeSubmitted, views, category, shadowHidden, hashedVideoID) VALUES"; + + db.exec(startOfQuery + "('vote-testtesttest', 1, 11, 2, 'vote-uuid-0', 'testman', 0, 50, 'sponsor', 0, '" + getHash('vote-testtesttest', 1) + "')"); + db.exec(startOfQuery + "('vote-testtesttest2', 1, 11, 2, 'vote-uuid-1', 'testman', 0, 50, 'sponsor', 0, '" + getHash('vote-testtesttest2', 1) + "')"); + db.exec(startOfQuery + "('vote-testtesttest2', 1, 11, 10, 'vote-uuid-1.5', 'testman', 0, 50, 'outro', 0, '" + getHash('vote-testtesttest2', 1) + "')"); + db.exec(startOfQuery + "('vote-testtesttest2', 1, 11, 10, 'vote-uuid-1.6', 'testman', 0, 50, 'interaction', 0, '" + getHash('vote-testtesttest2', 1) + "')"); + db.exec(startOfQuery + "('vote-testtesttest3', 20, 33, 10, 'vote-uuid-2', 'testman', 0, 50, 'intro', 0, '" + getHash('vote-testtesttest3', 1) + "')"); + db.exec(startOfQuery + "('vote-testtesttest,test', 1, 11, 100, 'vote-uuid-3', 'testman', 0, 50, 'sponsor', 0, '" + getHash('vote-testtesttest,test', 1) + "')"); + db.exec(startOfQuery + "('vote-test3', 1, 11, 2, 'vote-uuid-4', 'testman', 0, 50, 'sponsor', 0, '" + getHash('vote-test3', 1) + "')"); + db.exec(startOfQuery + "('vote-test3', 7, 22, -3, 'vote-uuid-5', 'testman', 0, 50, 'intro', 0, '" + getHash('vote-test3', 1) + "')"); + db.exec(startOfQuery + "('vote-multiple', 1, 11, 2, 'vote-uuid-6', 'testman', 0, 50, 'intro', 0, '" + getHash('vote-multiple', 1) + "')"); + db.exec(startOfQuery + "('vote-multiple', 20, 33, 2, 'vote-uuid-7', 'testman', 0, 50, 'intro', 0, '" + getHash('vote-multiple', 1) + "')"); + db.exec(startOfQuery + "('voter-submitter', 1, 11, 2, 'vote-uuid-8', '" + getHash("randomID") + "', 0, 50, 'sponsor', 0, '" + getHash('voter-submitter', 1) + "')"); + db.exec(startOfQuery + "('voter-submitter2', 1, 11, 2, 'vote-uuid-9', '" + getHash("randomID2") + "', 0, 50, 'sponsor', 0, '" + getHash('voter-submitter2', 1) + "')"); + db.exec(startOfQuery + "('voter-submitter2', 1, 11, 2, 'vote-uuid-10', '" + getHash("randomID3") + "', 0, 50, 'sponsor', 0, '" + getHash('voter-submitter2', 1) + "')"); + db.exec(startOfQuery + "('voter-submitter2', 1, 11, 2, 'vote-uuid-11', '" + getHash("randomID4") + "', 0, 50, 'sponsor', 0, '" + getHash('voter-submitter2', 1) + "')"); + db.exec(startOfQuery + "('own-submission-video', 1, 11, 500, 'own-submission-uuid', '"+ getHash('own-submission-id') +"', 0, 50, 'sponsor', 0, '" + getHash('own-submission-video', 1) + "')"); + db.exec(startOfQuery + "('not-own-submission-video', 1, 11, 500, 'not-own-submission-uuid', '"+ getHash('somebody-else-id') +"', 0, 50, 'sponsor', 0, '" + getHash('not-own-submission-video', 1) + "')"); + db.exec(startOfQuery + "('incorrect-category', 1, 11, 500, 'incorrect-category', '"+ getHash('somebody-else-id') +"', 0, 50, 'sponsor', 0, '" + getHash('incorrect-category', 1) + "')"); + db.exec(startOfQuery + "('incorrect-category-change', 1, 11, 500, 'incorrect-category-change', '"+ getHash('somebody-else-id') +"', 0, 50, 'sponsor', 0, '" + getHash('incorrect-category-change', 1) + "')"); db.exec("INSERT INTO vipUsers (userID) VALUES ('" + getHash("VIPUser") + "')"); privateDB.exec("INSERT INTO shadowBannedUsers (userID) VALUES ('" + getHash("randomID4") + "')"); diff --git a/test/youtubeMock.js b/test/youtubeMock.js index dd64898..cc722d9 100644 --- a/test/youtubeMock.js +++ b/test/youtubeMock.js @@ -8,6 +8,12 @@ YouTubeAPI.videos.list({ // https://developers.google.com/youtube/v3/docs/videos const YouTubeAPI = { + listVideos: (id, part, callback) => { + YouTubeAPI.videos.list({ + part: part, + id: id + }, callback); + }, videos: { list: (obj, callback) => { if (obj.id === "knownWrongID") {