You can now manage the attachments for an opening post by hitting edit.
The update system now uses the database as the source of truth for the last version rather than lastSchema.json Refactored several structs and bits of code, so we can avoid allocations for contexts where we never use a relative time. Clicking on the relative times on the topic list and the forum page should now take you to the post on the last page rather than just the last page. Added the reltime template function. Fixed some obsolete bits of code. Fixed some spelling mistakes. Fixed a bug where MaxBytesReader was capped at the maxFileSize rather than r.ContentLength. All of the client side templates should work again now. Shortened some statement names to save some horizontal space. accUpdateBuilder and SimpleUpdate now use updatePrebuilder behind the scenes to simplify things. Renamed selectItem to builder in AccSelectBuilder. Added a Total() method to accCountBuilder to reduce the amount of boilerplate used for row count queries. The "_builder" strings have been replaced with empty strings to help save memory, to make things slightly faster and to open the door to removing the query name in many contexts down the line. Added the open_edit and close_edit client hooks. Removed many query name checks. Split the attachment logic into separate functions and de-duplicated it between replies and topics. Improved the UI for editing topics in Nox. Used type aliases to reduce the amount of boilerplate in tables.go and patches.go Reduced the amount of boilerplate in the action post logic. Eliminated a map and a slice in the topic page for users who haven't given any likes. E.g. Guests. Fixed some long out-dated parts of the update instructions. Updated the update instructions to remove mention of the obsolete lastSchema.json Fixed a bug in init.js where /api/me was being loaded for guests. Added the MiniTopicGet, GlobalCount and CountInTopic methods to AttachmentStore. Added the MiniAttachment struct. Split the mod floaters out into their own template to reduce duplication. Removed a couple of redundant ParseForms. Added the common.skipUntilIfExistsOrLine function. Added the NotFoundJS and NotFoundJSQ functions. Added the lastReplyID and attachCount columns to the topics table.
This commit is contained in:
parent
548227104c
commit
3465e4c08f
|
@ -21,7 +21,6 @@ out/*
|
||||||
*.log
|
*.log
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.vscode/launch.json
|
.vscode/launch.json
|
||||||
schema/lastSchema.json
|
|
||||||
config/config.go
|
config/config.go
|
||||||
QueryGen
|
QueryGen
|
||||||
RouterGen
|
RouterGen
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -2,28 +2,128 @@ package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/Azareal/Gosora/query_gen"
|
"github.com/Azareal/Gosora/query_gen"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Attachments AttachmentStore
|
var Attachments AttachmentStore
|
||||||
|
|
||||||
|
type MiniAttachment struct {
|
||||||
|
ID int
|
||||||
|
SectionID int
|
||||||
|
OriginID int
|
||||||
|
UploadedBy int
|
||||||
|
Path string
|
||||||
|
|
||||||
|
Image bool
|
||||||
|
Ext string
|
||||||
|
}
|
||||||
|
|
||||||
type AttachmentStore interface {
|
type AttachmentStore interface {
|
||||||
Add(sectionID int, sectionTable string, originID int, originTable string, uploadedBy int, path string) error
|
Get(id int) (*MiniAttachment, error)
|
||||||
|
MiniTopicGet(id int) (alist []*MiniAttachment, err error)
|
||||||
|
Add(sectionID int, sectionTable string, originID int, originTable string, uploadedBy int, path string) (int, error)
|
||||||
|
GlobalCount() int
|
||||||
|
CountInTopic(tid int) int
|
||||||
|
CountInPath(path string) int
|
||||||
|
Delete(aid int) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type DefaultAttachmentStore struct {
|
type DefaultAttachmentStore struct {
|
||||||
add *sql.Stmt
|
get *sql.Stmt
|
||||||
|
getByTopic *sql.Stmt
|
||||||
|
add *sql.Stmt
|
||||||
|
count *sql.Stmt
|
||||||
|
countInTopic *sql.Stmt
|
||||||
|
countInPath *sql.Stmt
|
||||||
|
delete *sql.Stmt
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDefaultAttachmentStore() (*DefaultAttachmentStore, error) {
|
func NewDefaultAttachmentStore() (*DefaultAttachmentStore, error) {
|
||||||
acc := qgen.NewAcc()
|
acc := qgen.NewAcc()
|
||||||
return &DefaultAttachmentStore{
|
return &DefaultAttachmentStore{
|
||||||
add: acc.Insert("attachments").Columns("sectionID, sectionTable, originID, originTable, uploadedBy, path").Fields("?,?,?,?,?,?").Prepare(),
|
get: acc.Select("attachments").Columns("originID, sectionID, uploadedBy, path").Where("attachID = ?").Prepare(),
|
||||||
|
getByTopic: acc.Select("attachments").Columns("attachID, sectionID, uploadedBy, path").Where("originTable = 'topics' AND originID = ?").Prepare(),
|
||||||
|
add: acc.Insert("attachments").Columns("sectionID, sectionTable, originID, originTable, uploadedBy, path").Fields("?,?,?,?,?,?").Prepare(),
|
||||||
|
count: acc.Count("attachments").Prepare(),
|
||||||
|
countInTopic: acc.Count("attachments").Where("originTable = 'topics' and originID = ?").Prepare(),
|
||||||
|
countInPath: acc.Count("attachments").Where("path = ?").Prepare(),
|
||||||
|
delete: acc.Delete("attachments").Where("attachID = ?").Prepare(),
|
||||||
}, acc.FirstError()
|
}, acc.FirstError()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (store *DefaultAttachmentStore) Add(sectionID int, sectionTable string, originID int, originTable string, uploadedBy int, path string) error {
|
// TODO: Make this more generic so we can use it for reply attachments too
|
||||||
_, err := store.add.Exec(sectionID, sectionTable, originID, originTable, uploadedBy, path)
|
func (store *DefaultAttachmentStore) MiniTopicGet(id int) (alist []*MiniAttachment, err error) {
|
||||||
|
rows, err := store.getByTopic.Query(id)
|
||||||
|
defer rows.Close()
|
||||||
|
for rows.Next() {
|
||||||
|
attach := &MiniAttachment{OriginID: id}
|
||||||
|
err := rows.Scan(&attach.ID, &attach.SectionID, &attach.UploadedBy, &attach.Path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
extarr := strings.Split(attach.Path, ".")
|
||||||
|
if len(extarr) < 2 {
|
||||||
|
return nil, errors.New("corrupt attachment path")
|
||||||
|
}
|
||||||
|
attach.Ext = extarr[len(extarr)-1]
|
||||||
|
attach.Image = ImageFileExts.Contains(attach.Ext)
|
||||||
|
alist = append(alist, attach)
|
||||||
|
}
|
||||||
|
return alist, rows.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (store *DefaultAttachmentStore) Get(id int) (*MiniAttachment, error) {
|
||||||
|
attach := &MiniAttachment{ID: id}
|
||||||
|
err := store.get.QueryRow(id).Scan(&attach.OriginID, &attach.SectionID, &attach.UploadedBy, &attach.Path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
extarr := strings.Split(attach.Path, ".")
|
||||||
|
if len(extarr) < 2 {
|
||||||
|
return nil, errors.New("corrupt attachment path")
|
||||||
|
}
|
||||||
|
attach.Ext = extarr[len(extarr)-1]
|
||||||
|
attach.Image = ImageFileExts.Contains(attach.Ext)
|
||||||
|
return attach, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (store *DefaultAttachmentStore) Add(sectionID int, sectionTable string, originID int, originTable string, uploadedBy int, path string) (int, error) {
|
||||||
|
res, err := store.add.Exec(sectionID, sectionTable, originID, originTable, uploadedBy, path)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
lid, err := res.LastInsertId()
|
||||||
|
return int(lid), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (store *DefaultAttachmentStore) GlobalCount() (count int) {
|
||||||
|
err := store.count.QueryRow().Scan(&count)
|
||||||
|
if err != nil {
|
||||||
|
LogError(err)
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
func (store *DefaultAttachmentStore) CountInTopic(tid int) (count int) {
|
||||||
|
err := store.countInTopic.QueryRow(tid).Scan(&count)
|
||||||
|
if err != nil {
|
||||||
|
LogError(err)
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
func (store *DefaultAttachmentStore) CountInPath(path string) (count int) {
|
||||||
|
err := store.countInPath.QueryRow(path).Scan(&count)
|
||||||
|
if err != nil {
|
||||||
|
LogError(err)
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
func (store *DefaultAttachmentStore) Delete(aid int) error {
|
||||||
|
_, err := store.delete.Exec(aid)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Azareal/Gosora/query_gen"
|
"github.com/Azareal/Gosora/query_gen"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -33,7 +34,7 @@ var TmplPtrMap = make(map[string]interface{})
|
||||||
|
|
||||||
// Anti-spam token with rotated key
|
// Anti-spam token with rotated key
|
||||||
var JSTokenBox atomic.Value // TODO: Move this and some of these other globals somewhere else
|
var JSTokenBox atomic.Value // TODO: Move this and some of these other globals somewhere else
|
||||||
var SessionSigningKeyBox atomic.Value // For MFA to avoid hitting the database unneccesarily
|
var SessionSigningKeyBox atomic.Value // For MFA to avoid hitting the database unneccessarily
|
||||||
var OldSessionSigningKeyBox atomic.Value // Just in case we've signed with a key that's about to go stale so we don't annoy the user too much
|
var OldSessionSigningKeyBox atomic.Value // Just in case we've signed with a key that's about to go stale so we don't annoy the user too much
|
||||||
var IsDBDown int32 = 0 // 0 = false, 1 = true. this is value which should be manipulated with package atomic for representing whether the database is down so we don't spam the log with lots of redundant errors
|
var IsDBDown int32 = 0 // 0 = false, 1 = true. this is value which should be manipulated with package atomic for representing whether the database is down so we don't spam the log with lots of redundant errors
|
||||||
|
|
||||||
|
|
|
@ -314,12 +314,29 @@ func SecurityError(w http.ResponseWriter, r *http.Request, user User) RouteError
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotFound is used when the requested page doesn't exist
|
// NotFound is used when the requested page doesn't exist
|
||||||
// ? - Add a JSQ and JS version of this?
|
// ? - Add a JSQ version of this?
|
||||||
// ? - Add a user parameter?
|
// ? - Add a user parameter?
|
||||||
func NotFound(w http.ResponseWriter, r *http.Request, header *Header) RouteError {
|
func NotFound(w http.ResponseWriter, r *http.Request, header *Header) RouteError {
|
||||||
return CustomError(phrases.GetErrorPhrase("not_found_body"), 404, phrases.GetErrorPhrase("not_found_title"), w, r, header, GuestUser)
|
return CustomError(phrases.GetErrorPhrase("not_found_body"), 404, phrases.GetErrorPhrase("not_found_title"), w, r, header, GuestUser)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ? - Add a user parameter?
|
||||||
|
func NotFoundJS(w http.ResponseWriter, r *http.Request) RouteError {
|
||||||
|
w.WriteHeader(401)
|
||||||
|
writeJsonError(phrases.GetErrorPhrase("not_found_body"), w)
|
||||||
|
return HandledRouteError()
|
||||||
|
}
|
||||||
|
|
||||||
|
func NotFoundJSQ(w http.ResponseWriter, r *http.Request, header *Header, js bool) RouteError {
|
||||||
|
if js {
|
||||||
|
return NotFoundJS(w, r)
|
||||||
|
}
|
||||||
|
if header == nil {
|
||||||
|
header = DefaultHeader(w, GuestUser)
|
||||||
|
}
|
||||||
|
return NotFound(w, r, header)
|
||||||
|
}
|
||||||
|
|
||||||
// CustomError lets us make custom error types which aren't covered by the generic functions above
|
// CustomError lets us make custom error types which aren't covered by the generic functions above
|
||||||
func CustomError(errmsg string, errcode int, errtitle string, w http.ResponseWriter, r *http.Request, header *Header, user User) RouteError {
|
func CustomError(errmsg string, errcode int, errtitle string, w http.ResponseWriter, r *http.Request, header *Header, user User) RouteError {
|
||||||
if header == nil {
|
if header == nil {
|
||||||
|
|
|
@ -94,7 +94,7 @@ func (list SFileList) JSTmplInit() error {
|
||||||
|
|
||||||
preLen := len(data)
|
preLen := len(data)
|
||||||
data = replace(data, string(data[spaceIndex:endBrace]), "")
|
data = replace(data, string(data[spaceIndex:endBrace]), "")
|
||||||
data = replace(data, "))\n", "\n")
|
data = replace(data, "))\n", " \n")
|
||||||
endBrace -= preLen - len(data) // Offset it as we've deleted portions
|
endBrace -= preLen - len(data) // Offset it as we've deleted portions
|
||||||
fmt.Println("new endBrace: ", endBrace)
|
fmt.Println("new endBrace: ", endBrace)
|
||||||
fmt.Println("data: ", string(data))
|
fmt.Println("data: ", string(data))
|
||||||
|
@ -130,58 +130,38 @@ func (list SFileList) JSTmplInit() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
each("strconv.Itoa(", func(index int) {
|
each("strconv.Itoa(", func(index int) {
|
||||||
braceAt, hasEndBrace := skipUntilIfExists(data, index, ')')
|
braceAt, hasEndBrace := skipUntilIfExistsOrLine(data, index, ')')
|
||||||
// TODO: Make sure we don't go onto the next line in case someone misplaced a brace
|
|
||||||
if hasEndBrace {
|
if hasEndBrace {
|
||||||
data[braceAt] = ' ' // Blank it
|
data[braceAt] = ' ' // Blank it
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
each("w.Write([]byte(", func(index int) {
|
each("[]byte(", func(index int) {
|
||||||
braceAt, hasEndBrace := skipUntilIfExists(data, index, ')')
|
braceAt, hasEndBrace := skipUntilIfExistsOrLine(data, index, ')')
|
||||||
// TODO: Make sure we don't go onto the next line in case someone misplaced a brace
|
|
||||||
if hasEndBrace {
|
|
||||||
data[braceAt] = ' ' // Blank it
|
|
||||||
}
|
|
||||||
braceAt, hasEndBrace = skipUntilIfExists(data, braceAt, ')')
|
|
||||||
if hasEndBrace {
|
|
||||||
data[braceAt] = ' ' // Blank this one too
|
|
||||||
}
|
|
||||||
})
|
|
||||||
each(" = []byte(", func(index int) {
|
|
||||||
braceAt, hasEndBrace := skipUntilIfExists(data, index, ')')
|
|
||||||
// TODO: Make sure we don't go onto the next line in case someone misplaced a brace
|
|
||||||
if hasEndBrace {
|
if hasEndBrace {
|
||||||
data[braceAt] = ' ' // Blank it
|
data[braceAt] = ' ' // Blank it
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
each("w.Write(StringToBytes(", func(index int) {
|
each("StringToBytes(", func(index int) {
|
||||||
braceAt, hasEndBrace := skipUntilIfExists(data, index, ')')
|
braceAt, hasEndBrace := skipUntilIfExistsOrLine(data, index, ')')
|
||||||
// TODO: Make sure we don't go onto the next line in case someone misplaced a brace
|
|
||||||
if hasEndBrace {
|
|
||||||
data[braceAt] = ' ' // Blank it
|
|
||||||
}
|
|
||||||
braceAt, hasEndBrace = skipUntilIfExists(data, braceAt, ')')
|
|
||||||
if hasEndBrace {
|
|
||||||
data[braceAt] = ' ' // Blank this one too
|
|
||||||
}
|
|
||||||
})
|
|
||||||
each(" = StringToBytes(", func(index int) {
|
|
||||||
braceAt, hasEndBrace := skipUntilIfExists(data, index, ')')
|
|
||||||
// TODO: Make sure we don't go onto the next line in case someone misplaced a brace
|
|
||||||
if hasEndBrace {
|
if hasEndBrace {
|
||||||
data[braceAt] = ' ' // Blank it
|
data[braceAt] = ' ' // Blank it
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
each("w.Write(", func(index int) {
|
each("w.Write(", func(index int) {
|
||||||
braceAt, hasEndBrace := skipUntilIfExists(data, index, ')')
|
braceAt, hasEndBrace := skipUntilIfExistsOrLine(data, index, ')')
|
||||||
// TODO: Make sure we don't go onto the next line in case someone misplaced a brace
|
|
||||||
if hasEndBrace {
|
if hasEndBrace {
|
||||||
data[braceAt] = ' ' // Blank it
|
data[braceAt] = ' ' // Blank it
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
each("RelativeTime(", func(index int) {
|
||||||
|
braceAt, _ := skipUntilIfExistsOrLine(data, index, 10)
|
||||||
|
if data[braceAt-1] == ' ' {
|
||||||
|
data[braceAt-1] = ')' // Blank it
|
||||||
|
}
|
||||||
|
})
|
||||||
each("if ", func(index int) {
|
each("if ", func(index int) {
|
||||||
//fmt.Println("if index: ", index)
|
//fmt.Println("if index: ", index)
|
||||||
braceAt, hasBrace := skipUntilIfExists(data, index, '{')
|
braceAt, hasBrace := skipUntilIfExistsOrLine(data, index, '{')
|
||||||
if hasBrace {
|
if hasBrace {
|
||||||
if data[braceAt-1] != ' ' {
|
if data[braceAt-1] != ' ' {
|
||||||
panic("couldn't find space before brace, found ' " + string(data[braceAt-1]) + "' instead")
|
panic("couldn't find space before brace, found ' " + string(data[braceAt-1]) + "' instead")
|
||||||
|
@ -210,10 +190,12 @@ func (list SFileList) JSTmplInit() error {
|
||||||
data = replace(data, ", 10;", "")
|
data = replace(data, ", 10;", "")
|
||||||
data = replace(data, shortName+"_tmpl_phrase_id = RegisterTmplPhraseNames([]string{", "[")
|
data = replace(data, shortName+"_tmpl_phrase_id = RegisterTmplPhraseNames([]string{", "[")
|
||||||
data = replace(data, "var plist = GetTmplPhrasesBytes("+shortName+"_tmpl_phrase_id)", "let plist = tmplPhrases[\""+tmplName+"\"];")
|
data = replace(data, "var plist = GetTmplPhrasesBytes("+shortName+"_tmpl_phrase_id)", "let plist = tmplPhrases[\""+tmplName+"\"];")
|
||||||
//data = replace(data, "var phrases = GetTmplPhrasesBytes("+shortName+"_tmpl_phrase_id)", "let phrases = tmplPhrases[\""+tmplName+"\"];\nconsole.log('tmplName:','"+tmplName+"')\nconsole.log('phrases:', phrases);")
|
|
||||||
data = replace(data, "var cached_var_", "let cached_var_")
|
data = replace(data, "var cached_var_", "let cached_var_")
|
||||||
data = replace(data, " = []byte(", " = ")
|
data = replace(data, "[]byte(", "")
|
||||||
data = replace(data, " = StringToBytes(", " = ")
|
data = replace(data, "StringToBytes(", "")
|
||||||
|
// TODO: Format dates properly on the client side
|
||||||
|
data = replace(data, ".Format(\"2006-01-02 15:04:05\"", "")
|
||||||
|
data = replace(data, ", 10", "")
|
||||||
data = replace(data, "if ", "if(")
|
data = replace(data, "if ", "if(")
|
||||||
data = replace(data, "return nil", "return out")
|
data = replace(data, "return nil", "return out")
|
||||||
data = replace(data, " )", ")")
|
data = replace(data, " )", ")")
|
||||||
|
|
|
@ -190,6 +190,18 @@ func skipUntilIfExists(tmplData []byte, i int, expects byte) (newI int, hasIt bo
|
||||||
return j, false
|
return j, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func skipUntilIfExistsOrLine(tmplData []byte, i int, expects byte) (newI int, hasIt bool) {
|
||||||
|
j := i
|
||||||
|
for ; j < len(tmplData); j++ {
|
||||||
|
if tmplData[j] == 10 {
|
||||||
|
return j, false
|
||||||
|
} else if tmplData[j] == expects {
|
||||||
|
return j, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return j, false
|
||||||
|
}
|
||||||
|
|
||||||
func skipUntilCharsExist(tmplData []byte, i int, expects []byte) (newI int, hasIt bool) {
|
func skipUntilCharsExist(tmplData []byte, i int, expects []byte) (newI int, hasIt bool) {
|
||||||
j := i
|
j := i
|
||||||
expectIndex := 0
|
expectIndex := 0
|
||||||
|
|
|
@ -11,17 +11,16 @@ import (
|
||||||
var profileReplyStmts ProfileReplyStmts
|
var profileReplyStmts ProfileReplyStmts
|
||||||
|
|
||||||
type ProfileReply struct {
|
type ProfileReply struct {
|
||||||
ID int
|
ID int
|
||||||
ParentID int
|
ParentID int
|
||||||
Content string
|
Content string
|
||||||
CreatedBy int
|
CreatedBy int
|
||||||
Group int
|
Group int
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
RelativeCreatedAt string
|
LastEdit int
|
||||||
LastEdit int
|
LastEditBy int
|
||||||
LastEditBy int
|
ContentLines int
|
||||||
ContentLines int
|
IPAddress string
|
||||||
IPAddress string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProfileReplyStmts struct {
|
type ProfileReplyStmts struct {
|
||||||
|
|
|
@ -16,48 +16,46 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type ReplyUser struct {
|
type ReplyUser struct {
|
||||||
ID int
|
ID int
|
||||||
ParentID int
|
ParentID int
|
||||||
Content string
|
Content string
|
||||||
ContentHtml string
|
ContentHtml string
|
||||||
CreatedBy int
|
CreatedBy int
|
||||||
UserLink string
|
UserLink string
|
||||||
CreatedByName string
|
CreatedByName string
|
||||||
Group int
|
Group int
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
RelativeCreatedAt string
|
LastEdit int
|
||||||
LastEdit int
|
LastEditBy int
|
||||||
LastEditBy int
|
Avatar string
|
||||||
Avatar string
|
MicroAvatar string
|
||||||
MicroAvatar string
|
ClassName string
|
||||||
ClassName string
|
ContentLines int
|
||||||
ContentLines int
|
Tag string
|
||||||
Tag string
|
URL string
|
||||||
URL string
|
URLPrefix string
|
||||||
URLPrefix string
|
URLName string
|
||||||
URLName string
|
Level int
|
||||||
Level int
|
IPAddress string
|
||||||
IPAddress string
|
Liked bool
|
||||||
Liked bool
|
LikeCount int
|
||||||
LikeCount int
|
ActionType string
|
||||||
ActionType string
|
ActionIcon string
|
||||||
ActionIcon string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Reply struct {
|
type Reply struct {
|
||||||
ID int
|
ID int
|
||||||
ParentID int
|
ParentID int
|
||||||
Content string
|
Content string
|
||||||
CreatedBy int
|
CreatedBy int
|
||||||
Group int
|
Group int
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
RelativeCreatedAt string
|
LastEdit int
|
||||||
LastEdit int
|
LastEditBy int
|
||||||
LastEditBy int
|
ContentLines int
|
||||||
ContentLines int
|
IPAddress string
|
||||||
IPAddress string
|
Liked bool
|
||||||
Liked bool
|
LikeCount int
|
||||||
LikeCount int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrAlreadyLiked = errors.New("You already liked this!")
|
var ErrAlreadyLiked = errors.New("You already liked this!")
|
||||||
|
|
|
@ -40,5 +40,5 @@ func (store *SQLReplyStore) Create(topic *Topic, content string, ipaddress strin
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
return int(lastID), topic.AddReply(uid)
|
return int(lastID), topic.AddReply(int(lastID), uid)
|
||||||
}
|
}
|
||||||
|
|
|
@ -341,7 +341,7 @@ func HandleUploadRoute(w http.ResponseWriter, r *http.Request, user User, maxFil
|
||||||
size, unit := ConvertByteUnit(float64(maxFileSize))
|
size, unit := ConvertByteUnit(float64(maxFileSize))
|
||||||
return CustomError("Your upload is too big. Your files need to be smaller than "+strconv.Itoa(int(size))+unit+".", http.StatusExpectationFailed, "Error", w, r, nil, user)
|
return CustomError("Your upload is too big. Your files need to be smaller than "+strconv.Itoa(int(size))+unit+".", http.StatusExpectationFailed, "Error", w, r, nil, user)
|
||||||
}
|
}
|
||||||
r.Body = http.MaxBytesReader(w, r.Body, int64(maxFileSize))
|
r.Body = http.MaxBytesReader(w, r.Body, r.ContentLength)
|
||||||
|
|
||||||
err := r.ParseMultipartForm(int64(Megabyte))
|
err := r.ParseMultipartForm(int64(Megabyte))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -226,11 +226,12 @@ func CompileTemplates() error {
|
||||||
PollOption{1, "Something"},
|
PollOption{1, "Something"},
|
||||||
}, VoteCount: 7}
|
}, VoteCount: 7}
|
||||||
avatar, microAvatar := BuildAvatar(62, "")
|
avatar, microAvatar := BuildAvatar(62, "")
|
||||||
topic := TopicUser{1, "blah", "Blah", "Hey there!", 0, false, false, now, RelativeTime(now), now, RelativeTime(now), 0, "", "127.0.0.1", 1, 0, 1, "classname", poll.ID, "weird-data", BuildProfileURL("fake-user", 62), "Fake User", Config.DefaultGroup, avatar, microAvatar, 0, "", "", "", "", "", 58, false}
|
miniAttach := []*MiniAttachment{&MiniAttachment{Path: "/"}}
|
||||||
|
topic := TopicUser{1, "blah", "Blah", "Hey there!", 0, false, false, now, now, 1, 1, 0, "", "127.0.0.1", 1, 0, 1, 0, "classname", poll.ID, "weird-data", BuildProfileURL("fake-user", 62), "Fake User", Config.DefaultGroup, avatar, microAvatar, 0, "", "", "", "", "", 58, false, miniAttach}
|
||||||
var replyList []ReplyUser
|
var replyList []ReplyUser
|
||||||
// TODO: Do we want the UID on this to be 0?
|
// TODO: Do we want the UID on this to be 0?
|
||||||
avatar, microAvatar = BuildAvatar(0, "")
|
avatar, microAvatar = BuildAvatar(0, "")
|
||||||
replyList = append(replyList, ReplyUser{0, 0, "Yo!", "Yo!", 0, "alice", "Alice", Config.DefaultGroup, now, RelativeTime(now), 0, 0, avatar, microAvatar, "", 0, "", "", "", "", 0, "127.0.0.1", false, 1, "", ""})
|
replyList = append(replyList, ReplyUser{0, 0, "Yo!", "Yo!", 0, "alice", "Alice", Config.DefaultGroup, now, 0, 0, avatar, microAvatar, "", 0, "", "", "", "", 0, "127.0.0.1", false, 1, "", ""})
|
||||||
|
|
||||||
var varList = make(map[string]tmpl.VarItem)
|
var varList = make(map[string]tmpl.VarItem)
|
||||||
var compile = func(name string, expects string, expectsInt interface{}) (tmpl string, err error) {
|
var compile = func(name string, expects string, expectsInt interface{}) (tmpl string, err error) {
|
||||||
|
@ -285,7 +286,7 @@ func CompileTemplates() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
var topicsList []*TopicsRow
|
var topicsList []*TopicsRow
|
||||||
topicsList = append(topicsList, &TopicsRow{1, "topic-title", "Topic Title", "The topic content.", 1, false, false, now, now, "Date", user3.ID, 1, "", "127.0.0.1", 1, 0, 1, 1, "classname", "", &user2, "", 0, &user3, "General", "/forum/general.2"})
|
topicsList = append(topicsList, &TopicsRow{1, "topic-title", "Topic Title", "The topic content.", 1, false, false, now, now, user3.ID, 1, 1, "", "127.0.0.1", 1, 0, 1, 1, 0, "classname", "", &user2, "", 0, &user3, "General", "/forum/general.2"})
|
||||||
header2.Title = "Topic List"
|
header2.Title = "Topic List"
|
||||||
topicListPage := TopicListPage{header, topicsList, forumList, Config.DefaultForum, TopicListSort{"lastupdated", false}, Paginator{[]int{1}, 1, 1}}
|
topicListPage := TopicListPage{header, topicsList, forumList, Config.DefaultForum, TopicListSort{"lastupdated", false}, Paginator{[]int{1}, 1, 1}}
|
||||||
/*topicListTmpl, err := compile("topics", "common.TopicListPage", topicListPage)
|
/*topicListTmpl, err := compile("topics", "common.TopicListPage", topicListPage)
|
||||||
|
@ -439,7 +440,7 @@ func CompileJSTemplates() error {
|
||||||
// TODO: Fix the import loop so we don't have to use this hack anymore
|
// TODO: Fix the import loop so we don't have to use this hack anymore
|
||||||
c.SetBuildTags("!no_templategen,tmplgentopic")
|
c.SetBuildTags("!no_templategen,tmplgentopic")
|
||||||
|
|
||||||
var topicsRow = &TopicsRow{1, "topic-title", "Topic Title", "The topic content.", 1, false, false, now, now, "Date", user3.ID, 1, "", "127.0.0.1", 1, 0, 1, 1, "classname", "", &user2, "", 0, &user3, "General", "/forum/general.2"}
|
var topicsRow = &TopicsRow{1, "topic-title", "Topic Title", "The topic content.", 1, false, false, now, now, user3.ID, 1, 1, "", "127.0.0.1", 1, 0, 1, 0, 1, "classname", "", &user2, "", 0, &user3, "General", "/forum/general.2"}
|
||||||
topicListItemTmpl, err := c.Compile("topics_topic.html", "templates/", "*common.TopicsRow", topicsRow, varList)
|
topicListItemTmpl, err := c.Compile("topics_topic.html", "templates/", "*common.TopicsRow", topicsRow, varList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -450,11 +451,12 @@ func CompileJSTemplates() error {
|
||||||
PollOption{1, "Something"},
|
PollOption{1, "Something"},
|
||||||
}, VoteCount: 7}
|
}, VoteCount: 7}
|
||||||
avatar, microAvatar := BuildAvatar(62, "")
|
avatar, microAvatar := BuildAvatar(62, "")
|
||||||
topic := TopicUser{1, "blah", "Blah", "Hey there!", 62, false, false, now, RelativeTime(now), now, RelativeTime(now), 0, "", "127.0.0.1", 1, 0, 1, "classname", poll.ID, "weird-data", BuildProfileURL("fake-user", 62), "Fake User", Config.DefaultGroup, avatar, microAvatar, 0, "", "", "", "", "", 58, false}
|
miniAttach := []*MiniAttachment{&MiniAttachment{Path: "/"}}
|
||||||
|
topic := TopicUser{1, "blah", "Blah", "Hey there!", 62, false, false, now, now, 1, 1, 0, "", "127.0.0.1", 1, 0, 1, 0, "classname", poll.ID, "weird-data", BuildProfileURL("fake-user", 62), "Fake User", Config.DefaultGroup, avatar, microAvatar, 0, "", "", "", "", "", 58, false, miniAttach}
|
||||||
var replyList []ReplyUser
|
var replyList []ReplyUser
|
||||||
// TODO: Do we really want the UID here to be zero?
|
// TODO: Do we really want the UID here to be zero?
|
||||||
avatar, microAvatar = BuildAvatar(0, "")
|
avatar, microAvatar = BuildAvatar(0, "")
|
||||||
replyList = append(replyList, ReplyUser{0, 0, "Yo!", "Yo!", 0, "alice", "Alice", Config.DefaultGroup, now, RelativeTime(now), 0, 0, avatar, microAvatar, "", 0, "", "", "", "", 0, "127.0.0.1", false, 1, "", ""})
|
replyList = append(replyList, ReplyUser{0, 0, "Yo!", "Yo!", 0, "alice", "Alice", Config.DefaultGroup, now, 0, 0, avatar, microAvatar, "", 0, "", "", "", "", 0, "127.0.0.1", false, 1, "", ""})
|
||||||
|
|
||||||
varList = make(map[string]tmpl.VarItem)
|
varList = make(map[string]tmpl.VarItem)
|
||||||
header.Title = "Topic Name"
|
header.Title = "Topic Name"
|
||||||
|
@ -639,10 +641,17 @@ func InitTemplates() error {
|
||||||
if !ok {
|
if !ok {
|
||||||
panic("timeInt is not a time.Time")
|
panic("timeInt is not a time.Time")
|
||||||
}
|
}
|
||||||
//return time.String()
|
|
||||||
return time.Format("2006-01-02 15:04:05")
|
return time.Format("2006-01-02 15:04:05")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmap["reltime"] = func(timeInt interface{}) interface{} {
|
||||||
|
time, ok := timeInt.(time.Time)
|
||||||
|
if !ok {
|
||||||
|
panic("timeInt is not a time.Time")
|
||||||
|
}
|
||||||
|
return RelativeTime(time)
|
||||||
|
}
|
||||||
|
|
||||||
fmap["scope"] = func(name interface{}) interface{} {
|
fmap["scope"] = func(name interface{}) interface{} {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,6 +91,7 @@ func NewCTemplateSet() *CTemplateSet {
|
||||||
//"langf":true,
|
//"langf":true,
|
||||||
"level": true,
|
"level": true,
|
||||||
"abstime": true,
|
"abstime": true,
|
||||||
|
"reltime": true,
|
||||||
"scope": true,
|
"scope": true,
|
||||||
"dyntmpl": true,
|
"dyntmpl": true,
|
||||||
},
|
},
|
||||||
|
@ -959,6 +960,16 @@ ArgLoop:
|
||||||
// TODO: Refactor this
|
// TODO: Refactor this
|
||||||
litString(leftParam+".Format(\"2006-01-02 15:04:05\")", false)
|
litString(leftParam+".Format(\"2006-01-02 15:04:05\")", false)
|
||||||
break ArgLoop
|
break ArgLoop
|
||||||
|
case "reltime":
|
||||||
|
// TODO: Implement level literals
|
||||||
|
leftOperand := node.Args[pos+1].String()
|
||||||
|
if len(leftOperand) == 0 {
|
||||||
|
panic("The leftoperand for function reltime cannot be left blank")
|
||||||
|
}
|
||||||
|
leftParam, _ := c.compileIfVarSub(con, leftOperand)
|
||||||
|
// TODO: Refactor this
|
||||||
|
litString("common.RelativeTime("+leftParam+")", false)
|
||||||
|
break ArgLoop
|
||||||
case "scope":
|
case "scope":
|
||||||
literal = true
|
literal = true
|
||||||
break ArgLoop
|
break ArgLoop
|
||||||
|
|
219
common/topic.go
219
common/topic.go
|
@ -22,51 +22,51 @@ import (
|
||||||
// ? - Add a TopicMeta struct for *Forums?
|
// ? - Add a TopicMeta struct for *Forums?
|
||||||
|
|
||||||
type Topic struct {
|
type Topic struct {
|
||||||
ID int
|
ID int
|
||||||
Link string
|
Link string
|
||||||
Title string
|
Title string
|
||||||
Content string
|
Content string
|
||||||
CreatedBy int
|
CreatedBy int
|
||||||
IsClosed bool
|
IsClosed bool
|
||||||
Sticky bool
|
Sticky bool
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
RelativeCreatedAt string
|
LastReplyAt time.Time
|
||||||
LastReplyAt time.Time
|
LastReplyBy int
|
||||||
RelativeLastReplyAt string
|
LastReplyID int
|
||||||
//LastReplyBy int
|
ParentID int
|
||||||
ParentID int
|
Status string // Deprecated. Marked for removal.
|
||||||
Status string // Deprecated. Marked for removal.
|
IPAddress string
|
||||||
IPAddress string
|
ViewCount int64
|
||||||
ViewCount int64
|
PostCount int
|
||||||
PostCount int
|
LikeCount int
|
||||||
LikeCount int
|
AttachCount int
|
||||||
ClassName string // CSS Class Name
|
ClassName string // CSS Class Name
|
||||||
Poll int
|
Poll int
|
||||||
Data string // Used for report metadata
|
Data string // Used for report metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
type TopicUser struct {
|
type TopicUser struct {
|
||||||
ID int
|
ID int
|
||||||
Link string
|
Link string
|
||||||
Title string
|
Title string
|
||||||
Content string // TODO: Avoid converting this to bytes in templates, particularly if it's long
|
Content string // TODO: Avoid converting this to bytes in templates, particularly if it's long
|
||||||
CreatedBy int
|
CreatedBy int
|
||||||
IsClosed bool
|
IsClosed bool
|
||||||
Sticky bool
|
Sticky bool
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
RelativeCreatedAt string
|
LastReplyAt time.Time
|
||||||
LastReplyAt time.Time
|
LastReplyBy int
|
||||||
RelativeLastReplyAt string
|
LastReplyID int
|
||||||
//LastReplyBy int
|
ParentID int
|
||||||
ParentID int
|
Status string // Deprecated. Marked for removal.
|
||||||
Status string // Deprecated. Marked for removal.
|
IPAddress string
|
||||||
IPAddress string
|
ViewCount int64
|
||||||
ViewCount int64
|
PostCount int
|
||||||
PostCount int
|
LikeCount int
|
||||||
LikeCount int
|
AttachCount int
|
||||||
ClassName string
|
ClassName string
|
||||||
Poll int
|
Poll int
|
||||||
Data string // Used for report metadata
|
Data string // Used for report metadata
|
||||||
|
|
||||||
UserLink string
|
UserLink string
|
||||||
CreatedByName string
|
CreatedByName string
|
||||||
|
@ -81,30 +81,32 @@ type TopicUser struct {
|
||||||
URLName string
|
URLName string
|
||||||
Level int
|
Level int
|
||||||
Liked bool
|
Liked bool
|
||||||
|
|
||||||
|
Attachments []*MiniAttachment
|
||||||
}
|
}
|
||||||
|
|
||||||
type TopicsRow struct {
|
type TopicsRow struct {
|
||||||
ID int
|
ID int
|
||||||
Link string
|
Link string
|
||||||
Title string
|
Title string
|
||||||
Content string
|
Content string
|
||||||
CreatedBy int
|
CreatedBy int
|
||||||
IsClosed bool
|
IsClosed bool
|
||||||
Sticky bool
|
Sticky bool
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
//RelativeCreatedAt string
|
LastReplyAt time.Time
|
||||||
LastReplyAt time.Time
|
LastReplyBy int
|
||||||
RelativeLastReplyAt string
|
LastReplyID int
|
||||||
LastReplyBy int
|
ParentID int
|
||||||
ParentID int
|
Status string // Deprecated. Marked for removal. -Is there anything we could use it for?
|
||||||
Status string // Deprecated. Marked for removal. -Is there anything we could use it for?
|
IPAddress string
|
||||||
IPAddress string
|
ViewCount int64
|
||||||
ViewCount int64
|
PostCount int
|
||||||
PostCount int
|
LikeCount int
|
||||||
LikeCount int
|
AttachCount int
|
||||||
LastPage int
|
LastPage int
|
||||||
ClassName string
|
ClassName string
|
||||||
Data string // Used for report metadata
|
Data string // Used for report metadata
|
||||||
|
|
||||||
Creator *User
|
Creator *User
|
||||||
CSS template.CSS
|
CSS template.CSS
|
||||||
|
@ -126,10 +128,12 @@ type WsTopicsRow struct {
|
||||||
LastReplyAt time.Time
|
LastReplyAt time.Time
|
||||||
RelativeLastReplyAt string
|
RelativeLastReplyAt string
|
||||||
LastReplyBy int
|
LastReplyBy int
|
||||||
|
LastReplyID int
|
||||||
ParentID int
|
ParentID int
|
||||||
ViewCount int64
|
ViewCount int64
|
||||||
PostCount int
|
PostCount int
|
||||||
LikeCount int
|
LikeCount int
|
||||||
|
AttachCount int
|
||||||
ClassName string
|
ClassName string
|
||||||
Creator *WsJSONUser
|
Creator *WsJSONUser
|
||||||
LastUser *WsJSONUser
|
LastUser *WsJSONUser
|
||||||
|
@ -137,24 +141,26 @@ type WsTopicsRow struct {
|
||||||
ForumLink string
|
ForumLink string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Can we get the client side to render the relative times instead?
|
||||||
func (row *TopicsRow) WebSockets() *WsTopicsRow {
|
func (row *TopicsRow) WebSockets() *WsTopicsRow {
|
||||||
return &WsTopicsRow{row.ID, row.Link, row.Title, row.CreatedBy, row.IsClosed, row.Sticky, row.CreatedAt, row.LastReplyAt, row.RelativeLastReplyAt, row.LastReplyBy, row.ParentID, row.ViewCount, row.PostCount, row.LikeCount, row.ClassName, row.Creator.WebSockets(), row.LastUser.WebSockets(), row.ForumName, row.ForumLink}
|
return &WsTopicsRow{row.ID, row.Link, row.Title, row.CreatedBy, row.IsClosed, row.Sticky, row.CreatedAt, row.LastReplyAt, RelativeTime(row.LastReplyAt), row.LastReplyBy, row.LastReplyID, row.ParentID, row.ViewCount, row.PostCount, row.LikeCount, row.AttachCount, row.ClassName, row.Creator.WebSockets(), row.LastUser.WebSockets(), row.ForumName, row.ForumLink}
|
||||||
}
|
}
|
||||||
|
|
||||||
type TopicStmts struct {
|
type TopicStmts struct {
|
||||||
addRepliesToTopic *sql.Stmt
|
addReplies *sql.Stmt
|
||||||
lock *sql.Stmt
|
updateLastReply *sql.Stmt
|
||||||
unlock *sql.Stmt
|
lock *sql.Stmt
|
||||||
moveTo *sql.Stmt
|
unlock *sql.Stmt
|
||||||
stick *sql.Stmt
|
moveTo *sql.Stmt
|
||||||
unstick *sql.Stmt
|
stick *sql.Stmt
|
||||||
hasLikedTopic *sql.Stmt
|
unstick *sql.Stmt
|
||||||
createLike *sql.Stmt
|
hasLikedTopic *sql.Stmt
|
||||||
addLikesToTopic *sql.Stmt
|
createLike *sql.Stmt
|
||||||
delete *sql.Stmt
|
addLikesToTopic *sql.Stmt
|
||||||
edit *sql.Stmt
|
delete *sql.Stmt
|
||||||
setPoll *sql.Stmt
|
edit *sql.Stmt
|
||||||
createActionReply *sql.Stmt
|
setPoll *sql.Stmt
|
||||||
|
createAction *sql.Stmt
|
||||||
|
|
||||||
getTopicUser *sql.Stmt // TODO: Can we get rid of this?
|
getTopicUser *sql.Stmt // TODO: Can we get rid of this?
|
||||||
getByReplyID *sql.Stmt
|
getByReplyID *sql.Stmt
|
||||||
|
@ -165,21 +171,22 @@ var topicStmts TopicStmts
|
||||||
func init() {
|
func init() {
|
||||||
DbInits.Add(func(acc *qgen.Accumulator) error {
|
DbInits.Add(func(acc *qgen.Accumulator) error {
|
||||||
topicStmts = TopicStmts{
|
topicStmts = TopicStmts{
|
||||||
addRepliesToTopic: acc.Update("topics").Set("postCount = postCount + ?, lastReplyBy = ?, lastReplyAt = UTC_TIMESTAMP()").Where("tid = ?").Prepare(),
|
addReplies: acc.Update("topics").Set("postCount = postCount + ?, lastReplyBy = ?, lastReplyAt = UTC_TIMESTAMP()").Where("tid = ?").Prepare(),
|
||||||
lock: acc.Update("topics").Set("is_closed = 1").Where("tid = ?").Prepare(),
|
updateLastReply: acc.Update("topics").Set("lastReplyID = ?").Where("lastReplyID > ? AND tid = ?").Prepare(),
|
||||||
unlock: acc.Update("topics").Set("is_closed = 0").Where("tid = ?").Prepare(),
|
lock: acc.Update("topics").Set("is_closed = 1").Where("tid = ?").Prepare(),
|
||||||
moveTo: acc.Update("topics").Set("parentID = ?").Where("tid = ?").Prepare(),
|
unlock: acc.Update("topics").Set("is_closed = 0").Where("tid = ?").Prepare(),
|
||||||
stick: acc.Update("topics").Set("sticky = 1").Where("tid = ?").Prepare(),
|
moveTo: acc.Update("topics").Set("parentID = ?").Where("tid = ?").Prepare(),
|
||||||
unstick: acc.Update("topics").Set("sticky = 0").Where("tid = ?").Prepare(),
|
stick: acc.Update("topics").Set("sticky = 1").Where("tid = ?").Prepare(),
|
||||||
hasLikedTopic: acc.Select("likes").Columns("targetItem").Where("sentBy = ? and targetItem = ? and targetType = 'topics'").Prepare(),
|
unstick: acc.Update("topics").Set("sticky = 0").Where("tid = ?").Prepare(),
|
||||||
createLike: acc.Insert("likes").Columns("weight, targetItem, targetType, sentBy, createdAt").Fields("?,?,?,?,UTC_TIMESTAMP()").Prepare(),
|
hasLikedTopic: acc.Select("likes").Columns("targetItem").Where("sentBy = ? and targetItem = ? and targetType = 'topics'").Prepare(),
|
||||||
addLikesToTopic: acc.Update("topics").Set("likeCount = likeCount + ?").Where("tid = ?").Prepare(),
|
createLike: acc.Insert("likes").Columns("weight, targetItem, targetType, sentBy, createdAt").Fields("?,?,?,?,UTC_TIMESTAMP()").Prepare(),
|
||||||
delete: acc.Delete("topics").Where("tid = ?").Prepare(),
|
addLikesToTopic: acc.Update("topics").Set("likeCount = likeCount + ?").Where("tid = ?").Prepare(),
|
||||||
edit: acc.Update("topics").Set("title = ?, content = ?, parsed_content = ?").Where("tid = ?").Prepare(), // TODO: Only run the content update bits on non-polls, does this matter?
|
delete: acc.Delete("topics").Where("tid = ?").Prepare(),
|
||||||
setPoll: acc.Update("topics").Set("content = '', parsed_content = '', poll = ?").Where("tid = ? AND poll = 0").Prepare(),
|
edit: acc.Update("topics").Set("title = ?, content = ?, parsed_content = ?").Where("tid = ?").Prepare(), // TODO: Only run the content update bits on non-polls, does this matter?
|
||||||
createActionReply: acc.Insert("replies").Columns("tid, actionType, ipaddress, createdBy, createdAt, lastUpdated, content, parsed_content").Fields("?,?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),'',''").Prepare(),
|
setPoll: acc.Update("topics").Set("content = '', parsed_content = '', poll = ?").Where("tid = ? AND poll = 0").Prepare(),
|
||||||
|
createAction: acc.Insert("replies").Columns("tid, actionType, ipaddress, createdBy, createdAt, lastUpdated, content, parsed_content").Fields("?,?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),'',''").Prepare(),
|
||||||
|
|
||||||
getTopicUser: acc.SimpleLeftJoin("topics", "users", "topics.title, topics.content, topics.createdBy, topics.createdAt, topics.is_closed, topics.sticky, topics.parentID, topics.ipaddress, topics.views, topics.postCount, topics.likeCount, topics.poll, users.name, users.avatar, users.group, users.url_prefix, users.url_name, users.level", "topics.createdBy = users.uid", "tid = ?", "", ""),
|
getTopicUser: acc.SimpleLeftJoin("topics", "users", "topics.title, topics.content, topics.createdBy, topics.createdAt, topics.lastReplyAt, topics.lastReplyBy, topics.lastReplyID, topics.is_closed, topics.sticky, topics.parentID, topics.ipaddress, topics.views, topics.postCount, topics.likeCount, topics.attachCount,topics.poll, users.name, users.avatar, users.group, users.url_prefix, users.url_name, users.level", "topics.createdBy = users.uid", "tid = ?", "", ""),
|
||||||
getByReplyID: acc.SimpleLeftJoin("replies", "topics", "topics.tid, topics.title, topics.content, topics.createdBy, topics.createdAt, topics.is_closed, topics.sticky, topics.parentID, topics.ipaddress, topics.views, topics.postCount, topics.likeCount, topics.poll, topics.data", "replies.tid = topics.tid", "rid = ?", "", ""),
|
getByReplyID: acc.SimpleLeftJoin("replies", "topics", "topics.tid, topics.title, topics.content, topics.createdBy, topics.createdAt, topics.is_closed, topics.sticky, topics.parentID, topics.ipaddress, topics.views, topics.postCount, topics.likeCount, topics.poll, topics.data", "replies.tid = topics.tid", "rid = ?", "", ""),
|
||||||
}
|
}
|
||||||
return acc.FirstError()
|
return acc.FirstError()
|
||||||
|
@ -197,8 +204,12 @@ func (topic *Topic) cacheRemove() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Write a test for this
|
// TODO: Write a test for this
|
||||||
func (topic *Topic) AddReply(uid int) (err error) {
|
func (topic *Topic) AddReply(rid int, uid int) (err error) {
|
||||||
_, err = topicStmts.addRepliesToTopic.Exec(1, uid, topic.ID)
|
_, err = topicStmts.addReplies.Exec(1, uid, topic.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = topicStmts.updateLastReply.Exec(rid, rid, topic.ID)
|
||||||
topic.cacheRemove()
|
topic.cacheRemove()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -314,11 +325,20 @@ func (topic *Topic) SetPoll(pollID int) error {
|
||||||
|
|
||||||
// TODO: Have this go through the ReplyStore?
|
// TODO: Have this go through the ReplyStore?
|
||||||
func (topic *Topic) CreateActionReply(action string, ipaddress string, uid int) (err error) {
|
func (topic *Topic) CreateActionReply(action string, ipaddress string, uid int) (err error) {
|
||||||
_, err = topicStmts.createActionReply.Exec(topic.ID, action, ipaddress, uid)
|
res, err := topicStmts.createAction.Exec(topic.ID, action, ipaddress, uid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = topicStmts.addRepliesToTopic.Exec(1, uid, topic.ID)
|
_, err = topicStmts.addReplies.Exec(1, uid, topic.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
lid, err := res.LastInsertId()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rid := int(lid)
|
||||||
|
_, err = topicStmts.updateLastReply.Exec(rid, rid, topic.ID)
|
||||||
topic.cacheRemove()
|
topic.cacheRemove()
|
||||||
// ? - Update the last topic cache for the parent forum?
|
// ? - Update the last topic cache for the parent forum?
|
||||||
return err
|
return err
|
||||||
|
@ -336,7 +356,7 @@ func (topic *Topic) Copy() Topic {
|
||||||
return *topic
|
return *topic
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Load LastReplyAt?
|
// TODO: Load LastReplyAt and LastReplyID?
|
||||||
func TopicByReplyID(rid int) (*Topic, error) {
|
func TopicByReplyID(rid int) (*Topic, error) {
|
||||||
topic := Topic{ID: 0}
|
topic := Topic{ID: 0}
|
||||||
err := topicStmts.getByReplyID.QueryRow(rid).Scan(&topic.ID, &topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.ViewCount, &topic.PostCount, &topic.LikeCount, &topic.Poll, &topic.Data)
|
err := topicStmts.getByReplyID.QueryRow(rid).Scan(&topic.ID, &topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.ViewCount, &topic.PostCount, &topic.LikeCount, &topic.Poll, &topic.Data)
|
||||||
|
@ -376,14 +396,15 @@ func GetTopicUser(user *User, tid int) (tu TopicUser, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
tu = TopicUser{ID: tid}
|
tu = TopicUser{ID: tid}
|
||||||
err = topicStmts.getTopicUser.QueryRow(tid).Scan(&tu.Title, &tu.Content, &tu.CreatedBy, &tu.CreatedAt, &tu.IsClosed, &tu.Sticky, &tu.ParentID, &tu.IPAddress, &tu.ViewCount, &tu.PostCount, &tu.LikeCount, &tu.Poll, &tu.CreatedByName, &tu.Avatar, &tu.Group, &tu.URLPrefix, &tu.URLName, &tu.Level)
|
// TODO: This misses some important bits...
|
||||||
|
err = topicStmts.getTopicUser.QueryRow(tid).Scan(&tu.Title, &tu.Content, &tu.CreatedBy, &tu.CreatedAt, &tu.LastReplyAt, &tu.LastReplyBy, &tu.LastReplyID, &tu.IsClosed, &tu.Sticky, &tu.ParentID, &tu.IPAddress, &tu.ViewCount, &tu.PostCount, &tu.LikeCount, &tu.AttachCount, &tu.Poll, &tu.CreatedByName, &tu.Avatar, &tu.Group, &tu.URLPrefix, &tu.URLName, &tu.Level)
|
||||||
tu.Avatar, tu.MicroAvatar = BuildAvatar(tu.CreatedBy, tu.Avatar)
|
tu.Avatar, tu.MicroAvatar = BuildAvatar(tu.CreatedBy, tu.Avatar)
|
||||||
tu.Link = BuildTopicURL(NameToSlug(tu.Title), tu.ID)
|
tu.Link = BuildTopicURL(NameToSlug(tu.Title), tu.ID)
|
||||||
tu.UserLink = BuildProfileURL(NameToSlug(tu.CreatedByName), tu.CreatedBy)
|
tu.UserLink = BuildProfileURL(NameToSlug(tu.CreatedByName), tu.CreatedBy)
|
||||||
tu.Tag = Groups.DirtyGet(tu.Group).Tag
|
tu.Tag = Groups.DirtyGet(tu.Group).Tag
|
||||||
|
|
||||||
if tcache != nil {
|
if tcache != nil {
|
||||||
theTopic := Topic{ID: tu.ID, Link: tu.Link, Title: tu.Title, Content: tu.Content, CreatedBy: tu.CreatedBy, IsClosed: tu.IsClosed, Sticky: tu.Sticky, CreatedAt: tu.CreatedAt, LastReplyAt: tu.LastReplyAt, ParentID: tu.ParentID, IPAddress: tu.IPAddress, ViewCount: tu.ViewCount, PostCount: tu.PostCount, LikeCount: tu.LikeCount, Poll: tu.Poll}
|
theTopic := Topic{ID: tu.ID, Link: tu.Link, Title: tu.Title, Content: tu.Content, CreatedBy: tu.CreatedBy, IsClosed: tu.IsClosed, Sticky: tu.Sticky, CreatedAt: tu.CreatedAt, LastReplyAt: tu.LastReplyAt, LastReplyID: tu.LastReplyID, ParentID: tu.ParentID, IPAddress: tu.IPAddress, ViewCount: tu.ViewCount, PostCount: tu.PostCount, LikeCount: tu.LikeCount, AttachCount: tu.AttachCount, Poll: tu.Poll}
|
||||||
//log.Printf("theTopic: %+v\n", theTopic)
|
//log.Printf("theTopic: %+v\n", theTopic)
|
||||||
_ = tcache.Add(&theTopic)
|
_ = tcache.Add(&theTopic)
|
||||||
}
|
}
|
||||||
|
@ -409,11 +430,13 @@ func copyTopicToTopicUser(topic *Topic, user *User) (tu TopicUser) {
|
||||||
tu.Sticky = topic.Sticky
|
tu.Sticky = topic.Sticky
|
||||||
tu.CreatedAt = topic.CreatedAt
|
tu.CreatedAt = topic.CreatedAt
|
||||||
tu.LastReplyAt = topic.LastReplyAt
|
tu.LastReplyAt = topic.LastReplyAt
|
||||||
|
tu.LastReplyBy = topic.LastReplyBy
|
||||||
tu.ParentID = topic.ParentID
|
tu.ParentID = topic.ParentID
|
||||||
tu.IPAddress = topic.IPAddress
|
tu.IPAddress = topic.IPAddress
|
||||||
tu.ViewCount = topic.ViewCount
|
tu.ViewCount = topic.ViewCount
|
||||||
tu.PostCount = topic.PostCount
|
tu.PostCount = topic.PostCount
|
||||||
tu.LikeCount = topic.LikeCount
|
tu.LikeCount = topic.LikeCount
|
||||||
|
tu.AttachCount = topic.AttachCount
|
||||||
tu.Poll = topic.Poll
|
tu.Poll = topic.Poll
|
||||||
tu.Data = topic.Data
|
tu.Data = topic.Data
|
||||||
|
|
||||||
|
|
|
@ -211,7 +211,7 @@ func (tList *DefaultTopicList) getList(page int, orderby string, argList []inter
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Prepare common qlist lengths to speed this up in common cases, prepared statements are prepared lazily anyway, so it probably doesn't matter if we do ten or so
|
// TODO: Prepare common qlist lengths to speed this up in common cases, prepared statements are prepared lazily anyway, so it probably doesn't matter if we do ten or so
|
||||||
stmt, err := qgen.Builder.SimpleSelect("topics", "tid, title, content, createdBy, is_closed, sticky, createdAt, lastReplyAt, lastReplyBy, parentID, views, postCount, likeCount", "parentID IN("+qlist+")", orderq, "?,?")
|
stmt, err := qgen.Builder.SimpleSelect("topics", "tid, title, content, createdBy, is_closed, sticky, createdAt, lastReplyAt, lastReplyBy, lastReplyID, parentID, views, postCount, likeCount", "parentID IN("+qlist+")", orderq, "?,?")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, Paginator{nil, 1, 1}, err
|
return nil, Paginator{nil, 1, 1}, err
|
||||||
}
|
}
|
||||||
|
@ -230,7 +230,7 @@ func (tList *DefaultTopicList) getList(page int, orderby string, argList []inter
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
// TODO: Embed Topic structs in TopicsRow to make it easier for us to reuse this work in the topic cache
|
// TODO: Embed Topic structs in TopicsRow to make it easier for us to reuse this work in the topic cache
|
||||||
topicItem := TopicsRow{ID: 0}
|
topicItem := TopicsRow{ID: 0}
|
||||||
err := rows.Scan(&topicItem.ID, &topicItem.Title, &topicItem.Content, &topicItem.CreatedBy, &topicItem.IsClosed, &topicItem.Sticky, &topicItem.CreatedAt, &topicItem.LastReplyAt, &topicItem.LastReplyBy, &topicItem.ParentID, &topicItem.ViewCount, &topicItem.PostCount, &topicItem.LikeCount)
|
err := rows.Scan(&topicItem.ID, &topicItem.Title, &topicItem.Content, &topicItem.CreatedBy, &topicItem.IsClosed, &topicItem.Sticky, &topicItem.CreatedAt, &topicItem.LastReplyAt, &topicItem.LastReplyBy, &topicItem.LastReplyID, &topicItem.ParentID, &topicItem.ViewCount, &topicItem.PostCount, &topicItem.LikeCount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, Paginator{nil, 1, 1}, err
|
return nil, Paginator{nil, 1, 1}, err
|
||||||
}
|
}
|
||||||
|
@ -241,8 +241,6 @@ func (tList *DefaultTopicList) getList(page int, orderby string, argList []inter
|
||||||
topicItem.ForumName = forum.Name
|
topicItem.ForumName = forum.Name
|
||||||
topicItem.ForumLink = forum.Link
|
topicItem.ForumLink = forum.Link
|
||||||
|
|
||||||
//topicItem.RelativeCreatedAt = RelativeTime(topicItem.CreatedAt)
|
|
||||||
topicItem.RelativeLastReplyAt = RelativeTime(topicItem.LastReplyAt)
|
|
||||||
// TODO: Create a specialised function with a bit less overhead for getting the last page for a post count
|
// TODO: Create a specialised function with a bit less overhead for getting the last page for a post count
|
||||||
_, _, lastPage := PageOffset(topicItem.PostCount, 1, Config.ItemsPerPage)
|
_, _, lastPage := PageOffset(topicItem.PostCount, 1, Config.ItemsPerPage)
|
||||||
topicItem.LastPage = lastPage
|
topicItem.LastPage = lastPage
|
||||||
|
|
|
@ -57,7 +57,7 @@ func NewDefaultTopicStore(cache TopicCache) (*DefaultTopicStore, error) {
|
||||||
}
|
}
|
||||||
return &DefaultTopicStore{
|
return &DefaultTopicStore{
|
||||||
cache: cache,
|
cache: cache,
|
||||||
get: acc.Select("topics").Columns("title, content, createdBy, createdAt, lastReplyAt, is_closed, sticky, parentID, ipaddress, views, postCount, likeCount, poll, data").Where("tid = ?").Prepare(),
|
get: acc.Select("topics").Columns("title, content, createdBy, createdAt, lastReplyAt, lastReplyID, is_closed, sticky, parentID, ipaddress, views, postCount, likeCount, attachCount, poll, data").Where("tid = ?").Prepare(),
|
||||||
exists: acc.Select("topics").Columns("tid").Where("tid = ?").Prepare(),
|
exists: acc.Select("topics").Columns("tid").Where("tid = ?").Prepare(),
|
||||||
topicCount: acc.Count("topics").Prepare(),
|
topicCount: acc.Count("topics").Prepare(),
|
||||||
create: acc.Insert("topics").Columns("parentID, title, content, parsed_content, createdAt, lastReplyAt, lastReplyBy, ipaddress, words, createdBy").Fields("?,?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),?,?,?,?").Prepare(),
|
create: acc.Insert("topics").Columns("parentID, title, content, parsed_content, createdAt, lastReplyAt, lastReplyBy, ipaddress, words, createdBy").Fields("?,?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),?,?,?,?").Prepare(),
|
||||||
|
@ -71,7 +71,7 @@ func (mts *DefaultTopicStore) DirtyGet(id int) *Topic {
|
||||||
}
|
}
|
||||||
|
|
||||||
topic = &Topic{ID: id}
|
topic = &Topic{ID: id}
|
||||||
err = mts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.LastReplyAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.ViewCount, &topic.PostCount, &topic.LikeCount, &topic.Poll, &topic.Data)
|
err = mts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.LastReplyAt, &topic.LastReplyID, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.ViewCount, &topic.PostCount, &topic.LikeCount, &topic.AttachCount, &topic.Poll, &topic.Data)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
topic.Link = BuildTopicURL(NameToSlug(topic.Title), id)
|
topic.Link = BuildTopicURL(NameToSlug(topic.Title), id)
|
||||||
_ = mts.cache.Add(topic)
|
_ = mts.cache.Add(topic)
|
||||||
|
@ -88,7 +88,7 @@ func (mts *DefaultTopicStore) Get(id int) (topic *Topic, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
topic = &Topic{ID: id}
|
topic = &Topic{ID: id}
|
||||||
err = mts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.LastReplyAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.ViewCount, &topic.PostCount, &topic.LikeCount, &topic.Poll, &topic.Data)
|
err = mts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.LastReplyAt, &topic.LastReplyID, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.ViewCount, &topic.PostCount, &topic.LikeCount, &topic.AttachCount, &topic.Poll, &topic.Data)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
topic.Link = BuildTopicURL(NameToSlug(topic.Title), id)
|
topic.Link = BuildTopicURL(NameToSlug(topic.Title), id)
|
||||||
_ = mts.cache.Add(topic)
|
_ = mts.cache.Add(topic)
|
||||||
|
@ -99,14 +99,14 @@ func (mts *DefaultTopicStore) Get(id int) (topic *Topic, err error) {
|
||||||
// BypassGet will always bypass the cache and pull the topic directly from the database
|
// BypassGet will always bypass the cache and pull the topic directly from the database
|
||||||
func (mts *DefaultTopicStore) BypassGet(id int) (*Topic, error) {
|
func (mts *DefaultTopicStore) BypassGet(id int) (*Topic, error) {
|
||||||
topic := &Topic{ID: id}
|
topic := &Topic{ID: id}
|
||||||
err := mts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.LastReplyAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.ViewCount, &topic.PostCount, &topic.LikeCount, &topic.Poll, &topic.Data)
|
err := mts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.LastReplyAt, &topic.LastReplyID, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.ViewCount, &topic.PostCount, &topic.LikeCount, &topic.AttachCount, &topic.Poll, &topic.Data)
|
||||||
topic.Link = BuildTopicURL(NameToSlug(topic.Title), id)
|
topic.Link = BuildTopicURL(NameToSlug(topic.Title), id)
|
||||||
return topic, err
|
return topic, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mts *DefaultTopicStore) Reload(id int) error {
|
func (mts *DefaultTopicStore) Reload(id int) error {
|
||||||
topic := &Topic{ID: id}
|
topic := &Topic{ID: id}
|
||||||
err := mts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.LastReplyAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.ViewCount, &topic.PostCount, &topic.LikeCount, &topic.Poll, &topic.Data)
|
err := mts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.LastReplyAt, &topic.LastReplyID, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.ViewCount, &topic.PostCount, &topic.LikeCount, &topic.AttachCount, &topic.Poll, &topic.Data)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
topic.Link = BuildTopicURL(NameToSlug(topic.Title), id)
|
topic.Link = BuildTopicURL(NameToSlug(topic.Title), id)
|
||||||
_ = mts.cache.Set(topic)
|
_ = mts.cache.Set(topic)
|
||||||
|
|
|
@ -202,6 +202,7 @@ func ConvertByteInUnit(bytes float64, unit string) (count float64) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Write a test for this
|
// TODO: Write a test for this
|
||||||
|
// TODO: Localise this?
|
||||||
func FriendlyUnitToBytes(quantity int, unit string) (bytes int, err error) {
|
func FriendlyUnitToBytes(quantity int, unit string) (bytes int, err error) {
|
||||||
switch unit {
|
switch unit {
|
||||||
case "PB":
|
case "PB":
|
||||||
|
@ -323,7 +324,7 @@ func WeakPassword(password string, username string, email string) error {
|
||||||
return errors.New("Your password needs to be at-least eight characters long")
|
return errors.New("Your password needs to be at-least eight characters long")
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(lowPassword, "test") || /*strings.Contains(password,"123456") || */ strings.Contains(password, "123") || strings.Contains(lowPassword, "password") || strings.Contains(lowPassword, "qwerty") || strings.Contains(lowPassword, "fuck") || strings.Contains(lowPassword, "love") {
|
if strings.Contains(lowPassword, "test") || strings.Contains(password, "123") || strings.Contains(lowPassword, "password") || strings.Contains(lowPassword, "qwerty") || strings.Contains(lowPassword, "fuck") || strings.Contains(lowPassword, "love") {
|
||||||
return errors.New("You may not have 'test', '123', 'password', 'qwerty', 'love' or 'fuck' in your password")
|
return errors.New("You may not have 'test', '123', 'password', 'qwerty', 'love' or 'fuck' in your password")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ func (hub *WsHubImpl) Start() {
|
||||||
AddScheduledSecondTask(hub.Tick)
|
AddScheduledSecondTask(hub.Tick)
|
||||||
}
|
}
|
||||||
|
|
||||||
// This Tick is seperate from the admin one, as we want to process that in parallel with this due to the blocking calls to gopsutil
|
// This Tick is separate from the admin one, as we want to process that in parallel with this due to the blocking calls to gopsutil
|
||||||
func (hub *WsHubImpl) Tick() error {
|
func (hub *WsHubImpl) Tick() error {
|
||||||
// Don't waste CPU time if nothing has happened
|
// Don't waste CPU time if nothing has happened
|
||||||
// TODO: Get a topic list method which strips stickies?
|
// TODO: Get a topic list method which strips stickies?
|
||||||
|
|
|
@ -2,8 +2,6 @@ echo "Updating the dependencies"
|
||||||
go get
|
go get
|
||||||
|
|
||||||
echo "Updating Gosora"
|
echo "Updating Gosora"
|
||||||
rm ./schema/lastSchema.json
|
|
||||||
cp ./schema/schema.json ./schema/lastSchema.json
|
|
||||||
git stash
|
git stash
|
||||||
git pull origin master
|
git pull origin master
|
||||||
git stash apply
|
git stash apply
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
echo "Building the patcher"
|
echo "Building the patcher"
|
||||||
cp ./schema/schema.json ./schema/lastSchema.json
|
|
||||||
go generate
|
go generate
|
||||||
go build -o Patcher "./patcher"
|
go build -o Patcher "./patcher"
|
|
@ -8,10 +8,6 @@ if %errorlevel% neq 0 (
|
||||||
)
|
)
|
||||||
|
|
||||||
echo Updating Gosora
|
echo Updating Gosora
|
||||||
cd schema
|
|
||||||
del /Q lastSchema.json
|
|
||||||
copy schema.json lastSchema.json
|
|
||||||
cd ..
|
|
||||||
git stash
|
git stash
|
||||||
if %errorlevel% neq 0 (
|
if %errorlevel% neq 0 (
|
||||||
pause
|
pause
|
||||||
|
|
|
@ -4,7 +4,7 @@ The update system is currently under development, but you can run `dev-update.ba
|
||||||
|
|
||||||
If you run into any issues doing so, please open an issue: https://github.com/Azareal/Gosora/issues/new
|
If you run into any issues doing so, please open an issue: https://github.com/Azareal/Gosora/issues/new
|
||||||
|
|
||||||
If you want to manually patch Gosora rather than relying on the above scripts to do it, you'll first have to create a copy of `./schema/schema.json` named `./schema/lastSchema.json`, and then, you'll overwrite the files with the new ones with `git pull origin master`.
|
If you want to manually patch Gosora rather than relying on the above scripts to do it, you'll first want to save your changes with `git stash`, and then, you'll overwrite the files with the new ones with `git pull origin master`, and then, you can re-apply your custom changes with `git stash apply`
|
||||||
|
|
||||||
After that, you'll need to run `go build ./patcher`.
|
After that, you'll need to run `go build ./patcher`.
|
||||||
|
|
||||||
|
@ -16,14 +16,9 @@ The update system is currently under development, but you can run `dev-update-li
|
||||||
|
|
||||||
If you run into any issues doing so, please open an issue: https://github.com/Azareal/Gosora/issues/new
|
If you run into any issues doing so, please open an issue: https://github.com/Azareal/Gosora/issues/new
|
||||||
|
|
||||||
If you want to manually patch Gosora rather than relying on the above scripts to do it, you'll first have to create a copy of `./schema/schema.json` named `./schema/lastSchema.json`, and then, you'll overwrite the files with the new ones with `git pull origin master`.
|
If you want to manually patch Gosora rather than relying on the above scripts to do it, you'll first want to save your changes with `git stash`, and then, you'll overwrite the files with the new ones with `git pull origin master`, and then, you'll re-apply your changes with `git stash apply`.
|
||||||
|
|
||||||
After that, you'll need to run the following code block:
|
After that, you'll need to run `go build -o Patcher "./patcher"`
|
||||||
```
|
|
||||||
cd ./patcher
|
|
||||||
go build -o Patcher
|
|
||||||
mv ./Patcher ..
|
|
||||||
```
|
|
||||||
|
|
||||||
Once you've done that, you just need to run `./Patcher` to apply the latest patches to the database, etc.
|
Once you've done that, you just need to run `./Patcher` to apply the latest patches to the database, etc.
|
||||||
|
|
||||||
|
@ -46,14 +41,9 @@ Replace that name and email with whatever you like. This name and email only app
|
||||||
|
|
||||||
If you get an access denied error, then you might need to run `chown -R gosora /home/gosora` and `chgrp -R www-data /home/gosora` to fix the ownership of the files.
|
If you get an access denied error, then you might need to run `chown -R gosora /home/gosora` and `chgrp -R www-data /home/gosora` to fix the ownership of the files.
|
||||||
|
|
||||||
If you want to manually patch Gosora rather than relying on the above scripts to do it, you'll first have to create a copy of `./schema/schema.json` named `./schema/lastSchema.json`, and then, you'll overwrite the files with the new ones with `git pull origin master`.
|
If you want to manually patch Gosora rather than relying on the above scripts to do it, you'll first want to save your changes with `git stash`, and then, you'll overwrite the files with the new ones with `git pull origin master`, and then, you'll re-apply your changes with `git stash apply`.
|
||||||
|
|
||||||
After that, you'll need to run:
|
After that, you'll need to run `go build -o Patcher "./patcher"`
|
||||||
```
|
|
||||||
cd ./patcher
|
|
||||||
go build -o Patcher
|
|
||||||
mv ./Patcher ..
|
|
||||||
```
|
|
||||||
|
|
||||||
Once you've done that, you just need to run `./Patcher` to apply the latest patches to the database, etc.
|
Once you've done that, you just need to run `./Patcher` to apply the latest patches to the database, etc.
|
||||||
|
|
||||||
|
|
180
gen_router.go
180
gen_router.go
|
@ -128,6 +128,8 @@ var RouteMap = map[string]interface{}{
|
||||||
"routes.UnlockTopicSubmit": routes.UnlockTopicSubmit,
|
"routes.UnlockTopicSubmit": routes.UnlockTopicSubmit,
|
||||||
"routes.MoveTopicSubmit": routes.MoveTopicSubmit,
|
"routes.MoveTopicSubmit": routes.MoveTopicSubmit,
|
||||||
"routes.LikeTopicSubmit": routes.LikeTopicSubmit,
|
"routes.LikeTopicSubmit": routes.LikeTopicSubmit,
|
||||||
|
"routes.AddAttachToTopicSubmit": routes.AddAttachToTopicSubmit,
|
||||||
|
"routes.RemoveAttachFromTopicSubmit": routes.RemoveAttachFromTopicSubmit,
|
||||||
"routes.ViewTopic": routes.ViewTopic,
|
"routes.ViewTopic": routes.ViewTopic,
|
||||||
"routes.CreateReplySubmit": routes.CreateReplySubmit,
|
"routes.CreateReplySubmit": routes.CreateReplySubmit,
|
||||||
"routes.ReplyEditSubmit": routes.ReplyEditSubmit,
|
"routes.ReplyEditSubmit": routes.ReplyEditSubmit,
|
||||||
|
@ -261,29 +263,31 @@ var routeMapEnum = map[string]int{
|
||||||
"routes.UnlockTopicSubmit": 103,
|
"routes.UnlockTopicSubmit": 103,
|
||||||
"routes.MoveTopicSubmit": 104,
|
"routes.MoveTopicSubmit": 104,
|
||||||
"routes.LikeTopicSubmit": 105,
|
"routes.LikeTopicSubmit": 105,
|
||||||
"routes.ViewTopic": 106,
|
"routes.AddAttachToTopicSubmit": 106,
|
||||||
"routes.CreateReplySubmit": 107,
|
"routes.RemoveAttachFromTopicSubmit": 107,
|
||||||
"routes.ReplyEditSubmit": 108,
|
"routes.ViewTopic": 108,
|
||||||
"routes.ReplyDeleteSubmit": 109,
|
"routes.CreateReplySubmit": 109,
|
||||||
"routes.ReplyLikeSubmit": 110,
|
"routes.ReplyEditSubmit": 110,
|
||||||
"routes.ProfileReplyCreateSubmit": 111,
|
"routes.ReplyDeleteSubmit": 111,
|
||||||
"routes.ProfileReplyEditSubmit": 112,
|
"routes.ReplyLikeSubmit": 112,
|
||||||
"routes.ProfileReplyDeleteSubmit": 113,
|
"routes.ProfileReplyCreateSubmit": 113,
|
||||||
"routes.PollVote": 114,
|
"routes.ProfileReplyEditSubmit": 114,
|
||||||
"routes.PollResults": 115,
|
"routes.ProfileReplyDeleteSubmit": 115,
|
||||||
"routes.AccountLogin": 116,
|
"routes.PollVote": 116,
|
||||||
"routes.AccountRegister": 117,
|
"routes.PollResults": 117,
|
||||||
"routes.AccountLogout": 118,
|
"routes.AccountLogin": 118,
|
||||||
"routes.AccountLoginSubmit": 119,
|
"routes.AccountRegister": 119,
|
||||||
"routes.AccountLoginMFAVerify": 120,
|
"routes.AccountLogout": 120,
|
||||||
"routes.AccountLoginMFAVerifySubmit": 121,
|
"routes.AccountLoginSubmit": 121,
|
||||||
"routes.AccountRegisterSubmit": 122,
|
"routes.AccountLoginMFAVerify": 122,
|
||||||
"routes.DynamicRoute": 123,
|
"routes.AccountLoginMFAVerifySubmit": 123,
|
||||||
"routes.UploadedFile": 124,
|
"routes.AccountRegisterSubmit": 124,
|
||||||
"routes.StaticFile": 125,
|
"routes.DynamicRoute": 125,
|
||||||
"routes.RobotsTxt": 126,
|
"routes.UploadedFile": 126,
|
||||||
"routes.SitemapXml": 127,
|
"routes.StaticFile": 127,
|
||||||
"routes.BadRoute": 128,
|
"routes.RobotsTxt": 128,
|
||||||
|
"routes.SitemapXml": 129,
|
||||||
|
"routes.BadRoute": 130,
|
||||||
}
|
}
|
||||||
var reverseRouteMapEnum = map[int]string{
|
var reverseRouteMapEnum = map[int]string{
|
||||||
0: "routes.Overview",
|
0: "routes.Overview",
|
||||||
|
@ -392,29 +396,31 @@ var reverseRouteMapEnum = map[int]string{
|
||||||
103: "routes.UnlockTopicSubmit",
|
103: "routes.UnlockTopicSubmit",
|
||||||
104: "routes.MoveTopicSubmit",
|
104: "routes.MoveTopicSubmit",
|
||||||
105: "routes.LikeTopicSubmit",
|
105: "routes.LikeTopicSubmit",
|
||||||
106: "routes.ViewTopic",
|
106: "routes.AddAttachToTopicSubmit",
|
||||||
107: "routes.CreateReplySubmit",
|
107: "routes.RemoveAttachFromTopicSubmit",
|
||||||
108: "routes.ReplyEditSubmit",
|
108: "routes.ViewTopic",
|
||||||
109: "routes.ReplyDeleteSubmit",
|
109: "routes.CreateReplySubmit",
|
||||||
110: "routes.ReplyLikeSubmit",
|
110: "routes.ReplyEditSubmit",
|
||||||
111: "routes.ProfileReplyCreateSubmit",
|
111: "routes.ReplyDeleteSubmit",
|
||||||
112: "routes.ProfileReplyEditSubmit",
|
112: "routes.ReplyLikeSubmit",
|
||||||
113: "routes.ProfileReplyDeleteSubmit",
|
113: "routes.ProfileReplyCreateSubmit",
|
||||||
114: "routes.PollVote",
|
114: "routes.ProfileReplyEditSubmit",
|
||||||
115: "routes.PollResults",
|
115: "routes.ProfileReplyDeleteSubmit",
|
||||||
116: "routes.AccountLogin",
|
116: "routes.PollVote",
|
||||||
117: "routes.AccountRegister",
|
117: "routes.PollResults",
|
||||||
118: "routes.AccountLogout",
|
118: "routes.AccountLogin",
|
||||||
119: "routes.AccountLoginSubmit",
|
119: "routes.AccountRegister",
|
||||||
120: "routes.AccountLoginMFAVerify",
|
120: "routes.AccountLogout",
|
||||||
121: "routes.AccountLoginMFAVerifySubmit",
|
121: "routes.AccountLoginSubmit",
|
||||||
122: "routes.AccountRegisterSubmit",
|
122: "routes.AccountLoginMFAVerify",
|
||||||
123: "routes.DynamicRoute",
|
123: "routes.AccountLoginMFAVerifySubmit",
|
||||||
124: "routes.UploadedFile",
|
124: "routes.AccountRegisterSubmit",
|
||||||
125: "routes.StaticFile",
|
125: "routes.DynamicRoute",
|
||||||
126: "routes.RobotsTxt",
|
126: "routes.UploadedFile",
|
||||||
127: "routes.SitemapXml",
|
127: "routes.StaticFile",
|
||||||
128: "routes.BadRoute",
|
128: "routes.RobotsTxt",
|
||||||
|
129: "routes.SitemapXml",
|
||||||
|
130: "routes.BadRoute",
|
||||||
}
|
}
|
||||||
var osMapEnum = map[string]int{
|
var osMapEnum = map[string]int{
|
||||||
"unknown": 0,
|
"unknown": 0,
|
||||||
|
@ -705,7 +711,7 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
counters.GlobalViewCounter.Bump()
|
counters.GlobalViewCounter.Bump()
|
||||||
|
|
||||||
if prefix == "/static" {
|
if prefix == "/static" {
|
||||||
counters.RouteViewCounter.Bump(125)
|
counters.RouteViewCounter.Bump(127)
|
||||||
req.URL.Path += extraData
|
req.URL.Path += extraData
|
||||||
routes.StaticFile(w, req)
|
routes.StaticFile(w, req)
|
||||||
return
|
return
|
||||||
|
@ -1780,15 +1786,40 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = common.ParseForm(w,req,user)
|
counters.RouteViewCounter.Bump(105)
|
||||||
|
err = routes.LikeTopicSubmit(w,req,user,extraData)
|
||||||
|
case "/topic/attach/add/submit/":
|
||||||
|
err = common.MemberOnly(w,req,user)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = common.HandleUploadRoute(w,req,user,int(common.Config.MaxRequestSize))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = common.NoUploadSessionMismatch(w,req,user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
counters.RouteViewCounter.Bump(105)
|
|
||||||
err = routes.LikeTopicSubmit(w,req,user,extraData)
|
|
||||||
default:
|
|
||||||
counters.RouteViewCounter.Bump(106)
|
counters.RouteViewCounter.Bump(106)
|
||||||
|
err = routes.AddAttachToTopicSubmit(w,req,user,extraData)
|
||||||
|
case "/topic/attach/remove/submit/":
|
||||||
|
err = common.NoSessionMismatch(w,req,user)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = common.MemberOnly(w,req,user)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
counters.RouteViewCounter.Bump(107)
|
||||||
|
err = routes.RemoveAttachFromTopicSubmit(w,req,user,extraData)
|
||||||
|
default:
|
||||||
|
counters.RouteViewCounter.Bump(108)
|
||||||
head, err := common.UserCheck(w,req,&user)
|
head, err := common.UserCheck(w,req,&user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1812,7 +1843,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
counters.RouteViewCounter.Bump(107)
|
counters.RouteViewCounter.Bump(109)
|
||||||
err = routes.CreateReplySubmit(w,req,user)
|
err = routes.CreateReplySubmit(w,req,user)
|
||||||
case "/reply/edit/submit/":
|
case "/reply/edit/submit/":
|
||||||
err = common.NoSessionMismatch(w,req,user)
|
err = common.NoSessionMismatch(w,req,user)
|
||||||
|
@ -1825,7 +1856,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
counters.RouteViewCounter.Bump(108)
|
counters.RouteViewCounter.Bump(110)
|
||||||
err = routes.ReplyEditSubmit(w,req,user,extraData)
|
err = routes.ReplyEditSubmit(w,req,user,extraData)
|
||||||
case "/reply/delete/submit/":
|
case "/reply/delete/submit/":
|
||||||
err = common.NoSessionMismatch(w,req,user)
|
err = common.NoSessionMismatch(w,req,user)
|
||||||
|
@ -1838,7 +1869,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
counters.RouteViewCounter.Bump(109)
|
counters.RouteViewCounter.Bump(111)
|
||||||
err = routes.ReplyDeleteSubmit(w,req,user,extraData)
|
err = routes.ReplyDeleteSubmit(w,req,user,extraData)
|
||||||
case "/reply/like/submit/":
|
case "/reply/like/submit/":
|
||||||
err = common.NoSessionMismatch(w,req,user)
|
err = common.NoSessionMismatch(w,req,user)
|
||||||
|
@ -1851,12 +1882,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = common.ParseForm(w,req,user)
|
counters.RouteViewCounter.Bump(112)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
counters.RouteViewCounter.Bump(110)
|
|
||||||
err = routes.ReplyLikeSubmit(w,req,user,extraData)
|
err = routes.ReplyLikeSubmit(w,req,user,extraData)
|
||||||
}
|
}
|
||||||
case "/profile":
|
case "/profile":
|
||||||
|
@ -1872,7 +1898,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
counters.RouteViewCounter.Bump(111)
|
counters.RouteViewCounter.Bump(113)
|
||||||
err = routes.ProfileReplyCreateSubmit(w,req,user)
|
err = routes.ProfileReplyCreateSubmit(w,req,user)
|
||||||
case "/profile/reply/edit/submit/":
|
case "/profile/reply/edit/submit/":
|
||||||
err = common.NoSessionMismatch(w,req,user)
|
err = common.NoSessionMismatch(w,req,user)
|
||||||
|
@ -1885,7 +1911,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
counters.RouteViewCounter.Bump(112)
|
counters.RouteViewCounter.Bump(114)
|
||||||
err = routes.ProfileReplyEditSubmit(w,req,user,extraData)
|
err = routes.ProfileReplyEditSubmit(w,req,user,extraData)
|
||||||
case "/profile/reply/delete/submit/":
|
case "/profile/reply/delete/submit/":
|
||||||
err = common.NoSessionMismatch(w,req,user)
|
err = common.NoSessionMismatch(w,req,user)
|
||||||
|
@ -1898,7 +1924,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
counters.RouteViewCounter.Bump(113)
|
counters.RouteViewCounter.Bump(115)
|
||||||
err = routes.ProfileReplyDeleteSubmit(w,req,user,extraData)
|
err = routes.ProfileReplyDeleteSubmit(w,req,user,extraData)
|
||||||
}
|
}
|
||||||
case "/poll":
|
case "/poll":
|
||||||
|
@ -1914,23 +1940,23 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
counters.RouteViewCounter.Bump(114)
|
counters.RouteViewCounter.Bump(116)
|
||||||
err = routes.PollVote(w,req,user,extraData)
|
err = routes.PollVote(w,req,user,extraData)
|
||||||
case "/poll/results/":
|
case "/poll/results/":
|
||||||
counters.RouteViewCounter.Bump(115)
|
counters.RouteViewCounter.Bump(117)
|
||||||
err = routes.PollResults(w,req,user,extraData)
|
err = routes.PollResults(w,req,user,extraData)
|
||||||
}
|
}
|
||||||
case "/accounts":
|
case "/accounts":
|
||||||
switch(req.URL.Path) {
|
switch(req.URL.Path) {
|
||||||
case "/accounts/login/":
|
case "/accounts/login/":
|
||||||
counters.RouteViewCounter.Bump(116)
|
counters.RouteViewCounter.Bump(118)
|
||||||
head, err := common.UserCheck(w,req,&user)
|
head, err := common.UserCheck(w,req,&user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = routes.AccountLogin(w,req,user,head)
|
err = routes.AccountLogin(w,req,user,head)
|
||||||
case "/accounts/create/":
|
case "/accounts/create/":
|
||||||
counters.RouteViewCounter.Bump(117)
|
counters.RouteViewCounter.Bump(119)
|
||||||
head, err := common.UserCheck(w,req,&user)
|
head, err := common.UserCheck(w,req,&user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1947,7 +1973,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
counters.RouteViewCounter.Bump(118)
|
counters.RouteViewCounter.Bump(120)
|
||||||
err = routes.AccountLogout(w,req,user)
|
err = routes.AccountLogout(w,req,user)
|
||||||
case "/accounts/login/submit/":
|
case "/accounts/login/submit/":
|
||||||
err = common.ParseForm(w,req,user)
|
err = common.ParseForm(w,req,user)
|
||||||
|
@ -1955,10 +1981,10 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
counters.RouteViewCounter.Bump(119)
|
counters.RouteViewCounter.Bump(121)
|
||||||
err = routes.AccountLoginSubmit(w,req,user)
|
err = routes.AccountLoginSubmit(w,req,user)
|
||||||
case "/accounts/mfa_verify/":
|
case "/accounts/mfa_verify/":
|
||||||
counters.RouteViewCounter.Bump(120)
|
counters.RouteViewCounter.Bump(122)
|
||||||
head, err := common.UserCheck(w,req,&user)
|
head, err := common.UserCheck(w,req,&user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1970,7 +1996,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
counters.RouteViewCounter.Bump(121)
|
counters.RouteViewCounter.Bump(123)
|
||||||
err = routes.AccountLoginMFAVerifySubmit(w,req,user)
|
err = routes.AccountLoginMFAVerifySubmit(w,req,user)
|
||||||
case "/accounts/create/submit/":
|
case "/accounts/create/submit/":
|
||||||
err = common.ParseForm(w,req,user)
|
err = common.ParseForm(w,req,user)
|
||||||
|
@ -1978,7 +2004,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
counters.RouteViewCounter.Bump(122)
|
counters.RouteViewCounter.Bump(124)
|
||||||
err = routes.AccountRegisterSubmit(w,req,user)
|
err = routes.AccountRegisterSubmit(w,req,user)
|
||||||
}
|
}
|
||||||
/*case "/sitemaps": // TODO: Count these views
|
/*case "/sitemaps": // TODO: Count these views
|
||||||
|
@ -1994,7 +2020,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
w.Header().Del("Content-Type")
|
w.Header().Del("Content-Type")
|
||||||
w.Header().Del("Content-Encoding")
|
w.Header().Del("Content-Encoding")
|
||||||
}
|
}
|
||||||
counters.RouteViewCounter.Bump(124)
|
counters.RouteViewCounter.Bump(126)
|
||||||
req.URL.Path += extraData
|
req.URL.Path += extraData
|
||||||
// TODO: Find a way to propagate errors up from this?
|
// TODO: Find a way to propagate errors up from this?
|
||||||
r.UploadHandler(w,req) // TODO: Count these views
|
r.UploadHandler(w,req) // TODO: Count these views
|
||||||
|
@ -2004,10 +2030,10 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
// TODO: Add support for favicons and robots.txt files
|
// TODO: Add support for favicons and robots.txt files
|
||||||
switch(extraData) {
|
switch(extraData) {
|
||||||
case "robots.txt":
|
case "robots.txt":
|
||||||
counters.RouteViewCounter.Bump(126)
|
counters.RouteViewCounter.Bump(128)
|
||||||
return routes.RobotsTxt(w,req)
|
return routes.RobotsTxt(w,req)
|
||||||
/*case "sitemap.xml":
|
/*case "sitemap.xml":
|
||||||
counters.RouteViewCounter.Bump(127)
|
counters.RouteViewCounter.Bump(129)
|
||||||
return routes.SitemapXml(w,req)*/
|
return routes.SitemapXml(w,req)*/
|
||||||
}
|
}
|
||||||
return common.NotFound(w,req,nil)
|
return common.NotFound(w,req,nil)
|
||||||
|
@ -2018,7 +2044,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
r.RUnlock()
|
r.RUnlock()
|
||||||
|
|
||||||
if ok {
|
if ok {
|
||||||
counters.RouteViewCounter.Bump(123) // TODO: Be more specific about *which* dynamic route it is
|
counters.RouteViewCounter.Bump(125) // TODO: Be more specific about *which* dynamic route it is
|
||||||
req.URL.Path += extraData
|
req.URL.Path += extraData
|
||||||
return handle(w,req,user)
|
return handle(w,req,user)
|
||||||
}
|
}
|
||||||
|
@ -2029,7 +2055,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
} else {
|
} else {
|
||||||
r.DumpRequest(req,"Bad Route")
|
r.DumpRequest(req,"Bad Route")
|
||||||
}
|
}
|
||||||
counters.RouteViewCounter.Bump(128)
|
counters.RouteViewCounter.Bump(130)
|
||||||
return common.NotFound(w,req,nil)
|
return common.NotFound(w,req,nil)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -92,7 +92,7 @@ func userStoreTest(t *testing.T, newUserID int) {
|
||||||
expect(t, cond, prefix+" "+midfix+" "+suffix)
|
expect(t, cond, prefix+" "+midfix+" "+suffix)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Add email checks too? Do them seperately?
|
// TODO: Add email checks too? Do them separately?
|
||||||
var expectUser = func(user *common.User, uid int, name string, group int, super bool, admin bool, mod bool, banned bool) {
|
var expectUser = func(user *common.User, uid int, name string, group int, super bool, admin bool, mod bool, banned bool) {
|
||||||
expect(t, user.ID == uid, fmt.Sprintf("user.ID should be %d. Got '%d' instead.", uid, user.ID))
|
expect(t, user.ID == uid, fmt.Sprintf("user.ID should be %d. Got '%d' instead.", uid, user.ID))
|
||||||
expect(t, user.Name == name, fmt.Sprintf("user.Name should be '%s', not '%s'", name, user.Name))
|
expect(t, user.Name == name, fmt.Sprintf("user.Name should be '%s', not '%s'", name, user.Name))
|
||||||
|
|
|
@ -2,6 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -89,20 +90,29 @@ type SchemaFile struct {
|
||||||
MinVersion string // TODO: Minimum version of Gosora to jump to this version, might be tricky as we don't store this in the schema file, maybe store it in the database
|
MinVersion string // TODO: Minimum version of Gosora to jump to this version, might be tricky as we don't store this in the schema file, maybe store it in the database
|
||||||
}
|
}
|
||||||
|
|
||||||
func patcher(scanner *bufio.Scanner) error {
|
func loadSchema() (schemaFile SchemaFile, err error) {
|
||||||
fmt.Println("Loading the schema file")
|
fmt.Println("Loading the schema file")
|
||||||
data, err := ioutil.ReadFile("./schema/lastSchema.json")
|
data, err := ioutil.ReadFile("./schema/lastSchema.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return schemaFile, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var schemaFile SchemaFile
|
|
||||||
err = json.Unmarshal(data, &schemaFile)
|
err = json.Unmarshal(data, &schemaFile)
|
||||||
if err != nil {
|
return schemaFile, err
|
||||||
return err
|
}
|
||||||
}
|
|
||||||
dbVersion, err := strconv.Atoi(schemaFile.DBVersion)
|
func patcher(scanner *bufio.Scanner) error {
|
||||||
if err != nil {
|
var dbVersion int
|
||||||
|
err := qgen.NewAcc().Select("updates").Columns("dbVersion").QueryRow().Scan(&dbVersion)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
schemaFile, err := loadSchema()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dbVersion, err = strconv.Atoi(schemaFile.DBVersion)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,6 +123,7 @@ func patcher(scanner *bufio.Scanner) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the queued up patches
|
// Run the queued up patches
|
||||||
|
var patched int
|
||||||
for index, patch := range pslice {
|
for index, patch := range pslice {
|
||||||
if dbVersion > index {
|
if dbVersion > index {
|
||||||
continue
|
continue
|
||||||
|
@ -121,6 +132,14 @@ func patcher(scanner *bufio.Scanner) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
patched++
|
||||||
|
}
|
||||||
|
|
||||||
|
if patched > 0 {
|
||||||
|
_, err := qgen.NewAcc().Update("updates").Set("dbVersion = ?").Exec(len(pslice))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -7,6 +7,9 @@ import (
|
||||||
"github.com/Azareal/Gosora/query_gen"
|
"github.com/Azareal/Gosora/query_gen"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type tblColumn = qgen.DBTableColumn
|
||||||
|
type tblKey = qgen.DBTableKey
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
addPatch(0, patch0)
|
addPatch(0, patch0)
|
||||||
addPatch(1, patch1)
|
addPatch(1, patch1)
|
||||||
|
@ -18,6 +21,7 @@ func init() {
|
||||||
addPatch(7, patch7)
|
addPatch(7, patch7)
|
||||||
addPatch(8, patch8)
|
addPatch(8, patch8)
|
||||||
addPatch(9, patch9)
|
addPatch(9, patch9)
|
||||||
|
addPatch(10, patch10)
|
||||||
}
|
}
|
||||||
|
|
||||||
func patch0(scanner *bufio.Scanner) (err error) {
|
func patch0(scanner *bufio.Scanner) (err error) {
|
||||||
|
@ -32,11 +36,11 @@ func patch0(scanner *bufio.Scanner) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
err = execStmt(qgen.Builder.CreateTable("menus", "", "",
|
err = execStmt(qgen.Builder.CreateTable("menus", "", "",
|
||||||
[]qgen.DBTableColumn{
|
[]tblColumn{
|
||||||
qgen.DBTableColumn{"mid", "int", 0, false, true, ""},
|
tblColumn{"mid", "int", 0, false, true, ""},
|
||||||
},
|
},
|
||||||
[]qgen.DBTableKey{
|
[]tblKey{
|
||||||
qgen.DBTableKey{"mid", "primary"},
|
tblKey{"mid", "primary"},
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -44,26 +48,26 @@ func patch0(scanner *bufio.Scanner) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
err = execStmt(qgen.Builder.CreateTable("menu_items", "", "",
|
err = execStmt(qgen.Builder.CreateTable("menu_items", "", "",
|
||||||
[]qgen.DBTableColumn{
|
[]tblColumn{
|
||||||
qgen.DBTableColumn{"miid", "int", 0, false, true, ""},
|
tblColumn{"miid", "int", 0, false, true, ""},
|
||||||
qgen.DBTableColumn{"mid", "int", 0, false, false, ""},
|
tblColumn{"mid", "int", 0, false, false, ""},
|
||||||
qgen.DBTableColumn{"name", "varchar", 200, false, false, ""},
|
tblColumn{"name", "varchar", 200, false, false, ""},
|
||||||
qgen.DBTableColumn{"htmlID", "varchar", 200, false, false, "''"},
|
tblColumn{"htmlID", "varchar", 200, false, false, "''"},
|
||||||
qgen.DBTableColumn{"cssClass", "varchar", 200, false, false, "''"},
|
tblColumn{"cssClass", "varchar", 200, false, false, "''"},
|
||||||
qgen.DBTableColumn{"position", "varchar", 100, false, false, ""},
|
tblColumn{"position", "varchar", 100, false, false, ""},
|
||||||
qgen.DBTableColumn{"path", "varchar", 200, false, false, "''"},
|
tblColumn{"path", "varchar", 200, false, false, "''"},
|
||||||
qgen.DBTableColumn{"aria", "varchar", 200, false, false, "''"},
|
tblColumn{"aria", "varchar", 200, false, false, "''"},
|
||||||
qgen.DBTableColumn{"tooltip", "varchar", 200, false, false, "''"},
|
tblColumn{"tooltip", "varchar", 200, false, false, "''"},
|
||||||
qgen.DBTableColumn{"tmplName", "varchar", 200, false, false, "''"},
|
tblColumn{"tmplName", "varchar", 200, false, false, "''"},
|
||||||
qgen.DBTableColumn{"order", "int", 0, false, false, "0"},
|
tblColumn{"order", "int", 0, false, false, "0"},
|
||||||
|
|
||||||
qgen.DBTableColumn{"guestOnly", "boolean", 0, false, false, "0"},
|
tblColumn{"guestOnly", "boolean", 0, false, false, "0"},
|
||||||
qgen.DBTableColumn{"memberOnly", "boolean", 0, false, false, "0"},
|
tblColumn{"memberOnly", "boolean", 0, false, false, "0"},
|
||||||
qgen.DBTableColumn{"staffOnly", "boolean", 0, false, false, "0"},
|
tblColumn{"staffOnly", "boolean", 0, false, false, "0"},
|
||||||
qgen.DBTableColumn{"adminOnly", "boolean", 0, false, false, "0"},
|
tblColumn{"adminOnly", "boolean", 0, false, false, "0"},
|
||||||
},
|
},
|
||||||
[]qgen.DBTableKey{
|
[]tblKey{
|
||||||
qgen.DBTableKey{"miid", "primary"},
|
tblKey{"miid", "primary"},
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -159,25 +163,20 @@ func patch2(scanner *bufio.Scanner) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func patch3(scanner *bufio.Scanner) error {
|
func patch3(scanner *bufio.Scanner) error {
|
||||||
err := execStmt(qgen.Builder.CreateTable("registration_logs", "", "",
|
return execStmt(qgen.Builder.CreateTable("registration_logs", "", "",
|
||||||
[]qgen.DBTableColumn{
|
[]tblColumn{
|
||||||
qgen.DBTableColumn{"rlid", "int", 0, false, true, ""},
|
tblColumn{"rlid", "int", 0, false, true, ""},
|
||||||
qgen.DBTableColumn{"username", "varchar", 100, false, false, ""},
|
tblColumn{"username", "varchar", 100, false, false, ""},
|
||||||
qgen.DBTableColumn{"email", "varchar", 100, false, false, ""},
|
tblColumn{"email", "varchar", 100, false, false, ""},
|
||||||
qgen.DBTableColumn{"failureReason", "varchar", 100, false, false, ""},
|
tblColumn{"failureReason", "varchar", 100, false, false, ""},
|
||||||
qgen.DBTableColumn{"success", "bool", 0, false, false, "0"}, // Did this attempt succeed?
|
tblColumn{"success", "bool", 0, false, false, "0"}, // Did this attempt succeed?
|
||||||
qgen.DBTableColumn{"ipaddress", "varchar", 200, false, false, ""},
|
tblColumn{"ipaddress", "varchar", 200, false, false, ""},
|
||||||
qgen.DBTableColumn{"doneAt", "createdAt", 0, false, false, ""},
|
tblColumn{"doneAt", "createdAt", 0, false, false, ""},
|
||||||
},
|
},
|
||||||
[]qgen.DBTableKey{
|
[]tblKey{
|
||||||
qgen.DBTableKey{"rlid", "primary"},
|
tblKey{"rlid", "primary"},
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func patch4(scanner *bufio.Scanner) error {
|
func patch4(scanner *bufio.Scanner) error {
|
||||||
|
@ -229,16 +228,16 @@ func patch4(scanner *bufio.Scanner) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
err = execStmt(qgen.Builder.CreateTable("pages", "utf8mb4", "utf8mb4_general_ci",
|
err = execStmt(qgen.Builder.CreateTable("pages", "utf8mb4", "utf8mb4_general_ci",
|
||||||
[]qgen.DBTableColumn{
|
[]tblColumn{
|
||||||
qgen.DBTableColumn{"pid", "int", 0, false, true, ""},
|
tblColumn{"pid", "int", 0, false, true, ""},
|
||||||
qgen.DBTableColumn{"name", "varchar", 200, false, false, ""},
|
tblColumn{"name", "varchar", 200, false, false, ""},
|
||||||
qgen.DBTableColumn{"title", "varchar", 200, false, false, ""},
|
tblColumn{"title", "varchar", 200, false, false, ""},
|
||||||
qgen.DBTableColumn{"body", "text", 0, false, false, ""},
|
tblColumn{"body", "text", 0, false, false, ""},
|
||||||
qgen.DBTableColumn{"allowedGroups", "text", 0, false, false, ""},
|
tblColumn{"allowedGroups", "text", 0, false, false, ""},
|
||||||
qgen.DBTableColumn{"menuID", "int", 0, false, false, "-1"},
|
tblColumn{"menuID", "int", 0, false, false, "-1"},
|
||||||
},
|
},
|
||||||
[]qgen.DBTableKey{
|
[]tblKey{
|
||||||
qgen.DBTableKey{"pid", "primary"},
|
tblKey{"pid", "primary"},
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -267,21 +266,21 @@ func patch5(scanner *bufio.Scanner) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
err = execStmt(qgen.Builder.CreateTable("users_2fa_keys", "utf8mb4", "utf8mb4_general_ci",
|
err = execStmt(qgen.Builder.CreateTable("users_2fa_keys", "utf8mb4", "utf8mb4_general_ci",
|
||||||
[]qgen.DBTableColumn{
|
[]tblColumn{
|
||||||
qgen.DBTableColumn{"uid", "int", 0, false, false, ""},
|
tblColumn{"uid", "int", 0, false, false, ""},
|
||||||
qgen.DBTableColumn{"secret", "varchar", 100, false, false, ""},
|
tblColumn{"secret", "varchar", 100, false, false, ""},
|
||||||
qgen.DBTableColumn{"scratch1", "varchar", 50, false, false, ""},
|
tblColumn{"scratch1", "varchar", 50, false, false, ""},
|
||||||
qgen.DBTableColumn{"scratch2", "varchar", 50, false, false, ""},
|
tblColumn{"scratch2", "varchar", 50, false, false, ""},
|
||||||
qgen.DBTableColumn{"scratch3", "varchar", 50, false, false, ""},
|
tblColumn{"scratch3", "varchar", 50, false, false, ""},
|
||||||
qgen.DBTableColumn{"scratch4", "varchar", 50, false, false, ""},
|
tblColumn{"scratch4", "varchar", 50, false, false, ""},
|
||||||
qgen.DBTableColumn{"scratch5", "varchar", 50, false, false, ""},
|
tblColumn{"scratch5", "varchar", 50, false, false, ""},
|
||||||
qgen.DBTableColumn{"scratch6", "varchar", 50, false, false, ""},
|
tblColumn{"scratch6", "varchar", 50, false, false, ""},
|
||||||
qgen.DBTableColumn{"scratch7", "varchar", 50, false, false, ""},
|
tblColumn{"scratch7", "varchar", 50, false, false, ""},
|
||||||
qgen.DBTableColumn{"scratch8", "varchar", 50, false, false, ""},
|
tblColumn{"scratch8", "varchar", 50, false, false, ""},
|
||||||
qgen.DBTableColumn{"createdAt", "createdAt", 0, false, false, ""},
|
tblColumn{"createdAt", "createdAt", 0, false, false, ""},
|
||||||
},
|
},
|
||||||
[]qgen.DBTableKey{
|
[]tblKey{
|
||||||
qgen.DBTableKey{"uid", "primary"},
|
tblKey{"uid", "primary"},
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -292,28 +291,18 @@ func patch5(scanner *bufio.Scanner) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func patch6(scanner *bufio.Scanner) error {
|
func patch6(scanner *bufio.Scanner) error {
|
||||||
err := execStmt(qgen.Builder.SimpleInsert("settings", "name, content, type", "'rapid_loading','1','bool'"))
|
return execStmt(qgen.Builder.SimpleInsert("settings", "name, content, type", "'rapid_loading','1','bool'"))
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func patch7(scanner *bufio.Scanner) error {
|
func patch7(scanner *bufio.Scanner) error {
|
||||||
err := execStmt(qgen.Builder.CreateTable("users_avatar_queue", "", "",
|
return execStmt(qgen.Builder.CreateTable("users_avatar_queue", "", "",
|
||||||
[]qgen.DBTableColumn{
|
[]tblColumn{
|
||||||
qgen.DBTableColumn{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key
|
tblColumn{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key
|
||||||
},
|
},
|
||||||
[]qgen.DBTableKey{
|
[]tblKey{
|
||||||
qgen.DBTableKey{"uid", "primary"},
|
tblKey{"uid", "primary"},
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func renameRoutes(routes map[string]string) error {
|
func renameRoutes(routes map[string]string) error {
|
||||||
|
@ -369,17 +358,12 @@ func patch8(scanner *bufio.Scanner) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = execStmt(qgen.Builder.CreateTable("updates", "", "",
|
return execStmt(qgen.Builder.CreateTable("updates", "", "",
|
||||||
[]qgen.DBTableColumn{
|
[]tblColumn{
|
||||||
qgen.DBTableColumn{"dbVersion", "int", 0, false, false, "0"},
|
tblColumn{"dbVersion", "int", 0, false, false, "0"},
|
||||||
},
|
},
|
||||||
[]qgen.DBTableKey{},
|
[]tblKey{},
|
||||||
))
|
))
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func patch9(scanner *bufio.Scanner) error {
|
func patch9(scanner *bufio.Scanner) error {
|
||||||
|
@ -389,21 +373,60 @@ func patch9(scanner *bufio.Scanner) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = execStmt(qgen.Builder.CreateTable("login_logs", "", "",
|
return execStmt(qgen.Builder.CreateTable("login_logs", "", "",
|
||||||
[]qgen.DBTableColumn{
|
[]tblColumn{
|
||||||
qgen.DBTableColumn{"lid", "int", 0, false, true, ""},
|
tblColumn{"lid", "int", 0, false, true, ""},
|
||||||
qgen.DBTableColumn{"uid", "int", 0, false, false, ""},
|
tblColumn{"uid", "int", 0, false, false, ""},
|
||||||
qgen.DBTableColumn{"success", "bool", 0, false, false, "0"}, // Did this attempt succeed?
|
tblColumn{"success", "bool", 0, false, false, "0"}, // Did this attempt succeed?
|
||||||
qgen.DBTableColumn{"ipaddress", "varchar", 200, false, false, ""},
|
tblColumn{"ipaddress", "varchar", 200, false, false, ""},
|
||||||
qgen.DBTableColumn{"doneAt", "createdAt", 0, false, false, ""},
|
tblColumn{"doneAt", "createdAt", 0, false, false, ""},
|
||||||
},
|
},
|
||||||
[]qgen.DBTableKey{
|
[]tblKey{
|
||||||
qgen.DBTableKey{"lid", "primary"},
|
tblKey{"lid", "primary"},
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
var acc = qgen.NewAcc
|
||||||
|
var itoa = strconv.Itoa
|
||||||
|
|
||||||
|
func patch10(scanner *bufio.Scanner) error {
|
||||||
|
err := execStmt(qgen.Builder.AddColumn("topics", tblColumn{"attachCount", "int", 0, false, false, "0"}))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = execStmt(qgen.Builder.AddColumn("topics", tblColumn{"lastReplyID", "int", 0, false, false, "0"}))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
// We could probably do something more efficient, but as there shouldn't be too many sites right now, we can probably cheat a little, otherwise it'll take forever to get things done
|
||||||
|
err = acc().Select("topics").Cols("tid").EachInt(func(tid int) error {
|
||||||
|
stid := itoa(tid)
|
||||||
|
|
||||||
|
count, err := acc().Count("attachments").Where("originTable = 'topics' and originID = " + stid).Total()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasReply = false
|
||||||
|
err = acc().Select("replies").Cols("rid").Where("tid = " + stid).Orderby("rid DESC").Limit("1").EachInt(func(rid int) error {
|
||||||
|
hasReply = true
|
||||||
|
_, err := acc().Update("topics").Set("lastReplyID = ?, attachCount = ?").Where("tid = "+stid).Exec(rid, count)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !hasReply {
|
||||||
|
_, err = acc().Update("topics").Set("attachCount = ?").Where("tid = " + stid).Exec(count)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = acc().Insert("updates").Columns("dbVersion").Fields("0").Exec()
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
208
public/global.js
208
public/global.js
|
@ -224,11 +224,8 @@ function runWebSockets() {
|
||||||
|
|
||||||
// TODO: Add support for other alert feeds like PM Alerts
|
// TODO: Add support for other alert feeds like PM Alerts
|
||||||
var generalAlerts = document.getElementById("general_alerts");
|
var generalAlerts = document.getElementById("general_alerts");
|
||||||
if(alertList.length < 8) {
|
if(alertList.length < 8) loadAlerts(generalAlerts);
|
||||||
loadAlerts(generalAlerts);
|
else updateAlertList(generalAlerts);
|
||||||
} else {
|
|
||||||
updateAlertList(generalAlerts);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -374,6 +371,7 @@ function mainInit(){
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
$('.hide_on_edit').addClass("edit_opened");
|
$('.hide_on_edit').addClass("edit_opened");
|
||||||
$('.show_on_edit').addClass("edit_opened");
|
$('.show_on_edit').addClass("edit_opened");
|
||||||
|
runHook("open_edit");
|
||||||
});
|
});
|
||||||
|
|
||||||
$(".topic_item .submit_edit").click(function(event){
|
$(".topic_item .submit_edit").click(function(event){
|
||||||
|
@ -388,6 +386,7 @@ function mainInit(){
|
||||||
|
|
||||||
$('.hide_on_edit').removeClass("edit_opened");
|
$('.hide_on_edit').removeClass("edit_opened");
|
||||||
$('.show_on_edit').removeClass("edit_opened");
|
$('.show_on_edit').removeClass("edit_opened");
|
||||||
|
runHook("close_edit");
|
||||||
|
|
||||||
let formAction = this.form.getAttribute("action");
|
let formAction = this.form.getAttribute("action");
|
||||||
$.ajax({
|
$.ajax({
|
||||||
|
@ -566,74 +565,185 @@ function mainInit(){
|
||||||
$(".topic_create_form").hide();
|
$(".topic_create_form").hide();
|
||||||
});
|
});
|
||||||
|
|
||||||
function uploadFileHandler() {
|
function uploadFileHandler(fileList,maxFiles = 5, step1 = () => {}, step2 = () => {}) {
|
||||||
var fileList = this.files;
|
|
||||||
// Truncate the number of files to 5
|
|
||||||
let files = [];
|
let files = [];
|
||||||
for(var i = 0; i < fileList.length && i < 5; i++) {
|
for(var i = 0; i < fileList.length && i < 5; i++) {
|
||||||
files[i] = fileList[i];
|
files[i] = fileList[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iterate over the files
|
|
||||||
let totalSize = 0;
|
let totalSize = 0;
|
||||||
for(let i = 0; i < files.length; i++) {
|
for(let i = 0; i < files.length; i++) {
|
||||||
console.log("files[" + i + "]",files[i]);
|
console.log("files[" + i + "]",files[i]);
|
||||||
totalSize += files[i]["size"];
|
totalSize += files[i]["size"];
|
||||||
|
}
|
||||||
|
if(totalSize > me.Site.MaxRequestSize) {
|
||||||
|
throw("You can't upload this much at once, max: " + me.Site.MaxRequestSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(let i = 0; i < files.length; i++) {
|
||||||
let reader = new FileReader();
|
let reader = new FileReader();
|
||||||
reader.onload = function(e) {
|
reader.onload = (e) => {
|
||||||
var fileDock = document.getElementById("upload_file_dock");
|
let filename = files[i]["name"];
|
||||||
var fileItem = document.createElement("label");
|
step1(e,filename)
|
||||||
console.log("fileItem",fileItem);
|
|
||||||
|
|
||||||
if(!files[i]["name"].indexOf('.' > -1)) {
|
|
||||||
// TODO: Surely, there's a prettier and more elegant way of doing this?
|
|
||||||
alert("This file doesn't have an extension");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var ext = files[i]["name"].split('.').pop();
|
|
||||||
fileItem.innerText = "." + ext;
|
|
||||||
fileItem.className = "formbutton uploadItem";
|
|
||||||
fileItem.style.backgroundImage = "url("+e.target.result+")";
|
|
||||||
|
|
||||||
fileDock.appendChild(fileItem);
|
|
||||||
|
|
||||||
let reader = new FileReader();
|
let reader = new FileReader();
|
||||||
reader.onload = function(e) {
|
reader.onload = (e2) => {
|
||||||
crypto.subtle.digest('SHA-256',e.target.result)
|
crypto.subtle.digest('SHA-256',e2.target.result)
|
||||||
.then(function(hash) {
|
.then((hash) => {
|
||||||
const hashArray = Array.from(new Uint8Array(hash))
|
const hashArray = Array.from(new Uint8Array(hash))
|
||||||
return hashArray.map(b => ('00' + b.toString(16)).slice(-2)).join('')
|
return hashArray.map(b => ('00' + b.toString(16)).slice(-2)).join('')
|
||||||
}).then(function(hash) {
|
}).then(hash => step2(e,hash,filename));
|
||||||
console.log("hash",hash);
|
|
||||||
let content = document.getElementById("input_content")
|
|
||||||
console.log("content.value", content.value);
|
|
||||||
|
|
||||||
let attachItem;
|
|
||||||
if(content.value == "") attachItem = "//" + window.location.host + "/attachs/" + hash + "." + ext;
|
|
||||||
else attachItem = "\r\n//" + window.location.host + "/attachs/" + hash + "." + ext;
|
|
||||||
content.value = content.value + attachItem;
|
|
||||||
console.log("content.value", content.value);
|
|
||||||
|
|
||||||
// For custom / third party text editors
|
|
||||||
attachItemCallback(attachItem);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
reader.readAsArrayBuffer(files[i]);
|
reader.readAsArrayBuffer(files[i]);
|
||||||
}
|
}
|
||||||
reader.readAsDataURL(files[i]);
|
reader.readAsDataURL(files[i]);
|
||||||
}
|
}
|
||||||
if(totalSize > me.Site.MaxRequestSize) {
|
}
|
||||||
|
|
||||||
|
// TODO: Surely, there's a prettier and more elegant way of doing this?
|
||||||
|
function getExt(filename) {
|
||||||
|
if(!filename.indexOf('.' > -1)) {
|
||||||
|
throw("This file doesn't have an extension");
|
||||||
|
}
|
||||||
|
return filename.split('.').pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attachment Manager
|
||||||
|
function uploadAttachHandler2() {
|
||||||
|
let fileDock = this.closest(".attach_edit_bay");
|
||||||
|
try {
|
||||||
|
uploadFileHandler(this.files, 5, () => {},
|
||||||
|
(e, hash, filename) => {
|
||||||
|
console.log("hash",hash);
|
||||||
|
|
||||||
|
let formData = new FormData();
|
||||||
|
formData.append("session",me.User.Session);
|
||||||
|
for(let i = 0; i < this.files.length; i++) {
|
||||||
|
formData.append("upload_files",this.files[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let req = new XMLHttpRequest();
|
||||||
|
req.addEventListener("load", () => {
|
||||||
|
let data = JSON.parse(req.responseText);
|
||||||
|
let fileItem = document.createElement("div");
|
||||||
|
let ext = getExt(filename);
|
||||||
|
// TODO: Check if this is actually an image, maybe push ImageFileExts to the client from the server in some sort of gen.js?
|
||||||
|
// TODO: Use client templates here
|
||||||
|
fileItem.className = "attach_item attach_image_holder";
|
||||||
|
fileItem.innerHTML = "<img src='"+e.target.result+"' height=24 width=24 /><span class='attach_item_path' aid='"+data[hash+"."+ext]+"' fullpath='//" + window.location.host + "/attachs/" + hash + "." + ext+"'>"+hash+"."+ext+"</span><button class='attach_item_select'>Select</button><button class='attach_item_copy'>Copy</button>";
|
||||||
|
fileDock.insertBefore(fileItem,fileDock.querySelector(".attach_item_buttons"));
|
||||||
|
|
||||||
|
$(".attach_item_select").unbind("click");
|
||||||
|
$(".attach_item_copy").unbind("click");
|
||||||
|
bindAttachItems()
|
||||||
|
});
|
||||||
|
req.open("POST","//"+window.location.host+"/topic/attach/add/submit/"+fileDock.getAttribute("tid"));
|
||||||
|
req.send(formData);
|
||||||
|
});
|
||||||
|
} catch(e) {
|
||||||
// TODO: Use a notice instead
|
// TODO: Use a notice instead
|
||||||
alert("You can't upload this much data at once, max: " + me.Site.MaxRequestSize);
|
alert(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quick Topic / Quick Reply
|
||||||
|
function uploadAttachHandler() {
|
||||||
|
try {
|
||||||
|
uploadFileHandler(this.files,5,(e,filename) => {
|
||||||
|
// TODO: Use client templates here
|
||||||
|
let fileDock = document.getElementById("upload_file_dock");
|
||||||
|
let fileItem = document.createElement("label");
|
||||||
|
console.log("fileItem",fileItem);
|
||||||
|
|
||||||
|
let ext = getExt(filename)
|
||||||
|
fileItem.innerText = "." + ext;
|
||||||
|
fileItem.className = "formbutton uploadItem";
|
||||||
|
// TODO: Check if this is actually an image
|
||||||
|
fileItem.style.backgroundImage = "url("+e.target.result+")";
|
||||||
|
|
||||||
|
fileDock.appendChild(fileItem);
|
||||||
|
},(e,hash, filename) => {
|
||||||
|
console.log("hash",hash);
|
||||||
|
let ext = getExt(filename)
|
||||||
|
let content = document.getElementById("input_content")
|
||||||
|
console.log("content.value", content.value);
|
||||||
|
|
||||||
|
let attachItem;
|
||||||
|
if(content.value == "") attachItem = "//" + window.location.host + "/attachs/" + hash + "." + ext;
|
||||||
|
else attachItem = "\r\n//" + window.location.host + "/attachs/" + hash + "." + ext;
|
||||||
|
content.value = content.value + attachItem;
|
||||||
|
console.log("content.value", content.value);
|
||||||
|
|
||||||
|
// For custom / third party text editors
|
||||||
|
attachItemCallback(attachItem);
|
||||||
|
});
|
||||||
|
} catch(e) {
|
||||||
|
// TODO: Use a notice instead
|
||||||
|
alert(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var uploadFiles = document.getElementById("upload_files");
|
var uploadFiles = document.getElementById("upload_files");
|
||||||
if(uploadFiles != null) {
|
if(uploadFiles != null) {
|
||||||
uploadFiles.addEventListener("change", uploadFileHandler, false);
|
uploadFiles.addEventListener("change", uploadAttachHandler, false);
|
||||||
}
|
}
|
||||||
|
var uploadFilesOp = document.getElementById("upload_files_op");
|
||||||
|
if(uploadFilesOp != null) {
|
||||||
|
uploadFilesOp.addEventListener("change", uploadAttachHandler2, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyToClipboard(str) {
|
||||||
|
const el = document.createElement('textarea');
|
||||||
|
el.value = str;
|
||||||
|
el.setAttribute('readonly', '');
|
||||||
|
el.style.position = 'absolute';
|
||||||
|
el.style.left = '-9999px';
|
||||||
|
document.body.appendChild(el);
|
||||||
|
el.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
document.body.removeChild(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
function bindAttachItems() {
|
||||||
|
$(".attach_item_select").click(function(){
|
||||||
|
let hold = $(this).closest(".attach_item");
|
||||||
|
if(hold.hasClass("attach_item_selected")) {
|
||||||
|
hold.removeClass("attach_item_selected");
|
||||||
|
} else {
|
||||||
|
hold.addClass("attach_item_selected");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$(".attach_item_copy").click(function(){
|
||||||
|
let hold = $(this).closest(".attach_item");
|
||||||
|
let pathNode = hold.find(".attach_item_path");
|
||||||
|
copyToClipboard(pathNode.attr("fullPath"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
bindAttachItems();
|
||||||
|
|
||||||
|
$(".attach_item_delete").click(function(){
|
||||||
|
let formData = new URLSearchParams();
|
||||||
|
formData.append("session",me.User.Session);
|
||||||
|
|
||||||
|
let aidList = "";
|
||||||
|
let elems = document.getElementsByClassName("attach_item_selected");
|
||||||
|
if(elems == null) return;
|
||||||
|
|
||||||
|
for(let i = 0; i < elems.length; i++) {
|
||||||
|
let pathNode = elems[i].querySelector(".attach_item_path");
|
||||||
|
console.log("pathNode",pathNode);
|
||||||
|
aidList += pathNode.getAttribute("aid") + ",";
|
||||||
|
elems[i].remove();
|
||||||
|
}
|
||||||
|
if(aidList.length > 0) aidList = aidList.slice(0, -1);
|
||||||
|
console.log("aidList",aidList)
|
||||||
|
formData.append("aids",aidList);
|
||||||
|
|
||||||
|
let req = new XMLHttpRequest();
|
||||||
|
let fileDock = this.closest(".attach_edit_bay");
|
||||||
|
req.open("POST","//"+window.location.host+"/topic/attach/remove/submit/"+fileDock.getAttribute("tid"),true);
|
||||||
|
req.send(formData);
|
||||||
|
});
|
||||||
|
|
||||||
$(".moderate_link").click((event) => {
|
$(".moderate_link").click((event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
@ -643,10 +753,11 @@ function mainInit(){
|
||||||
$(this).click(function(){
|
$(this).click(function(){
|
||||||
selectedTopics.push(parseInt($(this).attr("data-tid"),10));
|
selectedTopics.push(parseInt($(this).attr("data-tid"),10));
|
||||||
if(selectedTopics.length==1) {
|
if(selectedTopics.length==1) {
|
||||||
$(".mod_floater_head span").html("What do you want to do with this topic?");
|
var msg = "What do you want to do with this topic?";
|
||||||
} else {
|
} else {
|
||||||
$(".mod_floater_head span").html("What do you want to do with these "+selectedTopics.length+" topics?");
|
var msg = "What do you want to do with these "+selectedTopics.length+" topics?";
|
||||||
}
|
}
|
||||||
|
$(".mod_floater_head span").html(msg);
|
||||||
$(this).addClass("topic_selected");
|
$(this).addClass("topic_selected");
|
||||||
$(".mod_floater").removeClass("auto_hide");
|
$(".mod_floater").removeClass("auto_hide");
|
||||||
});
|
});
|
||||||
|
@ -670,7 +781,6 @@ function mainInit(){
|
||||||
let selectNode = this.form.querySelector(".mod_floater_options");
|
let selectNode = this.form.querySelector(".mod_floater_options");
|
||||||
let optionNode = selectNode.options[selectNode.selectedIndex];
|
let optionNode = selectNode.options[selectNode.selectedIndex];
|
||||||
let action = optionNode.getAttribute("val");
|
let action = optionNode.getAttribute("val");
|
||||||
//console.log("action", action);
|
|
||||||
|
|
||||||
// Handle these specially
|
// Handle these specially
|
||||||
switch(action) {
|
switch(action) {
|
||||||
|
|
|
@ -12,6 +12,8 @@ var hooks = {
|
||||||
"after_phrases":[],
|
"after_phrases":[],
|
||||||
"after_add_alert":[],
|
"after_add_alert":[],
|
||||||
"after_update_alert_list":[],
|
"after_update_alert_list":[],
|
||||||
|
"open_edit":[],
|
||||||
|
"close_edit":[],
|
||||||
};
|
};
|
||||||
var ranInitHooks = {}
|
var ranInitHooks = {}
|
||||||
|
|
||||||
|
@ -130,7 +132,7 @@ function fetchPhrases() {
|
||||||
(() => {
|
(() => {
|
||||||
runInitHook("pre_iife");
|
runInitHook("pre_iife");
|
||||||
let loggedIn = document.head.querySelector("[property='x-loggedin']").content;
|
let loggedIn = document.head.querySelector("[property='x-loggedin']").content;
|
||||||
if(loggedIn) {
|
if(loggedIn=="true") {
|
||||||
fetch("/api/me/")
|
fetch("/api/me/")
|
||||||
.then((resp) => resp.json())
|
.then((resp) => resp.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
|
|
|
@ -40,28 +40,41 @@ func (builder *accDeleteBuilder) Run(args ...interface{}) (int, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type accUpdateBuilder struct {
|
type accUpdateBuilder struct {
|
||||||
table string
|
up *updatePrebuilder
|
||||||
set string
|
|
||||||
where string
|
|
||||||
|
|
||||||
build *Accumulator
|
build *Accumulator
|
||||||
}
|
}
|
||||||
|
|
||||||
func (update *accUpdateBuilder) Set(set string) *accUpdateBuilder {
|
func (update *accUpdateBuilder) Set(set string) *accUpdateBuilder {
|
||||||
update.set = set
|
update.up.set = set
|
||||||
return update
|
return update
|
||||||
}
|
}
|
||||||
|
|
||||||
func (update *accUpdateBuilder) Where(where string) *accUpdateBuilder {
|
func (update *accUpdateBuilder) Where(where string) *accUpdateBuilder {
|
||||||
if update.where != "" {
|
if update.up.where != "" {
|
||||||
update.where += " AND "
|
update.up.where += " AND "
|
||||||
}
|
}
|
||||||
update.where += where
|
update.up.where += where
|
||||||
return update
|
return update
|
||||||
}
|
}
|
||||||
|
|
||||||
func (update *accUpdateBuilder) Prepare() *sql.Stmt {
|
func (update *accUpdateBuilder) WhereQ(sel *selectPrebuilder) *accUpdateBuilder {
|
||||||
return update.build.SimpleUpdate(update.table, update.set, update.where)
|
update.up.whereSubQuery = sel
|
||||||
|
return update
|
||||||
|
}
|
||||||
|
|
||||||
|
func (builder *accUpdateBuilder) Prepare() *sql.Stmt {
|
||||||
|
if builder.up.whereSubQuery != nil {
|
||||||
|
return builder.build.prepare(builder.build.adapter.SimpleUpdateSelect(builder.up))
|
||||||
|
}
|
||||||
|
return builder.build.prepare(builder.build.adapter.SimpleUpdate(builder.up))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (builder *accUpdateBuilder) Exec(args ...interface{}) (res sql.Result, err error) {
|
||||||
|
query, err := builder.build.adapter.SimpleUpdate(builder.up)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
return builder.build.exec(query, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
type AccSelectBuilder struct {
|
type AccSelectBuilder struct {
|
||||||
|
@ -77,17 +90,22 @@ type AccSelectBuilder struct {
|
||||||
build *Accumulator
|
build *Accumulator
|
||||||
}
|
}
|
||||||
|
|
||||||
func (selectItem *AccSelectBuilder) Columns(columns string) *AccSelectBuilder {
|
func (builder *AccSelectBuilder) Columns(columns string) *AccSelectBuilder {
|
||||||
selectItem.columns = columns
|
builder.columns = columns
|
||||||
return selectItem
|
return builder
|
||||||
}
|
}
|
||||||
|
|
||||||
func (selectItem *AccSelectBuilder) Where(where string) *AccSelectBuilder {
|
func (builder *AccSelectBuilder) Cols(columns string) *AccSelectBuilder {
|
||||||
if selectItem.where != "" {
|
builder.columns = columns
|
||||||
selectItem.where += " AND "
|
return builder
|
||||||
|
}
|
||||||
|
|
||||||
|
func (builder *AccSelectBuilder) Where(where string) *AccSelectBuilder {
|
||||||
|
if builder.where != "" {
|
||||||
|
builder.where += " AND "
|
||||||
}
|
}
|
||||||
selectItem.where += where
|
builder.where += where
|
||||||
return selectItem
|
return builder
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Don't implement the SQL at the accumulator level but the adapter level
|
// TODO: Don't implement the SQL at the accumulator level but the adapter level
|
||||||
|
@ -115,28 +133,28 @@ func (selectItem *AccSelectBuilder) InQ(column string, subBuilder *AccSelectBuil
|
||||||
return selectItem
|
return selectItem
|
||||||
}
|
}
|
||||||
|
|
||||||
func (selectItem *AccSelectBuilder) DateCutoff(column string, quantity int, unit string) *AccSelectBuilder {
|
func (builder *AccSelectBuilder) DateCutoff(column string, quantity int, unit string) *AccSelectBuilder {
|
||||||
selectItem.dateCutoff = &dateCutoff{column, quantity, unit}
|
builder.dateCutoff = &dateCutoff{column, quantity, unit}
|
||||||
return selectItem
|
return builder
|
||||||
}
|
}
|
||||||
|
|
||||||
func (selectItem *AccSelectBuilder) Orderby(orderby string) *AccSelectBuilder {
|
func (builder *AccSelectBuilder) Orderby(orderby string) *AccSelectBuilder {
|
||||||
selectItem.orderby = orderby
|
builder.orderby = orderby
|
||||||
return selectItem
|
return builder
|
||||||
}
|
}
|
||||||
|
|
||||||
func (selectItem *AccSelectBuilder) Limit(limit string) *AccSelectBuilder {
|
func (builder *AccSelectBuilder) Limit(limit string) *AccSelectBuilder {
|
||||||
selectItem.limit = limit
|
builder.limit = limit
|
||||||
return selectItem
|
return builder
|
||||||
}
|
}
|
||||||
|
|
||||||
func (selectItem *AccSelectBuilder) Prepare() *sql.Stmt {
|
func (builder *AccSelectBuilder) Prepare() *sql.Stmt {
|
||||||
// TODO: Phase out the procedural API and use the adapter's OO API? The OO API might need a bit more work before we do that and it needs to be rolled out to MSSQL.
|
// TODO: Phase out the procedural API and use the adapter's OO API? The OO API might need a bit more work before we do that and it needs to be rolled out to MSSQL.
|
||||||
if selectItem.dateCutoff != nil || selectItem.inChain != nil {
|
if builder.dateCutoff != nil || builder.inChain != nil {
|
||||||
selectBuilder := selectItem.build.GetAdapter().Builder().Select().FromAcc(selectItem)
|
selectBuilder := builder.build.GetAdapter().Builder().Select().FromAcc(builder)
|
||||||
return selectItem.build.prepare(selectItem.build.GetAdapter().ComplexSelect(selectBuilder))
|
return builder.build.prepare(builder.build.GetAdapter().ComplexSelect(selectBuilder))
|
||||||
}
|
}
|
||||||
return selectItem.build.SimpleSelect(selectItem.table, selectItem.columns, selectItem.where, selectItem.orderby, selectItem.limit)
|
return builder.build.SimpleSelect(builder.table, builder.columns, builder.where, builder.orderby, builder.limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (builder *AccSelectBuilder) query() (string, error) {
|
func (builder *AccSelectBuilder) query() (string, error) {
|
||||||
|
@ -145,15 +163,15 @@ func (builder *AccSelectBuilder) query() (string, error) {
|
||||||
selectBuilder := builder.build.GetAdapter().Builder().Select().FromAcc(builder)
|
selectBuilder := builder.build.GetAdapter().Builder().Select().FromAcc(builder)
|
||||||
return builder.build.GetAdapter().ComplexSelect(selectBuilder)
|
return builder.build.GetAdapter().ComplexSelect(selectBuilder)
|
||||||
}
|
}
|
||||||
return builder.build.adapter.SimpleSelect("_builder", builder.table, builder.columns, builder.where, builder.orderby, builder.limit)
|
return builder.build.adapter.SimpleSelect("", builder.table, builder.columns, builder.where, builder.orderby, builder.limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (selectItem *AccSelectBuilder) Query(args ...interface{}) (*sql.Rows, error) {
|
func (builder *AccSelectBuilder) Query(args ...interface{}) (*sql.Rows, error) {
|
||||||
stmt := selectItem.Prepare()
|
stmt := builder.Prepare()
|
||||||
if stmt != nil {
|
if stmt != nil {
|
||||||
return stmt.Query(args...)
|
return stmt.Query(args...)
|
||||||
}
|
}
|
||||||
return nil, selectItem.build.FirstError()
|
return nil, builder.build.FirstError()
|
||||||
}
|
}
|
||||||
|
|
||||||
type AccRowWrap struct {
|
type AccRowWrap struct {
|
||||||
|
@ -245,7 +263,7 @@ func (insert *accInsertBuilder) Prepare() *sql.Stmt {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (builder *accInsertBuilder) Exec(args ...interface{}) (res sql.Result, err error) {
|
func (builder *accInsertBuilder) Exec(args ...interface{}) (res sql.Result, err error) {
|
||||||
query, err := builder.build.adapter.SimpleInsert("_builder", builder.table, builder.columns, builder.fields)
|
query, err := builder.build.adapter.SimpleInsert("", builder.table, builder.columns, builder.fields)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
@ -253,7 +271,7 @@ func (builder *accInsertBuilder) Exec(args ...interface{}) (res sql.Result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (builder *accInsertBuilder) Run(args ...interface{}) (int, error) {
|
func (builder *accInsertBuilder) Run(args ...interface{}) (int, error) {
|
||||||
query, err := builder.build.adapter.SimpleInsert("_builder", builder.table, builder.columns, builder.fields)
|
query, err := builder.build.adapter.SimpleInsert("", builder.table, builder.columns, builder.fields)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
@ -292,4 +310,13 @@ func (count *accCountBuilder) Prepare() *sql.Stmt {
|
||||||
return count.build.SimpleCount(count.table, count.where, count.limit)
|
return count.build.SimpleCount(count.table, count.where, count.limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (count *accCountBuilder) Total() (total int, err error) {
|
||||||
|
stmt := count.Prepare()
|
||||||
|
if stmt == nil {
|
||||||
|
return 0, count.build.FirstError()
|
||||||
|
}
|
||||||
|
err = stmt.QueryRow().Scan(&total)
|
||||||
|
return total, err
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Add a Sum builder for summing viewchunks up into one number for the dashboard?
|
// TODO: Add a Sum builder for summing viewchunks up into one number for the dashboard?
|
||||||
|
|
|
@ -95,52 +95,56 @@ func (build *Accumulator) Tx(handler func(*TransactionBuilder) error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *Accumulator) SimpleSelect(table string, columns string, where string, orderby string, limit string) *sql.Stmt {
|
func (build *Accumulator) SimpleSelect(table string, columns string, where string, orderby string, limit string) *sql.Stmt {
|
||||||
return build.prepare(build.adapter.SimpleSelect("_builder", table, columns, where, orderby, limit))
|
return build.prepare(build.adapter.SimpleSelect("", table, columns, where, orderby, limit))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *Accumulator) SimpleCount(table string, where string, limit string) *sql.Stmt {
|
func (build *Accumulator) SimpleCount(table string, where string, limit string) *sql.Stmt {
|
||||||
return build.prepare(build.adapter.SimpleCount("_builder", table, where, limit))
|
return build.prepare(build.adapter.SimpleCount("", table, where, limit))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *Accumulator) SimpleLeftJoin(table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) *sql.Stmt {
|
func (build *Accumulator) SimpleLeftJoin(table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) *sql.Stmt {
|
||||||
return build.prepare(build.adapter.SimpleLeftJoin("_builder", table1, table2, columns, joiners, where, orderby, limit))
|
return build.prepare(build.adapter.SimpleLeftJoin("", table1, table2, columns, joiners, where, orderby, limit))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *Accumulator) SimpleInnerJoin(table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) *sql.Stmt {
|
func (build *Accumulator) SimpleInnerJoin(table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) *sql.Stmt {
|
||||||
return build.prepare(build.adapter.SimpleInnerJoin("_builder", table1, table2, columns, joiners, where, orderby, limit))
|
return build.prepare(build.adapter.SimpleInnerJoin("", table1, table2, columns, joiners, where, orderby, limit))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *Accumulator) CreateTable(table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) *sql.Stmt {
|
func (build *Accumulator) CreateTable(table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) *sql.Stmt {
|
||||||
return build.prepare(build.adapter.CreateTable("_builder", table, charset, collation, columns, keys))
|
return build.prepare(build.adapter.CreateTable("", table, charset, collation, columns, keys))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *Accumulator) SimpleInsert(table string, columns string, fields string) *sql.Stmt {
|
func (build *Accumulator) SimpleInsert(table string, columns string, fields string) *sql.Stmt {
|
||||||
return build.prepare(build.adapter.SimpleInsert("_builder", table, columns, fields))
|
return build.prepare(build.adapter.SimpleInsert("", table, columns, fields))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *Accumulator) SimpleInsertSelect(ins DBInsert, sel DBSelect) *sql.Stmt {
|
func (build *Accumulator) SimpleInsertSelect(ins DBInsert, sel DBSelect) *sql.Stmt {
|
||||||
return build.prepare(build.adapter.SimpleInsertSelect("_builder", ins, sel))
|
return build.prepare(build.adapter.SimpleInsertSelect("", ins, sel))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *Accumulator) SimpleInsertLeftJoin(ins DBInsert, sel DBJoin) *sql.Stmt {
|
func (build *Accumulator) SimpleInsertLeftJoin(ins DBInsert, sel DBJoin) *sql.Stmt {
|
||||||
return build.prepare(build.adapter.SimpleInsertLeftJoin("_builder", ins, sel))
|
return build.prepare(build.adapter.SimpleInsertLeftJoin("", ins, sel))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *Accumulator) SimpleInsertInnerJoin(ins DBInsert, sel DBJoin) *sql.Stmt {
|
func (build *Accumulator) SimpleInsertInnerJoin(ins DBInsert, sel DBJoin) *sql.Stmt {
|
||||||
return build.prepare(build.adapter.SimpleInsertInnerJoin("_builder", ins, sel))
|
return build.prepare(build.adapter.SimpleInsertInnerJoin("", ins, sel))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *Accumulator) SimpleUpdate(table string, set string, where string) *sql.Stmt {
|
func (build *Accumulator) SimpleUpdate(table string, set string, where string) *sql.Stmt {
|
||||||
return build.prepare(build.adapter.SimpleUpdate("_builder", table, set, where))
|
return build.prepare(build.adapter.SimpleUpdate(qUpdate(table, set, where)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (build *Accumulator) SimpleUpdateSelect(table string, set string, where string) *sql.Stmt {
|
||||||
|
return build.prepare(build.adapter.SimpleUpdateSelect(qUpdate(table, set, where)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *Accumulator) SimpleDelete(table string, where string) *sql.Stmt {
|
func (build *Accumulator) SimpleDelete(table string, where string) *sql.Stmt {
|
||||||
return build.prepare(build.adapter.SimpleDelete("_builder", table, where))
|
return build.prepare(build.adapter.SimpleDelete("", table, where))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 *Accumulator) Purge(table string) *sql.Stmt {
|
func (build *Accumulator) Purge(table string) *sql.Stmt {
|
||||||
return build.prepare(build.adapter.Purge("_builder", table))
|
return build.prepare(build.adapter.Purge("", table))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *Accumulator) prepareTx(tx *sql.Tx, res string, err error) (stmt *sql.Stmt) {
|
func (build *Accumulator) prepareTx(tx *sql.Tx, res string, err error) (stmt *sql.Stmt) {
|
||||||
|
@ -155,63 +159,63 @@ func (build *Accumulator) prepareTx(tx *sql.Tx, res string, err error) (stmt *sq
|
||||||
|
|
||||||
// These ones support transactions
|
// These ones support transactions
|
||||||
func (build *Accumulator) SimpleSelectTx(tx *sql.Tx, table string, columns string, where string, orderby string, limit string) (stmt *sql.Stmt) {
|
func (build *Accumulator) 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)
|
res, err := build.adapter.SimpleSelect("", table, columns, where, orderby, limit)
|
||||||
return build.prepareTx(tx, res, err)
|
return build.prepareTx(tx, res, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *Accumulator) SimpleCountTx(tx *sql.Tx, table string, where string, limit string) (stmt *sql.Stmt) {
|
func (build *Accumulator) 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("", table, where, limit)
|
||||||
return build.prepareTx(tx, res, err)
|
return build.prepareTx(tx, res, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *Accumulator) SimpleLeftJoinTx(tx *sql.Tx, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (stmt *sql.Stmt) {
|
func (build *Accumulator) 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("", table1, table2, columns, joiners, where, orderby, limit)
|
||||||
return build.prepareTx(tx, res, err)
|
return build.prepareTx(tx, res, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *Accumulator) SimpleInnerJoinTx(tx *sql.Tx, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (stmt *sql.Stmt) {
|
func (build *Accumulator) 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("", table1, table2, columns, joiners, where, orderby, limit)
|
||||||
return build.prepareTx(tx, res, err)
|
return build.prepareTx(tx, res, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *Accumulator) CreateTableTx(tx *sql.Tx, table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) (stmt *sql.Stmt) {
|
func (build *Accumulator) CreateTableTx(tx *sql.Tx, table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) (stmt *sql.Stmt) {
|
||||||
res, err := build.adapter.CreateTable("_builder", table, charset, collation, columns, keys)
|
res, err := build.adapter.CreateTable("", table, charset, collation, columns, keys)
|
||||||
return build.prepareTx(tx, res, err)
|
return build.prepareTx(tx, res, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *Accumulator) SimpleInsertTx(tx *sql.Tx, table string, columns string, fields string) (stmt *sql.Stmt) {
|
func (build *Accumulator) 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("", table, columns, fields)
|
||||||
return build.prepareTx(tx, res, err)
|
return build.prepareTx(tx, res, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *Accumulator) SimpleInsertSelectTx(tx *sql.Tx, ins DBInsert, sel DBSelect) (stmt *sql.Stmt) {
|
func (build *Accumulator) SimpleInsertSelectTx(tx *sql.Tx, ins DBInsert, sel DBSelect) (stmt *sql.Stmt) {
|
||||||
res, err := build.adapter.SimpleInsertSelect("_builder", ins, sel)
|
res, err := build.adapter.SimpleInsertSelect("", ins, sel)
|
||||||
return build.prepareTx(tx, res, err)
|
return build.prepareTx(tx, res, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *Accumulator) SimpleInsertLeftJoinTx(tx *sql.Tx, ins DBInsert, sel DBJoin) (stmt *sql.Stmt) {
|
func (build *Accumulator) SimpleInsertLeftJoinTx(tx *sql.Tx, ins DBInsert, sel DBJoin) (stmt *sql.Stmt) {
|
||||||
res, err := build.adapter.SimpleInsertLeftJoin("_builder", ins, sel)
|
res, err := build.adapter.SimpleInsertLeftJoin("", ins, sel)
|
||||||
return build.prepareTx(tx, res, err)
|
return build.prepareTx(tx, res, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *Accumulator) SimpleInsertInnerJoinTx(tx *sql.Tx, ins DBInsert, sel DBJoin) (stmt *sql.Stmt) {
|
func (build *Accumulator) SimpleInsertInnerJoinTx(tx *sql.Tx, ins DBInsert, sel DBJoin) (stmt *sql.Stmt) {
|
||||||
res, err := build.adapter.SimpleInsertInnerJoin("_builder", ins, sel)
|
res, err := build.adapter.SimpleInsertInnerJoin("", ins, sel)
|
||||||
return build.prepareTx(tx, res, err)
|
return build.prepareTx(tx, res, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *Accumulator) SimpleUpdateTx(tx *sql.Tx, table string, set string, where string) (stmt *sql.Stmt) {
|
func (build *Accumulator) 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(qUpdate(table, set, where))
|
||||||
return build.prepareTx(tx, res, err)
|
return build.prepareTx(tx, res, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *Accumulator) SimpleDeleteTx(tx *sql.Tx, table string, where string) (stmt *sql.Stmt) {
|
func (build *Accumulator) SimpleDeleteTx(tx *sql.Tx, table string, where string) (stmt *sql.Stmt) {
|
||||||
res, err := build.adapter.SimpleDelete("_builder", table, where)
|
res, err := build.adapter.SimpleDelete("", table, where)
|
||||||
return build.prepareTx(tx, res, err)
|
return build.prepareTx(tx, res, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 *Accumulator) PurgeTx(tx *sql.Tx, table string) (stmt *sql.Stmt) {
|
func (build *Accumulator) PurgeTx(tx *sql.Tx, table string) (stmt *sql.Stmt) {
|
||||||
res, err := build.adapter.Purge("_builder", table)
|
res, err := build.adapter.Purge("", table)
|
||||||
return build.prepareTx(tx, res, err)
|
return build.prepareTx(tx, res, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,7 +224,7 @@ func (build *Accumulator) Delete(table string) *accDeleteBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *Accumulator) Update(table string) *accUpdateBuilder {
|
func (build *Accumulator) Update(table string) *accUpdateBuilder {
|
||||||
return &accUpdateBuilder{table, "", "", build}
|
return &accUpdateBuilder{qUpdate(table, "", ""), build}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *Accumulator) Select(table string) *AccSelectBuilder {
|
func (build *Accumulator) Select(table string) *AccSelectBuilder {
|
||||||
|
|
|
@ -85,60 +85,60 @@ func (build *builder) prepare(res string, err error) (*sql.Stmt, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *builder) SimpleSelect(table string, columns string, where string, orderby string, limit string) (stmt *sql.Stmt, err error) {
|
func (build *builder) SimpleSelect(table string, columns string, where string, orderby string, limit string) (stmt *sql.Stmt, err error) {
|
||||||
return build.prepare(build.adapter.SimpleSelect("_builder", table, columns, where, orderby, limit))
|
return build.prepare(build.adapter.SimpleSelect("", table, columns, where, orderby, limit))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *builder) SimpleCount(table string, where string, limit string) (stmt *sql.Stmt, err error) {
|
func (build *builder) SimpleCount(table string, where string, limit string) (stmt *sql.Stmt, err error) {
|
||||||
return build.prepare(build.adapter.SimpleCount("_builder", table, where, limit))
|
return build.prepare(build.adapter.SimpleCount("", table, where, limit))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *builder) SimpleLeftJoin(table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (stmt *sql.Stmt, err error) {
|
func (build *builder) SimpleLeftJoin(table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (stmt *sql.Stmt, err error) {
|
||||||
return build.prepare(build.adapter.SimpleLeftJoin("_builder", table1, table2, columns, joiners, where, orderby, limit))
|
return build.prepare(build.adapter.SimpleLeftJoin("", table1, table2, columns, joiners, where, orderby, limit))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *builder) SimpleInnerJoin(table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (stmt *sql.Stmt, err error) {
|
func (build *builder) SimpleInnerJoin(table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (stmt *sql.Stmt, err error) {
|
||||||
return build.prepare(build.adapter.SimpleInnerJoin("_builder", table1, table2, columns, joiners, where, orderby, limit))
|
return build.prepare(build.adapter.SimpleInnerJoin("", table1, table2, columns, joiners, where, orderby, limit))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *builder) DropTable(table string) (stmt *sql.Stmt, err error) {
|
func (build *builder) DropTable(table string) (stmt *sql.Stmt, err error) {
|
||||||
return build.prepare(build.adapter.DropTable("_builder", table))
|
return build.prepare(build.adapter.DropTable("", table))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *builder) CreateTable(table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) (stmt *sql.Stmt, err error) {
|
func (build *builder) CreateTable(table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) (stmt *sql.Stmt, err error) {
|
||||||
return build.prepare(build.adapter.CreateTable("_builder", table, charset, collation, columns, keys))
|
return build.prepare(build.adapter.CreateTable("", table, charset, collation, columns, keys))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *builder) AddColumn(table string, column DBTableColumn) (stmt *sql.Stmt, err error) {
|
func (build *builder) AddColumn(table string, column DBTableColumn) (stmt *sql.Stmt, err error) {
|
||||||
return build.prepare(build.adapter.AddColumn("_builder", table, column))
|
return build.prepare(build.adapter.AddColumn("", table, column))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *builder) SimpleInsert(table string, columns string, fields string) (stmt *sql.Stmt, err error) {
|
func (build *builder) SimpleInsert(table string, columns string, fields string) (stmt *sql.Stmt, err error) {
|
||||||
return build.prepare(build.adapter.SimpleInsert("_builder", table, columns, fields))
|
return build.prepare(build.adapter.SimpleInsert("", table, columns, fields))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *builder) SimpleInsertSelect(ins DBInsert, sel DBSelect) (stmt *sql.Stmt, err error) {
|
func (build *builder) SimpleInsertSelect(ins DBInsert, sel DBSelect) (stmt *sql.Stmt, err error) {
|
||||||
return build.prepare(build.adapter.SimpleInsertSelect("_builder", ins, sel))
|
return build.prepare(build.adapter.SimpleInsertSelect("", ins, sel))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *builder) SimpleInsertLeftJoin(ins DBInsert, sel DBJoin) (stmt *sql.Stmt, err error) {
|
func (build *builder) SimpleInsertLeftJoin(ins DBInsert, sel DBJoin) (stmt *sql.Stmt, err error) {
|
||||||
return build.prepare(build.adapter.SimpleInsertLeftJoin("_builder", ins, sel))
|
return build.prepare(build.adapter.SimpleInsertLeftJoin("", ins, sel))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *builder) SimpleInsertInnerJoin(ins DBInsert, sel DBJoin) (stmt *sql.Stmt, err error) {
|
func (build *builder) SimpleInsertInnerJoin(ins DBInsert, sel DBJoin) (stmt *sql.Stmt, err error) {
|
||||||
return build.prepare(build.adapter.SimpleInsertInnerJoin("_builder", ins, sel))
|
return build.prepare(build.adapter.SimpleInsertInnerJoin("", ins, sel))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *builder) SimpleUpdate(table string, set string, where string) (stmt *sql.Stmt, err error) {
|
func (build *builder) SimpleUpdate(table string, set string, where string) (stmt *sql.Stmt, err error) {
|
||||||
return build.prepare(build.adapter.SimpleUpdate("_builder", table, set, where))
|
return build.prepare(build.adapter.SimpleUpdate(qUpdate(table, set, where)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *builder) SimpleDelete(table string, where string) (stmt *sql.Stmt, err error) {
|
func (build *builder) SimpleDelete(table string, where string) (stmt *sql.Stmt, err error) {
|
||||||
return build.prepare(build.adapter.SimpleDelete("_builder", table, where))
|
return build.prepare(build.adapter.SimpleDelete("", table, where))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 *builder) Purge(table string) (stmt *sql.Stmt, err error) {
|
func (build *builder) Purge(table string) (stmt *sql.Stmt, err error) {
|
||||||
return build.prepare(build.adapter.Purge("_builder", table))
|
return build.prepare(build.adapter.Purge("", table))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *builder) prepareTx(tx *sql.Tx, res string, err error) (*sql.Stmt, error) {
|
func (build *builder) prepareTx(tx *sql.Tx, res string, err error) (*sql.Stmt, error) {
|
||||||
|
@ -150,62 +150,62 @@ func (build *builder) prepareTx(tx *sql.Tx, res string, err error) (*sql.Stmt, e
|
||||||
|
|
||||||
// These ones support transactions
|
// These ones support transactions
|
||||||
func (build *builder) SimpleSelectTx(tx *sql.Tx, table string, columns string, where string, orderby string, limit string) (stmt *sql.Stmt, err error) {
|
func (build *builder) SimpleSelectTx(tx *sql.Tx, table string, columns string, where string, orderby string, limit string) (stmt *sql.Stmt, err error) {
|
||||||
res, err := build.adapter.SimpleSelect("_builder", table, columns, where, orderby, limit)
|
res, err := build.adapter.SimpleSelect("", table, columns, where, orderby, limit)
|
||||||
return build.prepareTx(tx, res, err)
|
return build.prepareTx(tx, res, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *builder) SimpleCountTx(tx *sql.Tx, table string, where string, limit string) (stmt *sql.Stmt, err error) {
|
func (build *builder) SimpleCountTx(tx *sql.Tx, table string, where string, limit string) (stmt *sql.Stmt, err error) {
|
||||||
res, err := build.adapter.SimpleCount("_builder", table, where, limit)
|
res, err := build.adapter.SimpleCount("", table, where, limit)
|
||||||
return build.prepareTx(tx, res, err)
|
return build.prepareTx(tx, res, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *builder) SimpleLeftJoinTx(tx *sql.Tx, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (stmt *sql.Stmt, err error) {
|
func (build *builder) SimpleLeftJoinTx(tx *sql.Tx, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (stmt *sql.Stmt, err error) {
|
||||||
res, err := build.adapter.SimpleLeftJoin("_builder", table1, table2, columns, joiners, where, orderby, limit)
|
res, err := build.adapter.SimpleLeftJoin("", table1, table2, columns, joiners, where, orderby, limit)
|
||||||
return build.prepareTx(tx, res, err)
|
return build.prepareTx(tx, res, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *builder) SimpleInnerJoinTx(tx *sql.Tx, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (stmt *sql.Stmt, err error) {
|
func (build *builder) SimpleInnerJoinTx(tx *sql.Tx, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (stmt *sql.Stmt, err error) {
|
||||||
res, err := build.adapter.SimpleInnerJoin("_builder", table1, table2, columns, joiners, where, orderby, limit)
|
res, err := build.adapter.SimpleInnerJoin("", table1, table2, columns, joiners, where, orderby, limit)
|
||||||
return build.prepareTx(tx, res, err)
|
return build.prepareTx(tx, res, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *builder) CreateTableTx(tx *sql.Tx, table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) (stmt *sql.Stmt, err error) {
|
func (build *builder) CreateTableTx(tx *sql.Tx, table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) (stmt *sql.Stmt, err error) {
|
||||||
res, err := build.adapter.CreateTable("_builder", table, charset, collation, columns, keys)
|
res, err := build.adapter.CreateTable("", table, charset, collation, columns, keys)
|
||||||
return build.prepareTx(tx, res, err)
|
return build.prepareTx(tx, res, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *builder) SimpleInsertTx(tx *sql.Tx, table string, columns string, fields string) (stmt *sql.Stmt, err error) {
|
func (build *builder) SimpleInsertTx(tx *sql.Tx, table string, columns string, fields string) (stmt *sql.Stmt, err error) {
|
||||||
res, err := build.adapter.SimpleInsert("_builder", table, columns, fields)
|
res, err := build.adapter.SimpleInsert("", table, columns, fields)
|
||||||
return build.prepareTx(tx, res, err)
|
return build.prepareTx(tx, res, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *builder) SimpleInsertSelectTx(tx *sql.Tx, ins DBInsert, sel DBSelect) (stmt *sql.Stmt, err error) {
|
func (build *builder) SimpleInsertSelectTx(tx *sql.Tx, ins DBInsert, sel DBSelect) (stmt *sql.Stmt, err error) {
|
||||||
res, err := build.adapter.SimpleInsertSelect("_builder", ins, sel)
|
res, err := build.adapter.SimpleInsertSelect("", ins, sel)
|
||||||
return build.prepareTx(tx, res, err)
|
return build.prepareTx(tx, res, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *builder) SimpleInsertLeftJoinTx(tx *sql.Tx, ins DBInsert, sel DBJoin) (stmt *sql.Stmt, err error) {
|
func (build *builder) SimpleInsertLeftJoinTx(tx *sql.Tx, ins DBInsert, sel DBJoin) (stmt *sql.Stmt, err error) {
|
||||||
res, err := build.adapter.SimpleInsertLeftJoin("_builder", ins, sel)
|
res, err := build.adapter.SimpleInsertLeftJoin("", ins, sel)
|
||||||
return build.prepareTx(tx, res, err)
|
return build.prepareTx(tx, res, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *builder) SimpleInsertInnerJoinTx(tx *sql.Tx, ins DBInsert, sel DBJoin) (stmt *sql.Stmt, err error) {
|
func (build *builder) SimpleInsertInnerJoinTx(tx *sql.Tx, ins DBInsert, sel DBJoin) (stmt *sql.Stmt, err error) {
|
||||||
res, err := build.adapter.SimpleInsertInnerJoin("_builder", ins, sel)
|
res, err := build.adapter.SimpleInsertInnerJoin("", ins, sel)
|
||||||
return build.prepareTx(tx, res, err)
|
return build.prepareTx(tx, res, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *builder) SimpleUpdateTx(tx *sql.Tx, table string, set string, where string) (stmt *sql.Stmt, err error) {
|
func (build *builder) SimpleUpdateTx(tx *sql.Tx, table string, set string, where string) (stmt *sql.Stmt, err error) {
|
||||||
res, err := build.adapter.SimpleUpdate("_builder", table, set, where)
|
res, err := build.adapter.SimpleUpdate(qUpdate(table, set, where))
|
||||||
return build.prepareTx(tx, res, err)
|
return build.prepareTx(tx, res, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *builder) SimpleDeleteTx(tx *sql.Tx, table string, where string) (stmt *sql.Stmt, err error) {
|
func (build *builder) SimpleDeleteTx(tx *sql.Tx, table string, where string) (stmt *sql.Stmt, err error) {
|
||||||
res, err := build.adapter.SimpleDelete("_builder", table, where)
|
res, err := build.adapter.SimpleDelete("", table, where)
|
||||||
return build.prepareTx(tx, res, err)
|
return build.prepareTx(tx, res, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 *builder) PurgeTx(tx *sql.Tx, table string) (stmt *sql.Stmt, err error) {
|
func (build *builder) PurgeTx(tx *sql.Tx, table string) (stmt *sql.Stmt, err error) {
|
||||||
res, err := build.adapter.Purge("_builder", table)
|
res, err := build.adapter.Purge("", table)
|
||||||
return build.prepareTx(tx, res, err)
|
return build.prepareTx(tx, res, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,22 +11,22 @@ type prebuilder struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *prebuilder) Select(nlist ...string) *selectPrebuilder {
|
func (build *prebuilder) Select(nlist ...string) *selectPrebuilder {
|
||||||
name := optString(nlist, "_builder")
|
name := optString(nlist, "")
|
||||||
return &selectPrebuilder{name, "", "", "", "", "", nil, nil, "", build.adapter}
|
return &selectPrebuilder{name, "", "", "", "", "", nil, nil, "", build.adapter}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *prebuilder) Insert(nlist ...string) *insertPrebuilder {
|
func (build *prebuilder) Insert(nlist ...string) *insertPrebuilder {
|
||||||
name := optString(nlist, "_builder")
|
name := optString(nlist, "")
|
||||||
return &insertPrebuilder{name, "", "", "", build.adapter}
|
return &insertPrebuilder{name, "", "", "", build.adapter}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *prebuilder) Update(nlist ...string) *updatePrebuilder {
|
func (build *prebuilder) Update(nlist ...string) *updatePrebuilder {
|
||||||
name := optString(nlist, "_builder")
|
name := optString(nlist, "")
|
||||||
return &updatePrebuilder{name, "", "", "", build.adapter}
|
return &updatePrebuilder{name, "", "", "", nil, build.adapter}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *prebuilder) Delete(nlist ...string) *deletePrebuilder {
|
func (build *prebuilder) Delete(nlist ...string) *deletePrebuilder {
|
||||||
name := optString(nlist, "_builder")
|
name := optString(nlist, "")
|
||||||
return &deletePrebuilder{name, "", "", build.adapter}
|
return &deletePrebuilder{name, "", "", build.adapter}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,14 +60,19 @@ func (delete *deletePrebuilder) Parse() {
|
||||||
}
|
}
|
||||||
|
|
||||||
type updatePrebuilder struct {
|
type updatePrebuilder struct {
|
||||||
name string
|
name string
|
||||||
table string
|
table string
|
||||||
set string
|
set string
|
||||||
where string
|
where string
|
||||||
|
whereSubQuery *selectPrebuilder
|
||||||
|
|
||||||
build Adapter
|
build Adapter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func qUpdate(table string, set string, where string) *updatePrebuilder {
|
||||||
|
return &updatePrebuilder{table: table, set: set, where: where}
|
||||||
|
}
|
||||||
|
|
||||||
func (update *updatePrebuilder) Table(table string) *updatePrebuilder {
|
func (update *updatePrebuilder) Table(table string) *updatePrebuilder {
|
||||||
update.table = table
|
update.table = table
|
||||||
return update
|
return update
|
||||||
|
@ -86,12 +91,17 @@ func (update *updatePrebuilder) Where(where string) *updatePrebuilder {
|
||||||
return update
|
return update
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (update *updatePrebuilder) WhereQ(sel *selectPrebuilder) *updatePrebuilder {
|
||||||
|
update.whereSubQuery = sel
|
||||||
|
return update
|
||||||
|
}
|
||||||
|
|
||||||
func (update *updatePrebuilder) Text() (string, error) {
|
func (update *updatePrebuilder) Text() (string, error) {
|
||||||
return update.build.SimpleUpdate(update.name, update.table, update.set, update.where)
|
return update.build.SimpleUpdate(update)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (update *updatePrebuilder) Parse() {
|
func (update *updatePrebuilder) Parse() {
|
||||||
update.build.SimpleUpdate(update.name, update.table, update.set, update.where)
|
update.build.SimpleUpdate(update)
|
||||||
}
|
}
|
||||||
|
|
||||||
type selectPrebuilder struct {
|
type selectPrebuilder struct {
|
||||||
|
@ -151,7 +161,7 @@ func (selectItem *selectPrebuilder) FromAcc(accBuilder *AccSelectBuilder) *selec
|
||||||
|
|
||||||
selectItem.dateCutoff = accBuilder.dateCutoff
|
selectItem.dateCutoff = accBuilder.dateCutoff
|
||||||
if accBuilder.inChain != nil {
|
if accBuilder.inChain != nil {
|
||||||
selectItem.inChain = &selectPrebuilder{"__builder", accBuilder.inChain.table, accBuilder.inChain.columns, accBuilder.inChain.where, accBuilder.inChain.orderby, accBuilder.inChain.limit, accBuilder.inChain.dateCutoff, nil, "", selectItem.build}
|
selectItem.inChain = &selectPrebuilder{"", accBuilder.inChain.table, accBuilder.inChain.columns, accBuilder.inChain.where, accBuilder.inChain.orderby, accBuilder.inChain.limit, accBuilder.inChain.dateCutoff, nil, "", selectItem.build}
|
||||||
selectItem.inColumn = accBuilder.inColumn
|
selectItem.inColumn = accBuilder.inColumn
|
||||||
}
|
}
|
||||||
return selectItem
|
return selectItem
|
||||||
|
|
|
@ -45,9 +45,6 @@ func (adapter *MssqlAdapter) DbVersion() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (adapter *MssqlAdapter) DropTable(name string, table string) (string, error) {
|
func (adapter *MssqlAdapter) DropTable(name string, table string) (string, error) {
|
||||||
if name == "" {
|
|
||||||
return "", errors.New("You need a name for this statement")
|
|
||||||
}
|
|
||||||
if table == "" {
|
if table == "" {
|
||||||
return "", errors.New("You need a name for this table")
|
return "", errors.New("You need a name for this table")
|
||||||
}
|
}
|
||||||
|
@ -59,9 +56,6 @@ func (adapter *MssqlAdapter) DropTable(name string, table string) (string, error
|
||||||
// TODO: Convert any remaining stringy types to nvarchar
|
// TODO: Convert any remaining stringy types to nvarchar
|
||||||
// We may need to change the CreateTable API to better suit Mssql and the other database drivers which are coming up
|
// We may need to change the CreateTable API to better suit Mssql and the other database drivers which are coming up
|
||||||
func (adapter *MssqlAdapter) CreateTable(name string, table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) (string, error) {
|
func (adapter *MssqlAdapter) CreateTable(name string, table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) (string, error) {
|
||||||
if name == "" {
|
|
||||||
return "", errors.New("You need a name for this statement")
|
|
||||||
}
|
|
||||||
if table == "" {
|
if table == "" {
|
||||||
return "", errors.New("You need a name for this table")
|
return "", errors.New("You need a name for this table")
|
||||||
}
|
}
|
||||||
|
@ -142,9 +136,6 @@ func (adapter *MssqlAdapter) parseColumn(column DBTableColumn) (col DBTableColum
|
||||||
|
|
||||||
// TODO: Test this, not sure if some things work
|
// TODO: Test this, not sure if some things work
|
||||||
func (adapter *MssqlAdapter) AddColumn(name string, table string, column DBTableColumn) (string, error) {
|
func (adapter *MssqlAdapter) AddColumn(name string, table string, column DBTableColumn) (string, error) {
|
||||||
if name == "" {
|
|
||||||
return "", errors.New("You need a name for this statement")
|
|
||||||
}
|
|
||||||
if table == "" {
|
if table == "" {
|
||||||
return "", errors.New("You need a name for this table")
|
return "", errors.New("You need a name for this table")
|
||||||
}
|
}
|
||||||
|
@ -156,9 +147,6 @@ func (adapter *MssqlAdapter) AddColumn(name string, table string, column DBTable
|
||||||
}
|
}
|
||||||
|
|
||||||
func (adapter *MssqlAdapter) SimpleInsert(name string, table string, columns string, fields string) (string, error) {
|
func (adapter *MssqlAdapter) SimpleInsert(name string, table string, columns string, fields string) (string, error) {
|
||||||
if name == "" {
|
|
||||||
return "", errors.New("You need a name for this statement")
|
|
||||||
}
|
|
||||||
if table == "" {
|
if table == "" {
|
||||||
return "", errors.New("You need a name for this table")
|
return "", errors.New("You need a name for this table")
|
||||||
}
|
}
|
||||||
|
@ -237,9 +225,6 @@ func (adapter *MssqlAdapter) SimpleReplace(name string, table string, columns st
|
||||||
}
|
}
|
||||||
|
|
||||||
func (adapter *MssqlAdapter) SimpleUpsert(name string, table string, columns string, fields string, where string) (string, error) {
|
func (adapter *MssqlAdapter) SimpleUpsert(name string, table string, columns string, fields string, where string) (string, error) {
|
||||||
if name == "" {
|
|
||||||
return "", errors.New("You need a name for this statement")
|
|
||||||
}
|
|
||||||
if table == "" {
|
if table == "" {
|
||||||
return "", errors.New("You need a name for this table")
|
return "", errors.New("You need a name for this table")
|
||||||
}
|
}
|
||||||
|
@ -332,19 +317,16 @@ func (adapter *MssqlAdapter) SimpleUpsert(name string, table string, columns str
|
||||||
return querystr, nil
|
return querystr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (adapter *MssqlAdapter) SimpleUpdate(name string, table string, set string, where string) (string, error) {
|
func (adapter *MssqlAdapter) SimpleUpdate(up *updatePrebuilder) (string, error) {
|
||||||
if name == "" {
|
if up.table == "" {
|
||||||
return "", errors.New("You need a name for this statement")
|
|
||||||
}
|
|
||||||
if table == "" {
|
|
||||||
return "", errors.New("You need a name for this table")
|
return "", errors.New("You need a name for this table")
|
||||||
}
|
}
|
||||||
if set == "" {
|
if up.set == "" {
|
||||||
return "", errors.New("You need to set data in this update statement")
|
return "", errors.New("You need to set data in this update statement")
|
||||||
}
|
}
|
||||||
|
|
||||||
var querystr = "UPDATE [" + table + "] SET "
|
var querystr = "UPDATE [" + up.table + "] SET "
|
||||||
for _, item := range processSet(set) {
|
for _, item := range processSet(up.set) {
|
||||||
querystr += "[" + item.Column + "] ="
|
querystr += "[" + item.Column + "] ="
|
||||||
for _, token := range item.Expr {
|
for _, token := range item.Expr {
|
||||||
switch token.Type {
|
switch token.Type {
|
||||||
|
@ -370,9 +352,9 @@ func (adapter *MssqlAdapter) SimpleUpdate(name string, table string, set string,
|
||||||
querystr = querystr[0 : len(querystr)-1]
|
querystr = querystr[0 : len(querystr)-1]
|
||||||
|
|
||||||
// Add support for BETWEEN x.x
|
// Add support for BETWEEN x.x
|
||||||
if len(where) != 0 {
|
if len(up.where) != 0 {
|
||||||
querystr += " WHERE"
|
querystr += " WHERE"
|
||||||
for _, loc := range processWhere(where) {
|
for _, loc := range processWhere(up.where) {
|
||||||
for _, token := range loc.Expr {
|
for _, token := range loc.Expr {
|
||||||
switch token.Type {
|
switch token.Type {
|
||||||
case "function", "operator", "number", "substitute", "or":
|
case "function", "operator", "number", "substitute", "or":
|
||||||
|
@ -394,14 +376,15 @@ func (adapter *MssqlAdapter) SimpleUpdate(name string, table string, set string,
|
||||||
querystr = querystr[0 : len(querystr)-4]
|
querystr = querystr[0 : len(querystr)-4]
|
||||||
}
|
}
|
||||||
|
|
||||||
adapter.pushStatement(name, "update", querystr)
|
adapter.pushStatement(up.name, "update", querystr)
|
||||||
return querystr, nil
|
return querystr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (adapter *MssqlAdapter) SimpleUpdateSelect(up *updatePrebuilder) (string, error) {
|
||||||
|
return "", errors.New("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
func (adapter *MssqlAdapter) SimpleDelete(name string, table string, where string) (string, error) {
|
func (adapter *MssqlAdapter) SimpleDelete(name string, table string, where string) (string, error) {
|
||||||
if name == "" {
|
|
||||||
return "", errors.New("You need a name for this statement")
|
|
||||||
}
|
|
||||||
if table == "" {
|
if table == "" {
|
||||||
return "", errors.New("You need a name for this table")
|
return "", errors.New("You need a name for this table")
|
||||||
}
|
}
|
||||||
|
@ -441,9 +424,6 @@ func (adapter *MssqlAdapter) SimpleDelete(name string, table string, where strin
|
||||||
|
|
||||||
// We don't want to accidentally wipe tables, so we'll have a separate method for purging tables instead
|
// We don't want to accidentally wipe tables, so we'll have a separate method for purging tables instead
|
||||||
func (adapter *MssqlAdapter) Purge(name string, table string) (string, error) {
|
func (adapter *MssqlAdapter) Purge(name string, table string) (string, error) {
|
||||||
if name == "" {
|
|
||||||
return "", errors.New("You need a name for this statement")
|
|
||||||
}
|
|
||||||
if table == "" {
|
if table == "" {
|
||||||
return "", errors.New("You need a name for this table")
|
return "", errors.New("You need a name for this table")
|
||||||
}
|
}
|
||||||
|
@ -452,9 +432,6 @@ func (adapter *MssqlAdapter) Purge(name string, table string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (adapter *MssqlAdapter) SimpleSelect(name string, table string, columns string, where string, orderby string, limit string) (string, error) {
|
func (adapter *MssqlAdapter) SimpleSelect(name string, table string, columns string, where string, orderby string, limit string) (string, error) {
|
||||||
if name == "" {
|
|
||||||
return "", errors.New("You need a name for this statement")
|
|
||||||
}
|
|
||||||
if table == "" {
|
if table == "" {
|
||||||
return "", errors.New("You need a name for this table")
|
return "", errors.New("You need a name for this table")
|
||||||
}
|
}
|
||||||
|
@ -554,9 +531,6 @@ func (adapter *MssqlAdapter) ComplexSelect(preBuilder *selectPrebuilder) (string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (adapter *MssqlAdapter) SimpleLeftJoin(name string, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (string, error) {
|
func (adapter *MssqlAdapter) SimpleLeftJoin(name string, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (string, error) {
|
||||||
if name == "" {
|
|
||||||
return "", errors.New("You need a name for this statement")
|
|
||||||
}
|
|
||||||
if table1 == "" {
|
if table1 == "" {
|
||||||
return "", errors.New("You need a name for the left table")
|
return "", errors.New("You need a name for the left table")
|
||||||
}
|
}
|
||||||
|
@ -683,9 +657,6 @@ func (adapter *MssqlAdapter) SimpleLeftJoin(name string, table1 string, table2 s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (adapter *MssqlAdapter) SimpleInnerJoin(name string, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (string, error) {
|
func (adapter *MssqlAdapter) SimpleInnerJoin(name string, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (string, error) {
|
||||||
if name == "" {
|
|
||||||
return "", errors.New("You need a name for this statement")
|
|
||||||
}
|
|
||||||
if table1 == "" {
|
if table1 == "" {
|
||||||
return "", errors.New("You need a name for the left table")
|
return "", errors.New("You need a name for the left table")
|
||||||
}
|
}
|
||||||
|
@ -1067,9 +1038,6 @@ func (adapter *MssqlAdapter) SimpleInsertInnerJoin(name string, ins DBInsert, se
|
||||||
}
|
}
|
||||||
|
|
||||||
func (adapter *MssqlAdapter) SimpleCount(name string, table string, where string, limit string) (string, error) {
|
func (adapter *MssqlAdapter) SimpleCount(name string, table string, where string, limit string) (string, error) {
|
||||||
if name == "" {
|
|
||||||
return "", errors.New("You need a name for this statement")
|
|
||||||
}
|
|
||||||
if table == "" {
|
if table == "" {
|
||||||
return "", errors.New("You need a name for this table")
|
return "", errors.New("You need a name for this table")
|
||||||
}
|
}
|
||||||
|
@ -1116,7 +1084,7 @@ func (adapter *MssqlAdapter) Builder() *prebuilder {
|
||||||
func (adapter *MssqlAdapter) Write() error {
|
func (adapter *MssqlAdapter) Write() error {
|
||||||
var stmts, body string
|
var stmts, body string
|
||||||
for _, name := range adapter.BufferOrder {
|
for _, name := range adapter.BufferOrder {
|
||||||
if name[0] == '_' {
|
if name == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
stmt := adapter.Buffer[name]
|
stmt := adapter.Buffer[name]
|
||||||
|
|
|
@ -83,21 +83,16 @@ func (adapter *MysqlAdapter) DbVersion() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (adapter *MysqlAdapter) DropTable(name string, table string) (string, error) {
|
func (adapter *MysqlAdapter) DropTable(name string, table string) (string, error) {
|
||||||
if name == "" {
|
|
||||||
return "", errors.New("You need a name for this statement")
|
|
||||||
}
|
|
||||||
if table == "" {
|
if table == "" {
|
||||||
return "", errors.New("You need a name for this table")
|
return "", errors.New("You need a name for this table")
|
||||||
}
|
}
|
||||||
querystr := "DROP TABLE IF EXISTS `" + table + "`;"
|
querystr := "DROP TABLE IF EXISTS `" + table + "`;"
|
||||||
|
// TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator
|
||||||
adapter.pushStatement(name, "drop-table", querystr)
|
adapter.pushStatement(name, "drop-table", querystr)
|
||||||
return querystr, nil
|
return querystr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (adapter *MysqlAdapter) CreateTable(name string, table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) (string, error) {
|
func (adapter *MysqlAdapter) CreateTable(name string, table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) (string, error) {
|
||||||
if name == "" {
|
|
||||||
return "", errors.New("You need a name for this statement")
|
|
||||||
}
|
|
||||||
if table == "" {
|
if table == "" {
|
||||||
return "", errors.New("You need a name for this table")
|
return "", errors.New("You need a name for this table")
|
||||||
}
|
}
|
||||||
|
@ -133,6 +128,7 @@ func (adapter *MysqlAdapter) CreateTable(name string, table string, charset stri
|
||||||
querystr += " COLLATE " + collation
|
querystr += " COLLATE " + collation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator
|
||||||
adapter.pushStatement(name, "create-table", querystr+";")
|
adapter.pushStatement(name, "create-table", querystr+";")
|
||||||
return querystr + ";", nil
|
return querystr + ";", nil
|
||||||
}
|
}
|
||||||
|
@ -178,23 +174,18 @@ func (adapter *MysqlAdapter) parseColumn(column DBTableColumn) (col DBTableColum
|
||||||
// TODO: Support AFTER column
|
// TODO: Support AFTER column
|
||||||
// TODO: Test to make sure everything works here
|
// TODO: Test to make sure everything works here
|
||||||
func (adapter *MysqlAdapter) AddColumn(name string, table string, column DBTableColumn) (string, error) {
|
func (adapter *MysqlAdapter) AddColumn(name string, table string, column DBTableColumn) (string, error) {
|
||||||
if name == "" {
|
|
||||||
return "", errors.New("You need a name for this statement")
|
|
||||||
}
|
|
||||||
if table == "" {
|
if table == "" {
|
||||||
return "", errors.New("You need a name for this table")
|
return "", errors.New("You need a name for this table")
|
||||||
}
|
}
|
||||||
|
|
||||||
column, size, end := adapter.parseColumn(column)
|
column, size, end := adapter.parseColumn(column)
|
||||||
querystr := "ALTER TABLE `" + table + "` ADD COLUMN " + "`" + column.Name + "` " + column.Type + size + end + ";"
|
querystr := "ALTER TABLE `" + table + "` ADD COLUMN " + "`" + column.Name + "` " + column.Type + size + end + ";"
|
||||||
|
// TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator
|
||||||
adapter.pushStatement(name, "add-column", querystr)
|
adapter.pushStatement(name, "add-column", querystr)
|
||||||
return querystr, nil
|
return querystr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (adapter *MysqlAdapter) SimpleInsert(name string, table string, columns string, fields string) (string, error) {
|
func (adapter *MysqlAdapter) SimpleInsert(name string, table string, columns string, fields string) (string, error) {
|
||||||
if name == "" {
|
|
||||||
return "", errors.New("You need a name for this statement")
|
|
||||||
}
|
|
||||||
if table == "" {
|
if table == "" {
|
||||||
return "", errors.New("You need a name for this table")
|
return "", errors.New("You need a name for this table")
|
||||||
}
|
}
|
||||||
|
@ -218,6 +209,7 @@ func (adapter *MysqlAdapter) SimpleInsert(name string, table string, columns str
|
||||||
}
|
}
|
||||||
querystr += ")"
|
querystr += ")"
|
||||||
|
|
||||||
|
// TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator
|
||||||
adapter.pushStatement(name, "insert", querystr)
|
adapter.pushStatement(name, "insert", querystr)
|
||||||
return querystr, nil
|
return querystr, nil
|
||||||
}
|
}
|
||||||
|
@ -239,9 +231,6 @@ func (adapter *MysqlAdapter) buildColumns(columns string) (querystr string) {
|
||||||
|
|
||||||
// ! DEPRECATED
|
// ! DEPRECATED
|
||||||
func (adapter *MysqlAdapter) SimpleReplace(name string, table string, columns string, fields string) (string, error) {
|
func (adapter *MysqlAdapter) SimpleReplace(name string, table string, columns string, fields string) (string, error) {
|
||||||
if name == "" {
|
|
||||||
return "", errors.New("You need a name for this statement")
|
|
||||||
}
|
|
||||||
if table == "" {
|
if table == "" {
|
||||||
return "", errors.New("You need a name for this table")
|
return "", errors.New("You need a name for this table")
|
||||||
}
|
}
|
||||||
|
@ -258,14 +247,12 @@ func (adapter *MysqlAdapter) SimpleReplace(name string, table string, columns st
|
||||||
}
|
}
|
||||||
querystr = querystr[0 : len(querystr)-1]
|
querystr = querystr[0 : len(querystr)-1]
|
||||||
|
|
||||||
|
// TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator
|
||||||
adapter.pushStatement(name, "replace", querystr+")")
|
adapter.pushStatement(name, "replace", querystr+")")
|
||||||
return querystr + ")", nil
|
return querystr + ")", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (adapter *MysqlAdapter) SimpleUpsert(name string, table string, columns string, fields string, where string) (string, error) {
|
func (adapter *MysqlAdapter) SimpleUpsert(name string, table string, columns string, fields string, where string) (string, error) {
|
||||||
if name == "" {
|
|
||||||
return "", errors.New("You need a name for this statement")
|
|
||||||
}
|
|
||||||
if table == "" {
|
if table == "" {
|
||||||
return "", errors.New("You need a name for this table")
|
return "", errors.New("You need a name for this table")
|
||||||
}
|
}
|
||||||
|
@ -305,23 +292,21 @@ func (adapter *MysqlAdapter) SimpleUpsert(name string, table string, columns str
|
||||||
|
|
||||||
querystr += insertColumns + setBit
|
querystr += insertColumns + setBit
|
||||||
|
|
||||||
|
// TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator
|
||||||
adapter.pushStatement(name, "upsert", querystr)
|
adapter.pushStatement(name, "upsert", querystr)
|
||||||
return querystr, nil
|
return querystr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (adapter *MysqlAdapter) SimpleUpdate(name string, table string, set string, where string) (string, error) {
|
func (adapter *MysqlAdapter) SimpleUpdate(up *updatePrebuilder) (string, error) {
|
||||||
if name == "" {
|
if up.table == "" {
|
||||||
return "", errors.New("You need a name for this statement")
|
|
||||||
}
|
|
||||||
if table == "" {
|
|
||||||
return "", errors.New("You need a name for this table")
|
return "", errors.New("You need a name for this table")
|
||||||
}
|
}
|
||||||
if set == "" {
|
if up.set == "" {
|
||||||
return "", errors.New("You need to set data in this update statement")
|
return "", errors.New("You need to set data in this update statement")
|
||||||
}
|
}
|
||||||
|
|
||||||
var querystr = "UPDATE `" + table + "` SET "
|
var querystr = "UPDATE `" + up.table + "` SET "
|
||||||
for _, item := range processSet(set) {
|
for _, item := range processSet(up.set) {
|
||||||
querystr += "`" + item.Column + "` ="
|
querystr += "`" + item.Column + "` ="
|
||||||
for _, token := range item.Expr {
|
for _, token := range item.Expr {
|
||||||
switch token.Type {
|
switch token.Type {
|
||||||
|
@ -337,20 +322,18 @@ func (adapter *MysqlAdapter) SimpleUpdate(name string, table string, set string,
|
||||||
}
|
}
|
||||||
querystr = querystr[0 : len(querystr)-1]
|
querystr = querystr[0 : len(querystr)-1]
|
||||||
|
|
||||||
whereStr, err := adapter.buildWhere(where)
|
whereStr, err := adapter.buildWhere(up.where)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return querystr, err
|
return querystr, err
|
||||||
}
|
}
|
||||||
querystr += whereStr
|
querystr += whereStr
|
||||||
|
|
||||||
adapter.pushStatement(name, "update", querystr)
|
// TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator
|
||||||
|
adapter.pushStatement(up.name, "update", querystr)
|
||||||
return querystr, nil
|
return querystr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (adapter *MysqlAdapter) SimpleDelete(name string, table string, where string) (string, error) {
|
func (adapter *MysqlAdapter) SimpleDelete(name string, table string, where string) (string, error) {
|
||||||
if name == "" {
|
|
||||||
return "", errors.New("You need a name for this statement")
|
|
||||||
}
|
|
||||||
if table == "" {
|
if table == "" {
|
||||||
return "", errors.New("You need a name for this table")
|
return "", errors.New("You need a name for this table")
|
||||||
}
|
}
|
||||||
|
@ -378,15 +361,13 @@ func (adapter *MysqlAdapter) SimpleDelete(name string, table string, where strin
|
||||||
}
|
}
|
||||||
|
|
||||||
querystr = strings.TrimSpace(querystr[0 : len(querystr)-4])
|
querystr = strings.TrimSpace(querystr[0 : len(querystr)-4])
|
||||||
|
// TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator
|
||||||
adapter.pushStatement(name, "delete", querystr)
|
adapter.pushStatement(name, "delete", querystr)
|
||||||
return querystr, nil
|
return querystr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// We don't want to accidentally wipe tables, so we'll have a separate method for purging tables instead
|
// We don't want to accidentally wipe tables, so we'll have a separate method for purging tables instead
|
||||||
func (adapter *MysqlAdapter) Purge(name string, table string) (string, error) {
|
func (adapter *MysqlAdapter) Purge(name string, table string) (string, error) {
|
||||||
if name == "" {
|
|
||||||
return "", errors.New("You need a name for this statement")
|
|
||||||
}
|
|
||||||
if table == "" {
|
if table == "" {
|
||||||
return "", errors.New("You need a name for this table")
|
return "", errors.New("You need a name for this table")
|
||||||
}
|
}
|
||||||
|
@ -459,9 +440,6 @@ func (adapter *MysqlAdapter) buildOrderby(orderby string) (querystr string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (adapter *MysqlAdapter) SimpleSelect(name string, table string, columns string, where string, orderby string, limit string) (string, error) {
|
func (adapter *MysqlAdapter) SimpleSelect(name string, table string, columns string, where string, orderby string, limit string) (string, error) {
|
||||||
if name == "" {
|
|
||||||
return "", errors.New("You need a name for this statement")
|
|
||||||
}
|
|
||||||
if table == "" {
|
if table == "" {
|
||||||
return "", errors.New("You need a name for this table")
|
return "", errors.New("You need a name for this table")
|
||||||
}
|
}
|
||||||
|
@ -490,9 +468,6 @@ func (adapter *MysqlAdapter) SimpleSelect(name string, table string, columns str
|
||||||
}
|
}
|
||||||
|
|
||||||
func (adapter *MysqlAdapter) ComplexSelect(preBuilder *selectPrebuilder) (out string, err error) {
|
func (adapter *MysqlAdapter) ComplexSelect(preBuilder *selectPrebuilder) (out string, err error) {
|
||||||
if preBuilder.name == "" {
|
|
||||||
return "", errors.New("You need a name for this statement")
|
|
||||||
}
|
|
||||||
if preBuilder.table == "" {
|
if preBuilder.table == "" {
|
||||||
return "", errors.New("You need a name for this table")
|
return "", errors.New("You need a name for this table")
|
||||||
}
|
}
|
||||||
|
@ -531,9 +506,6 @@ func (adapter *MysqlAdapter) ComplexSelect(preBuilder *selectPrebuilder) (out st
|
||||||
}
|
}
|
||||||
|
|
||||||
func (adapter *MysqlAdapter) SimpleLeftJoin(name string, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (string, error) {
|
func (adapter *MysqlAdapter) SimpleLeftJoin(name string, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (string, error) {
|
||||||
if name == "" {
|
|
||||||
return "", errors.New("You need a name for this statement")
|
|
||||||
}
|
|
||||||
if table1 == "" {
|
if table1 == "" {
|
||||||
return "", errors.New("You need a name for the left table")
|
return "", errors.New("You need a name for the left table")
|
||||||
}
|
}
|
||||||
|
@ -560,9 +532,6 @@ func (adapter *MysqlAdapter) SimpleLeftJoin(name string, table1 string, table2 s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (adapter *MysqlAdapter) SimpleInnerJoin(name string, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (string, error) {
|
func (adapter *MysqlAdapter) SimpleInnerJoin(name string, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (string, error) {
|
||||||
if name == "" {
|
|
||||||
return "", errors.New("You need a name for this statement")
|
|
||||||
}
|
|
||||||
if table1 == "" {
|
if table1 == "" {
|
||||||
return "", errors.New("You need a name for the left table")
|
return "", errors.New("You need a name for the left table")
|
||||||
}
|
}
|
||||||
|
@ -588,6 +557,37 @@ func (adapter *MysqlAdapter) SimpleInnerJoin(name string, table1 string, table2
|
||||||
return querystr, nil
|
return querystr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (adapter *MysqlAdapter) SimpleUpdateSelect(up *updatePrebuilder) (string, error) {
|
||||||
|
sel := up.whereSubQuery
|
||||||
|
whereStr, err := adapter.buildWhere(sel.where)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var setter string
|
||||||
|
for _, item := range processSet(up.set) {
|
||||||
|
setter += "`" + item.Column + "` ="
|
||||||
|
for _, token := range item.Expr {
|
||||||
|
switch token.Type {
|
||||||
|
case "function", "operator", "number", "substitute", "or":
|
||||||
|
setter += " " + token.Contents
|
||||||
|
case "column":
|
||||||
|
setter += " `" + token.Contents + "`"
|
||||||
|
case "string":
|
||||||
|
setter += " '" + token.Contents + "'"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setter += ","
|
||||||
|
}
|
||||||
|
setter = setter[0 : len(setter)-1]
|
||||||
|
|
||||||
|
var querystr = "UPDATE `" + up.table + "` SET " + setter + " WHERE (SELECT" + adapter.buildJoinColumns(sel.columns) + " FROM `" + sel.table + "`" + whereStr + adapter.buildOrderby(sel.orderby) + adapter.buildLimit(sel.limit) + ")"
|
||||||
|
|
||||||
|
querystr = strings.TrimSpace(querystr)
|
||||||
|
adapter.pushStatement(up.name, "update", querystr)
|
||||||
|
return querystr, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (adapter *MysqlAdapter) SimpleInsertSelect(name string, ins DBInsert, sel DBSelect) (string, error) {
|
func (adapter *MysqlAdapter) SimpleInsertSelect(name string, ins DBInsert, sel DBSelect) (string, error) {
|
||||||
whereStr, err := adapter.buildWhere(sel.Where)
|
whereStr, err := adapter.buildWhere(sel.Where)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -692,9 +692,6 @@ func (adapter *MysqlAdapter) SimpleInsertInnerJoin(name string, ins DBInsert, se
|
||||||
}
|
}
|
||||||
|
|
||||||
func (adapter *MysqlAdapter) SimpleCount(name string, table string, where string, limit string) (querystr string, err error) {
|
func (adapter *MysqlAdapter) SimpleCount(name string, table string, where string, limit string) (querystr string, err error) {
|
||||||
if name == "" {
|
|
||||||
return "", errors.New("You need a name for this statement")
|
|
||||||
}
|
|
||||||
if table == "" {
|
if table == "" {
|
||||||
return "", errors.New("You need a name for this table")
|
return "", errors.New("You need a name for this table")
|
||||||
}
|
}
|
||||||
|
@ -778,7 +775,7 @@ func _gen_mysql() (err error) {
|
||||||
|
|
||||||
// Internal methods, not exposed in the interface
|
// Internal methods, not exposed in the interface
|
||||||
func (adapter *MysqlAdapter) pushStatement(name string, stype string, querystr string) {
|
func (adapter *MysqlAdapter) pushStatement(name string, stype string, querystr string) {
|
||||||
if name[0] == '_' {
|
if name == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
adapter.Buffer[name] = DBStmt{querystr, stype}
|
adapter.Buffer[name] = DBStmt{querystr, stype}
|
||||||
|
|
|
@ -43,9 +43,6 @@ func (adapter *PgsqlAdapter) DbVersion() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (adapter *PgsqlAdapter) DropTable(name string, table string) (string, error) {
|
func (adapter *PgsqlAdapter) DropTable(name string, table string) (string, error) {
|
||||||
if name == "" {
|
|
||||||
return "", errors.New("You need a name for this statement")
|
|
||||||
}
|
|
||||||
if table == "" {
|
if table == "" {
|
||||||
return "", errors.New("You need a name for this table")
|
return "", errors.New("You need a name for this table")
|
||||||
}
|
}
|
||||||
|
@ -57,9 +54,6 @@ func (adapter *PgsqlAdapter) DropTable(name string, table string) (string, error
|
||||||
// TODO: Implement this
|
// TODO: Implement this
|
||||||
// We may need to change the CreateTable API to better suit PGSQL and the other database drivers which are coming up
|
// We may need to change the CreateTable API to better suit PGSQL and the other database drivers which are coming up
|
||||||
func (adapter *PgsqlAdapter) CreateTable(name string, table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) (string, error) {
|
func (adapter *PgsqlAdapter) CreateTable(name string, table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) (string, error) {
|
||||||
if name == "" {
|
|
||||||
return "", errors.New("You need a name for this statement")
|
|
||||||
}
|
|
||||||
if table == "" {
|
if table == "" {
|
||||||
return "", errors.New("You need a name for this table")
|
return "", errors.New("You need a name for this table")
|
||||||
}
|
}
|
||||||
|
@ -120,9 +114,6 @@ func (adapter *PgsqlAdapter) CreateTable(name string, table string, charset stri
|
||||||
|
|
||||||
// TODO: Implement this
|
// TODO: Implement this
|
||||||
func (adapter *PgsqlAdapter) AddColumn(name string, table string, column DBTableColumn) (string, error) {
|
func (adapter *PgsqlAdapter) AddColumn(name string, table string, column DBTableColumn) (string, error) {
|
||||||
if name == "" {
|
|
||||||
return "", errors.New("You need a name for this statement")
|
|
||||||
}
|
|
||||||
if table == "" {
|
if table == "" {
|
||||||
return "", errors.New("You need a name for this table")
|
return "", errors.New("You need a name for this table")
|
||||||
}
|
}
|
||||||
|
@ -132,9 +123,6 @@ func (adapter *PgsqlAdapter) AddColumn(name string, table string, column DBTable
|
||||||
// TODO: Test this
|
// TODO: Test this
|
||||||
// ! We need to get the last ID out of this somehow, maybe add returning to every query? Might require some sort of wrapper over the sql statements
|
// ! We need to get the last ID out of this somehow, maybe add returning to every query? Might require some sort of wrapper over the sql statements
|
||||||
func (adapter *PgsqlAdapter) SimpleInsert(name string, table string, columns string, fields string) (string, error) {
|
func (adapter *PgsqlAdapter) SimpleInsert(name string, table string, columns string, fields string) (string, error) {
|
||||||
if name == "" {
|
|
||||||
return "", errors.New("You need a name for this statement")
|
|
||||||
}
|
|
||||||
if table == "" {
|
if table == "" {
|
||||||
return "", errors.New("You need a name for this table")
|
return "", errors.New("You need a name for this table")
|
||||||
}
|
}
|
||||||
|
@ -179,9 +167,6 @@ func (adapter *PgsqlAdapter) buildColumns(columns string) (querystr string) {
|
||||||
|
|
||||||
// TODO: Implement this
|
// TODO: Implement this
|
||||||
func (adapter *PgsqlAdapter) SimpleReplace(name string, table string, columns string, fields string) (string, error) {
|
func (adapter *PgsqlAdapter) SimpleReplace(name string, table string, columns string, fields string) (string, error) {
|
||||||
if name == "" {
|
|
||||||
return "", errors.New("You need a name for this statement")
|
|
||||||
}
|
|
||||||
if table == "" {
|
if table == "" {
|
||||||
return "", errors.New("You need a name for this table")
|
return "", errors.New("You need a name for this table")
|
||||||
}
|
}
|
||||||
|
@ -196,9 +181,6 @@ func (adapter *PgsqlAdapter) SimpleReplace(name string, table string, columns st
|
||||||
|
|
||||||
// TODO: Implement this
|
// TODO: Implement this
|
||||||
func (adapter *PgsqlAdapter) SimpleUpsert(name string, table string, columns string, fields string, where string) (string, error) {
|
func (adapter *PgsqlAdapter) SimpleUpsert(name string, table string, columns string, fields string, where string) (string, error) {
|
||||||
if name == "" {
|
|
||||||
return "", errors.New("You need a name for this statement")
|
|
||||||
}
|
|
||||||
if table == "" {
|
if table == "" {
|
||||||
return "", errors.New("You need a name for this table")
|
return "", errors.New("You need a name for this table")
|
||||||
}
|
}
|
||||||
|
@ -212,19 +194,16 @@ func (adapter *PgsqlAdapter) SimpleUpsert(name string, table string, columns str
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implemented, but we need CreateTable and a better installer to *test* it
|
// TODO: Implemented, but we need CreateTable and a better installer to *test* it
|
||||||
func (adapter *PgsqlAdapter) SimpleUpdate(name string, table string, set string, where string) (string, error) {
|
func (adapter *PgsqlAdapter) SimpleUpdate(up *updatePrebuilder) (string, error) {
|
||||||
if name == "" {
|
if up.table == "" {
|
||||||
return "", errors.New("You need a name for this statement")
|
|
||||||
}
|
|
||||||
if table == "" {
|
|
||||||
return "", errors.New("You need a name for this table")
|
return "", errors.New("You need a name for this table")
|
||||||
}
|
}
|
||||||
if set == "" {
|
if up.set == "" {
|
||||||
return "", errors.New("You need to set data in this update statement")
|
return "", errors.New("You need to set data in this update statement")
|
||||||
}
|
}
|
||||||
|
|
||||||
var querystr = "UPDATE \"" + table + "\" SET "
|
var querystr = "UPDATE \"" + up.table + "\" SET "
|
||||||
for _, item := range processSet(set) {
|
for _, item := range processSet(up.set) {
|
||||||
querystr += "`" + item.Column + "` ="
|
querystr += "`" + item.Column + "` ="
|
||||||
for _, token := range item.Expr {
|
for _, token := range item.Expr {
|
||||||
switch token.Type {
|
switch token.Type {
|
||||||
|
@ -248,9 +227,9 @@ func (adapter *PgsqlAdapter) SimpleUpdate(name string, table string, set string,
|
||||||
querystr = querystr[0 : len(querystr)-1]
|
querystr = querystr[0 : len(querystr)-1]
|
||||||
|
|
||||||
// Add support for BETWEEN x.x
|
// Add support for BETWEEN x.x
|
||||||
if len(where) != 0 {
|
if len(up.where) != 0 {
|
||||||
querystr += " WHERE"
|
querystr += " WHERE"
|
||||||
for _, loc := range processWhere(where) {
|
for _, loc := range processWhere(up.where) {
|
||||||
for _, token := range loc.Expr {
|
for _, token := range loc.Expr {
|
||||||
switch token.Type {
|
switch token.Type {
|
||||||
case "function":
|
case "function":
|
||||||
|
@ -274,15 +253,17 @@ func (adapter *PgsqlAdapter) SimpleUpdate(name string, table string, set string,
|
||||||
querystr = querystr[0 : len(querystr)-4]
|
querystr = querystr[0 : len(querystr)-4]
|
||||||
}
|
}
|
||||||
|
|
||||||
adapter.pushStatement(name, "update", querystr)
|
adapter.pushStatement(up.name, "update", querystr)
|
||||||
return querystr, nil
|
return querystr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Implement this
|
||||||
|
func (adapter *PgsqlAdapter) SimpleUpdateSelect(up *updatePrebuilder) (string, error) {
|
||||||
|
return "", errors.New("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Implement this
|
// TODO: Implement this
|
||||||
func (adapter *PgsqlAdapter) SimpleDelete(name string, table string, where string) (string, error) {
|
func (adapter *PgsqlAdapter) SimpleDelete(name string, table string, where string) (string, error) {
|
||||||
if name == "" {
|
|
||||||
return "", errors.New("You need a name for this statement")
|
|
||||||
}
|
|
||||||
if table == "" {
|
if table == "" {
|
||||||
return "", errors.New("You need a name for this table")
|
return "", errors.New("You need a name for this table")
|
||||||
}
|
}
|
||||||
|
@ -295,9 +276,6 @@ func (adapter *PgsqlAdapter) SimpleDelete(name string, table string, where strin
|
||||||
// TODO: Implement this
|
// TODO: Implement this
|
||||||
// We don't want to accidentally wipe tables, so we'll have a separate method for purging tables instead
|
// We don't want to accidentally wipe tables, so we'll have a separate method for purging tables instead
|
||||||
func (adapter *PgsqlAdapter) Purge(name string, table string) (string, error) {
|
func (adapter *PgsqlAdapter) Purge(name string, table string) (string, error) {
|
||||||
if name == "" {
|
|
||||||
return "", errors.New("You need a name for this statement")
|
|
||||||
}
|
|
||||||
if table == "" {
|
if table == "" {
|
||||||
return "", errors.New("You need a name for this table")
|
return "", errors.New("You need a name for this table")
|
||||||
}
|
}
|
||||||
|
@ -306,9 +284,6 @@ func (adapter *PgsqlAdapter) Purge(name string, table string) (string, error) {
|
||||||
|
|
||||||
// TODO: Implement this
|
// TODO: Implement this
|
||||||
func (adapter *PgsqlAdapter) SimpleSelect(name string, table string, columns string, where string, orderby string, limit string) (string, error) {
|
func (adapter *PgsqlAdapter) SimpleSelect(name string, table string, columns string, where string, orderby string, limit string) (string, error) {
|
||||||
if name == "" {
|
|
||||||
return "", errors.New("You need a name for this statement")
|
|
||||||
}
|
|
||||||
if table == "" {
|
if table == "" {
|
||||||
return "", errors.New("You need a name for this table")
|
return "", errors.New("You need a name for this table")
|
||||||
}
|
}
|
||||||
|
@ -320,9 +295,6 @@ func (adapter *PgsqlAdapter) SimpleSelect(name string, table string, columns str
|
||||||
|
|
||||||
// TODO: Implement this
|
// TODO: Implement this
|
||||||
func (adapter *PgsqlAdapter) ComplexSelect(prebuilder *selectPrebuilder) (string, error) {
|
func (adapter *PgsqlAdapter) ComplexSelect(prebuilder *selectPrebuilder) (string, error) {
|
||||||
if prebuilder.name == "" {
|
|
||||||
return "", errors.New("You need a name for this statement")
|
|
||||||
}
|
|
||||||
if prebuilder.table == "" {
|
if prebuilder.table == "" {
|
||||||
return "", errors.New("You need a name for this table")
|
return "", errors.New("You need a name for this table")
|
||||||
}
|
}
|
||||||
|
@ -334,9 +306,6 @@ func (adapter *PgsqlAdapter) ComplexSelect(prebuilder *selectPrebuilder) (string
|
||||||
|
|
||||||
// TODO: Implement this
|
// TODO: Implement this
|
||||||
func (adapter *PgsqlAdapter) SimpleLeftJoin(name string, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (string, error) {
|
func (adapter *PgsqlAdapter) SimpleLeftJoin(name string, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (string, error) {
|
||||||
if name == "" {
|
|
||||||
return "", errors.New("You need a name for this statement")
|
|
||||||
}
|
|
||||||
if table1 == "" {
|
if table1 == "" {
|
||||||
return "", errors.New("You need a name for the left table")
|
return "", errors.New("You need a name for the left table")
|
||||||
}
|
}
|
||||||
|
@ -354,9 +323,6 @@ func (adapter *PgsqlAdapter) SimpleLeftJoin(name string, table1 string, table2 s
|
||||||
|
|
||||||
// TODO: Implement this
|
// TODO: Implement this
|
||||||
func (adapter *PgsqlAdapter) SimpleInnerJoin(name string, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (string, error) {
|
func (adapter *PgsqlAdapter) SimpleInnerJoin(name string, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (string, error) {
|
||||||
if name == "" {
|
|
||||||
return "", errors.New("You need a name for this statement")
|
|
||||||
}
|
|
||||||
if table1 == "" {
|
if table1 == "" {
|
||||||
return "", errors.New("You need a name for the left table")
|
return "", errors.New("You need a name for the left table")
|
||||||
}
|
}
|
||||||
|
@ -389,9 +355,6 @@ func (adapter *PgsqlAdapter) SimpleInsertInnerJoin(name string, ins DBInsert, se
|
||||||
|
|
||||||
// TODO: Implement this
|
// TODO: Implement this
|
||||||
func (adapter *PgsqlAdapter) SimpleCount(name string, table string, where string, limit string) (string, error) {
|
func (adapter *PgsqlAdapter) SimpleCount(name string, table string, where string, limit string) (string, error) {
|
||||||
if name == "" {
|
|
||||||
return "", errors.New("You need a name for this statement")
|
|
||||||
}
|
|
||||||
if table == "" {
|
if table == "" {
|
||||||
return "", errors.New("You need a name for this table")
|
return "", errors.New("You need a name for this table")
|
||||||
}
|
}
|
||||||
|
@ -454,7 +417,7 @@ func _gen_pgsql() (err error) {
|
||||||
|
|
||||||
// Internal methods, not exposed in the interface
|
// Internal methods, not exposed in the interface
|
||||||
func (adapter *PgsqlAdapter) pushStatement(name string, stype string, querystr string) {
|
func (adapter *PgsqlAdapter) pushStatement(name string, stype string, querystr string) {
|
||||||
if name[0] == '_' {
|
if name == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
adapter.Buffer[name] = DBStmt{querystr, stype}
|
adapter.Buffer[name] = DBStmt{querystr, stype}
|
||||||
|
|
|
@ -110,7 +110,8 @@ type Adapter interface {
|
||||||
// TODO: Test this
|
// TODO: Test this
|
||||||
AddColumn(name string, table string, column DBTableColumn) (string, error)
|
AddColumn(name string, table string, column DBTableColumn) (string, error)
|
||||||
SimpleInsert(name string, table string, columns string, fields string) (string, error)
|
SimpleInsert(name string, table string, columns string, fields string) (string, error)
|
||||||
SimpleUpdate(name string, table string, set string, where string) (string, error)
|
SimpleUpdate(up *updatePrebuilder) (string, error)
|
||||||
|
SimpleUpdateSelect(up *updatePrebuilder) (string, error) // ! Experimental
|
||||||
SimpleDelete(name string, table string, where string) (string, error)
|
SimpleDelete(name string, table string, where string) (string, error)
|
||||||
Purge(name string, table string) (string, error)
|
Purge(name string, table string) (string, error)
|
||||||
SimpleSelect(name string, table string, columns string, where string, orderby string, limit string) (string, error)
|
SimpleSelect(name string, table string, columns string, where string, orderby string, limit string) (string, error)
|
||||||
|
|
|
@ -25,7 +25,7 @@ type TransactionBuilder struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *TransactionBuilder) SimpleDelete(table string, where string) (stmt *sql.Stmt, err error) {
|
func (build *TransactionBuilder) SimpleDelete(table string, where string) (stmt *sql.Stmt, err error) {
|
||||||
res, err := build.adapter.SimpleDelete("_builder", table, where)
|
res, err := build.adapter.SimpleDelete("", table, where)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return stmt, err
|
return stmt, err
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ func (build *TransactionBuilder) SimpleDelete(table string, where string) (stmt
|
||||||
|
|
||||||
// Quick* versions refer to it being quick to type not the performance. For performance critical transactions, you might want to use the Simple* methods or the *Tx methods on the main builder. Alternate suggestions for names are welcome :)
|
// Quick* versions refer to it being quick to type not the performance. For performance critical transactions, you might want to use the Simple* methods or the *Tx methods on the main builder. Alternate suggestions for names are welcome :)
|
||||||
func (build *TransactionBuilder) QuickDelete(table string, where string) *transactionStmt {
|
func (build *TransactionBuilder) QuickDelete(table string, where string) *transactionStmt {
|
||||||
res, err := build.adapter.SimpleDelete("_builder", table, where)
|
res, err := build.adapter.SimpleDelete("", table, where)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return newTransactionStmt(nil, err)
|
return newTransactionStmt(nil, err)
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ func (build *TransactionBuilder) QuickDelete(table string, where string) *transa
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *TransactionBuilder) SimpleInsert(table string, columns string, fields string) (stmt *sql.Stmt, err error) {
|
func (build *TransactionBuilder) SimpleInsert(table string, columns string, fields string) (stmt *sql.Stmt, err error) {
|
||||||
res, err := build.adapter.SimpleInsert("_builder", table, columns, fields)
|
res, err := build.adapter.SimpleInsert("", table, columns, fields)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return stmt, err
|
return stmt, err
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ func (build *TransactionBuilder) SimpleInsert(table string, columns string, fiel
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *TransactionBuilder) QuickInsert(table string, where string) *transactionStmt {
|
func (build *TransactionBuilder) QuickInsert(table string, where string) *transactionStmt {
|
||||||
res, err := build.adapter.SimpleDelete("_builder", table, where)
|
res, err := build.adapter.SimpleDelete("", table, where)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return newTransactionStmt(nil, err)
|
return newTransactionStmt(nil, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,8 @@
|
||||||
echo "Updating Gosora"
|
echo "Updating Gosora"
|
||||||
rm ./schema/lastSchema.json
|
|
||||||
cp ./schema/schema.json ./schema/lastSchema.json
|
|
||||||
git stash
|
git stash
|
||||||
git pull origin master
|
git pull origin master
|
||||||
git stash apply
|
git stash apply
|
||||||
|
|
||||||
echo "Patching Gosora"
|
echo "Patching Gosora"
|
||||||
cd ./patcher
|
go build -o Patcher "./patcher"
|
||||||
go generate
|
|
||||||
go build -o Patcher
|
|
||||||
mv ./Patcher ..
|
|
||||||
cd ..
|
|
||||||
./Patcher
|
./Patcher
|
|
@ -89,7 +89,9 @@ func topicRoutes() *RouteGroup {
|
||||||
Action("routes.LockTopicSubmit", "/topic/lock/submit/").LitBefore("req.URL.Path += extraData"),
|
Action("routes.LockTopicSubmit", "/topic/lock/submit/").LitBefore("req.URL.Path += extraData"),
|
||||||
Action("routes.UnlockTopicSubmit", "/topic/unlock/submit/", "extraData"),
|
Action("routes.UnlockTopicSubmit", "/topic/unlock/submit/", "extraData"),
|
||||||
Action("routes.MoveTopicSubmit", "/topic/move/submit/", "extraData"),
|
Action("routes.MoveTopicSubmit", "/topic/move/submit/", "extraData"),
|
||||||
Action("routes.LikeTopicSubmit", "/topic/like/submit/", "extraData").Before("ParseForm"),
|
Action("routes.LikeTopicSubmit", "/topic/like/submit/", "extraData"),
|
||||||
|
UploadAction("routes.AddAttachToTopicSubmit", "/topic/attach/add/submit/", "extraData").MaxSizeVar("int(common.Config.MaxRequestSize)"),
|
||||||
|
Action("routes.RemoveAttachFromTopicSubmit", "/topic/attach/remove/submit/", "extraData"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,7 +101,7 @@ func replyRoutes() *RouteGroup {
|
||||||
UploadAction("routes.CreateReplySubmit", "/reply/create/").MaxSizeVar("int(common.Config.MaxRequestSize)"), // TODO: Rename the route so it's /reply/create/submit/
|
UploadAction("routes.CreateReplySubmit", "/reply/create/").MaxSizeVar("int(common.Config.MaxRequestSize)"), // TODO: Rename the route so it's /reply/create/submit/
|
||||||
Action("routes.ReplyEditSubmit", "/reply/edit/submit/", "extraData"),
|
Action("routes.ReplyEditSubmit", "/reply/edit/submit/", "extraData"),
|
||||||
Action("routes.ReplyDeleteSubmit", "/reply/delete/submit/", "extraData"),
|
Action("routes.ReplyDeleteSubmit", "/reply/delete/submit/", "extraData"),
|
||||||
Action("routes.ReplyLikeSubmit", "/reply/like/submit/", "extraData").Before("ParseForm"),
|
Action("routes.ReplyLikeSubmit", "/reply/like/submit/", "extraData"),
|
||||||
//MemberView("routes.ReplyEdit","/reply/edit/","extraData"), // No js fallback
|
//MemberView("routes.ReplyEdit","/reply/edit/","extraData"), // No js fallback
|
||||||
//MemberView("routes.ReplyDelete","/reply/delete/","extraData"), // No js confirmation page? We could have a confirmation modal for the JS case
|
//MemberView("routes.ReplyDelete","/reply/delete/","extraData"), // No js confirmation page? We could have a confirmation modal for the JS case
|
||||||
)
|
)
|
||||||
|
|
|
@ -30,6 +30,7 @@ var successJSONBytes = []byte(`{"success":"1"}`)
|
||||||
var phraseLoginAlerts = []byte(`{"msgs":[{"msg":"Login to see your alerts","path":"/accounts/login"}],"msgCount":0}`)
|
var phraseLoginAlerts = []byte(`{"msgs":[{"msg":"Login to see your alerts","path":"/accounts/login"}],"msgCount":0}`)
|
||||||
|
|
||||||
// TODO: Refactor this endpoint
|
// TODO: Refactor this endpoint
|
||||||
|
// TODO: Move this into the routes package
|
||||||
func routeAPI(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
|
func routeAPI(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
|
// 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")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
@ -44,6 +45,7 @@ func routeAPI(w http.ResponseWriter, r *http.Request, user common.User) common.R
|
||||||
}
|
}
|
||||||
|
|
||||||
switch r.FormValue("module") {
|
switch r.FormValue("module") {
|
||||||
|
// TODO: Split this into it's own function
|
||||||
case "dismiss-alert":
|
case "dismiss-alert":
|
||||||
asid, err := strconv.Atoi(r.FormValue("asid"))
|
asid, err := strconv.Atoi(r.FormValue("asid"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -61,6 +63,7 @@ func routeAPI(w http.ResponseWriter, r *http.Request, user common.User) common.R
|
||||||
if common.EnableWebsockets && count > 0 {
|
if common.EnableWebsockets && count > 0 {
|
||||||
_ = common.WsHub.PushMessage(user.ID, `{"event":"dismiss-alert","asid":`+strconv.Itoa(asid)+`}`)
|
_ = common.WsHub.PushMessage(user.ID, `{"event":"dismiss-alert","asid":`+strconv.Itoa(asid)+`}`)
|
||||||
}
|
}
|
||||||
|
// TODO: Split this into it's own function
|
||||||
case "alerts": // A feed of events tailored for a specific user
|
case "alerts": // A feed of events tailored for a specific user
|
||||||
if !user.Loggedin {
|
if !user.Loggedin {
|
||||||
w.Write(phraseLoginAlerts)
|
w.Write(phraseLoginAlerts)
|
||||||
|
|
|
@ -21,7 +21,7 @@ var forumStmts ForumStmts
|
||||||
func init() {
|
func init() {
|
||||||
common.DbInits.Add(func(acc *qgen.Accumulator) error {
|
common.DbInits.Add(func(acc *qgen.Accumulator) error {
|
||||||
forumStmts = ForumStmts{
|
forumStmts = ForumStmts{
|
||||||
getTopics: acc.Select("topics").Columns("tid, title, content, createdBy, is_closed, sticky, createdAt, lastReplyAt, lastReplyBy, parentID, views, postCount, likeCount").Where("parentID = ?").Orderby("sticky DESC, lastReplyAt DESC, createdBy DESC").Limit("?,?").Prepare(),
|
getTopics: acc.Select("topics").Columns("tid, title, content, createdBy, is_closed, sticky, createdAt, lastReplyAt, lastReplyBy, lastReplyID, parentID, views, postCount, likeCount").Where("parentID = ?").Orderby("sticky DESC, lastReplyAt DESC, createdBy DESC").Limit("?,?").Prepare(),
|
||||||
}
|
}
|
||||||
return acc.FirstError()
|
return acc.FirstError()
|
||||||
})
|
})
|
||||||
|
@ -68,13 +68,12 @@ func ViewForum(w http.ResponseWriter, r *http.Request, user common.User, header
|
||||||
var reqUserList = make(map[int]bool)
|
var reqUserList = make(map[int]bool)
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var topicItem = common.TopicsRow{ID: 0}
|
var topicItem = common.TopicsRow{ID: 0}
|
||||||
err := rows.Scan(&topicItem.ID, &topicItem.Title, &topicItem.Content, &topicItem.CreatedBy, &topicItem.IsClosed, &topicItem.Sticky, &topicItem.CreatedAt, &topicItem.LastReplyAt, &topicItem.LastReplyBy, &topicItem.ParentID, &topicItem.ViewCount, &topicItem.PostCount, &topicItem.LikeCount)
|
err := rows.Scan(&topicItem.ID, &topicItem.Title, &topicItem.Content, &topicItem.CreatedBy, &topicItem.IsClosed, &topicItem.Sticky, &topicItem.CreatedAt, &topicItem.LastReplyAt, &topicItem.LastReplyBy, &topicItem.LastReplyID, &topicItem.ParentID, &topicItem.ViewCount, &topicItem.PostCount, &topicItem.LikeCount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.InternalError(err, w, r)
|
return common.InternalError(err, w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
topicItem.Link = common.BuildTopicURL(common.NameToSlug(topicItem.Title), topicItem.ID)
|
topicItem.Link = common.BuildTopicURL(common.NameToSlug(topicItem.Title), topicItem.ID)
|
||||||
topicItem.RelativeLastReplyAt = common.RelativeTime(topicItem.LastReplyAt)
|
|
||||||
// TODO: Create a specialised function with a bit less overhead for getting the last page for a post count
|
// TODO: Create a specialised function with a bit less overhead for getting the last page for a post count
|
||||||
_, _, lastPage := common.PageOffset(topicItem.PostCount, 1, common.Config.ItemsPerPage)
|
_, _, lastPage := common.PageOffset(topicItem.PostCount, 1, common.Config.ItemsPerPage)
|
||||||
topicItem.LastPage = lastPage
|
topicItem.LastPage = lastPage
|
||||||
|
|
|
@ -34,7 +34,7 @@ func ViewProfile(w http.ResponseWriter, r *http.Request, user common.User, heade
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
var replyCreatedAt time.Time
|
var replyCreatedAt time.Time
|
||||||
var replyContent, replyCreatedByName, replyRelativeCreatedAt, replyAvatar, replyMicroAvatar, replyTag, replyClassName string
|
var replyContent, replyCreatedByName, replyAvatar, replyMicroAvatar, replyTag, replyClassName string
|
||||||
var rid, replyCreatedBy, replyLastEdit, replyLastEditBy, replyLines, replyGroup int
|
var rid, replyCreatedBy, replyLastEdit, replyLastEditBy, replyLines, replyGroup int
|
||||||
var replyList []common.ReplyUser
|
var replyList []common.ReplyUser
|
||||||
|
|
||||||
|
@ -98,11 +98,9 @@ func ViewProfile(w http.ResponseWriter, r *http.Request, user common.User, heade
|
||||||
|
|
||||||
replyLiked := false
|
replyLiked := false
|
||||||
replyLikeCount := 0
|
replyLikeCount := 0
|
||||||
replyRelativeCreatedAt = common.RelativeTime(replyCreatedAt)
|
|
||||||
|
|
||||||
// TODO: Add a hook here
|
// TODO: Add a hook here
|
||||||
|
|
||||||
replyList = append(replyList, common.ReplyUser{rid, puser.ID, replyContent, common.ParseMessage(replyContent, 0, ""), replyCreatedBy, common.BuildProfileURL(common.NameToSlug(replyCreatedByName), replyCreatedBy), replyCreatedByName, replyGroup, replyCreatedAt, replyRelativeCreatedAt, replyLastEdit, replyLastEditBy, replyAvatar, replyMicroAvatar, replyClassName, replyLines, replyTag, "", "", "", 0, "", replyLiked, replyLikeCount, "", ""})
|
replyList = append(replyList, common.ReplyUser{rid, puser.ID, replyContent, common.ParseMessage(replyContent, 0, ""), replyCreatedBy, common.BuildProfileURL(common.NameToSlug(replyCreatedByName), replyCreatedBy), replyCreatedByName, replyGroup, replyCreatedAt, replyLastEdit, replyLastEditBy, replyAvatar, replyMicroAvatar, replyClassName, replyLines, replyTag, "", "", "", 0, "", replyLiked, replyLikeCount, "", ""})
|
||||||
}
|
}
|
||||||
err = rows.Err()
|
err = rows.Err()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -1,14 +1,8 @@
|
||||||
package routes
|
package routes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/hex"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -16,7 +10,6 @@ import (
|
||||||
"github.com/Azareal/Gosora/common/counters"
|
"github.com/Azareal/Gosora/common/counters"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: De-duplicate the upload logic
|
|
||||||
func CreateReplySubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
|
func CreateReplySubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
|
||||||
tid, err := strconv.Atoi(r.PostFormValue("tid"))
|
tid, err := strconv.Atoi(r.PostFormValue("tid"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -45,70 +38,9 @@ func CreateReplySubmit(w http.ResponseWriter, r *http.Request, user common.User)
|
||||||
// Handle the file attachments
|
// Handle the file attachments
|
||||||
// TODO: Stop duplicating this code
|
// TODO: Stop duplicating this code
|
||||||
if user.Perms.UploadFiles {
|
if user.Perms.UploadFiles {
|
||||||
files, ok := r.MultipartForm.File["upload_files"]
|
_, rerr := uploadAttachment(w, r, user, topic.ParentID, "forums", tid, "replies")
|
||||||
if ok {
|
if rerr != nil {
|
||||||
if len(files) > 5 {
|
return rerr
|
||||||
return common.LocalError("You can't attach more than five files", w, r, user)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, file := range files {
|
|
||||||
if file.Filename == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
log.Print("file.Filename ", file.Filename)
|
|
||||||
extarr := strings.Split(file.Filename, ".")
|
|
||||||
if len(extarr) < 2 {
|
|
||||||
return common.LocalError("Bad file", w, r, user)
|
|
||||||
}
|
|
||||||
ext := extarr[len(extarr)-1]
|
|
||||||
|
|
||||||
// TODO: Can we do this without a regex?
|
|
||||||
reg, err := regexp.Compile("[^A-Za-z0-9]+")
|
|
||||||
if err != nil {
|
|
||||||
return common.LocalError("Bad file extension", w, r, user)
|
|
||||||
}
|
|
||||||
ext = strings.ToLower(reg.ReplaceAllString(ext, ""))
|
|
||||||
if !common.AllowedFileExts.Contains(ext) {
|
|
||||||
return common.LocalError("You're not allowed to upload files with this extension", w, r, user)
|
|
||||||
}
|
|
||||||
|
|
||||||
infile, err := file.Open()
|
|
||||||
if err != nil {
|
|
||||||
return common.LocalError("Upload failed", w, r, user)
|
|
||||||
}
|
|
||||||
defer infile.Close()
|
|
||||||
|
|
||||||
hasher := sha256.New()
|
|
||||||
_, err = io.Copy(hasher, infile)
|
|
||||||
if err != nil {
|
|
||||||
return common.LocalError("Upload failed [Hashing Failed]", w, r, user)
|
|
||||||
}
|
|
||||||
infile.Close()
|
|
||||||
|
|
||||||
checksum := hex.EncodeToString(hasher.Sum(nil))
|
|
||||||
filename := checksum + "." + ext
|
|
||||||
outfile, err := os.Create("." + "/attachs/" + filename)
|
|
||||||
if err != nil {
|
|
||||||
return common.LocalError("Upload failed [File Creation Failed]", w, r, user)
|
|
||||||
}
|
|
||||||
defer outfile.Close()
|
|
||||||
|
|
||||||
infile, err = file.Open()
|
|
||||||
if err != nil {
|
|
||||||
return common.LocalError("Upload failed", w, r, user)
|
|
||||||
}
|
|
||||||
defer infile.Close()
|
|
||||||
|
|
||||||
_, err = io.Copy(outfile, infile)
|
|
||||||
if err != nil {
|
|
||||||
return common.LocalError("Upload failed [Copy Failed]", w, r, user)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = common.Attachments.Add(topic.ParentID, "forums", tid, "replies", user.ID, filename)
|
|
||||||
if err != nil {
|
|
||||||
return common.InternalError(err, w, r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,8 +59,8 @@ func CreateReplySubmit(w http.ResponseWriter, r *http.Request, user common.User)
|
||||||
var maxPollOptions = 10
|
var maxPollOptions = 10
|
||||||
var pollInputItems = make(map[int]string)
|
var pollInputItems = make(map[int]string)
|
||||||
for key, values := range r.Form {
|
for key, values := range r.Form {
|
||||||
common.DebugDetail("key: ", key)
|
//common.DebugDetail("key: ", key)
|
||||||
common.DebugDetailf("values: %+v\n", values)
|
//common.DebugDetailf("values: %+v\n", values)
|
||||||
for _, value := range values {
|
for _, value := range values {
|
||||||
if strings.HasPrefix(key, "pollinputitem[") {
|
if strings.HasPrefix(key, "pollinputitem[") {
|
||||||
halves := strings.Split(key, "[")
|
halves := strings.Split(key, "[")
|
||||||
|
|
327
routes/topic.go
327
routes/topic.go
|
@ -5,6 +5,7 @@ import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -22,6 +23,7 @@ import (
|
||||||
type TopicStmts struct {
|
type TopicStmts struct {
|
||||||
getReplies *sql.Stmt
|
getReplies *sql.Stmt
|
||||||
getLikedTopic *sql.Stmt
|
getLikedTopic *sql.Stmt
|
||||||
|
updateAttachs *sql.Stmt
|
||||||
}
|
}
|
||||||
|
|
||||||
var topicStmts TopicStmts
|
var topicStmts TopicStmts
|
||||||
|
@ -32,6 +34,8 @@ func init() {
|
||||||
topicStmts = TopicStmts{
|
topicStmts = TopicStmts{
|
||||||
getReplies: acc.SimpleLeftJoin("replies", "users", "replies.rid, replies.content, replies.createdBy, replies.createdAt, replies.lastEdit, replies.lastEditBy, users.avatar, users.name, users.group, users.url_prefix, users.url_name, users.level, replies.ipaddress, replies.likeCount, replies.actionType", "replies.createdBy = users.uid", "replies.tid = ?", "replies.rid ASC", "?,?"),
|
getReplies: acc.SimpleLeftJoin("replies", "users", "replies.rid, replies.content, replies.createdBy, replies.createdAt, replies.lastEdit, replies.lastEditBy, users.avatar, users.name, users.group, users.url_prefix, users.url_name, users.level, replies.ipaddress, replies.likeCount, replies.actionType", "replies.createdBy = users.uid", "replies.tid = ?", "replies.rid ASC", "?,?"),
|
||||||
getLikedTopic: acc.Select("likes").Columns("targetItem").Where("sentBy = ? && targetItem = ? && targetType = 'topics'").Prepare(),
|
getLikedTopic: acc.Select("likes").Columns("targetItem").Where("sentBy = ? && targetItem = ? && targetType = 'topics'").Prepare(),
|
||||||
|
// TODO: Less race-y attachment count updates
|
||||||
|
updateAttachs: acc.Update("topics").Set("attachCount = ?").Where("tid = ?").Prepare(),
|
||||||
}
|
}
|
||||||
return acc.FirstError()
|
return acc.FirstError()
|
||||||
})
|
})
|
||||||
|
@ -51,7 +55,6 @@ func ViewTopic(w http.ResponseWriter, r *http.Request, user common.User, header
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return common.InternalError(err, w, r)
|
return common.InternalError(err, w, r)
|
||||||
}
|
}
|
||||||
topic.ClassName = ""
|
|
||||||
|
|
||||||
ferr := common.ForumUserCheck(header, w, r, &user, topic.ParentID)
|
ferr := common.ForumUserCheck(header, w, r, &user, topic.ParentID)
|
||||||
if ferr != nil {
|
if ferr != nil {
|
||||||
|
@ -64,6 +67,7 @@ func ViewTopic(w http.ResponseWriter, r *http.Request, user common.User, header
|
||||||
header.Zone = "view_topic"
|
header.Zone = "view_topic"
|
||||||
header.Path = common.BuildTopicURL(common.NameToSlug(topic.Title), topic.ID)
|
header.Path = common.BuildTopicURL(common.NameToSlug(topic.Title), topic.ID)
|
||||||
|
|
||||||
|
// TODO: Cache ContentHTML when possible?
|
||||||
topic.ContentHTML = common.ParseMessage(topic.Content, topic.ParentID, "forums")
|
topic.ContentHTML = common.ParseMessage(topic.Content, topic.ParentID, "forums")
|
||||||
topic.ContentLines = strings.Count(topic.Content, "\n")
|
topic.ContentLines = strings.Count(topic.Content, "\n")
|
||||||
|
|
||||||
|
@ -76,7 +80,6 @@ func ViewTopic(w http.ResponseWriter, r *http.Request, user common.User, header
|
||||||
if postGroup.IsMod {
|
if postGroup.IsMod {
|
||||||
topic.ClassName = common.Config.StaffCSS
|
topic.ClassName = common.Config.StaffCSS
|
||||||
}
|
}
|
||||||
topic.RelativeCreatedAt = common.RelativeTime(topic.CreatedAt)
|
|
||||||
|
|
||||||
forum, err := common.Forums.Get(topic.ParentID)
|
forum, err := common.Forums.Get(topic.ParentID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -105,6 +108,15 @@ func ViewTopic(w http.ResponseWriter, r *http.Request, user common.User, header
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if topic.AttachCount > 0 {
|
||||||
|
attachs, err := common.Attachments.MiniTopicGet(topic.ID)
|
||||||
|
if err != nil {
|
||||||
|
// TODO: We might want to be a little permissive here in-case of a desync?
|
||||||
|
return common.InternalError(err, w, r)
|
||||||
|
}
|
||||||
|
topic.Attachments = attachs
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate the offset
|
// Calculate the offset
|
||||||
offset, page, lastPage := common.PageOffset(topic.PostCount, page, common.Config.ItemsPerPage)
|
offset, page, lastPage := common.PageOffset(topic.PostCount, page, common.Config.ItemsPerPage)
|
||||||
pageList := common.Paginate(topic.PostCount, common.Config.ItemsPerPage, 5)
|
pageList := common.Paginate(topic.PostCount, common.Config.ItemsPerPage, 5)
|
||||||
|
@ -150,33 +162,37 @@ func ViewTopic(w http.ResponseWriter, r *http.Request, user common.User, header
|
||||||
// TODO: Make a function for this? Build a more sophisticated noavatar handling system? Do bulk user loads and let the common.UserStore initialise this?
|
// TODO: Make a function for this? Build a more sophisticated noavatar handling system? Do bulk user loads and let the common.UserStore initialise this?
|
||||||
replyItem.Avatar, replyItem.MicroAvatar = common.BuildAvatar(replyItem.CreatedBy, replyItem.Avatar)
|
replyItem.Avatar, replyItem.MicroAvatar = common.BuildAvatar(replyItem.CreatedBy, replyItem.Avatar)
|
||||||
replyItem.Tag = postGroup.Tag
|
replyItem.Tag = postGroup.Tag
|
||||||
replyItem.RelativeCreatedAt = common.RelativeTime(replyItem.CreatedAt)
|
|
||||||
|
|
||||||
// We really shouldn't have inline HTML, we should do something about this...
|
// We really shouldn't have inline HTML, we should do something about this...
|
||||||
if replyItem.ActionType != "" {
|
if replyItem.ActionType != "" {
|
||||||
|
var action string
|
||||||
switch replyItem.ActionType {
|
switch replyItem.ActionType {
|
||||||
case "lock":
|
case "lock":
|
||||||
replyItem.ActionType = phrases.GetTmplPhrasef("topic.action_topic_lock", replyItem.UserLink, replyItem.CreatedByName)
|
action = "lock"
|
||||||
replyItem.ActionIcon = "🔒︎"
|
replyItem.ActionIcon = "🔒︎"
|
||||||
case "unlock":
|
case "unlock":
|
||||||
replyItem.ActionType = phrases.GetTmplPhrasef("topic.action_topic_unlock", replyItem.UserLink, replyItem.CreatedByName)
|
action = "unlock"
|
||||||
replyItem.ActionIcon = "🔓︎"
|
replyItem.ActionIcon = "🔓︎"
|
||||||
case "stick":
|
case "stick":
|
||||||
replyItem.ActionType = phrases.GetTmplPhrasef("topic.action_topic_stick", replyItem.UserLink, replyItem.CreatedByName)
|
action = "stick"
|
||||||
replyItem.ActionIcon = "📌︎"
|
replyItem.ActionIcon = "📌︎"
|
||||||
case "unstick":
|
case "unstick":
|
||||||
replyItem.ActionType = phrases.GetTmplPhrasef("topic.action_topic_unstick", replyItem.UserLink, replyItem.CreatedByName)
|
action = "unstick"
|
||||||
replyItem.ActionIcon = "📌︎"
|
replyItem.ActionIcon = "📌︎"
|
||||||
case "move":
|
case "move":
|
||||||
replyItem.ActionType = phrases.GetTmplPhrasef("topic.action_topic_move", replyItem.UserLink, replyItem.CreatedByName)
|
action = "move"
|
||||||
// TODO: Only fire this off if a corresponding phrase for the ActionType doesn't exist? Or maybe have some sort of action registry?
|
replyItem.ActionIcon = ""
|
||||||
default:
|
}
|
||||||
|
if action != "" {
|
||||||
|
replyItem.ActionType = phrases.GetTmplPhrasef("topic.action_topic_"+action, replyItem.UserLink, replyItem.CreatedByName)
|
||||||
|
} else {
|
||||||
|
// TODO: Only fire this off if a corresponding phrase for the ActionType doesn't exist? Or maybe have some sort of action registry?
|
||||||
replyItem.ActionType = phrases.GetTmplPhrasef("topic.action_topic_default", replyItem.ActionType)
|
replyItem.ActionType = phrases.GetTmplPhrasef("topic.action_topic_default", replyItem.ActionType)
|
||||||
replyItem.ActionIcon = ""
|
replyItem.ActionIcon = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if replyItem.LikeCount > 0 {
|
if replyItem.LikeCount > 0 && user.Liked > 0 {
|
||||||
likedMap[replyItem.ID] = len(tpage.ItemList)
|
likedMap[replyItem.ID] = len(tpage.ItemList)
|
||||||
likedQueryList = append(likedQueryList, replyItem.ID)
|
likedQueryList = append(likedQueryList, replyItem.ID)
|
||||||
}
|
}
|
||||||
|
@ -192,6 +208,7 @@ func ViewTopic(w http.ResponseWriter, r *http.Request, user common.User, header
|
||||||
|
|
||||||
// TODO: Add a config setting to disable the liked query for a burst of extra speed
|
// TODO: Add a config setting to disable the liked query for a burst of extra speed
|
||||||
if user.Liked > 0 && len(likedQueryList) > 1 /*&& user.LastLiked <= time.Now()*/ {
|
if user.Liked > 0 && len(likedQueryList) > 1 /*&& user.LastLiked <= time.Now()*/ {
|
||||||
|
// TODO: Abstract this
|
||||||
rows, err := qgen.NewAcc().Select("likes").Columns("targetItem").Where("sentBy = ? AND targetType = 'replies'").In("targetItem", likedQueryList[1:]).Query(user.ID)
|
rows, err := qgen.NewAcc().Select("likes").Columns("targetItem").Where("sentBy = ? AND targetType = 'replies'").In("targetItem", likedQueryList[1:]).Query(user.ID)
|
||||||
if err != nil && err != sql.ErrNoRows {
|
if err != nil && err != sql.ErrNoRows {
|
||||||
return common.InternalError(err, w, r)
|
return common.InternalError(err, w, r)
|
||||||
|
@ -219,6 +236,89 @@ func ViewTopic(w http.ResponseWriter, r *http.Request, user common.User, header
|
||||||
return rerr
|
return rerr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Avoid uploading this again if the attachment already exists? They'll resolve to the same hash either way, but we could save on some IO / bandwidth here
|
||||||
|
// TODO: Enforce the max request limit on all of this topic's attachments
|
||||||
|
// TODO: Test this route
|
||||||
|
func AddAttachToTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User, stid string) common.RouteError {
|
||||||
|
tid, err := strconv.Atoi(stid)
|
||||||
|
if err != nil {
|
||||||
|
return common.LocalErrorJS(phrases.GetErrorPhrase("id_must_be_integer"), w, r)
|
||||||
|
}
|
||||||
|
topic, err := common.Topics.Get(tid)
|
||||||
|
if err != nil {
|
||||||
|
return common.NotFoundJS(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ferr := common.SimpleForumUserCheck(w, r, &user, topic.ParentID)
|
||||||
|
if ferr != nil {
|
||||||
|
return ferr
|
||||||
|
}
|
||||||
|
if !user.Perms.ViewTopic || !user.Perms.EditTopic || !user.Perms.UploadFiles {
|
||||||
|
return common.NoPermissionsJS(w, r, user)
|
||||||
|
}
|
||||||
|
if topic.IsClosed && !user.Perms.CloseTopic {
|
||||||
|
return common.NoPermissionsJS(w, r, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the file attachments
|
||||||
|
pathMap, rerr := uploadAttachment(w, r, user, topic.ParentID, "forums", tid, "topics")
|
||||||
|
if rerr != nil {
|
||||||
|
// TODO: This needs to be a JS error...
|
||||||
|
return rerr
|
||||||
|
}
|
||||||
|
if len(pathMap) == 0 {
|
||||||
|
return common.InternalErrorJS(errors.New("no paths for attachment add"), w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
var elemStr string
|
||||||
|
for path, aids := range pathMap {
|
||||||
|
elemStr += "\"" + path + "\":\"" + aids + "\","
|
||||||
|
}
|
||||||
|
if len(elemStr) > 1 {
|
||||||
|
elemStr = elemStr[:len(elemStr)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write([]byte(`{"success":"1","elems":[{` + elemStr + `}]}`))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func RemoveAttachFromTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User, stid string) common.RouteError {
|
||||||
|
tid, err := strconv.Atoi(stid)
|
||||||
|
if err != nil {
|
||||||
|
return common.LocalErrorJS(phrases.GetErrorPhrase("id_must_be_integer"), w, r)
|
||||||
|
}
|
||||||
|
topic, err := common.Topics.Get(tid)
|
||||||
|
if err != nil {
|
||||||
|
return common.NotFoundJS(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ferr := common.SimpleForumUserCheck(w, r, &user, topic.ParentID)
|
||||||
|
if ferr != nil {
|
||||||
|
return ferr
|
||||||
|
}
|
||||||
|
if !user.Perms.ViewTopic || !user.Perms.EditTopic {
|
||||||
|
return common.NoPermissionsJS(w, r, user)
|
||||||
|
}
|
||||||
|
if topic.IsClosed && !user.Perms.CloseTopic {
|
||||||
|
return common.NoPermissionsJS(w, r, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, said := range strings.Split(r.PostFormValue("aids"), ",") {
|
||||||
|
aid, err := strconv.Atoi(said)
|
||||||
|
if err != nil {
|
||||||
|
return common.LocalErrorJS(phrases.GetErrorPhrase("id_must_be_integer"), w, r)
|
||||||
|
}
|
||||||
|
rerr := deleteAttachment(w, r, user, aid, true)
|
||||||
|
if rerr != nil {
|
||||||
|
// TODO: This needs to be a JS error...
|
||||||
|
return rerr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write(successJSONBytes)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ? - Should we add a new permission or permission zone (like per-forum permissions) specifically for profile comment creation
|
// ? - Should we add a new permission or permission zone (like per-forum permissions) specifically for profile comment creation
|
||||||
// ? - Should we allow banned users to make reports? How should we handle report abuse?
|
// ? - Should we allow banned users to make reports? How should we handle report abuse?
|
||||||
// TODO: Add a permission to stop certain users from using custom avatars
|
// TODO: Add a permission to stop certain users from using custom avatars
|
||||||
|
@ -337,8 +437,6 @@ func CreateTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User)
|
||||||
var maxPollOptions = 10
|
var maxPollOptions = 10
|
||||||
var pollInputItems = make(map[int]string)
|
var pollInputItems = make(map[int]string)
|
||||||
for key, values := range r.Form {
|
for key, values := range r.Form {
|
||||||
//common.DebugDetail("key: ", key)
|
|
||||||
//common.DebugDetailf("values: %+v\n", values)
|
|
||||||
for _, value := range values {
|
for _, value := range values {
|
||||||
if strings.HasPrefix(key, "pollinputitem[") {
|
if strings.HasPrefix(key, "pollinputitem[") {
|
||||||
halves := strings.Split(key, "[")
|
halves := strings.Split(key, "[")
|
||||||
|
@ -389,72 +487,10 @@ func CreateTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle the file attachments
|
// Handle the file attachments
|
||||||
// TODO: Stop duplicating this code
|
|
||||||
if user.Perms.UploadFiles {
|
if user.Perms.UploadFiles {
|
||||||
files, ok := r.MultipartForm.File["upload_files"]
|
_, rerr := uploadAttachment(w, r, user, fid, "forums", tid, "topics")
|
||||||
if ok {
|
if rerr != nil {
|
||||||
if len(files) > 5 {
|
return rerr
|
||||||
return common.LocalError("You can't attach more than five files", w, r, user)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, file := range files {
|
|
||||||
if file.Filename == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
common.DebugLog("file.Filename ", file.Filename)
|
|
||||||
extarr := strings.Split(file.Filename, ".")
|
|
||||||
if len(extarr) < 2 {
|
|
||||||
return common.LocalError("Bad file", w, r, user)
|
|
||||||
}
|
|
||||||
ext := extarr[len(extarr)-1]
|
|
||||||
|
|
||||||
// TODO: Can we do this without a regex?
|
|
||||||
reg, err := regexp.Compile("[^A-Za-z0-9]+")
|
|
||||||
if err != nil {
|
|
||||||
return common.LocalError("Bad file extension", w, r, user)
|
|
||||||
}
|
|
||||||
ext = strings.ToLower(reg.ReplaceAllString(ext, ""))
|
|
||||||
if !common.AllowedFileExts.Contains(ext) {
|
|
||||||
return common.LocalError("You're not allowed to upload files with this extension", w, r, user)
|
|
||||||
}
|
|
||||||
|
|
||||||
infile, err := file.Open()
|
|
||||||
if err != nil {
|
|
||||||
return common.LocalError("Upload failed", w, r, user)
|
|
||||||
}
|
|
||||||
defer infile.Close()
|
|
||||||
|
|
||||||
hasher := sha256.New()
|
|
||||||
_, err = io.Copy(hasher, infile)
|
|
||||||
if err != nil {
|
|
||||||
return common.LocalError("Upload failed [Hashing Failed]", w, r, user)
|
|
||||||
}
|
|
||||||
infile.Close()
|
|
||||||
|
|
||||||
checksum := hex.EncodeToString(hasher.Sum(nil))
|
|
||||||
filename := checksum + "." + ext
|
|
||||||
outfile, err := os.Create("." + "/attachs/" + filename)
|
|
||||||
if err != nil {
|
|
||||||
return common.LocalError("Upload failed [File Creation Failed]", w, r, user)
|
|
||||||
}
|
|
||||||
defer outfile.Close()
|
|
||||||
|
|
||||||
infile, err = file.Open()
|
|
||||||
if err != nil {
|
|
||||||
return common.LocalError("Upload failed", w, r, user)
|
|
||||||
}
|
|
||||||
defer infile.Close()
|
|
||||||
|
|
||||||
_, err = io.Copy(outfile, infile)
|
|
||||||
if err != nil {
|
|
||||||
return common.LocalError("Upload failed [Copy Failed]", w, r, user)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = common.Attachments.Add(fid, "forums", tid, "topics", user.ID, filename)
|
|
||||||
if err != nil {
|
|
||||||
return common.InternalError(err, w, r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -464,6 +500,141 @@ func CreateTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func uploadFilesWithHash(w http.ResponseWriter, r *http.Request, user common.User, dir string) (filenames []string, rerr common.RouteError) {
|
||||||
|
files, ok := r.MultipartForm.File["upload_files"]
|
||||||
|
if !ok {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if len(files) > 5 {
|
||||||
|
return nil, common.LocalError("You can't attach more than five files", w, r, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
if file.Filename == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
//common.DebugLog("file.Filename ", file.Filename)
|
||||||
|
|
||||||
|
extarr := strings.Split(file.Filename, ".")
|
||||||
|
if len(extarr) < 2 {
|
||||||
|
return nil, common.LocalError("Bad file", w, r, user)
|
||||||
|
}
|
||||||
|
ext := extarr[len(extarr)-1]
|
||||||
|
|
||||||
|
// TODO: Can we do this without a regex?
|
||||||
|
reg, err := regexp.Compile("[^A-Za-z0-9]+")
|
||||||
|
if err != nil {
|
||||||
|
return nil, common.LocalError("Bad file extension", w, r, user)
|
||||||
|
}
|
||||||
|
ext = strings.ToLower(reg.ReplaceAllString(ext, ""))
|
||||||
|
if !common.AllowedFileExts.Contains(ext) {
|
||||||
|
return nil, common.LocalError("You're not allowed to upload files with this extension", w, r, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
infile, err := file.Open()
|
||||||
|
if err != nil {
|
||||||
|
return nil, common.LocalError("Upload failed", w, r, user)
|
||||||
|
}
|
||||||
|
defer infile.Close()
|
||||||
|
|
||||||
|
hasher := sha256.New()
|
||||||
|
_, err = io.Copy(hasher, infile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, common.LocalError("Upload failed [Hashing Failed]", w, r, user)
|
||||||
|
}
|
||||||
|
infile.Close()
|
||||||
|
|
||||||
|
checksum := hex.EncodeToString(hasher.Sum(nil))
|
||||||
|
filename := checksum + "." + ext
|
||||||
|
outfile, err := os.Create(dir + filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, common.LocalError("Upload failed [File Creation Failed]", w, r, user)
|
||||||
|
}
|
||||||
|
defer outfile.Close()
|
||||||
|
|
||||||
|
infile, err = file.Open()
|
||||||
|
if err != nil {
|
||||||
|
return nil, common.LocalError("Upload failed", w, r, user)
|
||||||
|
}
|
||||||
|
defer infile.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(outfile, infile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, common.LocalError("Upload failed [Copy Failed]", w, r, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
filenames = append(filenames, filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
return filenames, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Add a table for the files and lock the file row when performing tasks related to the file
|
||||||
|
func deleteAttachment(w http.ResponseWriter, r *http.Request, user common.User, aid int, js bool) common.RouteError {
|
||||||
|
attach, err := common.Attachments.Get(aid)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return common.NotFoundJSQ(w, r, nil, js)
|
||||||
|
} else if err != nil {
|
||||||
|
return common.InternalErrorJSQ(err, w, r, js)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = common.Attachments.Delete(aid)
|
||||||
|
if err != nil {
|
||||||
|
return common.InternalErrorJSQ(err, w, r, js)
|
||||||
|
}
|
||||||
|
|
||||||
|
count := common.Attachments.CountInPath(attach.Path)
|
||||||
|
if err != nil {
|
||||||
|
return common.InternalErrorJSQ(err, w, r, js)
|
||||||
|
}
|
||||||
|
if count == 0 {
|
||||||
|
err := os.Remove("./attachs/" + attach.Path)
|
||||||
|
if err != nil {
|
||||||
|
return common.InternalErrorJSQ(err, w, r, js)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Stop duplicating this code
|
||||||
|
// TODO: Use a transaction here
|
||||||
|
func uploadAttachment(w http.ResponseWriter, r *http.Request, user common.User, sid int, sectionTable string, oid int, originTable string) (pathMap map[string]string, rerr common.RouteError) {
|
||||||
|
pathMap = make(map[string]string)
|
||||||
|
files, rerr := uploadFilesWithHash(w, r, user, "./attachs/")
|
||||||
|
if rerr != nil {
|
||||||
|
return nil, rerr
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, filename := range files {
|
||||||
|
aid, err := common.Attachments.Add(sid, sectionTable, oid, originTable, user.ID, filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, common.InternalError(err, w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ok := pathMap[filename]
|
||||||
|
if ok {
|
||||||
|
pathMap[filename] += "," + strconv.Itoa(aid)
|
||||||
|
} else {
|
||||||
|
pathMap[filename] = strconv.Itoa(aid)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch sectionTable {
|
||||||
|
case "topics":
|
||||||
|
_, err = topicStmts.updateAttachs.Exec(common.Attachments.CountInTopic(oid), oid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, common.InternalError(err, w, r)
|
||||||
|
}
|
||||||
|
err = common.Topics.Reload(oid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, common.InternalError(err, w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Update the stats after edits so that we don't under or over decrement stats during deletes
|
// TODO: Update the stats after edits so that we don't under or over decrement stats during deletes
|
||||||
// TODO: Disable stat updates in posts handled by plugin_guilds
|
// TODO: Disable stat updates in posts handled by plugin_guilds
|
||||||
func EditTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User, stid string) common.RouteError {
|
func EditTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User, stid string) common.RouteError {
|
||||||
|
|
|
@ -25,21 +25,8 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
{{if .CurrentUser.Loggedin}}
|
{{if .CurrentUser.Loggedin}}
|
||||||
<div class="mod_floater auto_hide">
|
{{template "topics_mod_floater.html"}}
|
||||||
<form method="post">
|
|
||||||
<div class="mod_floater_head">
|
|
||||||
<span></span>
|
|
||||||
</div>
|
|
||||||
<div class="mod_floater_body">
|
|
||||||
<select class="mod_floater_options">
|
|
||||||
<option val="delete">{{lang "topic_list.moderate_delete"}}</option>
|
|
||||||
<option val="lock">{{lang "topic_list.moderate_lock"}}</option>
|
|
||||||
<option val="move">{{lang "topic_list.moderate_move"}}</option>
|
|
||||||
</select>
|
|
||||||
<button>{{lang "topic_list.moderate_run"}}</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
{{if .CurrentUser.Perms.CreateTopic}}
|
{{if .CurrentUser.Perms.CreateTopic}}
|
||||||
<div id="forum_topic_create_form" class="rowblock topic_create_form quick_create_form" style="display: none;" aria-label="{{lang "quick_topic.aria"}}">
|
<div id="forum_topic_create_form" class="rowblock topic_create_form quick_create_form" style="display: none;" aria-label="{{lang "quick_topic.aria"}}">
|
||||||
<form id="quick_post_form" enctype="multipart/form-data" action="/topic/create/submit/?session={{.CurrentUser.Session}}" method="post"></form>
|
<form id="quick_post_form" enctype="multipart/form-data" action="/topic/create/submit/?session={{.CurrentUser.Session}}" method="post"></form>
|
||||||
|
@ -106,7 +93,7 @@
|
||||||
<a href="{{.LastUser.Link}}"><img src="{{.LastUser.MicroAvatar}}" height="64" alt="{{.LastUser.Name}}'s Avatar" title="{{.LastUser.Name}}'s Avatar" /></a>
|
<a href="{{.LastUser.Link}}"><img src="{{.LastUser.MicroAvatar}}" height="64" alt="{{.LastUser.Name}}'s Avatar" title="{{.LastUser.Name}}'s Avatar" /></a>
|
||||||
<span>
|
<span>
|
||||||
<a href="{{.LastUser.Link}}" class="lastName" style="font-size: 14px;" title="{{.LastUser.Name}}">{{.LastUser.Name}}</a><br>
|
<a href="{{.LastUser.Link}}" class="lastName" style="font-size: 14px;" title="{{.LastUser.Name}}">{{.LastUser.Name}}</a><br>
|
||||||
<a href="{{.Link}}?page={{.LastPage}}" class="rowsmall lastReplyAt" title="{{abstime .LastReplyAt}}">{{.RelativeLastReplyAt}}</a>
|
<a href="{{.Link}}?page={{.LastPage}}{{if .LastReplyID}}#post-{{.LastReplyID}}{{end}}" class="rowsmall lastReplyAt" title="{{abstime .LastReplyAt}}">{{reltime .LastReplyAt}}</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -77,7 +77,26 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="content_container">
|
<div class="content_container">
|
||||||
<div class="hide_on_edit topic_content user_content" itemprop="text">{{.Topic.ContentHTML}}</div>
|
<div class="hide_on_edit topic_content user_content" itemprop="text">{{.Topic.ContentHTML}}</div>
|
||||||
{{if .CurrentUser.Loggedin}}{{if .CurrentUser.Perms.EditTopic}}<textarea name="topic_content" class="show_on_edit topic_content_input">{{.Topic.Content}}</textarea>{{end}}{{end}}
|
{{if .CurrentUser.Loggedin}}{{if .CurrentUser.Perms.EditTopic}}<textarea name="topic_content" class="show_on_edit topic_content_input">{{.Topic.Content}}</textarea>
|
||||||
|
|
||||||
|
{{if .Topic.Attachments}}<div class="show_on_edit attach_edit_bay" tid="{{.Topic.ID}}">
|
||||||
|
{{range .Topic.Attachments}}
|
||||||
|
<div class="attach_item{{if .Image}} attach_image_holder{{end}}">
|
||||||
|
{{if .Image}}<img src="//{{$.Header.Site.URL}}/attachs/{{.Path}}?sectionID={{.SectionID}}§ionType=forums" height="24" width="24" />{{end}}
|
||||||
|
<span class="attach_item_path" aid="{{.ID}}" fullPath="//{{$.Header.Site.URL}}/attachs/{{.Path}}">{{.Path}}</span>
|
||||||
|
<button class="attach_item_select">Select</button>
|
||||||
|
<button class="attach_item_copy">Copy</button>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
<div class="attach_item attach_item_buttons">
|
||||||
|
{{if .CurrentUser.Perms.UploadFiles}}
|
||||||
|
<input name="upload_files" id="upload_files_op" multiple type="file" style="display: none;" />
|
||||||
|
<label for="upload_files_op" class="formbutton add_file_button">Upload</label>{{end}}
|
||||||
|
<button class="attach_item_delete">Delete</button>
|
||||||
|
</div>
|
||||||
|
</div>{{end}}
|
||||||
|
|
||||||
|
{{end}}{{end}}
|
||||||
<div class="controls button_container{{if .Topic.LikeCount}} has_likes{{end}}">
|
<div class="controls button_container{{if .Topic.LikeCount}} has_likes{{end}}">
|
||||||
<div class="action_button_left">
|
<div class="action_button_left">
|
||||||
{{if .CurrentUser.Loggedin}}
|
{{if .CurrentUser.Loggedin}}
|
||||||
|
@ -97,7 +116,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="action_button_right">
|
<div class="action_button_right">
|
||||||
<a class="action_button like_count hide_on_micro" aria-label="{{lang "topic.like_count_aria"}}">{{.Topic.LikeCount}}</a>
|
<a class="action_button like_count hide_on_micro" aria-label="{{lang "topic.like_count_aria"}}">{{.Topic.LikeCount}}</a>
|
||||||
<a class="action_button created_at hide_on_mobile" title="{{abstime .Topic.CreatedAt}}">{{.Topic.RelativeCreatedAt}}</a>
|
<a class="action_button created_at hide_on_mobile" title="{{abstime .Topic.CreatedAt}}">{{reltime .Topic.CreatedAt}}</a>
|
||||||
{{if .CurrentUser.Perms.ViewIPs}}<a href="/users/ips/?ip={{.Topic.IPAddress}}" title="{{lang "topic.ip_full_tooltip"}}" class="action_button ip_item hide_on_mobile" aria-hidden="true">{{.Topic.IPAddress}}</a>{{end}}
|
{{if .CurrentUser.Perms.ViewIPs}}<a href="/users/ips/?ip={{.Topic.IPAddress}}" title="{{lang "topic.ip_full_tooltip"}}" class="action_button ip_item hide_on_mobile" aria-hidden="true">{{.Topic.IPAddress}}</a>{{end}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="action_button_right">
|
<div class="action_button_right">
|
||||||
<a class="action_button like_count hide_on_micro" aria-label="{{lang "topic.post_like_count_tooltip"}}">{{.LikeCount}}</a>
|
<a class="action_button like_count hide_on_micro" aria-label="{{lang "topic.post_like_count_tooltip"}}">{{.LikeCount}}</a>
|
||||||
<a class="action_button created_at hide_on_mobile" title="{{abstime .CreatedAt}}">{{.RelativeCreatedAt}}</a>
|
<a class="action_button created_at hide_on_mobile" title="{{abstime .CreatedAt}}">{{reltime .CreatedAt}}</a>
|
||||||
{{if $.CurrentUser.Loggedin}}{{if $.CurrentUser.Perms.ViewIPs}}<a href="/users/ips/?ip={{.IPAddress}}" title="IP Address" class="action_button ip_item hide_on_mobile" aria-hidden="true">{{.IPAddress}}</a>{{end}}{{end}}
|
{{if $.CurrentUser.Loggedin}}{{if $.CurrentUser.Perms.ViewIPs}}<a href="/users/ips/?ip={{.IPAddress}}" title="IP Address" class="action_button ip_item hide_on_mobile" aria-hidden="true">{{.IPAddress}}</a>{{end}}{{end}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -32,22 +32,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{if .CurrentUser.Loggedin}}
|
{{if .CurrentUser.Loggedin}}
|
||||||
{{/** TODO: Hide these from unauthorised users? **/}}
|
{{template "topics_mod_floater.html"}}
|
||||||
<div class="mod_floater auto_hide">
|
|
||||||
<form method="post">
|
|
||||||
<div class="mod_floater_head">
|
|
||||||
<span></span>
|
|
||||||
</div>
|
|
||||||
<div class="mod_floater_body">
|
|
||||||
<select class="mod_floater_options">
|
|
||||||
<option val="delete">{{lang "topic_list.moderate_delete"}}</option>
|
|
||||||
<option val="lock">{{lang "topic_list.moderate_lock"}}</option>
|
|
||||||
<option val="move">{{lang "topic_list.moderate_move"}}</option>
|
|
||||||
</select>
|
|
||||||
<button class="mod_floater_submit">{{lang "topic_list.moderate_run"}}</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{if .ForumList}}
|
{{if .ForumList}}
|
||||||
{{/** TODO: Have a seperate forum list for moving topics? Maybe an AJAX forum search compatible with plugin_guilds? **/}}
|
{{/** TODO: Have a seperate forum list for moving topics? Maybe an AJAX forum search compatible with plugin_guilds? **/}}
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
{{/** TODO: Hide these from unauthorised users? **/}}
|
||||||
|
<div class="mod_floater auto_hide">
|
||||||
|
<form method="post">
|
||||||
|
<div class="mod_floater_head">
|
||||||
|
<span></span>
|
||||||
|
</div>
|
||||||
|
<div class="mod_floater_body">
|
||||||
|
<select class="mod_floater_options">
|
||||||
|
<option val="delete">{{lang "topic_list.moderate_delete"}}</option>
|
||||||
|
<option val="lock">{{lang "topic_list.moderate_lock"}}</option>
|
||||||
|
<option val="move">{{lang "topic_list.moderate_move"}}</option>
|
||||||
|
</select>
|
||||||
|
<button class="mod_floater_submit">{{lang "topic_list.moderate_run"}}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
|
@ -27,7 +27,7 @@
|
||||||
<a href="{{.LastUser.Link}}"><img src="{{.LastUser.MicroAvatar}}" height="64" alt="{{.LastUser.Name}}'s Avatar" title="{{.LastUser.Name}}'s Avatar" /></a>
|
<a href="{{.LastUser.Link}}"><img src="{{.LastUser.MicroAvatar}}" height="64" alt="{{.LastUser.Name}}'s Avatar" title="{{.LastUser.Name}}'s Avatar" /></a>
|
||||||
<span>
|
<span>
|
||||||
<a href="{{.LastUser.Link}}" class="lastName" style="font-size: 14px;" title="{{.LastUser.Name}}">{{.LastUser.Name}}</a><br>
|
<a href="{{.LastUser.Link}}" class="lastName" style="font-size: 14px;" title="{{.LastUser.Name}}">{{.LastUser.Name}}</a><br>
|
||||||
<a href="{{.Link}}?page={{.LastPage}}" class="rowsmall lastReplyAt" title="{{abstime .LastReplyAt}}">{{.RelativeLastReplyAt}}</a>
|
<a href="{{.Link}}?page={{.LastPage}}{{if .LastReplyID}}#post-{{.LastReplyID}}{{end}}" class="rowsmall lastReplyAt" title="{{abstime .LastReplyAt}}">{{reltime .LastReplyAt}}</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1006,8 +1006,6 @@ textarea {
|
||||||
padding-right: 42px;
|
padding-right: 42px;
|
||||||
padding-bottom: 18px;
|
padding-bottom: 18px;
|
||||||
height: min-content;
|
height: min-content;
|
||||||
/*overflow: hidden;
|
|
||||||
text-overflow: ellipsis;*/
|
|
||||||
}
|
}
|
||||||
.user_meta {
|
.user_meta {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -1129,6 +1127,9 @@ textarea {
|
||||||
content: "{{lang "topic.report_button_text" .}}";
|
content: "{{lang "topic.report_button_text" .}}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.attach_edit_bay {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
.zone_view_topic .pageset {
|
.zone_view_topic .pageset {
|
||||||
margin-bottom: 14px;
|
margin-bottom: 14px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -293,12 +293,17 @@ h2 {
|
||||||
.quick_create_form .topic_meta {
|
.quick_create_form .topic_meta {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
.quick_create_form input, .quick_create_form select {
|
||||||
|
margin-left: 0px;
|
||||||
|
margin-bottom: 0px;
|
||||||
|
}
|
||||||
.quick_create_form .topic_meta .topic_name_row {
|
.quick_create_form .topic_meta .topic_name_row {
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
.quick_create_form .topic_meta .topic_name_row:not(:only-child) {
|
.quick_create_form .topic_meta .topic_name_row:not(:only-child) {
|
||||||
margin-left: 8px;
|
margin-left: 6px;
|
||||||
}
|
}
|
||||||
.quick_create_form .topic_meta .topic_name_row:only-child input {
|
.quick_create_form .topic_meta .topic_name_row:only-child input {
|
||||||
margin-left: 0px;
|
margin-left: 0px;
|
||||||
|
@ -623,12 +628,24 @@ button, .formbutton, .panel_right_button:not(.has_inner_button) {
|
||||||
.topic_view_count:after {
|
.topic_view_count:after {
|
||||||
content: "{{lang "topic.view_count_suffix" . }}";
|
content: "{{lang "topic.view_count_suffix" . }}";
|
||||||
}
|
}
|
||||||
|
.edithead {
|
||||||
|
margin-left: 0px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
.topic_name_input {
|
.topic_name_input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-right: 12px;
|
margin-right: 10px;
|
||||||
|
margin-bottom: 0px;
|
||||||
|
margin-left: 0px;
|
||||||
|
margin-left: 0px;
|
||||||
}
|
}
|
||||||
.topic_item .submit_edit {
|
.topic_item .submit_edit {
|
||||||
margin-right: 16px;
|
/*margin-right: 16px;*/
|
||||||
|
}
|
||||||
|
.zone_view_topic button, .zone_view_topic .formbutton {
|
||||||
|
padding: 5px;
|
||||||
|
padding-top: 4px;
|
||||||
|
padding-bottom: 4px;
|
||||||
}
|
}
|
||||||
.postImage {
|
.postImage {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -688,7 +705,7 @@ button, .formbutton, .panel_right_button:not(.has_inner_button) {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
color: #bbbbbb;
|
color: #bbbbbb;
|
||||||
}
|
}
|
||||||
.action_item .content_container, .post_item .user_content {
|
.action_item .content_container, .post_item .user_content, .post_item .button_container {
|
||||||
background-color: #444444;
|
background-color: #444444;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
|
@ -698,8 +715,6 @@ button, .formbutton, .panel_right_button:not(.has_inner_button) {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
margin-bottom: auto;
|
margin-bottom: auto;
|
||||||
padding: 14px;
|
padding: 14px;
|
||||||
background-color: #444444;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
}
|
||||||
.post_item .action_button {
|
.post_item .action_button {
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
|
@ -713,11 +728,7 @@ button, .formbutton, .panel_right_button:not(.has_inner_button) {
|
||||||
.post_item .action_button_right {
|
.post_item .action_button_right {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
.post_item .controls:not(.has_likes) .like_count {
|
.post_item .controls:not(.has_likes) .like_count, .action_item .userinfo, .action_item .action_icon {
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action_item .userinfo, .action_item .action_icon {
|
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
.action_item .content_container {
|
.action_item .content_container {
|
||||||
|
@ -788,6 +799,49 @@ input[type=checkbox]:checked + label .sel {
|
||||||
content: "{{lang "topic.like_count_suffix" . }}";
|
content: "{{lang "topic.like_count_suffix" . }}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*.attach_edit_bay {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}*/
|
||||||
|
.attach_item {
|
||||||
|
display: flex;
|
||||||
|
background-color: #444444;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-top: 8px;
|
||||||
|
padding: 6px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.attach_item_selected {
|
||||||
|
background-color: #446644
|
||||||
|
}
|
||||||
|
.attach_item img {
|
||||||
|
margin-right: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.attach_image_holder span {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
.attach_edit_bay button {
|
||||||
|
margin-top: 8px;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* New */
|
||||||
|
.attach_item {
|
||||||
|
padding: 8px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.attach_image_holder span {
|
||||||
|
margin-right: auto;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
.attach_item button {
|
||||||
|
margin-top: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
.zone_view_topic .pageset {
|
.zone_view_topic .pageset {
|
||||||
margin-bottom: 14px;
|
margin-bottom: 14px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,9 @@
|
||||||
$(".alerts").html(alertCount + " new alerts");
|
$(".alerts").html(alertCount + " new alerts");
|
||||||
$(".user_box").addClass("has_alerts");
|
$(".user_box").addClass("has_alerts");
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
addHook("open_edit", () => $('.topic_block').addClass("edithead"));
|
||||||
|
addHook("close_edit", () => $('.topic_block').removeClass("edithead"));
|
||||||
})();
|
})();
|
||||||
|
|
||||||
$(document).ready(() => {
|
$(document).ready(() => {
|
||||||
|
|
Loading…
Reference in New Issue