From d46af97e726c1e826a0ef8d7cc4d0c90124fa52c Mon Sep 17 00:00:00 2001 From: AppleBetas Date: Fri, 8 Sep 2017 21:19:32 -0400 Subject: [PATCH 01/61] Revamp beta dialog a bit, add help one, fix beta dialog script error, limit cursor picking to donors, --- client/js/place.js | 13 ++++++++++++- client/js/site.js | 9 +-------- public/css/global.css | 6 ++++++ views/layout.pug | 10 ++++++---- views/public/index.pug | 2 ++ views/public/views/beta-dialog.pug | 5 ++--- views/public/views/help-dialog.pug | 29 +++++++++++++++++++++++++++++ 7 files changed, 58 insertions(+), 16 deletions(-) create mode 100644 views/public/views/help-dialog.pug diff --git a/client/js/place.js b/client/js/place.js index f0ff5ced..57d40e9c 100644 --- a/client/js/place.js +++ b/client/js/place.js @@ -8,6 +8,7 @@ var size; var SignInDialogController = DialogController($("#sign-in-dialog")); var ChangelogDialogController = DialogController($("#changelog-dialog")); +var HelpDialogController = DialogController($("#help-dialog")); var BetaDialogController = DialogController($("#beta-dialog")); BetaDialogController.dialog.find("#signup").click(function() { placeAjax.post("/api/beta-signup", null, null).then(data => { @@ -1062,6 +1063,7 @@ var place = { }, pickColourUnderCursor: function() { + if(!this.canPlaceCustomColours) return; var cursor = this.getCanvasCursorPosition(); var colour = this.canvasController.getPixelColour(cursor.x, cursor.y); $("#colour-picker").minicolors("value", "#" + colour); @@ -1416,4 +1418,13 @@ if(place.isSignedIn()) { $(document).ready(function() { changelogController.getChangelogsForShow(); }); -} \ No newline at end of file +} + +$(document).ready(function() { + if(hashHandler.getHash()["beta"] != null) { + hashHandler.deleteHashKey("beta"); + BetaDialogController.show(); + } +}); + +$("#nav-help > a").click(() => HelpDialogController.show()); \ No newline at end of file diff --git a/client/js/site.js b/client/js/site.js index a402e448..9d99a509 100644 --- a/client/js/site.js +++ b/client/js/site.js @@ -246,11 +246,4 @@ if(("standalone" in window.navigator) && window.navigator.standalone){ } }, false); -} - -$(document).ready(function() { - if(hashHandler.getHash()["beta"] != null) { - hashHandler.deleteHashKey("beta"); - BetaDialogController.show(); - } -}); \ No newline at end of file +} \ No newline at end of file diff --git a/public/css/global.css b/public/css/global.css index bddefef1..a5d100ec 100644 --- a/public/css/global.css +++ b/public/css/global.css @@ -878,4 +878,10 @@ table.table.table-contained, td { #changelog-content .lead { margin-bottom: 5px; +} + +@media(min-width: 768px) { + #nav-help > a { + padding-left: 0; + } } \ No newline at end of file diff --git a/views/layout.pug b/views/layout.pug index 50c9ff13..68fe2837 100644 --- a/views/layout.pug +++ b/views/layout.pug @@ -8,7 +8,8 @@ block dependencies - if(typeof navbarSupportsTopMode === "undefined") navbarSupportsTopMode = false; - if(typeof isPoppedOut === "undefined") isPoppedOut = false; - var userAdmin = user ? user.admin : false, userMod = user ? user.moderator || userAdmin : false; -- var resourceVersion = "1"; // Increment this when you make a change to the HTML that absolutely requires CSS or JS refreshing +- if(typeof needsHelp === "undefined") needsHelp = false; +- var resourceVersion = "2"; // Increment this when you make a change to the HTML that absolutely requires CSS or JS refreshing - css = resources.css.concat(css); - var bodyClasses = ["fixed-navbar"]; - if (user) bodyClasses.push("signed-in"); @@ -21,14 +22,13 @@ mixin renderBadge(badge, prefersShortText = false) mixin renderBadges(badges, prefersShortText = false) each badge in badges +renderBadge(badge, prefersShortText) -mixin renderNavItem(name, itemPath, icon) +mixin renderNavItem(name, itemPath, icon, showsName = true) - var isCurrentPage = itemPath == path || (isAdmin && itemPath == "/admin"); li(id=`nav-${name.toLowerCase().replace(/[^0-9a-z ]/gi, '').replace(/ /g, "-")}`, class=(isCurrentPage ? "active" : "")) a(href=itemPath) if icon i.fa.fa-fw(class=`fa-${icon}`) - . - #{name} + span(class=showsName ? "" : "visible-xs-inline") #{name} if isCurrentPage span.sr-only (current) mixin getViewExtensions(name) @@ -127,6 +127,8 @@ html(lang="en") else li#nav-auth-link a.btn.btn-sm.btn-nav-auth-link(href=(path == "/" ? "javascript:void(0)" : `/#signup&redirectURL=${redirectURLPart}`), data-place-trigger="openAuthDialog") Get Started + if needsHelp + +renderNavItem("Help", "javascript:void(0)", "question-circle", false) +getViewExtensions("nav_user_right") main block content diff --git a/views/public/index.pug b/views/public/index.pug index 813e37e2..4f252e17 100644 --- a/views/public/index.pug +++ b/views/public/index.pug @@ -3,6 +3,7 @@ block dependencies - var css = ["https://cdn.jsdelivr.net/jquery.minicolors/2.1.2/jquery.minicolors.css", "/css/place.css"]; - var pinnable = true; - var needsLegit = true; + - var needsHelp = true; - var pageDesc = "Work together to create something amazing. Join thousands of other users and see what happens when you share one huge canvas."; block content +getViewExtensions("main") @@ -23,4 +24,5 @@ block content #[small: a#changelog-opt-out(href="javascript:void(0)", data-dialog-dismiss=true) Don't show again]   #[a.btn.btn-popping(data-dialog-dismiss=true) Close] unless user include views/auth-dialog + include views/help-dialog - var js = ["/socket.io/socket.io.js", "https://cdn.jsdelivr.net/interact.js/1.2.6/interact.min.js", "https://cdn.jsdelivr.net/jquery.minicolors/2.1.2/jquery.minicolors.min.js", "https://cdn.jsdelivr.net/clipboard.js/1.6.0/clipboard.min.js", "/js/build/popout.js", "/js/build/place.js", "https://cdn.rawgit.com/rmm5t/jquery-timeago/180864a9c544a49e43719b457250af216d5e4c3a/jquery.timeago.js"]; \ No newline at end of file diff --git a/views/public/views/beta-dialog.pug b/views/public/views/beta-dialog.pug index 91084695..cd08a5ed 100644 --- a/views/public/views/beta-dialog.pug +++ b/views/public/views/beta-dialog.pug @@ -9,8 +9,7 @@ h1 Join the Beta Program p.subhead Live on the bleeding edge, and help us test features before they go live. .content - p Want to help us test features and get early access to them? Join the beta program and receive access to new features before everyone else does. We'll give you access to our latest feature set, and all you have to do is provide feedback so that we can make sure these features are great before releasing them to everyone. - p Sound good? - button.btn.btn-lg.btn-success#signup Signup + p Want to help us test features and get early access to them? Join the beta program and receive new features before everyone else does. We'll give you access to our latest feature set, and all you have to do is provide feedback so that we can make sure these features are great before releasing them to everyone. + button.btn.btn-lg.btn-success#signup Sign up br .dialog-footer.align-end: a.btn.btn-popping(data-dialog-dismiss=true) Close \ No newline at end of file diff --git a/views/public/views/help-dialog.pug b/views/public/views/help-dialog.pug new file mode 100644 index 00000000..9fe367b6 --- /dev/null +++ b/views/public/views/help-dialog.pug @@ -0,0 +1,29 @@ +.dialog-ctn + .dialog-overlay + .dialog#help-dialog + .close × + .pages + .active(tab-name="help") + .heading.no-margin + span.site #{config.siteName} + h1 Get Help + p.subhead Help using #{config.siteName}. + .content + h3 Getting an account + p You can sign in or create a #{config.siteName} account by clicking Get Started in the top navigation bar. Once you do this, you can enter your current account details or create a new one. We also allow authentication through various third-party services, if you would prefer that. + h3 Placing pixels + p Placing pixels is easy. Pan over to the area you want to place the pixel, zoom in, select your colour by clicking on it in the palette, and click where you want to place it. + h3 Keyboard Controls + p You can use the following keyboard controls on #{config.siteName}: + ul + li #[kbd G]: Toggle grid. + li #[kbd Space]: Toggle zoom in/out. + li #[kbd Esc]: Exit colour picker, exit dialog, deselect current colour. + li #[kbd P]: Select the current colour under your mouse pointer #[em (donors only)]. + li #[kbd ▲ / W]: Pan upward + li #[kbd ▼ / S]: Pan downward + li #[kbd ◀ / A]: Pan to the left + li #[kbd ▶ / D]: Pan to the right + h3 Other topics + p If you need help with anything else, please #[a(href="https://discord.gg/CgC8FTg") join our Discord here] so we can help you out. + .dialog-footer.align-end: a.btn.btn-popping(data-dialog-dismiss=true) Close \ No newline at end of file From 9472ecd85219d81e77a4d94ffe4e30d4b452d4fc Mon Sep 17 00:00:00 2001 From: AppleBetas Date: Fri, 8 Sep 2017 21:23:50 -0400 Subject: [PATCH 02/61] Fix javascript not automatically processing --- util/JavaScriptProcessor.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/util/JavaScriptProcessor.js b/util/JavaScriptProcessor.js index 35aed170..f6af96d9 100644 --- a/util/JavaScriptProcessor.js +++ b/util/JavaScriptProcessor.js @@ -25,6 +25,8 @@ class JavaScriptProcessor { // Clean existing built JavaScript gulp.task("clean", () => del([this.paths.scripts.built])); + // Rerun the task when a file changes + gulp.task("watch", () => gulp.watch(this.paths.scripts.src, ["scripts"])); // Process JavaScript gulp.task("scripts", (cb) => { this.app.logger.info('Babel', "Processing JavaScript…"); @@ -53,8 +55,7 @@ class JavaScriptProcessor { } watchJavaScript() { - // Rerun the task when a file changes - gulp.task("watch", () => gulp.watch(this.paths.scripts.src, ["scripts"])); + gulp.start(["watch"]); } } From 9692b9cd4a715f874067f40afc4ee5bdc2d5c473 Mon Sep 17 00:00:00 2001 From: AppleBetas Date: Fri, 8 Sep 2017 21:24:26 -0400 Subject: [PATCH 03/61] Fix notification error --- client/js/place.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/js/place.js b/client/js/place.js index 57d40e9c..209bfbe7 100644 --- a/client/js/place.js +++ b/client/js/place.js @@ -108,7 +108,7 @@ var notificationHandler = { let notif = new Notification(title, { body: message }); - notif.addEventListener('click', () => { + notif.addEventListener('click', (e) => { // focus on window parent.focus(); window.focus(); // fallback From 8fce51c1008eaf8e44e1564d8b52b01ef0690e03 Mon Sep 17 00:00:00 2001 From: AppleBetas Date: Fri, 8 Sep 2017 21:24:43 -0400 Subject: [PATCH 04/61] Fix get started padding --- public/css/global.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/css/global.css b/public/css/global.css index a5d100ec..359cf7c0 100644 --- a/public/css/global.css +++ b/public/css/global.css @@ -881,7 +881,7 @@ table.table.table-contained, td { } @media(min-width: 768px) { - #nav-help > a { + .signed-in #nav-help > a { padding-left: 0; } } \ No newline at end of file From 5afdd2420c173578a8c099061e470838118be825 Mon Sep 17 00:00:00 2001 From: AppleBetas Date: Fri, 8 Sep 2017 21:45:04 -0400 Subject: [PATCH 05/61] Fix password regex, captcha reset on sites without captcha --- client/js/site.js | 2 +- models/user.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/js/site.js b/client/js/site.js index 9d99a509..5cfcd417 100644 --- a/client/js/site.js +++ b/client/js/site.js @@ -105,7 +105,7 @@ function DialogController(dialog) { me.switchTab("2fa-auth"); return; } - if(tab == "sign-up") grecaptcha.reset(); + if(tab == "sign-up" && typeof grecaptcha != "undefined") grecaptcha.reset(); me.shake(); var error = "An unknown error occurred while attempting to authenticate you."; if(err && err.message) error = err.message; diff --git a/models/user.js b/models/user.js index 8c753c9e..e8bea5da 100644 --- a/models/user.js +++ b/models/user.js @@ -207,7 +207,7 @@ UserSchema.statics.findByUsername = function(username, callback = null) { } UserSchema.statics.getPasswordError = function(password) { - return password.match(/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])[0-9a-zA-Z ]{8,}$/) ? null : "That password cannot be used. Passwords are required to contain at least one digit, one uppercase letter, one lowercase letter, and be at least 8 characters in length."; + return password.match(/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])$/) || password.length < 8 ? null : "That password cannot be used. Passwords are required to contain at least one digit, one uppercase letter, one lowercase letter, and be at least 8 characters in length."; } UserSchema.statics.register = function(username, password, app, callback, OAuthID, OAuthName) { From cf4b33dd4f345558eee0c840c5379fd4200a8f01 Mon Sep 17 00:00:00 2001 From: AppleBetas Date: Fri, 8 Sep 2017 21:51:31 -0400 Subject: [PATCH 06/61] FUCK U TRAVIS SUCK MY BALLS --- controllers/AuthController.js | 4 ++-- util/JavaScriptProcessor.js | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/controllers/AuthController.js b/controllers/AuthController.js index 848f9c43..cb511ac9 100644 --- a/controllers/AuthController.js +++ b/controllers/AuthController.js @@ -14,7 +14,7 @@ exports.postSignIn = (req, res, next) => { if(!req.body.totpToken) return res.status(403).json({success: false, error: {code: "totp_needed", message: "Two-factor authentication is enabled for this account. Please specify your two-factor authentication token."}}); if(!speakeasy.totp.verify({ secret: user.totpSecret, encoding: 'base32', token: req.body.totpToken, window: 6 })) return res.status(403).json({success: false, error: {code: "invalid_totp", message: "We couldn't sign you in with that two-factor authentication token. Make sure you're entering the right code and it is updated."}}); } - if(!!req.body.keepSignedIn) req.session.maxAge = 1000 * 60 * 60 * 24 * 7; // keep signed in for 7 days + if(req.body.keepSignedIn) req.session.maxAge = 1000 * 60 * 60 * 24 * 7; // keep signed in for 7 days req.login(user, function(err) { if (err) return res.status(500).json({success: true, error: {message: "An unknown error occurred."}}); return res.json({success: true}); @@ -32,7 +32,7 @@ exports.postSignUp = (req, res, next) => { function doSignup() { User.register(req.body.username, req.body.password, req.place, function(user, error) { if(!user) return sendError(error); - if(!!req.body.keepSignedIn) req.session.maxAge = 1000 * 60 * 60 * 24 * 7; // keep signed in for 7 days + if(req.body.keepSignedIn) req.session.maxAge = 1000 * 60 * 60 * 24 * 7; // keep signed in for 7 days req.login(user, function(err) { if (err) return sendError(null); res.json({success: true}); diff --git a/util/JavaScriptProcessor.js b/util/JavaScriptProcessor.js index f6af96d9..fdb18335 100644 --- a/util/JavaScriptProcessor.js +++ b/util/JavaScriptProcessor.js @@ -1,5 +1,3 @@ -const fs = require("fs"); -const path = require("path"); const gulp = require("gulp"); const uglify = require("gulp-uglify"); const babel = require("gulp-babel"); From 69d806d20efe5e84eb53a1eef90e7f145da9d062 Mon Sep 17 00:00:00 2001 From: Jamie Bishop Date: Sat, 9 Sep 2017 10:12:38 +0100 Subject: [PATCH 07/61] fix signups x2 --- controllers/AuthController.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/controllers/AuthController.js b/controllers/AuthController.js index cb511ac9..77eded43 100644 --- a/controllers/AuthController.js +++ b/controllers/AuthController.js @@ -4,6 +4,8 @@ const passport = require("passport"); const speakeasy = require("speakeasy"); const absoluteURLRegex = new RegExp('^(?:[a-z]+:)?(//)?', 'i'); +const User = require('../models/user'); + exports.postSignIn = (req, res, next) => { require("../util/passport")(passport, req.place); if (req.user) return res.redirect("/"); From 8e41439d52950b9dd6d949f553e4fea1229aaac7 Mon Sep 17 00:00:00 2001 From: AppleBetas Date: Sat, 9 Sep 2017 10:38:00 -0400 Subject: [PATCH 08/61] Fix admin panel nav items --- views/admin_layout.pug | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/views/admin_layout.pug b/views/admin_layout.pug index ba3db907..72779e9b 100644 --- a/views/admin_layout.pug +++ b/views/admin_layout.pug @@ -1,6 +1,6 @@ extends layout -mixin renderNavItem(name, itemPath) - var isCurrentPage = itemPath == path; +mixin renderAdminNavItem(name, itemPath) + - var isCurrentPage = itemPath == path; li(class=(isCurrentPage ? "active": "")) a(href=itemPath) . @@ -21,12 +21,12 @@ block content .row .col-sm-3.col-md-2.sidebar ul.nav.nav-sidebar - +renderNavItem("Dashboard", "/admin/") - +renderNavItem("Recent Actions", "/admin/actions") - +renderNavItem("Moderator Log", "/admin/log") + +renderAdminNavItem("Dashboard", "/admin/") + +renderAdminNavItem("Recent Actions", "/admin/actions") + +renderAdminNavItem("Moderator Log", "/admin/log") ul.nav.nav-sidebar - +renderNavItem("Users", "/admin/users") - +renderNavItem("Reports", "/admin/reports") - +renderNavItem("Pixels", "/admin/pixels") + +renderAdminNavItem("Users", "/admin/users") + +renderAdminNavItem("Reports", "/admin/reports") + +renderAdminNavItem("Pixels", "/admin/pixels") .col-sm-9.col-sm-offset-3.col-md-10.col-md-offset-2.main block adminContent \ No newline at end of file From c505eacd1991aa3f38e6fcf5ce78ec6f60058e9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EF=A3=BFbetas?= Date: Sun, 10 Sep 2017 13:14:22 -0400 Subject: [PATCH 09/61] Update user.js --- models/user.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/user.js b/models/user.js index e8bea5da..e0e122e7 100644 --- a/models/user.js +++ b/models/user.js @@ -207,7 +207,7 @@ UserSchema.statics.findByUsername = function(username, callback = null) { } UserSchema.statics.getPasswordError = function(password) { - return password.match(/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])$/) || password.length < 8 ? null : "That password cannot be used. Passwords are required to contain at least one digit, one uppercase letter, one lowercase letter, and be at least 8 characters in length."; + return password.match(/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])$/) && password.length >= 8 ? null : "That password cannot be used. Passwords are required to contain at least one digit, one uppercase letter, one lowercase letter, and be at least 8 characters in length."; } UserSchema.statics.register = function(username, password, app, callback, OAuthID, OAuthName) { From 25c8e9152d04c686cee73f1f433c8aabe2e11776 Mon Sep 17 00:00:00 2001 From: Jamie Bishop Date: Thu, 14 Sep 2017 07:47:33 +0100 Subject: [PATCH 10/61] Disable password Regex checking for now --- models/user.js | 1 + 1 file changed, 1 insertion(+) diff --git a/models/user.js b/models/user.js index e0e122e7..e7f58a12 100644 --- a/models/user.js +++ b/models/user.js @@ -207,6 +207,7 @@ UserSchema.statics.findByUsername = function(username, callback = null) { } UserSchema.statics.getPasswordError = function(password) { + return null; // temp fix signups, not good return password.match(/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])$/) && password.length >= 8 ? null : "That password cannot be used. Passwords are required to contain at least one digit, one uppercase letter, one lowercase letter, and be at least 8 characters in length."; } From a4ed06aa73668b4a27d8fb057a3a4f0421fa1f53 Mon Sep 17 00:00:00 2001 From: Philippe Loctaux Date: Sun, 17 Sep 2017 18:42:39 +0200 Subject: [PATCH 11/61] updated timelapse url --- views/layout.pug | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/layout.pug b/views/layout.pug index 68fe2837..ada0d929 100644 --- a/views/layout.pug +++ b/views/layout.pug @@ -104,7 +104,7 @@ html(lang="en") ul.dropdown-menu +renderNavItem("Official Subreddit", "https://www.reddit.com/r/place20", "reddit-alien") +renderNavItem("Chat on Discord", "https://discord.gg/CgC8FTg", "comments") - +renderNavItem("Timelapses", "https://disk.ph1l3r.fr/place20/", "clock-o") + +renderNavItem("Timelapses", "https://timelapse.canvas.place", "clock-o") +renderNavItem("Source Code", "https://www.github.com/dynastic/place", "github") +renderNavItem("Donate", "https://www.paypal.me/AppleBetasPay", "money") if user && userMod From 3f26c82afa8dc88da839caf11973a7d305d42ed1 Mon Sep 17 00:00:00 2001 From: AppleBetas Date: Sun, 22 Oct 2017 12:25:38 -0400 Subject: [PATCH 12/61] Add support for sign up/in banners --- views/public/views/auth-dialog.pug | 6 ++++++ yarn.lock | 10 +++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/views/public/views/auth-dialog.pug b/views/public/views/auth-dialog.pug index 8a83fa9b..c8ed938a 100644 --- a/views/public/views/auth-dialog.pug +++ b/views/public/views/auth-dialog.pug @@ -38,6 +38,9 @@ .send-section button.btn.btn-popping(type="submit") Sign Up include social-buttons + if config.signUpBanner + br + div.alert.alert-success !{config.signUpBanner} div(tab-name="sign-in") .heading span.site #{config.siteName} @@ -55,6 +58,9 @@ .send-section button.btn.btn-popping(type="submit") Sign in include social-buttons + if config.signInBanner + br + div.alert.alert-success !{config.signInBanner} div.hides-switchers(tab-name="2fa-auth") .heading span.site #{config.siteName} diff --git a/yarn.lock b/yarn.lock index 1bf74df2..49692708 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2196,6 +2196,14 @@ gulp-babel@^6.1.2: through2 "^2.0.0" vinyl-sourcemaps-apply "^0.2.0" +gulp-changed@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/gulp-changed/-/gulp-changed-3.1.0.tgz#87cd1593a0bb4a5129dc2f229bece6d9488c272a" + dependencies: + gulp-util "^3.0.0" + pify "^2.3.0" + through2 "^2.0.0" + gulp-sourcemaps@^2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/gulp-sourcemaps/-/gulp-sourcemaps-2.6.0.tgz#7ccce899a8a3bfca1593a3348d0fbf41dd3f51e5" @@ -3710,7 +3718,7 @@ performance-now@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" -pify@^2.0.0: +pify@^2.0.0, pify@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" From 74adb7a9c25e92d03d32828ca7eecb4209baee75 Mon Sep 17 00:00:00 2001 From: AppleBetas Date: Sun, 22 Oct 2017 12:28:34 -0400 Subject: [PATCH 13/61] Allow null editors --- models/pixel.js | 2 +- models/user.js | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/models/pixel.js b/models/pixel.js index 9f0bff81..5b4f4d40 100644 --- a/models/pixel.js +++ b/models/pixel.js @@ -25,7 +25,7 @@ var PixelSchema = new Schema({ }, editorID: { type: Schema.ObjectId, - required: true + required: false }, lastModified: { type: Date, diff --git a/models/user.js b/models/user.js index e7f58a12..06e07bfe 100644 --- a/models/user.js +++ b/models/user.js @@ -373,7 +373,7 @@ UserSchema.statics.getPubliclyAvailableUserInfo = function(userID, overrideDataA info.userError = error; resolve(info); } - this.findById(userID).then((user) => { + var continueWithUser = (user) => { if (!user) return returnInfo("delete"); if (!overrideDataAccess && user.banned) return returnInfo("ban"); else if (!overrideDataAccess && user.deactivated) return returnInfo("deactivated"); @@ -385,7 +385,9 @@ UserSchema.statics.getPubliclyAvailableUserInfo = function(userID, overrideDataA } resolve(info); }).catch((err) => returnInfo("delete")); - }).catch((err) => { + } + if(!userID) return continueWithUser(null); + this.findById(userID).then(continueWithUser).catch((err) => { app.logger.capture("Error getting user info: " + err, { user: { _id: userID } }); returnInfo("delete"); }); From 98c744942e0272c84050844a0c7923a559984834 Mon Sep 17 00:00:00 2001 From: Jamie Bishop Date: Wed, 25 Oct 2017 12:27:46 +0100 Subject: [PATCH 14/61] fix null error in leaderboard manager --- util/LeaderboardManager.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/util/LeaderboardManager.js b/util/LeaderboardManager.js index f1f47d00..aae7410d 100644 --- a/util/LeaderboardManager.js +++ b/util/LeaderboardManager.js @@ -18,6 +18,7 @@ function LeaderboardManager(app) { var dateBackLastWeek = new Date(new Date().getTime() - (7 * 24 * 60 * 60 * 1000)); var pixelCounts = {}; Pixel.find({lastModified: {$gt: dateBackLastWeek}}, {editorID: 1}).stream().on("data", (pixel) => { + if (!pixel.editorID) return; var uid = pixel.editorID.toString(); if(!Object.keys(pixelCounts).includes(uid)) pixelCounts[uid] = 0; pixelCounts[uid]++; @@ -70,4 +71,4 @@ function LeaderboardManager(app) { LeaderboardManager.prototype = Object.create(LeaderboardManager.prototype); -module.exports = LeaderboardManager; \ No newline at end of file +module.exports = LeaderboardManager; From c80d8b1a20b80139b9ce3b952d0a11836351f7d4 Mon Sep 17 00:00:00 2001 From: Jamie Bishop Date: Mon, 27 Nov 2017 16:40:49 +0000 Subject: [PATCH 15/61] Add maintenance configuration options to deal with situations better --- config/config.example.js | 5 ++ controllers/AuthController.js | 14 ++++ views/public/account.pug | 4 ++ views/public/views/auth-dialog.pug | 102 ++++++++++++++++------------- 4 files changed, 80 insertions(+), 45 deletions(-) diff --git a/config/config.example.js b/config/config.example.js index 1bdd36b0..b8d61c47 100644 --- a/config/config.example.js +++ b/config/config.example.js @@ -14,6 +14,11 @@ module.exports = { //"raven": "", //"publicRaven": "", // "bugsnag": "", + 'maintenance': { + 'allowSignups': true, + 'allowLogins': true, + 'notice': undefined + }, "cachet": { // Setup reporting error count as a metric to Cachet "apiKey": "", diff --git a/controllers/AuthController.js b/controllers/AuthController.js index 77eded43..c78ff55e 100644 --- a/controllers/AuthController.js +++ b/controllers/AuthController.js @@ -7,11 +7,18 @@ const absoluteURLRegex = new RegExp('^(?:[a-z]+:)?(//)?', 'i'); const User = require('../models/user'); exports.postSignIn = (req, res, next) => { + const config = req.place.config; require("../util/passport")(passport, req.place); if (req.user) return res.redirect("/"); if (!req.body.username || !req.body.password) return res.status(400).json({success: false, error: {message: "A username and password are required."}}); passport.authenticate("local", function(err, user, info) { if (!user) return res.status(403).json({success: false, error: info.error || {message: "A username and password are required."}}); + if (!user.admin && config.maintenance && !config.maintenance.allowSignins) return res.status(403).json({ + success: false, + error: { + message: 'Logins disabled. Please do not call this endpoint any futher.' + } + }); if (user.twoFactorAuthEnabled()) { if(!req.body.totpToken) return res.status(403).json({success: false, error: {code: "totp_needed", message: "Two-factor authentication is enabled for this account. Please specify your two-factor authentication token."}}); if(!speakeasy.totp.verify({ secret: user.totpSecret, encoding: 'base32', token: req.body.totpToken, window: 6 })) return res.status(403).json({success: false, error: {code: "invalid_totp", message: "We couldn't sign you in with that two-factor authentication token. Make sure you're entering the right code and it is updated."}}); @@ -25,6 +32,13 @@ exports.postSignIn = (req, res, next) => { }; exports.postSignUp = (req, res, next) => { + const config = req.place.config; + if (config.maintenance && !config.maintenance.allowSignups) return res.status(403).json({ + success: false, + error: { + message: 'Signups disabled. Please do not call this endpoint any futher.' + } + }); function sendError(error) { res.json({success: false, error: error || {message: "An unknown error occurred", code: "unknown_error"}}); } diff --git a/views/public/account.pug b/views/public/account.pug index b7527c64..79884c3c 100644 --- a/views/public/account.pug +++ b/views/public/account.pug @@ -39,6 +39,10 @@ block content div span.value= profileUserInfo.statistics.placesThisWeek.toLocaleString() span.name pixel#{profileUserInfo.statistics.placesThisWeek == 1 ? "" : "s"} this week + if isSelf && config.maintenance.notice + div.alert.alert-danger + h4 Important infomation regarding your #{config.siteName} account: + p !{config.maintenance.notice} if hasNewPassword .alert.alert-success #[strong Success!] Your password has been changed successfully! hr diff --git a/views/public/views/auth-dialog.pug b/views/public/views/auth-dialog.pug index c8ed938a..eb93a435 100644 --- a/views/public/views/auth-dialog.pug +++ b/views/public/views/auth-dialog.pug @@ -8,59 +8,71 @@ span.site #{config.siteName} h1 Become a member p.subhead Join the #{config.siteName} community today to start placing. - form.form-signin(action="/signup") - label.sr-only(for="inputUsername") Username - input.form-control#inputUsername(type="text", placeholder="Username", name="username", required, autofocus, autocorrect="off", autocapitalize="off", spellcheck="false") - label.sr-only(for="inputPassword") Password - input.form-control.form-merge-top#inputPassword(type="password", placeholder="Password", name="password", required) - label.sr-only(for="inputPassword2") Password (again) - input.form-control.form-merge-bottom#inputPassword2(type="password", placeholder="Password (again)", name="passwordverify", required) - if captcha - . - !{renderCaptcha()} - if hasTOS || hasCommunityGuidelines + if config.maintenance.allowSignups || !config.maintenance + form.form-signin(action="/signup") + label.sr-only(for="inputUsername") Username + input.form-control#inputUsername(type="text", placeholder="Username", name="username", required, autofocus, autocorrect="off", autocapitalize="off", spellcheck="false") + label.sr-only(for="inputPassword") Password + input.form-control.form-merge-top#inputPassword(type="password", placeholder="Password", name="password", required) + label.sr-only(for="inputPassword2") Password (again) + input.form-control.form-merge-bottom#inputPassword2(type="password", placeholder="Password (again)", name="passwordverify", required) + if captcha + . + !{renderCaptcha()} + if hasTOS || hasCommunityGuidelines + .checkbox + label + input(type="checkbox", name="agreeToGuidelines") + span + | I agree to abide by the + if hasTOS + a(href="/tos") Terms of Service + if hasTOS && hasCommunityGuidelines + | and + if hasCommunityGuidelines + a(href="/guidelines") Community Guidelines + | . .checkbox label - input(type="checkbox", name="agreeToGuidelines") - span - | I agree to abide by the - if hasTOS - a(href="/tos") Terms of Service - if hasTOS && hasCommunityGuidelines - | and - if hasCommunityGuidelines - a(href="/guidelines") Community Guidelines - | . - .checkbox - label - input(type="checkbox", name="keepSignedIn", checked) - span Keep me signed in - .send-section - button.btn.btn-popping(type="submit") Sign Up - include social-buttons - if config.signUpBanner - br - div.alert.alert-success !{config.signUpBanner} + input(type="checkbox", name="keepSignedIn", checked) + span Keep me signed in + .send-section + button.btn.btn-popping(type="submit") Sign Up + include social-buttons + else + br + div.alert.alert-danger + h4 Signups for #{config.siteName} are currently disabled. + if config.maintenance.notice + p !{config.maintenance.notice} + div(tab-name="sign-in") .heading span.site #{config.siteName} h1 Welcome back p.subhead Sign in to your account to continue placing and save your stats. - form.form-signin(action="/signin") - label.sr-only(for="inputUsername") Username - input.form-control#inputUsername(type="text", placeholder="Username", name="username", required, autofocus, autocorrect="off", autocapitalizae="off", spellcheck="false") - label.sr-only(for="inputPassword") Password - input.form-control#inputPassword(type="password", placeholder="Password", name="password", required) - .checkbox - label - input#inputKeepSignIn(type="checkbox", name="keepSignedIn", checked) - span Keep me signed in - .send-section - button.btn.btn-popping(type="submit") Sign in - include social-buttons - if config.signInBanner + if config.maintenance.allowLogins || !config.maintenance + form.form-signin(action="/signin") + label.sr-only(for="inputUsername") Username + input.form-control#inputUsername(type="text", placeholder="Username", name="username", required, autofocus, autocorrect="off", autocapitalizae="off", spellcheck="false") + label.sr-only(for="inputPassword") Password + input.form-control#inputPassword(type="password", placeholder="Password", name="password", required) + .checkbox + label + input#inputKeepSignIn(type="checkbox", name="keepSignedIn", checked) + span Keep me signed in + .send-section + button.btn.btn-popping(type="submit") Sign in + include social-buttons + if config.signInBanner + br + div.alert.alert-success !{config.signInBanner} + else br - div.alert.alert-success !{config.signInBanner} + div.alert.alert-danger + h4 Signins to #{config.siteName} are currently disabled. + if config.maintenance.notice + p !{config.maintenance.notice} div.hides-switchers(tab-name="2fa-auth") .heading span.site #{config.siteName} From 444a931a5843a8f14721be39f6ab919e5c6c9558 Mon Sep 17 00:00:00 2001 From: Jamie Bishop Date: Wed, 29 Nov 2017 19:21:53 +0000 Subject: [PATCH 16/61] Fix regression introduced by c80d8b1a20b80139b9ce3b952d0a11836351f7d4 --- controllers/AuthController.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controllers/AuthController.js b/controllers/AuthController.js index c78ff55e..ee98e71a 100644 --- a/controllers/AuthController.js +++ b/controllers/AuthController.js @@ -13,7 +13,7 @@ exports.postSignIn = (req, res, next) => { if (!req.body.username || !req.body.password) return res.status(400).json({success: false, error: {message: "A username and password are required."}}); passport.authenticate("local", function(err, user, info) { if (!user) return res.status(403).json({success: false, error: info.error || {message: "A username and password are required."}}); - if (!user.admin && config.maintenance && !config.maintenance.allowSignins) return res.status(403).json({ + if (!user.admin && config.maintenance && !config.maintenance.allowLogins) return res.status(403).json({ success: false, error: { message: 'Logins disabled. Please do not call this endpoint any futher.' From d5cfaa4d91260b33e23ff94f9685a37154bc9a34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EF=A3=BFbetas?= Date: Sat, 23 Dec 2017 15:53:58 -0500 Subject: [PATCH 17/61] Add logging for 2FA, fix rate limit store logging (#236) * Update site.js * Update api.js --- client/js/site.js | 3 ++- routes/api.js | 25 ++----------------------- 2 files changed, 4 insertions(+), 24 deletions(-) diff --git a/client/js/site.js b/client/js/site.js index 5cfcd417..beab81fb 100644 --- a/client/js/site.js +++ b/client/js/site.js @@ -98,6 +98,7 @@ function DialogController(dialog) { window.location.reload(); } }).catch((err) => { + console.log(err, tab); if(tab == "sign-in" && err && err.code == "totp_needed") { $("#inputUsername2FA").val(form.find("#inputUsername").val()); $("#inputPassword2FA").val(form.find("#inputPassword").val()); @@ -246,4 +247,4 @@ if(("standalone" in window.navigator) && window.navigator.standalone){ } }, false); -} \ No newline at end of file +} diff --git a/routes/api.js b/routes/api.js index 9e9d95f0..a3fc1535 100644 --- a/routes/api.js +++ b/routes/api.js @@ -76,7 +76,7 @@ function APIRouter(app) { failCallback: (req, res, next, nextValidRequestDate) => { res.status(429).json({success: false, error:{message: "You're doing that too fast."}}); }, - handleStoreError: (error) => app.reportError("Sign up rate limit store error: " + error), + handleStoreError: (error) => app.reportError("Sign up rate limit store error:", error), proxyDepth: app.config.trustProxyDepth }); @@ -162,7 +162,7 @@ function APIRouter(app) { } }) }, - handleStoreError: (error) => app.reportError("Chat rate limit store error: " + error), + handleStoreError: (error) => app.reportError("Chat rate limit store error:", error), proxyDepth: app.config.trustProxyDepth }); @@ -197,27 +197,6 @@ function APIRouter(app) { router.get("/mod/similar_users/:userID", app.modMiddleware, ModeratorUserController.getAPISimilarUsers); router.get("/mod/actions", app.modMiddleware, ModeratorUserController.getAPIActions); - // Debug APIs - - if (app.config.debug) { - router.get("/trigger-error", function(req, res, next) { - app.reportError("Oh no! An error has happened!"); - res.status(500).json({ - success: false, - error: { - message: "The server done fucked up.", - code: "debug" - } - }); - }); - router.get("/trigger-error-report", function(req, res, next) { - app.errorTracker.handleErrorCheckingInterval(); - res.json({ - success: true - }); - }); - } - return router; } From 54d830049e9de84448bdf9c7d5a5160b1e184810 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EF=A3=BFbetas?= Date: Sat, 23 Dec 2017 15:55:50 -0500 Subject: [PATCH 18/61] Update user.js --- models/user.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/user.js b/models/user.js index 06e07bfe..cbc1d2cd 100644 --- a/models/user.js +++ b/models/user.js @@ -208,7 +208,7 @@ UserSchema.statics.findByUsername = function(username, callback = null) { UserSchema.statics.getPasswordError = function(password) { return null; // temp fix signups, not good - return password.match(/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])$/) && password.length >= 8 ? null : "That password cannot be used. Passwords are required to contain at least one digit, one uppercase letter, one lowercase letter, and be at least 8 characters in length."; + // return password.match(/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])$/) && password.length >= 8 ? null : "That password cannot be used. Passwords are required to contain at least one digit, one uppercase letter, one lowercase letter, and be at least 8 characters in length."; } UserSchema.statics.register = function(username, password, app, callback, OAuthID, OAuthName) { From 7d069720c027443246a9fb827614ca2055692b15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EF=A3=BFbetas?= Date: Sat, 23 Dec 2017 16:11:23 -0500 Subject: [PATCH 19/61] -re --- client/js/site.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/js/site.js b/client/js/site.js index beab81fb..f9ced917 100644 --- a/client/js/site.js +++ b/client/js/site.js @@ -77,7 +77,7 @@ function DialogController(dialog) { e.preventDefault(); var form = $(this); var call = form.attr("action"); - var tab = form.parent().attr("tab-name"); + var tab = form.parent().parent().attr("tab-name"); var data = form.serialize(); if(form.data("submitting")) return; var submitButton = form.find("input[type=submit], button[type=submit]"); @@ -98,7 +98,6 @@ function DialogController(dialog) { window.location.reload(); } }).catch((err) => { - console.log(err, tab); if(tab == "sign-in" && err && err.code == "totp_needed") { $("#inputUsername2FA").val(form.find("#inputUsername").val()); $("#inputPassword2FA").val(form.find("#inputPassword").val()); From 6cc38ec5f321ed3f33b9b61ff8ccfe802954664e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EF=A3=BFbetas?= Date: Sat, 23 Dec 2017 16:21:46 -0500 Subject: [PATCH 20/61] Update logger.js --- util/logger.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/util/logger.js b/util/logger.js index b5faec97..b3fa586d 100644 --- a/util/logger.js +++ b/util/logger.js @@ -63,6 +63,17 @@ if (config.bugsnag) { exports.bugnsnag = Bugsnag; } +exports.capture = (error, extra) => { + errors++; + + // extra is an optional param to give stuff context, like user's etc + + if (exports.raven) exports.raven.captureException(error, extra); + if (exports.bugsnag) exports.bugnsnag.notify(new Error(error), extra); + + exports.error('ERROR', error); +} + if (config.cachet && config.cachet.site && config.cachet.apiKey && config.cachet.metricID) { const Cachet = require("cachet-api"); const cachet = new Cachet({ @@ -77,17 +88,6 @@ if (config.cachet && config.cachet.site && config.cachet.apiKey && config.cachet }).then((response) => { errors = 0; exports.info('CACHET', `Published error data (count: ${errors}) for last checking interval.`); - }).catch((err) => console.capture("Couldn't publish errors to cachet: " + err)); + }).catch((err) => exports.capture("Couldn't publish errors to cachet", err)); }, 1000 * 60); } - -exports.capture = (error, extra) => { - errors++; - - // extra is an optional param to give stuff context, like user's etc - - if (exports.raven) exports.raven.captureException(error, extra); - if (exports.bugsnag) exports.bugnsnag.notify(new Error(error), extra); - - exports.error('ERROR', error); -} \ No newline at end of file From c10453ff4a258a2c397e378f9b60f416411dd095 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EF=A3=BFbetas?= Date: Sat, 23 Dec 2017 16:35:43 -0500 Subject: [PATCH 21/61] Update logger.js --- util/logger.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/util/logger.js b/util/logger.js index b3fa586d..f2e909ca 100644 --- a/util/logger.js +++ b/util/logger.js @@ -63,7 +63,7 @@ if (config.bugsnag) { exports.bugnsnag = Bugsnag; } -exports.capture = (error, extra) => { +exports.capture = (error, extra = null) => { errors++; // extra is an optional param to give stuff context, like user's etc @@ -71,7 +71,7 @@ exports.capture = (error, extra) => { if (exports.raven) exports.raven.captureException(error, extra); if (exports.bugsnag) exports.bugnsnag.notify(new Error(error), extra); - exports.error('ERROR', error); + exports.error('ERROR', error, extra); } if (config.cachet && config.cachet.site && config.cachet.apiKey && config.cachet.metricID) { From 80cf39f68deffe58f7a052a10b5fb13f2b605c85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EF=A3=BFbetas?= Date: Sat, 23 Dec 2017 17:08:45 -0500 Subject: [PATCH 22/61] Update layout.pug --- views/layout.pug | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/views/layout.pug b/views/layout.pug index ada0d929..2b13dddd 100644 --- a/views/layout.pug +++ b/views/layout.pug @@ -9,7 +9,7 @@ block dependencies - if(typeof isPoppedOut === "undefined") isPoppedOut = false; - var userAdmin = user ? user.admin : false, userMod = user ? user.moderator || userAdmin : false; - if(typeof needsHelp === "undefined") needsHelp = false; -- var resourceVersion = "2"; // Increment this when you make a change to the HTML that absolutely requires CSS or JS refreshing +- var resourceVersion = "3"; // Increment this when you make a change to the HTML that absolutely requires CSS or JS refreshing - css = resources.css.concat(css); - var bodyClasses = ["fixed-navbar"]; - if (user) bodyClasses.push("signed-in"); @@ -174,4 +174,4 @@ html(lang="en") })(window,document,"script","https://www.google-analytics.com/analytics.js","ga"); ga("create", "#{config.googleAnalyticsTrackingID}", "auto"); - ga("send", "pageview"); \ No newline at end of file + ga("send", "pageview"); From a95563ab97085355568243aea143b8ae2b41957d Mon Sep 17 00:00:00 2001 From: AppleBetas Date: Tue, 26 Dec 2017 09:57:59 -0500 Subject: [PATCH 23/61] Start block flags support --- client/js/place.js | 13 ++++++++++++- config/config.example.js | 7 ++++--- controllers/FeatureAvailabilityController.js | 3 ++- public/css/place.css | 1 + 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/client/js/place.js b/client/js/place.js index 209bfbe7..22306c33 100644 --- a/client/js/place.js +++ b/client/js/place.js @@ -149,7 +149,7 @@ var place = { notificationHandler: notificationHandler, hashHandler: hashHandler, messages: null, isOutdated: false, lastPixelUpdate: null, - colours: null, canPlaceCustomColours: false, hasTriedToFetchAvailability: false, customColour: null, + colours: null, pixelFlags: null, canPlaceCustomColours: false, hasTriedToFetchAvailability: false, customColour: null, cursorX: 0, cursorY: 0, templatesEnabled: false, @@ -285,6 +285,7 @@ var place = { placeAjax.get("/api/feature-availability", null, null).then((data) => { this.hasTriedToFetchAvailability = true; this.colours = data.availability.colours; + this.pixelFlags = data.availability.flags; this.canPlaceCustomColours = data.availability.user && data.availability.user.canPlaceCustomColours; this.templatesEnabled = data.availability.user && data.availability.user.hasTemplatesExperiment this.layoutTemplates(); @@ -524,6 +525,16 @@ var place = { this.colourPaletteOptionElements.push(elem[0]); }); this.updateColourSelectorPosition(); + if(this.pixelFlags && this.pixelFlags.length > 0) { + $("
").addClass("palette-separator").appendTo(contentContainer); + this.pixelFlags.forEach((flag, index) => { + var elem = $("
").addClass("colour-option flag-option").css("background-image", `url(${flag.image})`).attr("data-flag", index).attr("data-flag-id", flag.id).attr("title", `${flag.title}:\n${flag.description}`).attr("alt", flag.title); + if(flag.needsBorder) elem.addClass("is-white"); + elem.appendTo(contentContainer); + this.colourPaletteOptionElements.push(elem[0]); + console.log(flag); + }); + } } else { overlay.text(this.hasTriedToFetchAvailability ? "An error occurred while loading colours. Retrying…" : "Loading…").show(); } diff --git a/config/config.example.js b/config/config.example.js index b8d61c47..f62e42f1 100644 --- a/config/config.example.js +++ b/config/config.example.js @@ -11,8 +11,8 @@ module.exports = { "placeTimeout": 60, 'enableChangelogs': true, 'siteName': 'Place', - //"raven": "", - //"publicRaven": "", + // "raven": "", + // "publicRaven": "", // "bugsnag": "", 'maintenance': { 'allowSignups': true, @@ -67,5 +67,6 @@ module.exports = { "clientID": "", "clientSecret": "" } - } + }, + "pixelFlags": [] }; diff --git a/controllers/FeatureAvailabilityController.js b/controllers/FeatureAvailabilityController.js index e37666af..445619c9 100644 --- a/controllers/FeatureAvailabilityController.js +++ b/controllers/FeatureAvailabilityController.js @@ -1,6 +1,7 @@ exports.getAvailability = (req, res, next) => { var features = { - colours: req.place.colours + colours: req.place.colours, + flags: req.place.config.pixelFlags || [] } if(req.user) features.user = req.user.getFeatureAvailability(); res.json({ diff --git a/public/css/place.css b/public/css/place.css index 95cda3a5..5ce8bc1a 100644 --- a/public/css/place.css +++ b/public/css/place.css @@ -211,6 +211,7 @@ body { .colour-option { background: #fff linear-gradient(rgba(255,255,255,0.1), rgba(0,0,0,0.1)); + background-position: center; width: 26px; border-radius: 5px; cursor: pointer; From 2e5de9c5a8d60c4c07fe37dc17c1a53611887025 Mon Sep 17 00:00:00 2001 From: Eric Rabil Date: Thu, 28 Dec 2017 13:06:52 -0500 Subject: [PATCH 24/61] Added IP and user agent views for admins ONLY (#237) --- app.js | 22 +++++++++++++++++++++- controllers/AccountPageController.js | 9 +++++++-- models/access.js | 11 +++++++++++ models/user.js | 4 ++++ views/public/account.pug | 11 +++++++++++ 5 files changed, 54 insertions(+), 3 deletions(-) diff --git a/app.js b/app.js index 3bafb695..31477bf0 100644 --- a/app.js +++ b/app.js @@ -1,6 +1,8 @@ const mongoose = require("mongoose"); mongoose.promise = global.Promise; const recaptcha = require("express-recaptcha"); +const readline = require("readline").createInterface({input: process.stdin, output: process.stdout}); +const util = require("util"); const PaintingManager = require("./util/PaintingManager"); const HTTPServer = require("./util/HTTPServer"); const WebsocketServer = require("./util/WebsocketServer"); @@ -129,4 +131,22 @@ app.recreateRoutes = () => { manager.getAllPublicDirectoriesToRegister().then((directories) => continueWithServer(directories)).catch((err) => continueWithServer()); }); } -app.recreateRoutes(); \ No newline at end of file +app.recreateRoutes(); +readline.on('line', i => { + try { + var output = eval(i) + output instanceof Promise + ? output.then(a => { + console.log('Promise Resolved') + console.log(util.inspect(a, {depth: 0})) + }).catch(e => { + console.log('Promise Rejected') + console.log(e.stack) + }) + : output instanceof Object + ? console.log(util.inspect(output, {depth: 0})) + : console.log(output) + } catch (err) { + console.log(err.stack) + } +}) \ No newline at end of file diff --git a/controllers/AccountPageController.js b/controllers/AccountPageController.js index b33da4ff..3da21aee 100644 --- a/controllers/AccountPageController.js +++ b/controllers/AccountPageController.js @@ -14,8 +14,13 @@ exports.getAccount = (req, res, next) => { User.findByUsername(req.params.username).then((user) => { if(!user) return next(); if((user.banned || user.deactivated) && !(req.user.moderator || req.user.admin)) return next(); - user.getInfo(req.place).then((info) => { - return req.responseFactory.sendRenderedResponse("public/account", { profileUser: user, profileUserInfo: info, hasNewPassword: req.query.hasNewPassword }); + user.getInfo(req.place).then(async (info) => { + if (req.user.admin) { + const accessData = await user.getUniqueIPsAndUserAgents(); + user.ipAddresses = accessData.ipAddresses; + user.userAgents = accessData.userAgents; + } + return req.responseFactory.sendRenderedResponse("public/account", { profileUser: user, profileUserInfo: info, hasNewPassword: req.query.hasNewPassword}); }).catch((err) => next()); }).catch((err) => next()); }; diff --git a/models/access.js b/models/access.js index 2de1ced7..d0d19b28 100644 --- a/models/access.js +++ b/models/access.js @@ -55,6 +55,17 @@ AccessSchema.statics.findIPsForUser = function(user) { }); } +AccessSchema.statics.getUniqueIPsAndUserAgentsForUser = async function(user) { + const accesses = await this.find({userID: user._id}); + // The [...new Set(array)] is filtering all duplicate strings, and I found it was the most efficient. + const ipAddresses = [...new Set(accesses.map((access) => access.ipAddress))]; + const userAgents = [...new Set(accesses.map((access) => access.userAgent))]; + return { + ipAddresses, + userAgents, + }; +} + AccessSchema.statics.findSimilarIPUserIDs = function(user) { return new Promise((resolve, reject) => { this.findIPsForUser(user).then((ipAddresses) => { diff --git a/models/user.js b/models/user.js index cbc1d2cd..678f3284 100644 --- a/models/user.js +++ b/models/user.js @@ -198,6 +198,10 @@ UserSchema.methods.recordAccess = function(app, userAgent, ipAddress, key) { return Access.recordAccess(app, this.id, userAgent, ipAddress, key) } +UserSchema.methods.getUniqueIPsAndUserAgents = function() { + return Access.getUniqueIPsAndUserAgentsForUser(this); +} + UserSchema.statics.findByUsername = function(username, callback = null) { return this.findOne({ name: { diff --git a/views/public/account.pug b/views/public/account.pug index 79884c3c..e2c275b1 100644 --- a/views/public/account.pug +++ b/views/public/account.pug @@ -58,6 +58,17 @@ block content . . //- Disable this section for OAuth users because they cannot deactivate or change passwords at this time. + if user && user.admin + hr + h1 Access Data + h4 IP Addresses + ul + each ip in profileUser.ipAddresses + li= ip + h4 User Agents + ul + each ua in profileUser.userAgents + li= ua if isSelf && !profileUser.isOauth hr h1 My account From b95fb8cccd6600f0f33cc2bdd44cf93eee61fa9e Mon Sep 17 00:00:00 2001 From: AppleBetas Date: Sun, 31 Dec 2017 13:05:13 -0500 Subject: [PATCH 25/61] Fix #234 --- views/layout.pug | 3 ++- views/public/account.pug | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/views/layout.pug b/views/layout.pug index 2b13dddd..02464edc 100644 --- a/views/layout.pug +++ b/views/layout.pug @@ -3,6 +3,7 @@ block dependencies - if(typeof css === "undefined") css = []; - if(typeof pageTitle === "undefined") pageTitle = null; - if(typeof pageDesc === "undefined") pageDesc = null; +- if(typeof overrideNavPath === "undefined") overrideNavPath = null; - if(typeof isAdmin === "undefined") isAdmin = false; - if(typeof pinnable === "undefined") pinnable = false; - if(typeof navbarSupportsTopMode === "undefined") navbarSupportsTopMode = false; @@ -23,7 +24,7 @@ mixin renderBadges(badges, prefersShortText = false) each badge in badges +renderBadge(badge, prefersShortText) mixin renderNavItem(name, itemPath, icon, showsName = true) - - var isCurrentPage = itemPath == path || (isAdmin && itemPath == "/admin"); + - var isCurrentPage = itemPath == (overrideNavPath || path) || (isAdmin && itemPath == "/admin"); li(id=`nav-${name.toLowerCase().replace(/[^0-9a-z ]/gi, '').replace(/ /g, "-")}`, class=(isCurrentPage ? "active" : "")) a(href=itemPath) if icon diff --git a/views/public/account.pug b/views/public/account.pug index e2c275b1..d7008bb6 100644 --- a/views/public/account.pug +++ b/views/public/account.pug @@ -1,6 +1,7 @@ extends ../layout block dependencies - var isSelf = user ? user.id == profileUser.id : false; + - if (isSelf) var overrideNavPath = "/account"; - var pixel = profileUserInfo.latestPixel; - var boardImageCorrection = (1000 / config.boardSize) * 4; - var pageTitle = isSelf ? "Account Details" : `${profileUser.name}'s Profile`; From b5242df6ffa5ace7fd1ce264390decfce528faf8 Mon Sep 17 00:00:00 2001 From: Jamie Bishop Date: Sun, 31 Dec 2017 22:31:27 +0000 Subject: [PATCH 26/61] Resize to 1600x1600 (#238) * Resize canvas to 1600x1600 by default * Forgot to commit these too * Remove spammy log line --- app.js | 2 +- config/config.example.js | 2 +- public/css/place.css | 4 ++-- scripts/resize.js | 8 +++----- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/app.js b/app.js index 31477bf0..e0b20191 100644 --- a/app.js +++ b/app.js @@ -26,7 +26,7 @@ app.loadConfig = (path = "./config/config") => { app.colours = [... new Set((app.config.colours || ["#FFFFFF", "#E4E4E4", "#888888", "#222222", "#FFA7D1", "#E50000", "#E59500", "#A06A42", "#E5D900", "#94E044", "#02BE01", "#00D3DD", "#0083C7", "#0000EA", "#CF6EE4", "#820080"]).map((c) => c.toUpperCase()))]; if(!app.config.siteName) app.config.siteName = "Place"; if(!app.config.enableChangelogs) app.config.enableChangelogs = true; - if(!app.config.boardSize) app.config.boardSize = 1400; // default to 1400 if not specified in config + if(!app.config.boardSize) app.config.boardSize = 1600; // default to 1600 if not specified in config if(oldConfig && (oldConfig.secret != app.config.secret || oldConfig.database != app.config.database || oldConfig.boardSize != app.config.boardSize)) { app.logger.log("Configuration", "We are stopping the Place server because the database URL, secret, and/or board image size has been changed, which will require restarting the entire server."); process.exit(0); diff --git a/config/config.example.js b/config/config.example.js index f62e42f1..83bc9fe4 100644 --- a/config/config.example.js +++ b/config/config.example.js @@ -2,7 +2,7 @@ module.exports = { "secret": "CHANGETHISDONTUSETHISITSINSECURE", // <------- CHANGE THIS DONT USE THE DEFAULT YOU'LL GET HACKED AND DIE 100% "database": "mongodb://localhost/place", "port": 3000, - "boardSize": 1400, + "boardSize": 1600, "onlyListenLocal": true, "trustProxyDepth": 1, // How many levels of proxy to trust for IP "debug": false, diff --git a/public/css/place.css b/public/css/place.css index 5ce8bc1a..a5a10919 100644 --- a/public/css/place.css +++ b/public/css/place.css @@ -62,8 +62,8 @@ body { image-rendering: -webkit-optimize-contrast; image-rendering: crisp-edges; image-rendering: pixelated; - height: 1400px; - width: 1400px; + height: 1600px; + width: 1600px; } #zoom-controller { diff --git a/scripts/resize.js b/scripts/resize.js index 353939b4..b10123f7 100644 --- a/scripts/resize.js +++ b/scripts/resize.js @@ -19,10 +19,9 @@ let cursor = Pixel.find().cursor(); cursor.on('data', (pixel) => { i++; - let o = pixel; - - pixel.xPos += 200 - pixel.yPos += 200 + + pixel.xPos += 100 + pixel.yPos += 100 pixel.save(function(err, n) { if (err) return console.error("Error saving pixel " + err); @@ -31,7 +30,6 @@ cursor.on('data', (pixel) => { console.log(`Updated ${i} pixels`); process.exit(); } - console.log(`Updated pixel (${o.xPos}, ${o.yPos}) to (${pixel.xPos}, ${pixel.yPos})`); }); }); From 18c39bdd3a74756aa517c1848d247dd40f26f60d Mon Sep 17 00:00:00 2001 From: AppleBetas Date: Sun, 31 Dec 2017 17:31:36 -0500 Subject: [PATCH 27/61] Start scalable zoom --- client/js/place.js | 90 ++++++++++++++++++++++++++++---------------- public/css/place.css | 9 ++--- 2 files changed, 62 insertions(+), 37 deletions(-) diff --git a/client/js/place.js b/client/js/place.js index 22306c33..39c0eb58 100644 --- a/client/js/place.js +++ b/client/js/place.js @@ -131,7 +131,12 @@ var place = { zoomTo: 0, zoomTime: 0, zoomHandle: null, - fastZoom: false + fastZoom: false, + initialZoomPoint: 4, + zoomedInPoint: 40, + snapPoints: [0, 4, 40, 80], + zoomScale: 4, + wasZoomedFullyOut: false }, keys: { left: [37, 65], @@ -213,6 +218,7 @@ var place = { window.onresize = () => this.handleResize(); window.onhashchange = () => this.handleHashChange(); + $(window).on("mousewheel", (e) => this.mousewheelMoved(e)); this.zoomController = zoomController; this.cameraController = cameraController; @@ -221,6 +227,8 @@ var place = { var spawnPoint = this.getSpawnPoint(); this.setCanvasPosition(spawnPoint.x, spawnPoint.y); + this.setZoomScale(this.zooming.zoomScale); + $(this.coordinateElement).show(); $(this.userCountElement).show(); @@ -400,12 +408,17 @@ var place = { if(app.zooming.zoomedIn && this.selectedColour === null) { app.zoomFinished(); app.shouldShowPopover = false; - app.setZoomedIn(false); + app.setZoomScale(this.zooming.initialZoomPoint, true); event.preventDefault(); } }); }, + mousewheelMoved: function(e) { + var delta = typeof e.originalEvent.wheelDeltaY !== "undefined" ? e.originalEvent.wheelDeltaY : e.originalEvent.wheelDelta; + this.setZoomScale(this.zooming.zoomScale + (delta / 100)); + }, + getCanvasCursorPosition: function(x = null, y = null) { var zoom = this._getZoomMultiplier(); return {x: Math.round(((x ? x : this.cursorX) - $(this.cameraController).offset().left) / zoom), y: Math.round(((y ? y : this.cursorY) - $(this.cameraController).offset().top) / zoom)}; @@ -559,6 +572,7 @@ var place = { this.displayCtx.msImageSmoothingEnabled = false; this.displayCtx.imageSmoothingEnabled = false; this.updateDisplayCanvas(); + if(this.zooming.wasZoomedFullyOut) this.setZoomScale(0); this.updateGrid(); this.updateGridHint(this.lastX, this.lastY); this.updateColourSelectorPosition(); @@ -582,15 +596,6 @@ var place = { elem.css({left: position}); }, - /*setFullMapViewScale: function() { - var scale = 1; - if(this.isViewingFullMap()) { - var canvasContainer = $(this.zoomController).parent(); - var scale = Math.min(1, Math.min(canvasContainer.height() / size, canvasContainer.width() / size)); - } - $(this.canvas).css({ "transform": `scale(${scale}, ${scale})` }); - },*/ - setupDisplayCanvas: function(canvas) { this.displayCtx = canvas.getContext("2d"); this.handleResize(); @@ -616,7 +621,7 @@ var place = { }, _getZoomMultiplier: function() { - return this.zooming.zoomedIn ? 40 : 4; + return this.zooming.zoomScale; }, animateZoom: function(callback = null) { @@ -624,7 +629,8 @@ var place = { var x = this._lerp(this.zooming.panFromX, this.zooming.panToX, this.zooming.zoomTime); var y = this._lerp(this.zooming.panFromY, this.zooming.panToY, this.zooming.zoomTime); - this.setCanvasPosition(x, y) + $(this.zoomController).css("transform", `scale(${this._lerp(this.zooming.zoomFrom, this.zooming.zoomTo, this.zooming.zoomTime)})`); + this.setCanvasPosition(x, y); if (this.zooming.zoomTime >= 100) { this.zoomFinished(); @@ -638,9 +644,10 @@ var place = { }, zoomFinished: function() { + this.zooming.zoomScale = this.zooming.zoomTo; this.zooming.zooming = false; this.setCanvasPosition(this.zooming.panToX, this.zooming.panToY); - this.zooming.panToX = null, this.zooming.panToY = null; + this.zooming.panToX = null, this.zooming.panToY = null, this.zooming.zoomTo = null, this.zooming.zoomFrom = null; clearInterval(this.zooming.zoomHandle); var coord = this.getCoordinates(); this.hashHandler.modifyHash(coord); @@ -648,31 +655,48 @@ var place = { this.zooming.fastZoom = false; }, - setZoomedIn: function(zoomedIn) { - var app = this; + setZoomScale: function(scale, animated = false) { if(this.zooming.zoomHandle !== null) return; this.zooming.panFromX = this.panX; this.zooming.panFromY = this.panY; if(this.zooming.panToX == null) this.zooming.panToX = this.panX; if(this.zooming.panToY == null) this.zooming.panToY = this.panY; - this.zooming.zoomFrom = this._getCurrentZoom() - this.zooming.zoomTime = 0 - this.zooming.zooming = true - this.zooming.zoomedIn = zoomedIn; - this.zooming.zoomTo = this._getZoomMultiplier() - this.zooming.zoomHandle = setInterval(this.animateZoom.bind(this, function() { - $(app.grid).removeClass("zooming"); - }), 1); - - if (zoomedIn) $(this.zoomController).parent().addClass("zoomed"); - else $(this.zoomController).parent().removeClass("zoomed"); - $(this.grid).addClass("zooming"); + var newScale = this.normalizeZoomScale(scale); + if(animated) { + this.zooming.zoomTime = 0; + this.zooming.zoomFrom = this._getCurrentZoom(); + this.zooming.zoomTo = newScale; + this.zooming.zooming = true; + this.zooming.zoomHandle = setInterval(this.animateZoom.bind(this, () => $(this.grid).removeClass("zooming")), 1); + $(this.grid).addClass("zooming"); + } else { + this.zooming.zoomScale = newScale; + $(this.zoomController).css("transform", `scale(${newScale})`); + } + this.zooming.zoomedIn = newScale >= this.zooming.zoomedInPoint; + if(!this.zooming.zoomedIn) $(this.pixelDataPopover).hide(); this.updateDisplayCanvas(); + this.updateGrid(); this._adjustZoomButtonText(); }, + normalizeZoomScale: function(scale) { + var canvasContainer = $(this.zoomController).parent(); + var minScale = Math.min(1, Math.min((canvasContainer.height() - $("#page-nav").height()) / size, canvasContainer.width() / size)); + var newScale = Math.min(this.zooming.snapPoints[this.zooming.snapPoints.length - 1], Math.max(minScale, Math.max(this.zooming.snapPoints[0], scale))); + this.zooming.wasZoomedFullyOut = newScale <= minScale; + if (this.zooming.wasZoomedFullyOut && !$(this.colourPaletteElement).hasClass("full-canvas")) $(this.colourPaletteElement).addClass("full-canvas"); + else if(!this.zooming.wasZoomedFullyOut && $(this.colourPaletteElement).hasClass("full-canvas")) $(this.colourPaletteElement).removeClass("full-canvas"); + return newScale; + }, + toggleZoom: function() { - this.setZoomedIn(!this.zooming.zoomedIn); + if (this.zooming.zooming) return; + var scale = this.zooming.zoomScale; + if (scale < this.zooming.initialZoomPoint) this.setZoomScale(this.zooming.initialZoomPoint, true); + else if (scale < (this.zooming.initialZoomPoint + this.zooming.zoomedInPoint) / 2) this.setZoomScale(this.zooming.zoomedInPoint, true); + else if (scale <= this.zooming.zoomedInPoint) this.setZoomScale(this.zooming.initialZoomPoint, true); + else this.setZoomScale(this.zooming.zoomedInPoint, true); }, _adjustZoomButtonText: function() { @@ -765,8 +789,10 @@ var place = { updateGrid: function() { var zoom = this._getCurrentZoom(); + var x = ($(this.cameraController).offset().left - (zoom / 2)) % zoom; + var y = ($(this.cameraController).offset().top - (zoom / 2)) % zoom; $(this.grid).css({ - transform: `translate(${(($(this.cameraController).offset().left / zoom) - 0.5) * zoom % zoom}px, ${(($(this.cameraController).offset().top / zoom) - 0.5) * zoom % zoom}px)`, + transform: `translate(${x}px, ${y}px)`, backgroundSize: `${zoom}px ${zoom}px` }) }, @@ -816,7 +842,7 @@ var place = { contextMenu: function(event) { event.preventDefault(); if(this.selectedColour !== null) return this.deselectColour(); - if(this.zooming.zoomedIn) this.setZoomedIn(false); + this.setZoomScale(this.zooming.initialZoomPoint, true); }, getPixel: function(x, y, callback) { @@ -948,7 +974,7 @@ var place = { this.zooming.panFromX = this.panX; this.zooming.panFromY = this.panY; - this.setZoomedIn(actuallyZoom ? true : this.zooming.zoomedIn); // this is lazy as fuck but so am i + this.setZoomScale(actuallyZoom && !this.zooming.zoomedIn ? 40 : this.zooming.zoomScale, true); // this is lazy as fuck but so am i }, canvasClicked: function(x, y, event) { diff --git a/public/css/place.css b/public/css/place.css index 5ce8bc1a..7dc66dfc 100644 --- a/public/css/place.css +++ b/public/css/place.css @@ -68,17 +68,12 @@ body { #zoom-controller { transform-origin: center; - transform: scale(4, 4); z-index: 1; cursor: grab; cursor: -moz-grab; cursor: -webkit-grab; } -.zoomed > #zoom-controller { - transform: scale(40, 40); -} - #zoom-controller.grabbing { cursor: grabbing; cursor: -moz-grabbing; @@ -179,6 +174,10 @@ body { position: relative; } +#palette.full-canvas { + display: none; +} + #palette-content-ctn { overflow-x: auto; overflow-y: hidden; From 48aecd5a5ea46b91903946f00f24971ecfc6d69f Mon Sep 17 00:00:00 2001 From: AppleBetas Date: Sun, 31 Dec 2017 17:39:19 -0500 Subject: [PATCH 28/61] Bring back achievements tab --- views/public/views/place.pug | 2 +- views/public/views/popout-container.pug | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/views/public/views/place.pug b/views/public/views/place.pug index 15a92a1d..bfbf3a44 100644 --- a/views/public/views/place.pug +++ b/views/public/views/place.pug @@ -11,7 +11,7 @@ .popout-btns .popout-control.control.btn.btn-default(data-tab-name="chat"): i.fa.fa-comments.fa-fw .popout-control.control.btn.btn-default(data-tab-name="leaderboard"): i.fa.fa-trophy.fa-fw - //if user + if user .popout-control.control.btn.btn-default(data-tab-name="achievements"): i.fa.fa-star.fa-fw .popout-control.control.btn.btn-default(data-tab-name="active-users"): i.fa.fa-users.fa-fw .control-btns diff --git a/views/public/views/popout-container.pug b/views/public/views/popout-container.pug index 186505a7..e49b41b5 100644 --- a/views/public/views/popout-container.pug +++ b/views/public/views/popout-container.pug @@ -13,7 +13,7 @@ .tab-content#leaderboardTab(data-tab-name="leaderboard") .coming-soon p Loading… - //if user + if user .tab-content#achievementsTab(data-tab-name="achievements") .coming-soon p Coming Soon @@ -23,6 +23,6 @@ .tabbar .tab.active(data-tab-name="chat", title="Chat"): i.fa.fa-comments .tab(data-tab-name="leaderboard", title="Leaderboard"): i.fa.fa-trophy - //if user + if user .tab(data-tab-name="achievements", title="Achievements"): i.fa.fa-star .tab(data-tab-name="active-users", title="Active Users"): i.fa.fa-users \ No newline at end of file From 6cc8c1536cbe984615296d857acc888d44deb208 Mon Sep 17 00:00:00 2001 From: Eric Rabil Date: Sun, 31 Dec 2017 17:52:36 -0500 Subject: [PATCH 29/61] Update resize.js --- scripts/resize.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/scripts/resize.js b/scripts/resize.js index b10123f7..9212ae36 100644 --- a/scripts/resize.js +++ b/scripts/resize.js @@ -17,6 +17,16 @@ mongoose.connect(config.database).then(() => console.info('Connected to database let cursor = Pixel.find().cursor(); +let count = 0; +Pixel.count().then(c => { + count = c; + const printStatus = () => { + const rawPercentage = saved / count; + console.log(`Hey bitch we've updated ${rawPercentage * 100}% of the pixels`); + } + setInterval(() => printStatus(), 15000); +}); + cursor.on('data', (pixel) => { i++; @@ -39,4 +49,4 @@ cursor.on('close', function() { cursor.on('error', function(err) { console.error("Error saving pixel " + err); -}); \ No newline at end of file +}); From 021402c4e00dddff85f42eeaa92eef2254c0192b Mon Sep 17 00:00:00 2001 From: Eric Rabil Date: Sun, 31 Dec 2017 17:54:31 -0500 Subject: [PATCH 30/61] Update resize.js --- scripts/resize.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/resize.js b/scripts/resize.js index 9212ae36..1a0d7b19 100644 --- a/scripts/resize.js +++ b/scripts/resize.js @@ -13,7 +13,8 @@ var doneReading = false; let i = 0; let saved = 0; -mongoose.connect(config.database).then(() => console.info('Connected to database')); +mongoose.connect(config.database, {useMongoClient: true}).then(() => console.info('Connected to database')); +mongoose.Promise = global.Promise; let cursor = Pixel.find().cursor(); From 4422150fd6ca576fca38ebeeca0e76777c13c2de Mon Sep 17 00:00:00 2001 From: Eric Rabil Date: Sun, 31 Dec 2017 17:56:53 -0500 Subject: [PATCH 31/61] To achieve, or not to achieve. --- config/achievements.js | 126 ++++++++++++++++++++++++++ controllers/AchievementsController.js | 11 +++ models/user.js | 8 ++ routes/api.js | 2 + 4 files changed, 147 insertions(+) create mode 100644 config/achievements.js create mode 100644 controllers/AchievementsController.js diff --git a/config/achievements.js b/config/achievements.js new file mode 100644 index 00000000..ba3f539a --- /dev/null +++ b/config/achievements.js @@ -0,0 +1,126 @@ +const olderThan = (date = Date.now(), time = {milliseconds: 0, seconds: 0, minutes: 0, hours: 0, days: 0, weeks: 0, months: 0, years: 0}) => { + let computedTime = Date.now(); + if (time.milliseconds) { + computedTime -= time.milliseconds; + } + if (time.seconds) { + computedTime -= time.seconds * 1000; + } + if (time.minutes) { + computedTime -= time.minutes * 1000 * 60; + } + if (time.hours) { + computedTime -= time.hours * 1000 * 60 * 60; + } + if (time.days) { + computedTime -= time.days * 1000 * 60 * 60 * 24; + } + if (time.weeks) { + computedTime -= time.weeks * 1000 * 60 * 60 * 24 * 7; + } + if (time.months) { + let cTime = new Date(); + cTime.setTime(Date.now() - computedTime); + const newMonths = cTime.getMonth() + time.months; + cTime.setMonth(newMonths); + computedTime = cTime.getTime(); + } + if (time.years) { + computedTime -= time.years * 1000 * 60 * 60 * 24 * 365; + } + console.log("Minimum: " + computedTime); + console.log("Date: " + date); + return computedTime > date; +}; + +module.exports = [ + // User has placed at least five pixels + { + name: "First Pixel!", + description: "You've placed one pixel!", + imageURL: null, + meetsCriteria(user) { + return user.placeCount >= 1; + } + }, + { + name: "Ten Pixels!", + description: "Congrats on hitting ten pixels! Keep it going!", + imageURL: null, + meetsCriteria(user) { + return user.placeCount >= 10; + } + }, + { + name: "100 Pixels!", + description: "W00T! Let's go let's go let's go!!! Get to 1000 pixels!", + imageURL: null, + meetsCriteria(user) { + return user.placeCount >= 100; + } + }, + { + name: "1000 Pixels!", + description: "I see you, placing those pixels all sexy 'n shit.", + imageURL: null, + meetsCriteria(user) { + return user.placeCount >= 1000; + } + }, + { + name: "Addict", + description: "People can safely default to assuming you're on canvas.place", + imageURL: null, + meetsCriteria(user) { + return user.placeCount >= 10000; + } + }, + { + name: "Ultra-Addict", + description: "You play canvas.place so much, you should just run the website.", + imageURL: null, + meetsCriteria(user) { + return user.placeCount >= 50000; + } + }, + { + name: "Beginner", + description: "You're just starting out, but don't fret. You're going great places.", + imageURL: null, + meetsCriteria(user) { + return olderThan(user.creationDate, {days: 1}); + } + }, + { + name: "Novice", + description: "You're wiser than the average fellow but have much left to learn.", + imageURL: null, + meetsCriteria(user) { + return olderThan(user.creationDate, {weeks: 1}); + } + }, + { + name: "Intermediate", + description: "You're getting the hang of it! Keep at it.", + imageURL: null, + meetsCriteria(user) { + return olderThan(user.creationDate, {months: 1}); + } + }, + { + name: "Advanced", + description: "You've got it! I consider you to be proficient at the art of placing.", + imageURL: null, + meetsCriteria(user) { + return olderThan(user.creationDate, {months: 6}); + } + }, + { + name: "Expert", + description: "You're better than me, so..", + imageURL: null, + meetsCriteria(user) { + return olderThan(user.creationDate, {years: 1}); + } + } +]; \ No newline at end of file diff --git a/controllers/AchievementsController.js b/controllers/AchievementsController.js new file mode 100644 index 00000000..bea0f967 --- /dev/null +++ b/controllers/AchievementsController.js @@ -0,0 +1,11 @@ +const User = require("../models/user"); + +exports.getUserAchievements = (req, res, next) => { + if (!req.params.username) return res.status(400).json({success: false}); + const name = req.params.username; + User.findOne({name}).then(user => { + user.getAchievements().then(achievements => { + res.json({achievements}); + }); + }); +} \ No newline at end of file diff --git a/models/user.js b/models/user.js index 678f3284..3ab8f2f4 100644 --- a/models/user.js +++ b/models/user.js @@ -6,6 +6,7 @@ const Pixel = require("./pixel"); const Access = require("./access"); const dataTables = require("mongoose-datatables"); const TOSManager = require("../util/TOSManager"); +const achievements = require("../config/achievements"); var UserSchema = new Schema({ name: { @@ -120,6 +121,13 @@ UserSchema.pre("save", function(next) { } }); +UserSchema.methods.getAchievements = function() { + return new Promise((resolve, reject) => { + const userAchievements = achievements.filter(achievement => achievement.meetsCriteria(this)); + resolve(userAchievements); + }); +} + UserSchema.methods.comparePassword = function(passwd, cb) { bcrypt.compare(passwd, this.password, function(err, isMatch) { if (err) return cb(err); diff --git a/routes/api.js b/routes/api.js index a3fc1535..02a6ef51 100644 --- a/routes/api.js +++ b/routes/api.js @@ -15,6 +15,7 @@ const AccountPageController = require("../controllers/AccountPageController"); const TOTPSetupController = require("../controllers/TOTPSetupController"); const ChangelogController = require("../controllers/ChangelogController"); const WarpController = require("../controllers/WarpController"); +const AchievementsController = require("../controllers/AchievementsController"); function APIRouter(app) { let router = express.Router(); @@ -169,6 +170,7 @@ function APIRouter(app) { router.route("/chat").get(ChatController.getAPIChat).post([requireUser, chatRatelimit.prevent], ChatController.postAPIChatMessage); router.get("/user/:username", AccountPageController.getAPIAccount); + router.get("/user/:username/achievements", AchievementsController.getUserAchievements); router.get("/changelog/latest", ChangelogController.getLatestChangelog); router.route("/changelog/missed").get([requireUser, ChangelogController.getMissedChangelogs]).post([requireUser, ChangelogController.postMissedChangelogs]).delete([requireUser, ChangelogController.deleteMissedChangelogs]); From 8eef19cbdfa22bcca45ff268f0fc9799cb4d6ba4 Mon Sep 17 00:00:00 2001 From: Eric Rabil Date: Sun, 31 Dec 2017 17:57:04 -0500 Subject: [PATCH 32/61] Improved duplication removal from arrays --- models/access.js | 13 +++++++++---- views/layout.pug | 7 ++++--- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/models/access.js b/models/access.js index d0d19b28..fbfba415 100644 --- a/models/access.js +++ b/models/access.js @@ -23,6 +23,12 @@ var AccessSchema = new Schema({ } }); +const stripDuplicates = (arr = []) => { + for (let i = 0; i < arr.length; i++) { + if (arr.indexOf(arr[i]) !== i) arr.splice(i, 1); + } +} + AccessSchema.methods.toInfo = function() { return { userID: this.userID, @@ -57,9 +63,8 @@ AccessSchema.statics.findIPsForUser = function(user) { AccessSchema.statics.getUniqueIPsAndUserAgentsForUser = async function(user) { const accesses = await this.find({userID: user._id}); - // The [...new Set(array)] is filtering all duplicate strings, and I found it was the most efficient. - const ipAddresses = [...new Set(accesses.map((access) => access.ipAddress))]; - const userAgents = [...new Set(accesses.map((access) => access.userAgent))]; + const ipAddresses = stripDuplicates(accesses.map((access) => access.ipAddress)); + const userAgents = stripDuplicates(accesses.map((access) => access.userAgent)); return { ipAddresses, userAgents, @@ -71,7 +76,7 @@ AccessSchema.statics.findSimilarIPUserIDs = function(user) { this.findIPsForUser(user).then((ipAddresses) => { this.find({ ipAddress: { $in: ipAddresses }, userID: { $ne: user._id } }).then((accesses) => { var userIDs = accesses.map((access) => String(access.userID)); - resolve([...new Set(userIDs)]); + resolve(stripDuplicates((userIDs))); }).catch(reject); }).catch(reject); }); diff --git a/views/layout.pug b/views/layout.pug index 2b13dddd..313fca43 100644 --- a/views/layout.pug +++ b/views/layout.pug @@ -17,6 +17,7 @@ block dependencies - var hasCommunityGuidelines = fs.existsSync("./config/community_guidelines.md"); - var hasTOS = TOSManager.hasTOSSync(); - var hasPP = TOSManager.hasPrivacyPolicySync(); +- const stripDuplicates = (arr = []) => { for (let i = 0; i < arr.length; i++) { if (arr.indexOf(arr[i]) !== i) arr.splice(i, 1); } return arr; } mixin renderBadge(badge, prefersShortText = false) span.label.badge-label(class=`label-${badge.style || "default"}`, title=badge.title) #{prefersShortText && badge.shortText ? badge.shortText : badge.text} mixin renderBadges(badges, prefersShortText = false) @@ -67,7 +68,7 @@ html(lang="en") link(href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css", rel="stylesheet") link(href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css", rel="stylesheet") - css.unshift("/css/global.css") - each item in [...new Set(css)] + each item in stripDuplicates(css) link(href=item + "?v=" + resourceVersion, rel="stylesheet") +getViewExtensions("head")