Added the /api/me/ API endpoint.

Moved a script block into an API endpoint.
Split the gigantic switch in ServeHTTP into it's own function.
Scripts should load more efficiently now.
This commit is contained in:
Azareal 2018-08-12 01:53:42 +10:00
parent 18c5223143
commit a9a1d667c0
8 changed files with 486 additions and 395 deletions

View File

@ -138,6 +138,9 @@ func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (header
} }
} }
//h := w.Header()
//h.Set("Content-Security-Policy", "default-src 'self'")
// TODO: GDPR. Add a global control panel notice warning the admins of staff members who don't have 2FA enabled // TODO: GDPR. Add a global control panel notice warning the admins of staff members who don't have 2FA enabled
stats.Users = Users.GlobalCount() stats.Users = Users.GlobalCount()
stats.Groups = Groups.GlobalCount() stats.Groups = Groups.GlobalCount()

View File

@ -78,6 +78,39 @@ type WsJSONUser struct {
Liked int Liked int
} }
func (user *User) Me() *MeUser {
var groupID = user.Group
if user.TempGroup != 0 {
groupID = user.TempGroup
}
return &MeUser{user.ID, user.Link, user.Name, groupID, user.Active, user.IsMod, user.IsSuperMod, user.IsAdmin, user.IsBanned, user.Session, user.Avatar, user.MicroAvatar, user.Tag, user.Level, user.Score, user.Liked}
}
// For when users need to see their own data, I've omitted some redundancies and less useful items, so we don't wind up sending them on every request
type MeUser struct {
ID int
Link string
Name string
Group int
Active bool
IsMod bool
IsSuperMod bool
IsAdmin bool
IsBanned bool
// TODO: Implement these as copies (might already be the case for Perms, but we'll want to look at it's definition anyway)
//Perms Perms
//PluginPerms map[string]bool
Session string
Avatar string
MicroAvatar string
Tag string
Level int
Score int
Liked int
}
type UserStmts struct { type UserStmts struct {
activate *sql.Stmt activate *sql.Stmt
changeGroup *sql.Stmt changeGroup *sql.Stmt

File diff suppressed because it is too large Load Diff

View File

@ -7,6 +7,7 @@ var alertList = [];
var alertCount = 0; var alertCount = 0;
var moreTopicCount = 0; var moreTopicCount = 0;
var conn; var conn;
var me = {};
var selectedTopics = []; var selectedTopics = [];
var attachItemCallback = function(){} var attachItemCallback = function(){}
var hooks = { var hooks = {
@ -196,7 +197,7 @@ function runWebSockets() {
console.log("The WebSockets connection was opened"); console.log("The WebSockets connection was opened");
conn.send("page " + document.location.pathname + '\r'); conn.send("page " + document.location.pathname + '\r');
// TODO: Don't ask again, if it's denied. We could have a setting in the UCP which automatically requests this when someone flips desktop notifications on // TODO: Don't ask again, if it's denied. We could have a setting in the UCP which automatically requests this when someone flips desktop notifications on
if(loggedIn) { if(me.User.ID > 0) {
Notification.requestPermission(); Notification.requestPermission();
} }
} }
@ -258,14 +259,11 @@ function runWebSockets() {
for(var i = 0; i < messages.length; i++) { for(var i = 0; i < messages.length; i++) {
let message = messages[i]; let message = messages[i];
//console.log("Message: ",message); //console.log("Message: ",message);
let msgblocks = SplitN(message," ",3);
if(msgblocks.length < 3) continue;
if(message.startsWith("set ")) { if(message.startsWith("set ")) {
//msgblocks = message.split(' ',3);
let msgblocks = SplitN(message," ",3);
if(msgblocks.length < 3) continue;
document.querySelector(msgblocks[1]).innerHTML = msgblocks[2]; document.querySelector(msgblocks[1]).innerHTML = msgblocks[2];
} else if(message.startsWith("set-class ")) { } else if(message.startsWith("set-class ")) {
let msgblocks = SplitN(message," ",3);
if(msgblocks.length < 3) continue;
document.querySelector(msgblocks[1]).className = msgblocks[2]; document.querySelector(msgblocks[1]).className = msgblocks[2];
} }
} }
@ -278,7 +276,7 @@ function len(item) {
} }
function loadScript(name, callback) { function loadScript(name, callback) {
let url = "//" +siteURL+"/static/"+name let url = "//" +me.Site.URL+"/static/"+name
$.getScript(url) $.getScript(url)
.done(callback) .done(callback)
.fail((e,xhr,settings,ex) => { .fail((e,xhr,settings,ex) => {
@ -296,7 +294,7 @@ function DoNothingButPassBack(item) {
} }
function fetchPhrases() { function fetchPhrases() {
fetch("//" +siteURL+"/api/phrases/?query=status,topic_list") fetch("//" +me.Site.URL+"/api/phrases/?query=status,topic_list")
.then((resp) => resp.json()) .then((resp) => resp.json())
.then((data) => { .then((data) => {
console.log("loaded phrase endpoint data"); console.log("loaded phrase endpoint data");
@ -328,29 +326,44 @@ function fetchPhrases() {
}); });
} }
$(document).ready(function(){ (() => {
runHook("start_init"); runHook("pre_me");
if(loggedIn) { fetch("/api/me/")
let toLoad = 1; .then((resp) => resp.json())
loadScript("template_topics_topic.js", () => { .then((data) => {
console.log("Loaded template_topics_topic.js"); console.log("loaded me endpoint data");
toLoad--; console.log("data:",data);
if(toLoad===0) fetchPhrases(); me = data;
runHook("pre_init");
if(me.User.ID > 0) {
let toLoad = 1;
loadScript("template_topics_topic.js", () => {
console.log("Loaded template_topics_topic.js");
toLoad--;
if(toLoad===0) fetchPhrases();
});
}
// We can only get away with this because template_alert has no phrases, otherwise it too would have to be part of the "dance", I miss Go concurrency :(
loadScript("template_alert.js", () => {
console.log("Loaded template_alert.js");
alertsInitted = true;
var alertMenuList = document.getElementsByClassName("menu_alerts");
for(var i = 0; i < alertMenuList.length; i++) {
loadAlerts(alertMenuList[i]);
}
});
if(window["WebSocket"]) runWebSockets();
else conn = false;
$(document).ready(mainInit);
}); });
} })();
// We can only get away with this because template_alert has no phrases, otherwise it too would have to be part of the "dance", I miss Go concurrency :( function mainInit(){
loadScript("template_alert.js", () => { runHook("start_init");
console.log("Loaded template_alert.js");
alertsInitted = true;
var alertMenuList = document.getElementsByClassName("menu_alerts");
for(var i = 0; i < alertMenuList.length; i++) {
loadAlerts(alertMenuList[i]);
}
});
if(window["WebSocket"]) runWebSockets();
else conn = false;
$(".more_topics").click((event) => { $(".more_topics").click((event) => {
event.preventDefault(); event.preventDefault();
@ -484,7 +497,7 @@ $(document).ready(function(){
let formAction = $(this).closest('a').attr("href"); let formAction = $(this).closest('a').attr("href");
//console.log("Form Action:", formAction); //console.log("Form Action:", formAction);
$.ajax({ $.ajax({
url: formAction + "?session=" + session, url: formAction + "?session=" + me.User.Session,
type: "POST", type: "POST",
dataType: "json", dataType: "json",
error: ajaxError, error: ajaxError,
@ -559,7 +572,7 @@ $(document).ready(function(){
var formAction = $(this).closest('a').attr("href"); var formAction = $(this).closest('a').attr("href");
//console.log("Form Action:", formAction); //console.log("Form Action:", formAction);
//console.log(outData); //console.log(outData);
$.ajax({ url: formAction + "?session=" + session, type:"POST", dataType:"json", data: outData, error: ajaxError }); $.ajax({ url: formAction + "?session=" + me.User.Session, type:"POST", dataType:"json", data: outData, error: ajaxError });
blockParent.find('.hide_on_edit').show(); blockParent.find('.hide_on_edit').show();
blockParent.find('.show_on_edit').hide(); blockParent.find('.show_on_edit').hide();
}); });
@ -654,8 +667,8 @@ $(document).ready(function(){
console.log("content.value", content.value); console.log("content.value", content.value);
let attachItem; let attachItem;
if(content.value == "") attachItem = "//" + siteURL + "/attachs/" + hash + "." + ext; if(content.value == "") attachItem = "//" + me.Site.URL + "/attachs/" + hash + "." + ext;
else attachItem = "\r\n//" + siteURL + "/attachs/" + hash + "." + ext; else attachItem = "\r\n//" + me.Site.URL + "/attachs/" + hash + "." + ext;
content.value = content.value + attachItem; content.value = content.value + attachItem;
console.log("content.value", content.value); console.log("content.value", content.value);
@ -667,9 +680,9 @@ $(document).ready(function(){
} }
reader.readAsDataURL(files[i]); reader.readAsDataURL(files[i]);
} }
if(totalSize>maxRequestSize) { if(totalSize > me.Site.MaxRequestSize) {
// TODO: Use a notice instead // TODO: Use a notice instead
alert("You can't upload this much data at once, max: " + maxRequestSize); alert("You can't upload this much data at once, max: " + me.Site.MaxRequestSize);
} }
} }
@ -696,7 +709,7 @@ $(document).ready(function(){
}); });
let bulkActionSender = function(action, selectedTopics, fragBit) { let bulkActionSender = function(action, selectedTopics, fragBit) {
let url = "/topic/"+action+"/submit/"+fragBit+"?session=" + session; let url = "/topic/"+action+"/submit/"+fragBit+"?session=" + me.User.Session;
$.ajax({ $.ajax({
url: url, url: url,
type: "POST", type: "POST",
@ -746,7 +759,7 @@ $(document).ready(function(){
$("#themeSelectorSelect").change(function(){ $("#themeSelectorSelect").change(function(){
console.log("Changing the theme to " + this.options[this.selectedIndex].getAttribute("val")); console.log("Changing the theme to " + this.options[this.selectedIndex].getAttribute("val"));
$.ajax({ $.ajax({
url: this.form.getAttribute("action") + "?session=" + session, url: this.form.getAttribute("action") + "?session=" + me.User.Session,
type: "POST", type: "POST",
dataType: "json", dataType: "json",
data: { "newTheme": this.options[this.selectedIndex].getAttribute("val"), isJs: "1" }, data: { "newTheme": this.options[this.selectedIndex].getAttribute("val"), isJs: "1" },
@ -825,4 +838,4 @@ $(document).ready(function(){
}); });
runHook("end_init"); runHook("end_init");
}); };

View File

@ -644,7 +644,11 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
"after PreRoute\n" + "after PreRoute\n" +
"routeMapEnum: ", routeMapEnum) "routeMapEnum: ", routeMapEnum)
} }
router.routeSwitch(w, req, user, prefix, extraData)
}
func (router *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user common.User, prefix string, extraData string) {
var err common.RouteError var err common.RouteError
switch(prefix) {` + out + ` switch(prefix) {` + out + `
/*case "/sitemaps": // TODO: Count these views /*case "/sitemaps": // TODO: Count these views

View File

@ -14,6 +14,7 @@ func routes() {
apiGroup := newRouteGroup("/api/", apiGroup := newRouteGroup("/api/",
View("routeAPI", "/api/"), View("routeAPI", "/api/"),
View("routeAPIPhrases", "/api/phrases/"), // TODO: Be careful with exposing the panel phrases here View("routeAPIPhrases", "/api/phrases/"), // TODO: Be careful with exposing the panel phrases here
View("routes.APIMe", "/api/me/"),
View("routeJSAntispam", "/api/watches/"), View("routeJSAntispam", "/api/watches/"),
) )
addRouteGroup(apiGroup) addRouteGroup(apiGroup)

View File

@ -1,6 +1,7 @@
package routes package routes
import ( import (
"encoding/json"
"errors" "errors"
"net/http" "net/http"
"strconv" "strconv"
@ -220,3 +221,35 @@ func SitemapUsers(w http.ResponseWriter, r *http.Request) common.RouteError {
w.Write([]byte("<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n")) w.Write([]byte("<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n"))
return nil return nil
} }
type JsonMe struct {
User *common.MeUser
Site MeSite
}
// We don't want to expose too much information about the site, so we'll make this a small subset of common.site
type MeSite struct {
URL string
MaxRequestSize int
}
// APIMe returns information about the current logged-in user
// TODO: Find some way to stop intermediaries from doing compression to avoid the BREACH attack
// TODO: Decouple site settings into a different API? I'd like to avoid having too many requests, if possible, maybe we can use a different name for this?
func APIMe(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
// TODO: Don't make this too JSON dependent so that we can swap in newer more efficient formats
w.Header().Set("Content-Type", "application/json")
// We don't want an intermediary accidentally caching this
// TODO: Use this header anywhere with a user check?
w.Header().Set("Cache-Control", "private")
me := JsonMe{(&user).Me(), MeSite{common.Site.URL, common.Site.MaxRequestSize}}
jsonBytes, err := json.Marshal(me)
if err != nil {
return common.InternalErrorJS(err, w, r)
}
w.Write(jsonBytes)
return nil
}

View File

@ -11,12 +11,6 @@
{{range .Header.Scripts}} {{range .Header.Scripts}}
<script type="text/javascript" src="/static/{{.}}"></script> <script type="text/javascript" src="/static/{{.}}"></script>
{{end}} {{end}}
<script type="text/javascript">
var session = "{{.CurrentUser.Session}}";
var loggedIn = {{.CurrentUser.Loggedin}};
var siteURL = "{{.Header.Site.URL}}";
var maxRequestSize = "{{.Header.Site.MaxRequestSize}}";
</script>
<script type="text/javascript" src="/static/global.js"></script> <script type="text/javascript" src="/static/global.js"></script>
<meta name="viewport" content="width=device-width,initial-scale = 1.0, maximum-scale=1.0,user-scalable=no" /> <meta name="viewport" content="width=device-width,initial-scale = 1.0, maximum-scale=1.0,user-scalable=no" />
{{if .Header.MetaDesc}}<meta name="description" content="{{.Header.MetaDesc}}" />{{end}} {{if .Header.MetaDesc}}<meta name="description" content="{{.Header.MetaDesc}}" />{{end}}