Did a bit of refactoring on the template system.

Refactored the ReplyStore.
Added the Create method to the ProfileReplyStore.
Fixed a potential issue with the topics and replies generated by the installer.
Refactored the accumulator.
Refactored global.js

Added more tests.
This commit is contained in:
Azareal 2017-11-06 16:24:45 +00:00
parent aab949cb2d
commit 569b424ac8
16 changed files with 294 additions and 299 deletions

View File

@ -52,7 +52,6 @@ type Stmts struct {
addActivity *sql.Stmt addActivity *sql.Stmt
notifyOne *sql.Stmt notifyOne *sql.Stmt
addEmail *sql.Stmt addEmail *sql.Stmt
createProfileReply *sql.Stmt
addSubscription *sql.Stmt addSubscription *sql.Stmt
addForumPermsToForum *sql.Stmt addForumPermsToForum *sql.Stmt
addPlugin *sql.Stmt addPlugin *sql.Stmt
@ -438,13 +437,6 @@ func _gen_mssql() (err error) {
return err return err
} }
log.Print("Preparing createProfileReply statement.")
stmts.createProfileReply, err = db.Prepare("INSERT INTO [users_replies] ([uid],[content],[parsed_content],[createdAt],[createdBy],[ipaddress]) VALUES (?,?,?,GETUTCDATE(),?,?)")
if err != nil {
log.Print("Bad Query: ","INSERT INTO [users_replies] ([uid],[content],[parsed_content],[createdAt],[createdBy],[ipaddress]) VALUES (?,?,?,GETUTCDATE(),?,?)")
return err
}
log.Print("Preparing addSubscription statement.") log.Print("Preparing addSubscription statement.")
stmts.addSubscription, err = db.Prepare("INSERT INTO [activity_subscriptions] ([user],[targetID],[targetType],[level]) VALUES (?,?,?,2)") stmts.addSubscription, err = db.Prepare("INSERT INTO [activity_subscriptions] ([user],[targetID],[targetType],[level]) VALUES (?,?,?,2)")
if err != nil { if err != nil {

View File

@ -54,7 +54,6 @@ type Stmts struct {
addActivity *sql.Stmt addActivity *sql.Stmt
notifyOne *sql.Stmt notifyOne *sql.Stmt
addEmail *sql.Stmt addEmail *sql.Stmt
createProfileReply *sql.Stmt
addSubscription *sql.Stmt addSubscription *sql.Stmt
addForumPermsToForum *sql.Stmt addForumPermsToForum *sql.Stmt
addPlugin *sql.Stmt addPlugin *sql.Stmt
@ -396,12 +395,6 @@ func _gen_mysql() (err error) {
return err return err
} }
log.Print("Preparing createProfileReply statement.")
stmts.createProfileReply, err = db.Prepare("INSERT INTO `users_replies`(`uid`,`content`,`parsed_content`,`createdAt`,`createdBy`,`ipaddress`) VALUES (?,?,?,UTC_TIMESTAMP(),?,?)")
if err != nil {
return err
}
log.Print("Preparing addSubscription statement.") log.Print("Preparing addSubscription statement.")
stmts.addSubscription, err = db.Prepare("INSERT INTO `activity_subscriptions`(`user`,`targetID`,`targetType`,`level`) VALUES (?,?,?,2)") stmts.addSubscription, err = db.Prepare("INSERT INTO `activity_subscriptions`(`user`,`targetID`,`targetType`,`level`) VALUES (?,?,?,2)")
if err != nil { if err != nil {

View File

@ -559,17 +559,13 @@ func routeProfileReplyCreate(w http.ResponseWriter, r *http.Request, user User)
} }
content := html.EscapeString(preparseMessage(r.PostFormValue("reply-content"))) content := html.EscapeString(preparseMessage(r.PostFormValue("reply-content")))
_, err = stmts.createProfileReply.Exec(uid, content, parseMessage(content, 0, ""), user.ID, ipaddress) _, err = prstore.Create(uid, content, user.ID, ipaddress)
if err != nil { if err != nil {
return InternalError(err, w, r) return InternalError(err, w, r)
} }
var userName string if !users.Exists(uid) {
err = stmts.getUserName.QueryRow(uid).Scan(&userName)
if err == ErrNoRows {
return LocalError("The profile you're trying to post on doesn't exist.", w, r, user) return LocalError("The profile you're trying to post on doesn't exist.", w, r, user)
} else if err != nil {
return InternalError(err, w, r)
} }
http.Redirect(w, r, "/user/"+strconv.Itoa(uid), http.StatusSeeOther) http.Redirect(w, r, "/user/"+strconv.Itoa(uid), http.StatusSeeOther)

View File

@ -550,6 +550,8 @@ func topicStoreTest(t *testing.T) {
t.Error("The number of topics should be bigger than zero") t.Error("The number of topics should be bigger than zero")
t.Error("count", count) t.Error("count", count)
} }
// TODO: Test topic creation and retrieving that created topic plus reload and inspecting the cache
} }
func TestForumStore(t *testing.T) { func TestForumStore(t *testing.T) {
@ -737,16 +739,28 @@ func TestReplyStore(t *testing.T) {
reply, err := rstore.Get(1) reply, err := rstore.Get(1)
expectNilErr(t, err) expectNilErr(t, err)
expect(t, reply.ID == 1, fmt.Sprintf("RID #1 has the wrong ID. It should be 1 not %d", reply.ID))
expect(t, reply.ParentID == 1, fmt.Sprintf("The parent topic of RID #1 should be 1 not %d", reply.ParentID))
expect(t, reply.CreatedBy == 1, fmt.Sprintf("The creator of RID #1 should be 1 not %d", reply.CreatedBy))
expect(t, reply.Content == "A reply!", fmt.Sprintf("The contents of RID #1 should be 'A reply!' not %s", reply.Content))
expect(t, reply.IPAddress == "::1", fmt.Sprintf("The IPAddress of RID#1 should be '::1' not %s", reply.IPAddress))
if reply.ID != 1 { _, err = rstore.Get(2)
t.Errorf("RID #1 has the wrong ID. It should be 1 not %d", reply.ID) recordMustNotExist(t, err, "RID #2 shouldn't exist")
}
if reply.ParentID != 1 { // TODO: Test Create and Get
t.Errorf("The parent topic of RID #1 should be 1 not %d", reply.ParentID) //Create(tid int, content string, ipaddress string, fid int, uid int) (id int, err error)
} rid, err := rstore.Create(1, "Fofofo", "::1", 2, 1)
if reply.CreatedBy != 1 { expectNilErr(t, err)
t.Errorf("The creator of RID #1 should be 1 not %d", reply.CreatedBy) expect(t, rid == 2, fmt.Sprintf("The next reply ID should be 2 not %d", rid))
}
reply, err = rstore.Get(2)
expectNilErr(t, err)
expect(t, reply.ID == 2, fmt.Sprintf("RID #2 has the wrong ID. It should be 2 not %d", reply.ID))
expect(t, reply.ParentID == 1, fmt.Sprintf("The parent topic of RID #2 should be 1 not %d", reply.ParentID))
expect(t, reply.CreatedBy == 1, fmt.Sprintf("The creator of RID #2 should be 1 not %d", reply.CreatedBy))
expect(t, reply.Content == "Fofofo", fmt.Sprintf("The contents of RID #2 should be 'Fofofo' not %s", reply.Content))
expect(t, reply.IPAddress == "::1", fmt.Sprintf("The IPAddress of RID #2 should be '::1' not %s", reply.IPAddress))
} }
func TestProfileReplyStore(t *testing.T) { func TestProfileReplyStore(t *testing.T) {
@ -758,10 +772,30 @@ func TestProfileReplyStore(t *testing.T) {
} }
_, err := prstore.Get(-1) _, err := prstore.Get(-1)
recordMustNotExist(t, err, "RID #-1 shouldn't exist") recordMustNotExist(t, err, "PRID #-1 shouldn't exist")
_, err = prstore.Get(0) _, err = prstore.Get(0)
recordMustNotExist(t, err, "RID #0 shouldn't exist") recordMustNotExist(t, err, "PRID #0 shouldn't exist")
_, err = prstore.Get(1)
recordMustNotExist(t, err, "PRID #0 shouldn't exist")
var profileID = 1
prid, err := prstore.Create(profileID, "Haha", 1, "::1")
expect(t, err == nil, "Unable to create a profile reply")
expect(t, prid == 1, "The first profile reply should have an ID of 1")
profileReply, err := prstore.Get(1)
expect(t, err == nil, "PRID #1 should exist")
expect(t, profileReply.ID == 1, fmt.Sprintf("The profile reply should have an ID of 1 not %d", profileReply.ID))
expect(t, profileReply.ParentID == 1, fmt.Sprintf("The parent ID of the profile reply should be 1 not %d", profileReply.ParentID))
expect(t, profileReply.Content == "Haha", fmt.Sprintf("The profile reply's contents should be 'Haha' not '%s'", profileReply.Content))
expect(t, profileReply.CreatedBy == 1, fmt.Sprintf("The profile reply's creator should be 1 not %d", profileReply.CreatedBy))
expect(t, profileReply.IPAddress == "::1", fmt.Sprintf("The profile reply's IP Address should be '::1' not '%s'", profileReply.IPAddress))
//Get(id int) (*Reply, error)
//Create(profileID int, content string, createdBy int, ipaddress string) (id int, err error)
} }
func TestSlugs(t *testing.T) { func TestSlugs(t *testing.T) {

View File

@ -174,7 +174,7 @@ func TestBBCodeRender(t *testing.T) {
msg = "[rand]18446744073709551615[/rand]" // Unsigned 64-bit MAX msg = "[rand]18446744073709551615[/rand]" // Unsigned 64-bit MAX
t.Log("Testing string '" + msg + "'") t.Log("Testing string '" + msg + "'")
res = bbcodeFullParse(msg) res = bbcodeFullParse(msg)
conv, err = strconv.Atoi(res) _, err = strconv.Atoi(res)
if err != nil && res != "<span style='color: red;'>[Invalid Number]</span>[rand]18446744073709551615[/rand]" { if err != nil && res != "<span style='color: red;'>[Invalid Number]</span>[rand]18446744073709551615[/rand]" {
t.Error("Bad output:", "'"+res+"'") t.Error("Bad output:", "'"+res+"'")
t.Error("Expected a number between 0 and 18446744073709551615") t.Error("Expected a number between 0 and 18446744073709551615")
@ -182,7 +182,7 @@ func TestBBCodeRender(t *testing.T) {
msg = "[rand]170141183460469231731687303715884105727[/rand]" // Signed 128-bit MAX msg = "[rand]170141183460469231731687303715884105727[/rand]" // Signed 128-bit MAX
t.Log("Testing string '" + msg + "'") t.Log("Testing string '" + msg + "'")
res = bbcodeFullParse(msg) res = bbcodeFullParse(msg)
conv, err = strconv.Atoi(res) _, err = strconv.Atoi(res)
if err != nil && res != "<span style='color: red;'>[Invalid Number]</span>[rand]170141183460469231731687303715884105727[/rand]" { if err != nil && res != "<span style='color: red;'>[Invalid Number]</span>[rand]170141183460469231731687303715884105727[/rand]" {
t.Error("Bad output:", "'"+res+"'") t.Error("Bad output:", "'"+res+"'")
t.Error("Expected a number between 0 and 170141183460469231731687303715884105727") t.Error("Expected a number between 0 and 170141183460469231731687303715884105727")

View File

@ -10,22 +10,22 @@ var prstore ProfileReplyStore
type ProfileReplyStore interface { type ProfileReplyStore interface {
Get(id int) (*Reply, error) Get(id int) (*Reply, error)
Create(profileID int, content string, createdBy int, ipaddress string) (id int, err error)
} }
// TODO: Refactor this to stop using the global stmt store // TODO: Refactor this to stop using the global stmt store
// TODO: Add more methods to this like Create() // TODO: Add more methods to this like Create()
type SQLProfileReplyStore struct { type SQLProfileReplyStore struct {
get *sql.Stmt get *sql.Stmt
create *sql.Stmt
} }
func NewSQLProfileReplyStore() (*SQLProfileReplyStore, error) { func NewSQLProfileReplyStore() (*SQLProfileReplyStore, error) {
getUserReplyStmt, err := qgen.Builder.SimpleSelect("users_replies", "uid, content, createdBy, createdAt, lastEdit, lastEditBy, ipaddress", "rid = ?", "", "") acc := qgen.Builder.Accumulator()
if err != nil {
return nil, err
}
return &SQLProfileReplyStore{ return &SQLProfileReplyStore{
get: getUserReplyStmt, get: acc.SimpleSelect("users_replies", "uid, content, createdBy, createdAt, lastEdit, lastEditBy, ipaddress", "rid = ?", "", ""),
}, nil create: acc.SimpleInsert("users_replies", "uid, content, parsed_content, createdAt, createdBy, ipaddress", "?,?,?,UTC_TIMESTAMP(),?,?"),
}, acc.FirstError()
} }
func (store *SQLProfileReplyStore) Get(id int) (*Reply, error) { func (store *SQLProfileReplyStore) Get(id int) (*Reply, error) {
@ -33,3 +33,17 @@ func (store *SQLProfileReplyStore) Get(id int) (*Reply, error) {
err := store.get.QueryRow(id).Scan(&reply.ParentID, &reply.Content, &reply.CreatedBy, &reply.CreatedAt, &reply.LastEdit, &reply.LastEditBy, &reply.IPAddress) err := store.get.QueryRow(id).Scan(&reply.ParentID, &reply.Content, &reply.CreatedBy, &reply.CreatedAt, &reply.LastEdit, &reply.LastEditBy, &reply.IPAddress)
return &reply, err return &reply, err
} }
func (store *SQLProfileReplyStore) Create(profileID int, content string, createdBy int, ipaddress string) (id int, err error) {
res, err := store.create.Exec(profileID, content, parseMessage(content, 0, ""), createdBy, ipaddress)
if err != nil {
return 0, err
}
lastID, err := res.LastInsertId()
if err != nil {
return 0, err
}
// Should we reload the user?
return int(lastID), err
}

View File

@ -34,72 +34,72 @@ function bindToAlerts() {
} }
// TODO: Add the ability for users to dismiss alerts // TODO: Add the ability for users to dismiss alerts
function loadAlerts(menu_alerts) function loadAlerts(menuAlerts)
{ {
var alertListNode = menu_alerts.getElementsByClassName("alertList")[0]; var alertListNode = menuAlerts.getElementsByClassName("alertList")[0];
var alertCounterNode = menu_alerts.getElementsByClassName("alert_counter")[0]; var alertCounterNode = menuAlerts.getElementsByClassName("alert_counter")[0];
alertCounterNode.textContent = "0"; alertCounterNode.textContent = "0";
$.ajax({ $.ajax({
type: 'get', type: 'get',
dataType: 'json', dataType: 'json',
url:'/api/?action=get&module=alerts', url:'/api/?action=get&module=alerts',
success: function(data) { success: function(data) {
if("errmsg" in data) { if("errmsg" in data) {
alertListNode.innerHTML = "<div class='alertItem'>"+data.errmsg+"</div>"; alertListNode.innerHTML = "<div class='alertItem'>"+data.errmsg+"</div>";
return; return;
}
var alist = "";
for(var i in data.msgs) {
var msg = data.msgs[i];
var mmsg = msg.msg;
if("sub" in msg) {
for(var i = 0; i < msg.sub.length; i++) {
mmsg = mmsg.replace("\{"+i+"\}", msg.sub[i]);
//console.log("Sub #" + i + ":",msg.sub[i]);
}
}
if("avatar" in msg) {
alist += "<div class='alertItem withAvatar' style='background-image:url(\""+msg.avatar+"\");'><a class='text' data-asid='"+msg.asid+"' href=\""+msg.path+"\">"+mmsg+"</a></div>";
alertList.push("<div class='alertItem withAvatar' style='background-image:url(\""+msg.avatar+"\");'><a class='text' data-asid='"+msg.asid+"' href=\""+msg.path+"\">"+mmsg+"</a></div>");
} else {
alist += "<div class='alertItem'><a href=\""+msg.path+"\" class='text'>"+mmsg+"</a></div>";
alertList.push("<div class='alertItem'><a href=\""+msg.path+"\" class='text'>"+mmsg+"</a></div>");
}
//console.log(msg);
//console.log(mmsg);
}
if(alist == "") alist = "<div class='alertItem'>You don't have any alerts</div>";
alertListNode.innerHTML = alist;
if(data.msgCount != 0 && data.msgCount != undefined) {
alertCounterNode.textContent = data.msgCount;
menu_alerts.classList.add("has_alerts");
} else {
menu_alerts.classList.remove("has_alerts");
}
alertCount = data.msgCount;
bindToAlerts();
},
error: function(magic,theStatus,error) {
var errtxt
try {
var data = JSON.parse(magic.responseText);
if("errmsg" in data) errtxt = data.errmsg;
else errtxt = "Unable to get the alerts";
} catch(err) {
errtxt = "Unable to get the alerts";
console.log(magic.responseText);
console.log(err);
}
console.log("error: ",error);
alertListNode.innerHTML = "<div class='alertItem'>"+errtxt+"</div>";
} }
});
var alist = "";
for(var i in data.msgs) {
var msg = data.msgs[i];
var mmsg = msg.msg;
if("sub" in msg) {
for(var i = 0; i < msg.sub.length; i++) {
mmsg = mmsg.replace("\{"+i+"\}", msg.sub[i]);
//console.log("Sub #" + i + ":",msg.sub[i]);
}
}
if("avatar" in msg) {
alist += "<div class='alertItem withAvatar' style='background-image:url(\""+msg.avatar+"\");'><a class='text' data-asid='"+msg.asid+"' href=\""+msg.path+"\">"+mmsg+"</a></div>";
alertList.push("<div class='alertItem withAvatar' style='background-image:url(\""+msg.avatar+"\");'><a class='text' data-asid='"+msg.asid+"' href=\""+msg.path+"\">"+mmsg+"</a></div>");
} else {
alist += "<div class='alertItem'><a href=\""+msg.path+"\" class='text'>"+mmsg+"</a></div>";
alertList.push("<div class='alertItem'><a href=\""+msg.path+"\" class='text'>"+mmsg+"</a></div>");
}
//console.log(msg);
//console.log(mmsg);
}
if(alist == "") alist = "<div class='alertItem'>You don't have any alerts</div>";
alertListNode.innerHTML = alist;
if(data.msgCount != 0 && data.msgCount != undefined) {
alertCounterNode.textContent = data.msgCount;
menuAlerts.classList.add("has_alerts");
} else {
menuAlerts.classList.remove("has_alerts");
}
alertCount = data.msgCount;
bindToAlerts();
},
error: function(magic,theStatus,error) {
let errtxt
try {
var data = JSON.parse(magic.responseText);
if("errmsg" in data) errtxt = data.errmsg;
else errtxt = "Unable to get the alerts";
} catch(err) {
errtxt = "Unable to get the alerts";
console.log(magic.responseText);
console.log(err);
}
console.log("error", error);
alertListNode.innerHTML = "<div class='alertItem'>"+errtxt+"</div>";
}
});
} }
function SplitN(data,ch,n) { function SplitN(data,ch,n) {
@ -121,84 +121,86 @@ function SplitN(data,ch,n) {
return out; return out;
} }
$(document).ready(function(){ function runWebSockets() {
if(window["WebSocket"]) { if(window.location.protocol == "https:")
if(window.location.protocol == "https:") conn = new WebSocket("wss://" + document.location.host + "/ws/");
conn = new WebSocket("wss://" + document.location.host + "/ws/"); else conn = new WebSocket("ws://" + document.location.host + "/ws/");
else conn = new WebSocket("ws://" + document.location.host + "/ws/");
conn.onopen = function() { conn.onopen = function() {
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
Notification.requestPermission(); Notification.requestPermission();
} }
conn.onclose = function() { conn.onclose = function() {
conn = false; conn = false;
console.log("The WebSockets connection was closed"); console.log("The WebSockets connection was closed");
} }
conn.onmessage = function(event) { conn.onmessage = function(event) {
//console.log("WS_Message:", event.data); //console.log("WS_Message:", event.data);
if(event.data[0] == "{") { if(event.data[0] == "{") {
try { try {
var data = JSON.parse(event.data); var data = JSON.parse(event.data);
} catch(err) { } catch(err) {
console.log(err); console.log(err);
}
if ("msg" in data) {
var msg = data.msg
if("sub" in data)
for(var i = 0; i < data.sub.length; i++)
msg = msg.replace("\{"+i+"\}", data.sub[i]);
if("avatar" in data) alertList.push("<div class='alertItem withAvatar' style='background-image:url(\""+data.avatar+"\");'><a class='text' data-asid='"+data.asid+"' href=\""+data.path+"\">"+msg+"</a></div>");
else alertList.push("<div class='alertItem'><a href=\""+data.path+"\" class='text'>"+msg+"</a></div>");
if(alertList.length > 8) alertList.shift();
//console.log("post alertList",alertList);
alertCount++;
var alist = ""
for (var i = 0; i < alertList.length; i++) alist += alertList[i];
//console.log(alist);
// TODO: Add support for other alert feeds like PM Alerts
var general_alerts = document.getElementById("general_alerts");
var alertListNode = general_alerts.getElementsByClassName("alertList")[0];
var alertCounterNode = general_alerts.getElementsByClassName("alert_counter")[0];
alertListNode.innerHTML = alist;
alertCounterNode.textContent = alertCount;
// TODO: Add some sort of notification queue to avoid flooding the end-user with notices?
// TODO: Use the site name instead of "Something Happened"
if(Notification.permission === "granted") {
var n = new Notification("Something Happened",{
body: msg,
icon: data.avatar,
});
setTimeout(n.close.bind(n), 8000);
}
bindToAlerts();
}
} }
var messages = event.data.split('\r'); if ("msg" in data) {
for(var i = 0; i < messages.length; i++) { var msg = data.msg
//console.log("Message: ",messages[i]); if("sub" in data)
if(messages[i].startsWith("set ")) { for(var i = 0; i < data.sub.length; i++)
//msgblocks = messages[i].split(' ',3); msg = msg.replace("\{"+i+"\}", data.sub[i]);
let msgblocks = SplitN(messages[i]," ",3);
if(msgblocks.length < 3) continue; if("avatar" in data) alertList.push("<div class='alertItem withAvatar' style='background-image:url(\""+data.avatar+"\");'><a class='text' data-asid='"+data.asid+"' href=\""+data.path+"\">"+msg+"</a></div>");
document.querySelector(msgblocks[1]).innerHTML = msgblocks[2]; else alertList.push("<div class='alertItem'><a href=\""+data.path+"\" class='text'>"+msg+"</a></div>");
} else if(messages[i].startsWith("set-class ")) { if(alertList.length > 8) alertList.shift();
let msgblocks = SplitN(messages[i]," ",3); //console.log("post alertList",alertList);
if(msgblocks.length < 3) continue; alertCount++;
document.querySelector(msgblocks[1]).className = msgblocks[2];
var alist = ""
for (var i = 0; i < alertList.length; i++) alist += alertList[i];
//console.log(alist);
// TODO: Add support for other alert feeds like PM Alerts
var generalAlerts = document.getElementById("general_alerts");
var alertListNode = generalAlerts.getElementsByClassName("alertList")[0];
var alertCounterNode = generalAlerts.getElementsByClassName("alert_counter")[0];
alertListNode.innerHTML = alist;
alertCounterNode.textContent = alertCount;
// TODO: Add some sort of notification queue to avoid flooding the end-user with notices?
// TODO: Use the site name instead of "Something Happened"
if(Notification.permission === "granted") {
var n = new Notification("Something Happened",{
body: msg,
icon: data.avatar,
});
setTimeout(n.close.bind(n), 8000);
} }
bindToAlerts();
}
}
var messages = event.data.split('\r');
for(var i = 0; i < messages.length; i++) {
//console.log("Message: ",messages[i]);
if(messages[i].startsWith("set ")) {
//msgblocks = messages[i].split(' ',3);
let msgblocks = SplitN(messages[i]," ",3);
if(msgblocks.length < 3) continue;
document.querySelector(msgblocks[1]).innerHTML = msgblocks[2];
} else if(messages[i].startsWith("set-class ")) {
let msgblocks = SplitN(messages[i]," ",3);
if(msgblocks.length < 3) continue;
document.querySelector(msgblocks[1]).className = msgblocks[2];
} }
} }
} }
}
$(document).ready(function(){
if(window["WebSocket"]) runWebSockets();
else conn = false; else conn = false;
$(".open_edit").click(function(event){ $(".open_edit").click(function(event){

View File

@ -115,9 +115,7 @@ func (build *accBuilder) Purge(table string) *sql.Stmt {
return build.prepare(build.adapter.Purge("_builder", table)) return build.prepare(build.adapter.Purge("_builder", table))
} }
// These ones support transactions func (build *accBuilder) prepareTx(tx *sql.Tx, res string, err error) (stmt *sql.Stmt) {
func (build *accBuilder) SimpleSelectTx(tx *sql.Tx, table string, columns string, where string, orderby string, limit string) (stmt *sql.Stmt) {
res, err := build.adapter.SimpleSelect("_builder", table, columns, where, orderby, limit)
if err != nil { if err != nil {
build.recordError(err) build.recordError(err)
return nil return nil
@ -127,70 +125,40 @@ func (build *accBuilder) SimpleSelectTx(tx *sql.Tx, table string, columns string
return stmt return stmt
} }
// These ones support transactions
func (build *accBuilder) SimpleSelectTx(tx *sql.Tx, table string, columns string, where string, orderby string, limit string) (stmt *sql.Stmt) {
res, err := build.adapter.SimpleSelect("_builder", table, columns, where, orderby, limit)
return build.prepareTx(tx, res, err)
}
func (build *accBuilder) SimpleCountTx(tx *sql.Tx, table string, where string, limit string) (stmt *sql.Stmt) { func (build *accBuilder) SimpleCountTx(tx *sql.Tx, table string, where string, limit string) (stmt *sql.Stmt) {
res, err := build.adapter.SimpleCount("_builder", table, where, limit) res, err := build.adapter.SimpleCount("_builder", table, where, limit)
if err != nil { return build.prepareTx(tx, res, err)
build.recordError(err)
return nil
}
stmt, err = tx.Prepare(res)
build.recordError(err)
return stmt
} }
func (build *accBuilder) SimpleLeftJoinTx(tx *sql.Tx, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (stmt *sql.Stmt) { func (build *accBuilder) SimpleLeftJoinTx(tx *sql.Tx, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (stmt *sql.Stmt) {
res, err := build.adapter.SimpleLeftJoin("_builder", table1, table2, columns, joiners, where, orderby, limit) res, err := build.adapter.SimpleLeftJoin("_builder", table1, table2, columns, joiners, where, orderby, limit)
if err != nil { return build.prepareTx(tx, res, err)
build.recordError(err)
return nil
}
stmt, err = tx.Prepare(res)
build.recordError(err)
return stmt
} }
func (build *accBuilder) SimpleInnerJoinTx(tx *sql.Tx, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (stmt *sql.Stmt) { func (build *accBuilder) SimpleInnerJoinTx(tx *sql.Tx, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (stmt *sql.Stmt) {
res, err := build.adapter.SimpleInnerJoin("_builder", table1, table2, columns, joiners, where, orderby, limit) res, err := build.adapter.SimpleInnerJoin("_builder", table1, table2, columns, joiners, where, orderby, limit)
if err != nil { return build.prepareTx(tx, res, err)
build.recordError(err)
return nil
}
stmt, err = tx.Prepare(res)
build.recordError(err)
return stmt
} }
func (build *accBuilder) CreateTableTx(tx *sql.Tx, table string, charset string, collation string, columns []DB_Table_Column, keys []DB_Table_Key) (stmt *sql.Stmt) { func (build *accBuilder) CreateTableTx(tx *sql.Tx, table string, charset string, collation string, columns []DB_Table_Column, keys []DB_Table_Key) (stmt *sql.Stmt) {
res, err := build.adapter.CreateTable("_builder", table, charset, collation, columns, keys) res, err := build.adapter.CreateTable("_builder", table, charset, collation, columns, keys)
if err != nil { return build.prepareTx(tx, res, err)
build.recordError(err)
return nil
}
stmt, err = tx.Prepare(res)
build.recordError(err)
return stmt
} }
func (build *accBuilder) SimpleInsertTx(tx *sql.Tx, table string, columns string, fields string) (stmt *sql.Stmt) { func (build *accBuilder) SimpleInsertTx(tx *sql.Tx, table string, columns string, fields string) (stmt *sql.Stmt) {
res, err := build.adapter.SimpleInsert("_builder", table, columns, fields) res, err := build.adapter.SimpleInsert("_builder", table, columns, fields)
if err != nil { return build.prepareTx(tx, res, err)
build.recordError(err)
return nil
}
stmt, err = tx.Prepare(res)
build.recordError(err)
return stmt
} }
func (build *accBuilder) SimpleInsertSelectTx(tx *sql.Tx, ins DB_Insert, sel DB_Select) (stmt *sql.Stmt) { func (build *accBuilder) SimpleInsertSelectTx(tx *sql.Tx, ins DB_Insert, sel DB_Select) (stmt *sql.Stmt) {
res, err := build.adapter.SimpleInsertSelect("_builder", ins, sel) res, err := build.adapter.SimpleInsertSelect("_builder", ins, sel)
if err != nil { return build.prepareTx(tx, res, err)
build.recordError(err)
return nil
}
stmt, err = tx.Prepare(res)
build.recordError(err)
return stmt
} }
func (build *accBuilder) SimpleInsertLeftJoinTx(tx *sql.Tx, ins DB_Insert, sel DB_Join) (stmt *sql.Stmt) { func (build *accBuilder) SimpleInsertLeftJoinTx(tx *sql.Tx, ins DB_Insert, sel DB_Join) (stmt *sql.Stmt) {
@ -206,45 +174,21 @@ func (build *accBuilder) SimpleInsertLeftJoinTx(tx *sql.Tx, ins DB_Insert, sel D
func (build *accBuilder) SimpleInsertInnerJoinTx(tx *sql.Tx, ins DB_Insert, sel DB_Join) (stmt *sql.Stmt) { func (build *accBuilder) SimpleInsertInnerJoinTx(tx *sql.Tx, ins DB_Insert, sel DB_Join) (stmt *sql.Stmt) {
res, err := build.adapter.SimpleInsertInnerJoin("_builder", ins, sel) res, err := build.adapter.SimpleInsertInnerJoin("_builder", ins, sel)
if err != nil { return build.prepareTx(tx, res, err)
build.recordError(err)
return nil
}
stmt, err = tx.Prepare(res)
build.recordError(err)
return stmt
} }
func (build *accBuilder) SimpleUpdateTx(tx *sql.Tx, table string, set string, where string) (stmt *sql.Stmt) { func (build *accBuilder) SimpleUpdateTx(tx *sql.Tx, table string, set string, where string) (stmt *sql.Stmt) {
res, err := build.adapter.SimpleUpdate("_builder", table, set, where) res, err := build.adapter.SimpleUpdate("_builder", table, set, where)
if err != nil { return build.prepareTx(tx, res, err)
build.recordError(err)
return nil
}
stmt, err = tx.Prepare(res)
build.recordError(err)
return stmt
} }
func (build *accBuilder) SimpleDeleteTx(tx *sql.Tx, table string, where string) (stmt *sql.Stmt) { func (build *accBuilder) SimpleDeleteTx(tx *sql.Tx, table string, where string) (stmt *sql.Stmt) {
res, err := build.adapter.SimpleDelete("_builder", table, where) res, err := build.adapter.SimpleDelete("_builder", table, where)
if err != nil { return build.prepareTx(tx, res, err)
build.recordError(err)
return nil
}
stmt, err = tx.Prepare(res)
build.recordError(err)
return stmt
} }
// I don't know why you need this, but here it is x.x // I don't know why you need this, but here it is x.x
func (build *accBuilder) PurgeTx(tx *sql.Tx, table string) (stmt *sql.Stmt) { func (build *accBuilder) PurgeTx(tx *sql.Tx, table string) (stmt *sql.Stmt) {
res, err := build.adapter.Purge("_builder", table) res, err := build.adapter.Purge("_builder", table)
if err != nil { return build.prepareTx(tx, res, err)
build.recordError(err)
return nil
}
stmt, err = tx.Prepare(res)
build.recordError(err)
return stmt
} }

View File

@ -212,9 +212,9 @@ func seedTables(adapter qgen.DB_Adapter) error {
// //
qgen.Install.SimpleInsert("topics", "title, content, parsed_content, createdAt, lastReplyAt, lastReplyBy, createdBy, parentID", "'Test Topic','A topic automatically generated by the software.','A topic automatically generated by the software.',UTC_TIMESTAMP(),UTC_TIMESTAMP(),1,1,2") qgen.Install.SimpleInsert("topics", "title, content, parsed_content, createdAt, lastReplyAt, lastReplyBy, createdBy, parentID, ipaddress", "'Test Topic','A topic automatically generated by the software.','A topic automatically generated by the software.',UTC_TIMESTAMP(),UTC_TIMESTAMP(),1,1,2,'::1'")
qgen.Install.SimpleInsert("replies", "tid, content, parsed_content, createdAt, createdBy, lastUpdated, lastEdit, lastEditBy", "1,'A reply!','A reply!',UTC_TIMESTAMP(),1,UTC_TIMESTAMP(),0,0") qgen.Install.SimpleInsert("replies", "tid, content, parsed_content, createdAt, createdBy, lastUpdated, lastEdit, lastEditBy, ipaddress", "1,'A reply!','A reply!',UTC_TIMESTAMP(),1,UTC_TIMESTAMP(),0,0,'::1'")
return nil return nil
} }
@ -328,8 +328,6 @@ func writeInserts(adapter qgen.DB_Adapter) error {
adapter.SimpleInsert("addEmail", "emails", "email, uid, validated, token", "?,?,?,?") adapter.SimpleInsert("addEmail", "emails", "email, uid, validated, token", "?,?,?,?")
adapter.SimpleInsert("createProfileReply", "users_replies", "uid, content, parsed_content, createdAt, createdBy, ipaddress", "?,?,?,UTC_TIMESTAMP(),?,?")
adapter.SimpleInsert("addSubscription", "activity_subscriptions", "user, targetID, targetType, level", "?,?,?,2") adapter.SimpleInsert("addSubscription", "activity_subscriptions", "user, targetID, targetType, level", "?,?,?,2")
adapter.SimpleInsert("addForumPermsToForum", "forums_permissions", "gid,fid,preset,permissions", "?,?,?,?") adapter.SimpleInsert("addForumPermsToForum", "forums_permissions", "gid,fid,preset,permissions", "?,?,?,?")

View File

@ -16,18 +16,11 @@ type SQLReplyStore struct {
} }
func NewSQLReplyStore() (*SQLReplyStore, error) { func NewSQLReplyStore() (*SQLReplyStore, error) {
getReplyStmt, err := qgen.Builder.SimpleSelect("replies", "tid, content, createdBy, createdAt, lastEdit, lastEditBy, ipaddress, likeCount", "rid = ?", "", "") acc := qgen.Builder.Accumulator()
if err != nil {
return nil, err
}
createReplyStmt, err := qgen.Builder.SimpleInsert("replies", "tid, content, parsed_content, createdAt, lastUpdated, ipaddress, words, createdBy", "?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),?,?,?")
if err != nil {
return nil, err
}
return &SQLReplyStore{ return &SQLReplyStore{
get: getReplyStmt, get: acc.SimpleSelect("replies", "tid, content, createdBy, createdAt, lastEdit, lastEditBy, ipaddress, likeCount", "rid = ?", "", ""),
create: createReplyStmt, create: acc.SimpleInsert("replies", "tid, content, parsed_content, createdAt, lastUpdated, ipaddress, words, createdBy", "?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),?,?,?"),
}, nil }, acc.FirstError()
} }
func (store *SQLReplyStore) Get(id int) (*Reply, error) { func (store *SQLReplyStore) Get(id int) (*Reply, error) {

View File

@ -5,16 +5,48 @@ package main
import ( import (
"errors" "errors"
"log"
"regexp" "regexp"
"text/template/parse"
) )
var tagFinder *regexp.Regexp var tagFinder *regexp.Regexp
var limeFuncMap = map[string]interface{}{
"and": "&&",
"not": "!",
"or": "||",
"eq": true,
"ge": true,
"gt": true,
"le": true,
"lt": true,
"ne": true,
"add": true,
"subtract": true,
"multiply": true,
"divide": true,
}
func init() { func init() {
tagFinder = regexp.MustCompile(`(?s)\{\{(.*)\}\}`) tagFinder = regexp.MustCompile(`(?s)\{\{(.*)\}\}`)
} }
func mangoParse(tmpl string) error {
tree := parse.New(name, funcMap)
var treeSet = make(map[string]*parse.Tree)
tree, err = tree.Parse(content, "{{", "}}", treeSet, limeFuncMap)
if err != nil {
return err
}
treeLength := len(tree.Root.Nodes)
log.Print("treeLength", treeLength)
return nil
}
func icecreamSoup(tmpl string) error { func icecreamSoup(tmpl string) error {
if config.MinifyTemplates {
tmpl = minify(tmpl)
}
tagIndices := tagFinder.FindAllStringIndex(tmpl, -1) tagIndices := tagFinder.FindAllStringIndex(tmpl, -1)
if tagIndices != nil && len(tagIndices) > 0 { if tagIndices != nil && len(tagIndices) > 0 {

View File

@ -25,5 +25,5 @@ INSERT INTO [forums_permissions] ([gid],[fid],[permissions]) VALUES (3,2,'{"View
INSERT INTO [forums_permissions] ([gid],[fid],[permissions]) VALUES (4,2,'{"ViewTopic":true}'); INSERT INTO [forums_permissions] ([gid],[fid],[permissions]) VALUES (4,2,'{"ViewTopic":true}');
INSERT INTO [forums_permissions] ([gid],[fid],[permissions]) VALUES (5,2,'{"ViewTopic":true}'); INSERT INTO [forums_permissions] ([gid],[fid],[permissions]) VALUES (5,2,'{"ViewTopic":true}');
INSERT INTO [forums_permissions] ([gid],[fid],[permissions]) VALUES (6,2,'{"ViewTopic":true}'); INSERT INTO [forums_permissions] ([gid],[fid],[permissions]) VALUES (6,2,'{"ViewTopic":true}');
INSERT INTO [topics] ([title],[content],[parsed_content],[createdAt],[lastReplyAt],[lastReplyBy],[createdBy],[parentID]) VALUES ('TestTopic','Atopicautomaticallygeneratedbythesoftware.','Atopicautomaticallygeneratedbythesoftware.',GETUTCDATE(),GETUTCDATE(),1,1,2); INSERT INTO [topics] ([title],[content],[parsed_content],[createdAt],[lastReplyAt],[lastReplyBy],[createdBy],[parentID],[ipaddress]) VALUES ('TestTopic','Atopicautomaticallygeneratedbythesoftware.','Atopicautomaticallygeneratedbythesoftware.',GETUTCDATE(),GETUTCDATE(),1,1,2,'::1');
INSERT INTO [replies] ([tid],[content],[parsed_content],[createdAt],[createdBy],[lastUpdated],[lastEdit],[lastEditBy]) VALUES (1,'Areply!','Areply!',GETUTCDATE(),1,GETUTCDATE(),0,0); INSERT INTO [replies] ([tid],[content],[parsed_content],[createdAt],[createdBy],[lastUpdated],[lastEdit],[lastEditBy],[ipaddress]) VALUES (1,'Areply!','Areply!',GETUTCDATE(),1,GETUTCDATE(),0,0,'::1');

View File

@ -25,5 +25,5 @@ INSERT INTO `forums_permissions`(`gid`,`fid`,`permissions`) VALUES (3,2,'{"ViewT
INSERT INTO `forums_permissions`(`gid`,`fid`,`permissions`) VALUES (4,2,'{"ViewTopic":true}'); INSERT INTO `forums_permissions`(`gid`,`fid`,`permissions`) VALUES (4,2,'{"ViewTopic":true}');
INSERT INTO `forums_permissions`(`gid`,`fid`,`permissions`) VALUES (5,2,'{"ViewTopic":true}'); INSERT INTO `forums_permissions`(`gid`,`fid`,`permissions`) VALUES (5,2,'{"ViewTopic":true}');
INSERT INTO `forums_permissions`(`gid`,`fid`,`permissions`) VALUES (6,2,'{"ViewTopic":true}'); INSERT INTO `forums_permissions`(`gid`,`fid`,`permissions`) VALUES (6,2,'{"ViewTopic":true}');
INSERT INTO `topics`(`title`,`content`,`parsed_content`,`createdAt`,`lastReplyAt`,`lastReplyBy`,`createdBy`,`parentID`) VALUES ('TestTopic','Atopicautomaticallygeneratedbythesoftware.','Atopicautomaticallygeneratedbythesoftware.',UTC_TIMESTAMP(),UTC_TIMESTAMP(),1,1,2); INSERT INTO `topics`(`title`,`content`,`parsed_content`,`createdAt`,`lastReplyAt`,`lastReplyBy`,`createdBy`,`parentID`,`ipaddress`) VALUES ('TestTopic','Atopicautomaticallygeneratedbythesoftware.','Atopicautomaticallygeneratedbythesoftware.',UTC_TIMESTAMP(),UTC_TIMESTAMP(),1,1,2,'::1');
INSERT INTO `replies`(`tid`,`content`,`parsed_content`,`createdAt`,`createdBy`,`lastUpdated`,`lastEdit`,`lastEditBy`) VALUES (1,'Areply!','Areply!',UTC_TIMESTAMP(),1,UTC_TIMESTAMP(),0,0); INSERT INTO `replies`(`tid`,`content`,`parsed_content`,`createdAt`,`createdBy`,`lastUpdated`,`lastEdit`,`lastEditBy`,`ipaddress`) VALUES (1,'Areply!','Areply!',UTC_TIMESTAMP(),1,UTC_TIMESTAMP(),0,0,'::1');

View File

@ -498,6 +498,8 @@ var topic_alt_104 = []byte(`
`) `)
var profile_0 = []byte(` var profile_0 = []byte(`
<div id="profile_container">
<div id="profile_left_lane" class="colstack_left"> <div id="profile_left_lane" class="colstack_left">
<!--<header class="colstack_item colstack_head rowhead"> <!--<header class="colstack_item colstack_head rowhead">
<div class="rowitem"><h1>Profile</h1></div> <div class="rowitem"><h1>Profile</h1></div>
@ -639,6 +641,8 @@ var profile_42 = []byte(`' type="hidden" />
var profile_43 = []byte(` var profile_43 = []byte(`
</div> </div>
</div>
`) `)
var profile_44 = []byte(` var profile_44 = []byte(`
<script type="text/javascript"> <script type="text/javascript">

View File

@ -121,24 +121,7 @@ func (c *CTemplateSet) compileTemplate(name string, dir string, expects string,
c.FragmentCursor = make(map[string]int) c.FragmentCursor = make(map[string]int)
c.FragmentCursor[fname] = 0 c.FragmentCursor[fname] = 0
subtree := c.tlist[fname] out += c.rootIterate(c.tlist[fname], varholder, holdreflect, fname)
if dev.TemplateDebug {
fmt.Println(subtree.Root)
}
treeLength := len(subtree.Root.Nodes)
for index, node := range subtree.Root.Nodes {
if dev.TemplateDebug {
fmt.Println("Node: " + node.String())
}
c.previousNode = c.currentNode
c.currentNode = node.Type()
if treeLength != (index + 1) {
c.nextNode = subtree.Root.Nodes[index+1].Type()
}
out += c.compileSwitch(varholder, holdreflect, fname, node)
}
var importList string var importList string
if c.doImports { if c.doImports {
@ -178,6 +161,26 @@ w.Write([]byte(`, " + ", -1)
return fout, nil return fout, nil
} }
func (c *CTemplateSet) rootIterate(tree *parse.Tree, varholder string, holdreflect reflect.Value, fname string) (out string) {
if dev.TemplateDebug {
fmt.Println(tree.Root)
}
treeLength := len(tree.Root.Nodes)
for index, node := range tree.Root.Nodes {
if dev.TemplateDebug {
fmt.Println("Node: ", node.String())
}
c.previousNode = c.currentNode
c.currentNode = node.Type()
if treeLength != (index + 1) {
c.nextNode = tree.Root.Nodes[index+1].Type()
}
out += c.compileSwitch(varholder, holdreflect, fname, node)
}
return out
}
func (c *CTemplateSet) compileSwitch(varholder string, holdreflect reflect.Value, templateName string, node interface{}) (out string) { func (c *CTemplateSet) compileSwitch(varholder string, holdreflect reflect.Value, templateName string, node interface{}) (out string) {
if dev.TemplateDebug { if dev.TemplateDebug {
fmt.Println("in compileSwitch") fmt.Println("in compileSwitch")
@ -449,7 +452,6 @@ func (c *CTemplateSet) compileVarswitch(varholder string, holdreflect reflect.Va
fmt.Println("Chain Node:", n.Node) fmt.Println("Chain Node:", n.Node)
fmt.Println("Chain Node Args:", node.Args) fmt.Println("Chain Node Args:", node.Args)
} }
break
case *parse.IdentifierNode: case *parse.IdentifierNode:
if dev.TemplateDebug { if dev.TemplateDebug {
fmt.Println("Identifier Node:", node) fmt.Println("Identifier Node:", node)
@ -957,7 +959,6 @@ func (c *CTemplateSet) compileSubtemplate(pvarholder string, pholdreflect reflec
case *parse.DotNode: case *parse.DotNode:
varholder = pvarholder varholder = pvarholder
holdreflect = pholdreflect holdreflect = pholdreflect
break
case *parse.NilNode: case *parse.NilNode:
panic("Nil is not a command x.x") panic("Nil is not a command x.x")
default: default:
@ -995,19 +996,7 @@ func (c *CTemplateSet) compileSubtemplate(pvarholder string, pholdreflect reflec
c.localVars[fname]["."] = VarItemReflect{".", varholder, holdreflect} c.localVars[fname]["."] = VarItemReflect{".", varholder, holdreflect}
c.FragmentCursor[fname] = 0 c.FragmentCursor[fname] = 0
treeLength := len(subtree.Root.Nodes) out += c.rootIterate(subtree, varholder, holdreflect, fname)
for index, node := range subtree.Root.Nodes {
if dev.TemplateDebug {
fmt.Println("Node:", node.String())
}
c.previousNode = c.currentNode
c.currentNode = node.Type()
if treeLength != (index + 1) {
c.nextNode = subtree.Root.Nodes[index+1].Type()
}
out += c.compileSwitch(varholder, holdreflect, fname, node)
}
return out return out
} }

View File

@ -1,5 +1,7 @@
{{template "header.html" . }} {{template "header.html" . }}
<div id="profile_container">
<div id="profile_left_lane" class="colstack_left"> <div id="profile_left_lane" class="colstack_left">
<!--<header class="colstack_item colstack_head rowhead"> <!--<header class="colstack_item colstack_head rowhead">
<div class="rowitem"><h1>Profile</h1></div> <div class="rowitem"><h1>Profile</h1></div>
@ -98,6 +100,8 @@
{{end}} {{end}}
</div> </div>
</div>
{{/** Quick subpage switcher **/}} {{/** Quick subpage switcher **/}}
{{/** TODO: Stop inlining this **/}} {{/** TODO: Stop inlining this **/}}
<script type="text/javascript"> <script type="text/javascript">