Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b21a0bdae | ||
|
|
5311618d1d | ||
|
|
37ce14dda7 | ||
|
|
0d8d5845c9 | ||
|
|
143f534ba1 | ||
|
|
4db3d94ee8 | ||
|
|
e195cf77cd | ||
|
|
ac9622f1bd | ||
|
|
e8535b1a80 | ||
|
|
d81b763d9b | ||
|
|
1c5726be4c | ||
|
|
6da5abcefc | ||
|
|
c5e39f104f | ||
|
|
60d2f7cfd3 | ||
|
|
0ac8b44ce0 | ||
|
|
59fec74d08 | ||
|
|
52f142a2b9 | ||
|
|
3a7132d276 | ||
|
|
4f89894fd1 |
Generated
+28
-121
@@ -192,6 +192,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@decafcode/sqlite": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@decafcode/sqlite/-/sqlite-2.0.0.tgz",
|
||||
"integrity": "sha512-CvDlc1G2ExETrRXVjjHRZmZiDCY56pw3xU3pxWnOzkQ4j4bi+DUAYlQbFKwpb4BzRdFo84e7H+T2kmoEBf90JA==",
|
||||
"requires": {
|
||||
"node-gyp-build": "^4.2.0"
|
||||
}
|
||||
},
|
||||
"@jest/console": {
|
||||
"version": "24.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@jest/console/-/console-24.9.0.tgz",
|
||||
@@ -401,15 +409,6 @@
|
||||
"@babel/types": "^7.3.0"
|
||||
}
|
||||
},
|
||||
"@types/better-sqlite3": {
|
||||
"version": "5.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-5.4.0.tgz",
|
||||
"integrity": "sha512-nzm7lJ7l3jBmGUbtkL8cdOMhPkN6Pw2IM+b0V7iIKba+YKiLrjkIy7vVLsBIVnd7+lgzBzrHsXZxCaFTcmw5Ow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/integer": "*"
|
||||
}
|
||||
},
|
||||
"@types/body-parser": {
|
||||
"version": "1.17.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.1.tgz",
|
||||
@@ -474,12 +473,6 @@
|
||||
"@types/range-parser": "*"
|
||||
}
|
||||
},
|
||||
"@types/integer": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/integer/-/integer-1.0.0.tgz",
|
||||
"integrity": "sha512-3viiRKLoSP2Qr78nMoQjkDc0fan4BgmpOyV1+1gKjE8wWXo3QQ78WItO6f9WuBf3qe3ymDYhM65oqHTOZ0rFxw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/istanbul-lib-coverage": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz",
|
||||
@@ -666,12 +659,6 @@
|
||||
"normalize-path": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"arg": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.1.tgz",
|
||||
"integrity": "sha512-SlmP3fEA88MBv0PypnXZ8ZfJhwmDeIE3SP71j37AiXQBXYosPV0x6uISAaHYSlSVhmHOVkomen0tbGk6Anlebw==",
|
||||
"dev": true
|
||||
},
|
||||
"arr-diff": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
|
||||
@@ -895,15 +882,6 @@
|
||||
"tweetnacl": "^0.14.3"
|
||||
}
|
||||
},
|
||||
"better-sqlite3": {
|
||||
"version": "5.4.3",
|
||||
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-5.4.3.tgz",
|
||||
"integrity": "sha512-fPp+8f363qQIhuhLyjI4bu657J/FfMtgiiHKfaTsj3RWDkHlWC1yT7c6kHZDnBxzQVoAINuzg553qKmZ4F1rEw==",
|
||||
"requires": {
|
||||
"integer": "^2.1.0",
|
||||
"tar": "^4.4.10"
|
||||
}
|
||||
},
|
||||
"body-parser": {
|
||||
"version": "1.19.0",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
|
||||
@@ -1110,11 +1088,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"chownr": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz",
|
||||
"integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw=="
|
||||
},
|
||||
"ci-info": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz",
|
||||
@@ -1196,9 +1169,9 @@
|
||||
}
|
||||
},
|
||||
"commander": {
|
||||
"version": "2.20.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz",
|
||||
"integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==",
|
||||
"version": "2.20.3",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
@@ -1481,12 +1454,6 @@
|
||||
"integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=",
|
||||
"dev": true
|
||||
},
|
||||
"diff": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz",
|
||||
"integrity": "sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==",
|
||||
"dev": true
|
||||
},
|
||||
"diff-sequences": {
|
||||
"version": "24.9.0",
|
||||
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.9.0.tgz",
|
||||
@@ -1995,14 +1962,6 @@
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
|
||||
},
|
||||
"fs-minipass": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz",
|
||||
"integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==",
|
||||
"requires": {
|
||||
"minipass": "^2.6.0"
|
||||
}
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
@@ -2626,9 +2585,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"handlebars": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.4.3.tgz",
|
||||
"integrity": "sha512-B0W4A2U1ww3q7VVthTKfh+epHx+q4mCt6iK+zEAzbMBpWQAwxCeKxEGpj/1oQTpzPXDNSOG7hmG14TsISH50yw==",
|
||||
"version": "4.5.3",
|
||||
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.3.tgz",
|
||||
"integrity": "sha512-3yPecJoJHK/4c6aZhSvxOyG4vJKDshV36VHp0iVCDVh7o9w2vwi3NSnL2MMPj3YdduqaBcu7cGbggJQM0br9xA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"neo-async": "^2.6.0",
|
||||
@@ -2782,11 +2741,6 @@
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||
},
|
||||
"integer": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/integer/-/integer-2.1.0.tgz",
|
||||
"integrity": "sha512-vBtiSgrEiNocWvvZX1RVfeOKa2mCHLZQ2p9nkQkQZ/BvEiY+6CcUz0eyjvIiewjJoeNidzg2I+tpPJvpyspL1w=="
|
||||
},
|
||||
"invariant": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
|
||||
@@ -3830,24 +3784,8 @@
|
||||
"minimist": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
|
||||
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
|
||||
},
|
||||
"minipass": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz",
|
||||
"integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
|
||||
"requires": {
|
||||
"safe-buffer": "^5.1.2",
|
||||
"yallist": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"minizlib": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz",
|
||||
"integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==",
|
||||
"requires": {
|
||||
"minipass": "^2.9.0"
|
||||
}
|
||||
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
|
||||
"dev": true
|
||||
},
|
||||
"mixin-deep": {
|
||||
"version": "1.3.2",
|
||||
@@ -3874,6 +3812,7 @@
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
|
||||
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"minimist": "0.0.8"
|
||||
}
|
||||
@@ -3980,6 +3919,11 @@
|
||||
"lower-case": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"node-gyp-build": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.0.tgz",
|
||||
"integrity": "sha512-4oiumOLhCDU9Rronz8PZ5S4IvT39H5+JEv/hps9V8s7RSLhsac0TCP78ulnHXOo8X1wdpPiTayGlM1jr4IbnaQ=="
|
||||
},
|
||||
"node-int64": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
|
||||
@@ -4646,7 +4590,8 @@
|
||||
"safe-buffer": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
|
||||
"integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg=="
|
||||
"integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==",
|
||||
"dev": true
|
||||
},
|
||||
"safe-regex": {
|
||||
"version": "1.1.0",
|
||||
@@ -5203,20 +5148,6 @@
|
||||
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
|
||||
"dev": true
|
||||
},
|
||||
"tar": {
|
||||
"version": "4.4.13",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz",
|
||||
"integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==",
|
||||
"requires": {
|
||||
"chownr": "^1.1.1",
|
||||
"fs-minipass": "^1.2.5",
|
||||
"minipass": "^2.8.6",
|
||||
"minizlib": "^1.2.1",
|
||||
"mkdirp": "^0.5.0",
|
||||
"safe-buffer": "^5.1.2",
|
||||
"yallist": "^3.0.3"
|
||||
}
|
||||
},
|
||||
"test-exclude": {
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz",
|
||||
@@ -5348,19 +5279,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ts-node": {
|
||||
"version": "8.4.1",
|
||||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.4.1.tgz",
|
||||
"integrity": "sha512-5LpRN+mTiCs7lI5EtbXmF/HfMeCjzt7DH9CZwtkr6SywStrNQC723wG+aOWFiLNn7zT3kD/RnFqi3ZUfr4l5Qw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"arg": "^4.1.0",
|
||||
"diff": "^4.0.1",
|
||||
"make-error": "^1.1.1",
|
||||
"source-map-support": "^0.5.6",
|
||||
"yn": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"tunnel-agent": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||
@@ -5401,13 +5319,13 @@
|
||||
"dev": true
|
||||
},
|
||||
"uglify-js": {
|
||||
"version": "3.6.1",
|
||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.1.tgz",
|
||||
"integrity": "sha512-+dSJLJpXBb6oMHP+Yvw8hUgElz4gLTh82XuX68QiJVTXaE5ibl6buzhNkQdYhBlIhozWOC9ge16wyRmjG4TwVQ==",
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.7.2.tgz",
|
||||
"integrity": "sha512-uhRwZcANNWVLrxLfNFEdltoPNhECUR3lc+UdJoG9CBpMcSnKyWA94tc3eAujB1GcMY5Uwq8ZMp4qWpxWYDQmaA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"commander": "2.20.0",
|
||||
"commander": "~2.20.3",
|
||||
"source-map": "~0.6.1"
|
||||
}
|
||||
},
|
||||
@@ -5675,11 +5593,6 @@
|
||||
"integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
|
||||
"dev": true
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
|
||||
},
|
||||
"yargs": {
|
||||
"version": "13.3.0",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz",
|
||||
@@ -5707,12 +5620,6 @@
|
||||
"camelcase": "^5.0.0",
|
||||
"decamelize": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"yn": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
|
||||
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-3
@@ -5,7 +5,7 @@
|
||||
"main": "src/index.js",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"better-sqlite3": "^5.4.3",
|
||||
"@decafcode/sqlite": "^2.0.0",
|
||||
"compression": "^1.7.3",
|
||||
"date-fns": "^1.30.1",
|
||||
"debug": "^4.1.1",
|
||||
@@ -20,7 +20,6 @@
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/better-sqlite3": "^5.4.0",
|
||||
"@types/compression": "^0.0.36",
|
||||
"@types/debug": "^4.1.5",
|
||||
"@types/dotenv": "^6.1.1",
|
||||
@@ -33,7 +32,6 @@
|
||||
"jest-resolve": "^24.5.0",
|
||||
"prettier": "^1.16.4",
|
||||
"ts-jest": "^24.0.0",
|
||||
"ts-node": "^8.0.3",
|
||||
"typescript": "^3.6.4",
|
||||
"utility-types": "^3.6.1"
|
||||
},
|
||||
|
||||
@@ -82,6 +82,29 @@ create table "cm_user_activity" (
|
||||
)
|
||||
);
|
||||
|
||||
create table "cm_user_course" (
|
||||
"id" integer primary key not null,
|
||||
"profile_id" integer not null
|
||||
references "cm_user_data"("id")
|
||||
on delete cascade,
|
||||
"course_id" integer not null,
|
||||
"class_id" integer not null,
|
||||
"play_count" integer not null,
|
||||
"score_max" integer not null,
|
||||
"is_full_combo" text not null,
|
||||
"is_all_justice" text not null,
|
||||
"is_success" text not null,
|
||||
"score_rank" integer not null,
|
||||
"event_id" integer not null,
|
||||
"last_play_date" text not null,
|
||||
"param1" integer not null,
|
||||
"param2" integer not null,
|
||||
"param3" integer not null,
|
||||
"param4" integer not null,
|
||||
"is_clear" text not null,
|
||||
constraint "cm_user_course_uq" unique ("profile_id", "course_id")
|
||||
);
|
||||
|
||||
create table "cm_user_data_ex" (
|
||||
"id" integer primary key not null
|
||||
references "cm_user_data"("id")
|
||||
@@ -122,6 +145,23 @@ create table "cm_user_data_ex" (
|
||||
"ext_long5" integer not null
|
||||
);
|
||||
|
||||
create table "cm_user_duel_list" (
|
||||
"id" integer primary key not null,
|
||||
"profile_id" integer not null
|
||||
references "cm_user_data"("id")
|
||||
on delete cascade,
|
||||
"duel_id" integer not null,
|
||||
"progress" integer not null,
|
||||
"point" integer not null,
|
||||
"is_clear" boolean not null,
|
||||
"last_play_date" text not null,
|
||||
"param1" integer not null,
|
||||
"param2" integer not null,
|
||||
"param3" integer not null,
|
||||
"param4" integer not null,
|
||||
constraint "cm_user_duel_list_uq" unique ("profile_id", "duel_id")
|
||||
);
|
||||
|
||||
create table "cm_user_character" (
|
||||
"id" integer primary key not null,
|
||||
"profile_id" integer not null
|
||||
|
||||
@@ -98,6 +98,7 @@ create table "idz_settings" (
|
||||
on delete cascade,
|
||||
"music" integer not null,
|
||||
"pack" integer not null,
|
||||
"aura" integer not null,
|
||||
"paper_cup" integer not null, -- Not a boolean, oddly enough
|
||||
"gauges" integer not null
|
||||
);
|
||||
@@ -192,6 +193,7 @@ create table "idz_unlocks" (
|
||||
"id" integer primary key not null
|
||||
references "idz_profile"("id")
|
||||
on delete cascade,
|
||||
"auras" integer not null,
|
||||
"cup" integer not null,
|
||||
"gauges" integer not null,
|
||||
"music" integer not null,
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
-- This adds the required columns to save player aura settings and unlocks
|
||||
-- Default values are set according to what the game expects
|
||||
|
||||
alter table "idz_settings" add column "aura" integer NOT NULL DEFAULT '0';
|
||||
alter table "idz_unlocks" add column "auras" integer NOT NULL DEFAULT '1';
|
||||
@@ -0,0 +1,16 @@
|
||||
create table "cm_user_duel_list" (
|
||||
"id" integer primary key not null,
|
||||
"profile_id" integer not null
|
||||
references "cm_user_data"("id")
|
||||
on delete cascade,
|
||||
"duel_id" integer not null,
|
||||
"progress" integer not null,
|
||||
"point" integer not null,
|
||||
"is_clear" boolean not null,
|
||||
"last_play_date" text not null,
|
||||
"param1" integer not null,
|
||||
"param2" integer not null,
|
||||
"param3" integer not null,
|
||||
"param4" integer not null,
|
||||
constraint "cm_user_duel_list_uq" unique ("profile_id", "duel_id")
|
||||
);
|
||||
@@ -0,0 +1,22 @@
|
||||
create table "cm_user_course" (
|
||||
"id" integer primary key not null,
|
||||
"profile_id" integer not null
|
||||
references "cm_user_data"("id")
|
||||
on delete cascade,
|
||||
"course_id" integer not null,
|
||||
"class_id" integer not null,
|
||||
"play_count" integer not null,
|
||||
"score_max" integer not null,
|
||||
"is_full_combo" text not null,
|
||||
"is_all_justice" text not null,
|
||||
"is_success" text not null,
|
||||
"score_rank" integer not null,
|
||||
"event_id" integer not null,
|
||||
"last_play_date" text not null,
|
||||
"param1" integer not null,
|
||||
"param2" integer not null,
|
||||
"param3" integer not null,
|
||||
"param4" integer not null,
|
||||
"is_clear" text not null,
|
||||
constraint "cm_user_course_uq" unique ("profile_id", "course_id")
|
||||
);
|
||||
@@ -0,0 +1,23 @@
|
||||
-- This was not present in new DBs initialized to schema version 11.
|
||||
create table if not exists "cm_user_course" (
|
||||
"id" integer primary key not null,
|
||||
"profile_id" integer not null
|
||||
references "cm_user_data"("id")
|
||||
on delete cascade,
|
||||
"course_id" integer not null,
|
||||
"class_id" integer not null,
|
||||
"play_count" integer not null,
|
||||
"score_max" integer not null,
|
||||
"is_full_combo" text not null,
|
||||
"is_all_justice" text not null,
|
||||
"is_success" text not null,
|
||||
"score_rank" integer not null,
|
||||
"event_id" integer not null,
|
||||
"last_play_date" text not null,
|
||||
"param1" integer not null,
|
||||
"param2" integer not null,
|
||||
"param3" integer not null,
|
||||
"param4" integer not null,
|
||||
"is_clear" text not null,
|
||||
constraint "cm_user_course_uq" unique ("profile_id", "course_id")
|
||||
);
|
||||
@@ -0,0 +1,17 @@
|
||||
-- This was also not present in the db init scripts...
|
||||
create table if not exists "cm_user_duel_list" (
|
||||
"id" integer primary key not null,
|
||||
"profile_id" integer not null
|
||||
references "cm_user_data"("id")
|
||||
on delete cascade,
|
||||
"duel_id" integer not null,
|
||||
"progress" integer not null,
|
||||
"point" integer not null,
|
||||
"is_clear" boolean not null,
|
||||
"last_play_date" text not null,
|
||||
"param1" integer not null,
|
||||
"param2" integer not null,
|
||||
"param3" integer not null,
|
||||
"param4" integer not null,
|
||||
constraint "cm_user_duel_list_uq" unique ("profile_id", "duel_id")
|
||||
);
|
||||
@@ -1,17 +1,28 @@
|
||||
import { Repositories } from "../repo";
|
||||
import { GetUserCourseRequest } from "../request/getUserCourse";
|
||||
import { GetUserCourseResponse } from "../response/getUserCourse";
|
||||
import { readAimeId } from "../proto/base";
|
||||
import { paginationCookie } from "./_util";
|
||||
import { writeUserCourse } from "../proto/userCourse";
|
||||
|
||||
export default async function getUserCourse(
|
||||
rep: Repositories,
|
||||
req: GetUserCourseRequest
|
||||
): Promise<GetUserCourseResponse> {
|
||||
const aimeId = readAimeId(req.userId);
|
||||
const maxCount = parseInt(req.maxCount);
|
||||
const nextIndex = parseInt(req.nextIndex);
|
||||
|
||||
const profileId = await rep.userData().lookup(aimeId);
|
||||
const items = await rep
|
||||
.userCourse()
|
||||
.load(profileId, { limit: maxCount, offset: nextIndex });
|
||||
|
||||
|
||||
return {
|
||||
userId: req.userId,
|
||||
length: "0",
|
||||
nextIndex: "-1",
|
||||
userCourseList: [
|
||||
// This will get saved at some point I think
|
||||
],
|
||||
length: items.length.toString(),
|
||||
nextIndex: paginationCookie(items, { maxCount, nextIndex }),
|
||||
userCourseList: items.map(writeUserCourse)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,14 +1,23 @@
|
||||
import { Repositories } from "../repo";
|
||||
import { GetUserDuelRequest } from "../request/getUserDuel";
|
||||
import { GetUserDuelResponse } from "../response/getUserDuel";
|
||||
import { readAimeId } from "../proto/base";
|
||||
import { writeUserDuelList } from "../proto/userDuelList";
|
||||
|
||||
export default async function getUserDuel(
|
||||
rep: Repositories,
|
||||
req: GetUserDuelRequest
|
||||
): Promise<GetUserDuelResponse> {
|
||||
const aimeId = readAimeId(req.userId);
|
||||
// Get all duel entities if the client specifies to.
|
||||
const duelId = req.isAllDuel ? undefined : parseInt(req.duelId);
|
||||
|
||||
const profileId = await rep.userData().lookup(aimeId);
|
||||
const items = await rep.userDuelList().load(profileId, duelId);
|
||||
|
||||
return {
|
||||
userId: req.userId,
|
||||
length: "0",
|
||||
userDuelList: [],
|
||||
length: items.length.toString(),
|
||||
userDuelList: items.map(writeUserDuelList),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
import { Repositories } from "../repo";
|
||||
import { GetUserRecentRatingRequest } from "../request/getUserRecentRating";
|
||||
import { GetUserRecentRatingResponse } from "../response/getUserRecentRating";
|
||||
import { readAimeId } from "../proto/base";
|
||||
import { writeUserRecentRatingFromLog } from "../proto/userRecentRating";
|
||||
|
||||
export default async function getUserRecentRating(
|
||||
rep: Repositories,
|
||||
req: GetUserRecentRatingRequest
|
||||
): Promise<GetUserRecentRatingResponse> {
|
||||
// I don't really know what subset of data to return here.
|
||||
const aimeId = readAimeId(req.userId);
|
||||
|
||||
const profileId = await rep.userData().lookup(aimeId);
|
||||
// Return recent 30 plays to calculate rating
|
||||
const items = await rep.userPlaylog().loadLatest(profileId, 30);
|
||||
|
||||
return {
|
||||
userId: req.userId,
|
||||
length: "0",
|
||||
userRecentRatingList: [],
|
||||
length: items.length.toString(),
|
||||
userRecentRatingList: items.map(writeUserRecentRatingFromLog),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ export default function chunithm(db: DataSource) {
|
||||
|
||||
app.use(quirks);
|
||||
app.use(compression({ threshold: 0 }));
|
||||
app.use(express.json());
|
||||
app.use(express.json({ limit: "50mb" })); // that ought to be enough
|
||||
app.use(trace);
|
||||
|
||||
app.use("/ChuniServlet", wrapper);
|
||||
|
||||
@@ -11,7 +11,9 @@ import { readUserItem } from "../proto/userItem";
|
||||
import { readUserMusicDetail } from "../proto/userMusic";
|
||||
import { readUserActivity } from "../proto/userActivity";
|
||||
import { readUserDataEx } from "../proto/userDataEx";
|
||||
import { readUserDuelList } from "../proto/userDuelList";
|
||||
import { readUserPlaylog } from "../proto/userPlaylog";
|
||||
import { readUserCourse } from "../proto/userCourse";
|
||||
|
||||
// It shouldn't need to be said really, but seeing as this message (A) requires
|
||||
// fairly lengthy processing and (B) is only sent right at the end of a credit,
|
||||
@@ -65,9 +67,17 @@ export default async function upsertUserAll(
|
||||
await rep.userPlaylog().save(profileId, readUserPlaylog(item));
|
||||
}
|
||||
|
||||
for (const item of payload.userCourseList || []) {
|
||||
await rep.userCourse().save(profileId, readUserCourse(item));
|
||||
}
|
||||
|
||||
for (const item of payload.userDataEx || []) {
|
||||
await rep.userDataEx().save(profileId, readUserDataEx(item));
|
||||
}
|
||||
|
||||
for (const item of payload.userDuelList || []) {
|
||||
await rep.userDuelList().save(profileId, readUserDuelList(item));
|
||||
}
|
||||
|
||||
return { returnCode: "1" };
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
export interface UserCourseItem {
|
||||
courseId: number;
|
||||
classId: number;
|
||||
playCount: number;
|
||||
scoreMax: number;
|
||||
isFullCombo: boolean;
|
||||
isAllJustice: boolean;
|
||||
isSuccess: boolean;
|
||||
scoreRank: number;
|
||||
eventId: number;
|
||||
lastPlayDate: Date;
|
||||
param1: number;
|
||||
param2: number;
|
||||
param3: number;
|
||||
param4: number;
|
||||
isClear: boolean;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
export interface UserDuelListItem {
|
||||
duelId: number;
|
||||
progress: number;
|
||||
point: number;
|
||||
isClear: boolean;
|
||||
lastPlayDate: Date;
|
||||
param1: number;
|
||||
param2: number;
|
||||
param3: number;
|
||||
param4: number;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { UserCourseItem } from "../model/userCourse";
|
||||
import { Crush, readBoolean, readDate, writeObject } from "./base";
|
||||
|
||||
export type UserCourseJson = Crush<UserCourseItem>;
|
||||
|
||||
export function readUserCourse(json: UserCourseJson): UserCourseItem {
|
||||
return {
|
||||
classId: parseInt(json.classId),
|
||||
courseId: parseInt(json.courseId),
|
||||
eventId: parseInt(json.eventId),
|
||||
isAllJustice: readBoolean(json.isAllJustice),
|
||||
isClear: readBoolean(json.isClear),
|
||||
isFullCombo: readBoolean(json.isFullCombo),
|
||||
isSuccess: readBoolean(json.isSuccess),
|
||||
lastPlayDate: readDate(json.lastPlayDate),
|
||||
param1: parseInt(json.param1),
|
||||
param2: parseInt(json.param2),
|
||||
param3: parseInt(json.param3),
|
||||
param4: parseInt(json.param4),
|
||||
playCount: parseInt(json.playCount),
|
||||
scoreMax: parseInt(json.scoreMax),
|
||||
scoreRank: parseInt(json.scoreRank)
|
||||
}
|
||||
}
|
||||
|
||||
export function writeUserCourse(obj: UserCourseItem): UserCourseJson {
|
||||
return writeObject(obj);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Crush, readBoolean, readDate, writeObject } from "./base";
|
||||
import { UserDuelListItem } from "../model/userDuelList";
|
||||
|
||||
export type UserDuelListJson = Crush<UserDuelListItem>;
|
||||
|
||||
export function readUserDuelList(
|
||||
json: UserDuelListJson
|
||||
): UserDuelListItem {
|
||||
return {
|
||||
duelId: parseInt(json.duelId),
|
||||
progress: parseInt(json.progress),
|
||||
point: parseInt(json.point),
|
||||
isClear: readBoolean(json.isClear),
|
||||
lastPlayDate: readDate(json.lastPlayDate),
|
||||
param1: parseInt(json.param1),
|
||||
param2: parseInt(json.param2),
|
||||
param3: parseInt(json.param3),
|
||||
param4: parseInt(json.param4),
|
||||
};
|
||||
}
|
||||
|
||||
export function writeUserDuelList(
|
||||
obj: UserDuelListItem
|
||||
): UserDuelListJson {
|
||||
return writeObject(obj);
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { UserPlaylogItem } from "./../model/userPlaylog";
|
||||
import { Crush, writeObject } from "./base";
|
||||
import { UserRecentRatingItem } from "../model/userRecentRating";
|
||||
|
||||
@@ -19,3 +20,15 @@ export function writeUserRecentRating(
|
||||
): UserRecentRatingJson {
|
||||
return writeObject(obj);
|
||||
}
|
||||
|
||||
export function writeUserRecentRatingFromLog(
|
||||
obj: UserPlaylogItem
|
||||
): UserRecentRatingJson {
|
||||
return {
|
||||
musicId: obj.musicId.toString(),
|
||||
difficultId: obj.level.toString(),
|
||||
// game version not saved in play log, just return a fixed version now
|
||||
romVersionCode: "1030000",
|
||||
score: obj.score.toString(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { UserCourseRepository } from "./userCourse";
|
||||
|
||||
export { Page } from "./_defs";
|
||||
|
||||
import { UserActivityRepository } from "./userActivity";
|
||||
import { UserCharacterRepository } from "./userCharacter";
|
||||
import { UserDataRepository } from "./userData";
|
||||
import { UserDataExRepository } from "./userDataEx";
|
||||
import { UserDuelListRepository } from "./userDuelList";
|
||||
import { UserGameOptionRepository } from "./userGameOption";
|
||||
import { UserGameOptionExRepository } from "./userGameOptionEx";
|
||||
import { UserItemRepository } from "./userItem";
|
||||
@@ -20,6 +23,8 @@ export interface Repositories {
|
||||
|
||||
userDataEx(): UserDataExRepository;
|
||||
|
||||
userDuelList(): UserDuelListRepository;
|
||||
|
||||
userGameOption(): UserGameOptionRepository;
|
||||
|
||||
userGameOptionEx(): UserGameOptionExRepository;
|
||||
@@ -31,4 +36,6 @@ export interface Repositories {
|
||||
userMusic(): UserMusicRepository;
|
||||
|
||||
userPlaylog(): UserPlaylogRepository;
|
||||
|
||||
userCourse(): UserCourseRepository;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import { UserCourseItem } from "../model/userCourse";
|
||||
import { RepositoryN } from "./_defs";
|
||||
|
||||
export type UserCourseRepository = RepositoryN<UserCourseItem>;
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Page } from "./_defs";
|
||||
import { UserDataItem } from "../model/userData";
|
||||
import { UserDuelListItem } from "../model/userDuelList";
|
||||
import { Id } from "../../model";
|
||||
|
||||
export interface UserDuelListRepository {
|
||||
load(
|
||||
profileId: Id<UserDataItem>,
|
||||
duelId?: number,
|
||||
): Promise<UserDuelListItem[]>;
|
||||
|
||||
save(profileId: Id<UserDataItem>, obj: UserDuelListItem): Promise<void>;
|
||||
}
|
||||
@@ -7,4 +7,5 @@ export interface UserPlaylogRepository {
|
||||
// is nice to have.
|
||||
|
||||
save(profileId: Id<UserDataItem>, obj: UserPlaylogItem): Promise<void>;
|
||||
loadLatest(profileId: Id<UserDataItem>, size: number): Promise<UserPlaylogItem[]>;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,9 @@ import { UserMusicDetailJson } from "../proto/userMusic";
|
||||
import { UserActivityJson } from "../proto/userActivity";
|
||||
import { UserRecentRatingJson } from "../proto/userRecentRating";
|
||||
import { UserPlaylogJson } from "../proto/userPlaylog";
|
||||
import { UserCourseJson } from "../proto/userCourse";
|
||||
import { UserDataExJson } from "../proto/userDataEx";
|
||||
import { UserDuelListJson } from "../proto/userDuelList";
|
||||
|
||||
export interface UpsertUserAllRequest {
|
||||
/** Integer, AiMe ID */
|
||||
@@ -25,7 +27,9 @@ export interface UpsertUserAllRequest {
|
||||
userActivityList?: UserActivityJson[];
|
||||
userRecentRatingList?: UserRecentRatingJson[];
|
||||
userPlaylogList?: UserPlaylogJson[];
|
||||
userCourseList?: UserCourseJson[];
|
||||
userDataEx?: UserDataExJson[];
|
||||
userDuelList?: UserDuelListJson[];
|
||||
|
||||
/** String of binary digits */
|
||||
isNewMapList: string;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { UserCourseJson } from "../proto/userCourse";
|
||||
|
||||
export interface GetUserCourseResponse {
|
||||
/** Integer, AiMe ID */
|
||||
userId: string;
|
||||
@@ -8,6 +10,5 @@ export interface GetUserCourseResponse {
|
||||
/** Integer, pagination cookie */
|
||||
nextIndex: string;
|
||||
|
||||
/** TBD */
|
||||
userCourseList: [];
|
||||
userCourseList: UserCourseJson[];
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { UserDuelListJson } from "../proto/userDuelList";
|
||||
|
||||
export interface GetUserDuelResponse {
|
||||
/** Integer, AiMe ID */
|
||||
userId: string;
|
||||
@@ -5,6 +7,9 @@ export interface GetUserDuelResponse {
|
||||
/** Integer, number of results returned */
|
||||
length: string;
|
||||
|
||||
/** TBD */
|
||||
userDuelList: [];
|
||||
/**
|
||||
* List of duel entities, either all duel entities associated with `userId`
|
||||
* or single requested by `duelId` in request.
|
||||
*/
|
||||
userDuelList: UserDuelListJson[];
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { SqlUserActivityRepository } from "./userActivity";
|
||||
import { SqlUserCharacterRepository } from "./userCharacter";
|
||||
import { SqlUserDataRepository } from "./userData";
|
||||
import { SqlUserDataExRepository } from "./userDataEx";
|
||||
import { SqlUserDuelListRepository } from "./userDuelList";
|
||||
import { SqlUserGameOptionRepository } from "./userGameOption";
|
||||
import { SqlUserGameOptionExRepository } from "./userGameOptionEx";
|
||||
import { SqlUserItemRepository } from "./userItem";
|
||||
@@ -13,6 +14,7 @@ import { UserActivityRepository } from "../repo/userActivity";
|
||||
import { UserCharacterRepository } from "../repo/userCharacter";
|
||||
import { UserDataRepository } from "../repo/userData";
|
||||
import { UserDataExRepository } from "../repo/userDataEx";
|
||||
import { UserDuelListRepository } from "../repo/userDuelList";
|
||||
import { UserGameOptionRepository } from "../repo/userGameOption";
|
||||
import { UserGameOptionExRepository } from "../repo/userGameOptionEx";
|
||||
import { UserItemRepository } from "../repo/userItem";
|
||||
@@ -20,6 +22,8 @@ import { UserMapRepository } from "../repo/userMap";
|
||||
import { UserMusicRepository } from "../repo/userMusic";
|
||||
import { UserPlaylogRepository } from "../repo/userPlaylog";
|
||||
import { Transaction } from "../../sql";
|
||||
import { UserCourseRepository } from "../repo/userCourse";
|
||||
import { SqlUserCourseRepository } from "./userCourse";
|
||||
|
||||
export class SqlRepositories implements Repositories {
|
||||
constructor(private readonly _txn: Transaction) {}
|
||||
@@ -40,6 +44,10 @@ export class SqlRepositories implements Repositories {
|
||||
return new SqlUserDataExRepository(this._txn);
|
||||
}
|
||||
|
||||
userDuelList(): UserDuelListRepository {
|
||||
return new SqlUserDuelListRepository(this._txn);
|
||||
}
|
||||
|
||||
userGameOption(): UserGameOptionRepository {
|
||||
return new SqlUserGameOptionRepository(this._txn);
|
||||
}
|
||||
@@ -63,4 +71,8 @@ export class SqlRepositories implements Repositories {
|
||||
userPlaylog(): UserPlaylogRepository {
|
||||
return new SqlUserPlaylogRepository(this._txn);
|
||||
}
|
||||
|
||||
userCourse(): UserCourseRepository {
|
||||
return new SqlUserCourseRepository(this._txn);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import sql from "sql-bricks-postgres";
|
||||
import { createSqlMapper, T } from "../../sql/util";
|
||||
import { UserCourseRepository } from "../repo/userCourse";
|
||||
import { Id } from "../../model";
|
||||
import { Page } from "../repo";
|
||||
import { UserDataItem } from "../model/userData";
|
||||
import { UserCourseItem } from "../model/userCourse";
|
||||
import { Transaction } from "../../sql";
|
||||
|
||||
const { readRow, writeRow, colNames } = createSqlMapper({
|
||||
courseId: T.number,
|
||||
classId: T.number,
|
||||
playCount: T.number,
|
||||
scoreMax: T.number,
|
||||
isFullCombo: T.boolean,
|
||||
isAllJustice: T.boolean,
|
||||
isSuccess: T.boolean,
|
||||
scoreRank: T.number,
|
||||
eventId: T.number,
|
||||
lastPlayDate: T.Date,
|
||||
param1: T.number,
|
||||
param2: T.number,
|
||||
param3: T.number,
|
||||
param4: T.number,
|
||||
isClear: T.boolean
|
||||
});
|
||||
|
||||
export class SqlUserCourseRepository implements UserCourseRepository {
|
||||
constructor(private readonly _txn: Transaction) {}
|
||||
|
||||
async load(
|
||||
profileId: Id<UserDataItem>,
|
||||
page?: Page
|
||||
): Promise<UserCourseItem[]> {
|
||||
const stmt = sql
|
||||
.select("*")
|
||||
.from("cm_user_course")
|
||||
.where("profile_id", profileId);
|
||||
|
||||
if (page) {
|
||||
stmt.limit(page.limit).offset(page.offset);
|
||||
}
|
||||
|
||||
const rows = await this._txn.fetchRows(stmt);
|
||||
|
||||
return rows.map(readRow);
|
||||
}
|
||||
|
||||
save(profileId: Id<UserDataItem>, obj: UserCourseItem): Promise<void> {
|
||||
const stmt = sql
|
||||
.insert("cm_user_course", {
|
||||
id: this._txn.generateId(),
|
||||
profile_id: profileId,
|
||||
...writeRow(obj),
|
||||
})
|
||||
.onConflict("profile_id", "course_id")
|
||||
.doUpdate(colNames);
|
||||
|
||||
return this._txn.modify(stmt);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import sql from "sql-bricks-postgres";
|
||||
|
||||
import { UserDataItem } from "../model/userData";
|
||||
import { UserDuelListItem } from "../model/userDuelList";
|
||||
import { Page } from "../repo";
|
||||
import { UserDuelListRepository } from "../repo/userDuelList";
|
||||
import { Id } from "../../model";
|
||||
import { Transaction } from "../../sql";
|
||||
import { T, createSqlMapper } from "../../sql/util";
|
||||
|
||||
const { readRow, writeRow, colNames } = createSqlMapper({
|
||||
duelId: T.number,
|
||||
progress: T.number,
|
||||
point: T.number,
|
||||
isClear: T.boolean,
|
||||
lastPlayDate: T.Date,
|
||||
param1: T.number,
|
||||
param2: T.number,
|
||||
param3: T.number,
|
||||
param4: T.number,
|
||||
});
|
||||
|
||||
export class SqlUserDuelListRepository implements UserDuelListRepository {
|
||||
constructor(private readonly _txn: Transaction) {}
|
||||
|
||||
async load(
|
||||
profileId: Id<UserDataItem>,
|
||||
duelId?: number
|
||||
): Promise<UserDuelListItem[]> {
|
||||
const stmt = sql
|
||||
.select("*")
|
||||
.from("cm_user_duel_list")
|
||||
.where("profile_id", profileId);
|
||||
|
||||
if (duelId !== undefined) {
|
||||
stmt.where("duel_id", duelId);
|
||||
}
|
||||
|
||||
const rows = await this._txn.fetchRows(stmt);
|
||||
|
||||
return rows.map(readRow);
|
||||
}
|
||||
|
||||
save(profileId: Id<UserDataItem>, obj: UserDuelListItem): Promise<void> {
|
||||
const stmt = sql
|
||||
.insert("cm_user_duel_list", {
|
||||
id: this._txn.generateId(),
|
||||
profile_id: profileId,
|
||||
...writeRow(obj),
|
||||
})
|
||||
.onConflict("profile_id", "duel_id")
|
||||
.doUpdate(colNames);
|
||||
|
||||
return this._txn.modify(stmt);
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import { UserPlaylogRepository } from "../repo/userPlaylog";
|
||||
import { Transaction } from "../../sql";
|
||||
import { T, createSqlMapper } from "../../sql/util";
|
||||
|
||||
const { writeRow } = createSqlMapper({
|
||||
const { readRow, writeRow } = createSqlMapper({
|
||||
orderId: T.number,
|
||||
sortNumber: T.number,
|
||||
placeId: T.number,
|
||||
@@ -72,4 +72,16 @@ export class SqlUserPlaylogRepository implements UserPlaylogRepository {
|
||||
|
||||
return this._txn.modify(stmt);
|
||||
}
|
||||
|
||||
async loadLatest(profileId: Id<UserDataItem>, size: number): Promise<UserPlaylogItem[]> {
|
||||
const stmt = sql
|
||||
.select("music_id", "score", "level","user_play_date")
|
||||
.from("cm_user_playlog")
|
||||
.where("profile_id", profileId)
|
||||
.limit(size).order("user_play_date DESC");
|
||||
|
||||
const rows = await this._txn.fetchRows(stmt);
|
||||
|
||||
return rows.map(readRow);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,7 @@ export function saveProfile2(buf: Buffer): SaveProfileRequest2 {
|
||||
rows: storyRows,
|
||||
},
|
||||
unlocks: {
|
||||
auras: buf.readUInt16LE(0x010c),
|
||||
cup: buf.readUInt8(0x0110),
|
||||
gauges: buf.readUInt16LE(0x0114),
|
||||
music: buf.readUInt16LE(0x0140),
|
||||
@@ -90,6 +91,7 @@ export function saveProfile2(buf: Buffer): SaveProfileRequest2 {
|
||||
settings: {
|
||||
music: buf.readUInt16LE(0x045a),
|
||||
pack: buf.readUInt32LE(0x0034),
|
||||
aura: buf.readUInt8(0x002c),
|
||||
paperCup: buf.readUInt8(0x00f6),
|
||||
gauges: buf.readUInt8(0x00f7),
|
||||
},
|
||||
|
||||
@@ -69,6 +69,7 @@ export function saveProfile3(buf: Buffer): SaveProfileRequest2 {
|
||||
rows: storyRows,
|
||||
},
|
||||
unlocks: {
|
||||
auras: buf.readUInt16LE(0x010c),
|
||||
cup: buf.readUInt8(0x0110),
|
||||
gauges: buf.readUInt16LE(0x0114),
|
||||
music: buf.readUInt16LE(0x0140),
|
||||
@@ -92,6 +93,7 @@ export function saveProfile3(buf: Buffer): SaveProfileRequest2 {
|
||||
settings: {
|
||||
music: buf.readUInt16LE(0x04ee),
|
||||
pack: buf.readUInt32LE(0x0034),
|
||||
aura: buf.readUInt8(0x002c),
|
||||
paperCup: buf.readUInt8(0x00f6),
|
||||
gauges: buf.readUInt8(0x00f7),
|
||||
},
|
||||
|
||||
@@ -16,8 +16,8 @@ export function saveSettings(buf: Buffer): SaveSettingsRequest {
|
||||
pack: buf.readUInt32LE(0x000c),
|
||||
paperCup: buf.readUInt8(0x0011),
|
||||
gauges: buf.readUInt8(0x0012),
|
||||
aura: buf.readUInt8(0x0013),
|
||||
},
|
||||
field_0010: buf.readUInt8(0x0010),
|
||||
field_0013: buf.readUInt8(0x0013),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -71,6 +71,7 @@ export function loadProfile2(res: LoadProfileResponse2) {
|
||||
}
|
||||
|
||||
buf.writeUInt16LE(0x0065, 0x0000);
|
||||
buf.writeUInt16LE(res.unlocks.auras, 0x00b0);
|
||||
buf.writeUInt8(res.unlocks.cup, 0x00b4);
|
||||
buf.writeUInt16LE(res.unlocks.gauges, 0x00b8);
|
||||
buf.writeUInt32LE(res.unlocks.lastMileageReward, 0x01e8);
|
||||
@@ -92,6 +93,7 @@ export function loadProfile2(res: LoadProfileResponse2) {
|
||||
encodeMission(res.missions.solo).copy(buf, 0x06e4);
|
||||
encodeChara(res.chara).copy(buf, 0x070c);
|
||||
encodeBitmap(res.titles, 0xb4).copy(buf, 0x720);
|
||||
buf.writeUInt8(res.settings.aura, 0x07d6);
|
||||
buf.writeUInt8(res.settings.paperCup, 0x07d9);
|
||||
buf.writeUInt8(res.settings.gauges, 0x07da);
|
||||
buf.writeUInt32LE(res.teamId || 0xffffffff, 0x07e0);
|
||||
|
||||
@@ -71,6 +71,7 @@ export function loadProfile3(res: LoadProfileResponse3) {
|
||||
}
|
||||
|
||||
buf.writeUInt16LE(0x012e, 0x0000);
|
||||
buf.writeUInt16LE(res.unlocks.auras, 0x00b0);
|
||||
buf.writeUInt8(res.unlocks.cup, 0x00b4);
|
||||
buf.writeUInt16LE(res.unlocks.gauges, 0x00b8);
|
||||
buf.writeUInt32LE(res.unlocks.lastMileageReward, 0x0218);
|
||||
@@ -92,6 +93,7 @@ export function loadProfile3(res: LoadProfileResponse3) {
|
||||
encodeMission(res.missions.solo).copy(buf, 0x0858);
|
||||
encodeChara(res.chara).copy(buf, 0x0880);
|
||||
encodeBitmap(res.titles, 0xb4).copy(buf, 0x0894);
|
||||
buf.writeUInt8(res.settings.aura, 0x094a);
|
||||
buf.writeUInt8(res.settings.paperCup, 0x094d);
|
||||
buf.writeUInt8(res.settings.gauges, 0x094e);
|
||||
buf.writeUInt32LE(res.teamId || 0xffffffff, 0x0954);
|
||||
|
||||
@@ -50,13 +50,13 @@ export function loadTopTen(res: LoadTopTenResponse): Buffer {
|
||||
buf.writeUInt8(row.field_0F ? 1 : 0, innerOff + 0x000f); // Boolean
|
||||
buf.writeUInt8(row.field_10, innerOff + 0x0010);
|
||||
iconv.encode(row.driverName, "shift_jis").copy(buf, innerOff + 0x0014);
|
||||
iconv.encode(row.teamName, "shift_jis").copy(buf, innerOff + 0x0028);
|
||||
iconv.encode(row.team.name, "shift_jis").copy(buf, innerOff + 0x0028);
|
||||
iconv.encode(row.shopName, "shift_jis").copy(buf, innerOff + 0x0048);
|
||||
buf.writeUInt32LE(row.field_74, innerOff + 0x0074);
|
||||
buf.writeUInt16LE(row.field_78, innerOff + 0x0078);
|
||||
buf.writeUInt32LE(row.team.nameBg, innerOff + 0x0074);
|
||||
buf.writeUInt16LE(row.team.nameFx, innerOff + 0x0078);
|
||||
buf.writeUInt8(row.field_7C, innerOff + 0x007c);
|
||||
buf.writeUInt8(row.field_7D, innerOff + 0x007d);
|
||||
// 66 bytes of fucking nothing what
|
||||
// 66 bytes of empty space at the end. Maybe a string used to live here?
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,9 +27,10 @@ export async function createProfile(
|
||||
};
|
||||
|
||||
const missions: MissionState = { team: [], solo: [] };
|
||||
const settings: Settings = { music: 0, pack: 13640, paperCup: 0, gauges: 5 };
|
||||
const settings: Settings = { music: 0, pack: 13640, aura: 0, paperCup: 0, gauges: 5 };
|
||||
const story: Story = { x: 0, y: 0, rows: [] };
|
||||
const unlocks: Unlocks = {
|
||||
auras: 1,
|
||||
cup: 0,
|
||||
gauges: 1 << 5,
|
||||
music: 0,
|
||||
|
||||
@@ -10,6 +10,6 @@ export async function loadGarage(
|
||||
|
||||
return {
|
||||
type: "load_garage_res",
|
||||
cars: await w.car().loadAllCars(profileId),
|
||||
cars: await w.car().loadCars(profileId, 10, req.fetchOffset),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ export async function loadTopTen(
|
||||
}
|
||||
|
||||
const { routeNo, minTimestamp } = selector;
|
||||
const src = await w.timeAttack().loadTopTen(routeNo, minTimestamp);
|
||||
const src = await w.timeAttack().loadTop(routeNo, minTimestamp, 10);
|
||||
|
||||
if (src.length === 0) {
|
||||
continue;
|
||||
@@ -33,10 +33,8 @@ export async function loadTopTen(
|
||||
field_0F: false,
|
||||
field_10: 0,
|
||||
driverName: srcItem.driverName,
|
||||
teamName: process.env.TEAM_NAME || "",
|
||||
shopName: process.env.SHOP_NAME || "",
|
||||
field_74: 0,
|
||||
field_78: 0,
|
||||
team: srcItem.team,
|
||||
field_7C: 0,
|
||||
field_7D: 0,
|
||||
ta: srcItem.ta,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export interface Settings {
|
||||
music: number;
|
||||
pack: number;
|
||||
aura: number;
|
||||
paperCup: number;
|
||||
gauges: number;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export interface Unlocks {
|
||||
auras: number;
|
||||
cup: number;
|
||||
gauges: number;
|
||||
music: number;
|
||||
|
||||
+9
-3
@@ -11,7 +11,11 @@ export type TeamSpec = Subtract<
|
||||
export interface CarRepository {
|
||||
countCars(profileId: Id<Model.Profile>): Promise<number>;
|
||||
|
||||
loadAllCars(profileId: Id<Model.Profile>): Promise<Model.Car[]>;
|
||||
loadCars(
|
||||
profileId: Id<Model.Profile>,
|
||||
limit: number,
|
||||
offset: number
|
||||
): Promise<Model.Car[]>;
|
||||
|
||||
loadSelectedCar(profileId: Id<Model.Profile>): Promise<Model.Car>;
|
||||
|
||||
@@ -111,13 +115,15 @@ export interface TeamReservationRepository {
|
||||
// TODO extend and factorize
|
||||
export interface TopTenResult {
|
||||
driverName: string;
|
||||
team: Model.Team;
|
||||
ta: Model.TimeAttackScore;
|
||||
}
|
||||
|
||||
export interface TimeAttackRepository {
|
||||
loadTopTen(
|
||||
loadTop(
|
||||
routeNo: Model.RouteNo,
|
||||
minTimestamp: Date
|
||||
minTimestamp: Date,
|
||||
limit: number
|
||||
): Promise<TopTenResult[]>;
|
||||
|
||||
loadAll(profileId: Id<Model.Profile>): Promise<Model.TimeAttackScore[]>;
|
||||
|
||||
@@ -7,5 +7,4 @@ export interface SaveSettingsRequest {
|
||||
dpoint: number; // ?? why
|
||||
settings: Settings;
|
||||
field_0010: number;
|
||||
field_0013: number;
|
||||
}
|
||||
|
||||
@@ -7,10 +7,12 @@ export interface LoadTopTenResponseRow {
|
||||
field_0F: boolean;
|
||||
field_10: number;
|
||||
driverName: string;
|
||||
teamName: string;
|
||||
shopName: string;
|
||||
field_74: number;
|
||||
field_78: number;
|
||||
team: {
|
||||
name: string;
|
||||
nameBg: number;
|
||||
nameFx: number;
|
||||
};
|
||||
field_7C: number;
|
||||
field_7D: number;
|
||||
ta: TimeAttackScore;
|
||||
|
||||
+9
-2
@@ -40,11 +40,18 @@ export class SqlCarRepository implements CarRepository {
|
||||
return parseInt(row!.result);
|
||||
}
|
||||
|
||||
async loadAllCars(profileId: Id<Profile>): Promise<Car[]> {
|
||||
async loadCars(
|
||||
profileId: Id<Profile>,
|
||||
limit: number,
|
||||
offset: number
|
||||
): Promise<Car[]> {
|
||||
const loadSql = sql
|
||||
.select("c.*")
|
||||
.from("idz_car c")
|
||||
.where("c.profile_id", profileId);
|
||||
.where("c.profile_id", profileId)
|
||||
.orderBy("selector asc")
|
||||
.limit(limit)
|
||||
.offset(offset);
|
||||
|
||||
const rows = await this._txn.fetchRows(loadSql);
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ export class SqlSettingsRepository implements FacetRepository<Settings> {
|
||||
return {
|
||||
music: parseInt(row.music),
|
||||
pack: parseInt(row.pack),
|
||||
aura: parseInt(row.aura),
|
||||
paperCup: parseInt(row.paper_cup),
|
||||
gauges: parseInt(row.gauges),
|
||||
};
|
||||
@@ -35,11 +36,12 @@ export class SqlSettingsRepository implements FacetRepository<Settings> {
|
||||
id: profileId,
|
||||
music: settings.music,
|
||||
pack: settings.pack,
|
||||
aura: settings.aura,
|
||||
paper_cup: settings.paperCup,
|
||||
gauges: settings.gauges,
|
||||
})
|
||||
.onConflict("id")
|
||||
.doUpdate(["music", "pack", "paper_cup", "gauges"]);
|
||||
.doUpdate(["music", "pack", "aura", "paper_cup", "gauges"]);
|
||||
|
||||
await this._txn.modify(saveSql);
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ export class SqlTeamMemberRepository implements TeamMemberRepository {
|
||||
.where("id", teamId)
|
||||
.forUpdate();
|
||||
|
||||
await this._txn.modify(lockSql);
|
||||
await this._txn.fetchRow(lockSql);
|
||||
|
||||
// Double-check (with lock held) that there is room to join this team.
|
||||
// If this fails then the error will propagate to the client and it will
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import sql from "sql-bricks-postgres";
|
||||
|
||||
import { RouteNo } from "../model/base";
|
||||
import { ExtId, RouteNo } from "../model/base";
|
||||
import { CarSelector } from "../model/car";
|
||||
import { Team } from "../model/team";
|
||||
import { Profile } from "../model/profile";
|
||||
import { TimeAttackScore } from "../model/timeAttack";
|
||||
import { TimeAttackRepository, TopTenResult } from "../repo";
|
||||
@@ -23,23 +24,51 @@ function _extractRow(row: Row): TimeAttackScore {
|
||||
export class SqlTimeAttackRepository implements TimeAttackRepository {
|
||||
constructor(private readonly _txn: Transaction) {}
|
||||
|
||||
async loadTopTen(
|
||||
async loadTop(
|
||||
routeNo: RouteNo,
|
||||
minTimestamp: Date
|
||||
minTimestamp: Date,
|
||||
limit: number
|
||||
): Promise<TopTenResult[]> {
|
||||
// We're not using an ORM here so this join-heavy SQL is unfortunately
|
||||
// going to be a boilerplated mess.
|
||||
|
||||
const loadSql = sql
|
||||
.select("p.name", "ta.*")
|
||||
.select(
|
||||
// Profile
|
||||
"p.name as profile_name",
|
||||
// Team
|
||||
"t.ext_id as team_ext_id",
|
||||
"t.name as team_name",
|
||||
"t.name_bg as team_name_bg",
|
||||
"t.name_fx as team_name_fx",
|
||||
"t.register_time as team_register_time",
|
||||
// Time Attack
|
||||
"ta.*"
|
||||
)
|
||||
.from("idz_ta_best ta")
|
||||
.join("idz_profile p", { "ta.profile_id": "p.id" })
|
||||
// This is an inner join, so it will exclude anybody who is not currently
|
||||
// a member of a team from the leader boards. Since the only way to play
|
||||
// without a team is to be ejected from one we can consider this an edge
|
||||
// case that we probably don't need to worry about.
|
||||
.join("idz_team_member tm", { "ta.profile_id": "tm.id" })
|
||||
.join("idz_team t", { "tm.team_id": "t.id" })
|
||||
.where("ta.route_no", routeNo)
|
||||
.where(sql.gt("ta.timestamp", minTimestamp))
|
||||
.orderBy(["ta.total_time asc", "ta.timestamp asc"])
|
||||
.limit(10);
|
||||
.limit(limit);
|
||||
|
||||
const rows = await this._txn.fetchRows(loadSql);
|
||||
|
||||
return rows.map(row => ({
|
||||
driverName: row.name,
|
||||
driverName: row.profile_name,
|
||||
team: {
|
||||
extId: parseInt(row.team_ext_id) as ExtId<Team>,
|
||||
name: row.team_name,
|
||||
nameBg: parseInt(row.team_name_bg),
|
||||
nameFx: parseInt(row.team_name_fx),
|
||||
registerTime: new Date(row.team_register_time),
|
||||
},
|
||||
ta: _extractRow(row),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ export class SqlUnlocksRepository implements FacetRepository<Unlocks> {
|
||||
}
|
||||
|
||||
return {
|
||||
auras: parseInt(row.auras),
|
||||
cup: parseInt(row.cup),
|
||||
gauges: parseInt(row.gauges),
|
||||
music: parseInt(row.music),
|
||||
@@ -33,13 +34,14 @@ export class SqlUnlocksRepository implements FacetRepository<Unlocks> {
|
||||
const saveSql = sql
|
||||
.insert("idz_unlocks", {
|
||||
id: profileId,
|
||||
auras: unlocks.auras,
|
||||
cup: unlocks.cup,
|
||||
gauges: unlocks.gauges,
|
||||
music: unlocks.music,
|
||||
last_mileage_reward: unlocks.lastMileageReward,
|
||||
})
|
||||
.onConflict("id")
|
||||
.doUpdate(["cup", "gauges", "music", "last_mileage_reward"]);
|
||||
.doUpdate(["auras", "cup", "gauges", "music", "last_mileage_reward"]);
|
||||
|
||||
await this._txn.modify(saveSql);
|
||||
}
|
||||
|
||||
+38
-21
@@ -1,14 +1,10 @@
|
||||
import Database from "better-sqlite3";
|
||||
import Database, { ResultRow } from "@decafcode/sqlite";
|
||||
import { randomBytes } from "crypto";
|
||||
import * as sql from "sql-bricks-postgres";
|
||||
|
||||
import { DataSource, Row, Transaction } from "./api";
|
||||
import { Id } from "../model";
|
||||
|
||||
type MixedRow = {
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
// bless me father for i have sinned
|
||||
const fuFixup = new RegExp(" FOR UPDATE$");
|
||||
|
||||
@@ -41,7 +37,7 @@ function _preprocess(stmt: sql.Statement) {
|
||||
};
|
||||
}
|
||||
|
||||
function _postprocess(obj: MixedRow): Row {
|
||||
function _postprocess(obj: ResultRow): Row {
|
||||
const result = {};
|
||||
|
||||
for (const [k, v] of Object.entries(obj)) {
|
||||
@@ -58,7 +54,7 @@ function _postprocess(obj: MixedRow): Row {
|
||||
}
|
||||
|
||||
class SqliteTransaction implements Transaction {
|
||||
constructor(private readonly _db: Database.Database) {}
|
||||
constructor(private readonly _db: Database) {}
|
||||
|
||||
generateId<T>(): Id<T> {
|
||||
const buf = randomBytes(8);
|
||||
@@ -73,26 +69,43 @@ class SqliteTransaction implements Transaction {
|
||||
|
||||
modify(stmt: sql.Statement): Promise<void> {
|
||||
const params = _preprocess(stmt);
|
||||
const prepared = this._db.prepare(params.text);
|
||||
|
||||
this._db.prepare(params.text).run(...params.values);
|
||||
try {
|
||||
prepared.run(params.values);
|
||||
} finally {
|
||||
prepared.close();
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
fetchRow(stmt: sql.SelectStatement): Promise<Row | undefined> {
|
||||
const params = _preprocess(stmt);
|
||||
const raw = this._db.prepare(params.text).get(...params.values);
|
||||
const result = raw && _postprocess(raw);
|
||||
const prepared = this._db.prepare(params.text);
|
||||
|
||||
return Promise.resolve(result);
|
||||
try {
|
||||
const raw = prepared.one(params.values);
|
||||
const result = raw && _postprocess(raw);
|
||||
|
||||
return Promise.resolve(result);
|
||||
} finally {
|
||||
prepared.close();
|
||||
}
|
||||
}
|
||||
|
||||
fetchRows(stmt: sql.SelectStatement): Promise<Row[]> {
|
||||
const params = _preprocess(stmt);
|
||||
const raw = this._db.prepare(params.text).all(...params.values);
|
||||
const result = raw.map(_postprocess);
|
||||
const prepared = this._db.prepare(params.text);
|
||||
|
||||
return Promise.resolve(result);
|
||||
try {
|
||||
const raw = prepared.all(params.values);
|
||||
const result = raw.map(_postprocess);
|
||||
|
||||
return Promise.resolve(result);
|
||||
} finally {
|
||||
prepared.close();
|
||||
}
|
||||
}
|
||||
|
||||
raw(sql: string): Promise<void> {
|
||||
@@ -110,28 +123,32 @@ class SqliteDataSource implements DataSource {
|
||||
): Promise<T> {
|
||||
const db = new Database(this._path);
|
||||
|
||||
db.defaultSafeIntegers();
|
||||
db.prepare("pragma foreign_keys = on").run();
|
||||
db.prepare("begin").run();
|
||||
|
||||
try {
|
||||
db.exec("begin");
|
||||
|
||||
const txn = new SqliteTransaction(db);
|
||||
const result = await callback(txn);
|
||||
|
||||
db.prepare("commit").run();
|
||||
db.exec("commit");
|
||||
|
||||
return result;
|
||||
} catch (e) {
|
||||
db.prepare("rollback").run();
|
||||
db.exec("rollback");
|
||||
|
||||
return Promise.reject(e);
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
|
||||
vacuum(): Promise<void> {
|
||||
const db = new Database(this._path);
|
||||
|
||||
db.prepare("vacuum").run();
|
||||
try {
|
||||
db.exec("vacuum");
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
@echo off
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
where /q node
|
||||
if errorlevel 1 (
|
||||
echo NodeJS does not appear to be installed.
|
||||
@@ -20,9 +22,6 @@ if not exist node_modules (
|
||||
echo The server's dependencies will now be installed.
|
||||
echo This process requires an internet connection and may take some time.
|
||||
echo.
|
||||
echo NOTE: If install fails try running the following command as admin:
|
||||
echo npm install -g windows-build-tools
|
||||
echo.
|
||||
|
||||
pause
|
||||
call npm install
|
||||
@@ -40,8 +39,10 @@ if not exist node_modules (
|
||||
echo.
|
||||
)
|
||||
|
||||
rem Project Diva requires TLSv1.0
|
||||
|
||||
set DEBUG=app:*
|
||||
node .\bin\index.js
|
||||
node --tls-min-v1.0 .\bin\index.js
|
||||
|
||||
:end
|
||||
echo.
|
||||
|
||||
Vendored
+1
-1
@@ -7,7 +7,7 @@ declare module "sql-bricks-postgres" {
|
||||
}
|
||||
|
||||
interface LimitClause extends sql.SelectStatement {
|
||||
offset(value: number): sql.Statement;
|
||||
offset(value: number): sql.SelectStatement;
|
||||
}
|
||||
|
||||
export interface PgInsertStatement extends sql.InsertStatement {
|
||||
|
||||
Reference in New Issue
Block a user