Added the Not method to the router generator.
Refactored the router generator. Added the NoBanned and NoSessionMismatch middlewares. Removed some of the implicit returns. Refactored some of the routes and their associated stores. Added more Codebeat exclusions. Added the AddReply method to *Topic. Added the Like method to *Topic. MemberOnly now emits a LoginRequired error rather than a generic NoPermissions error. Made progress with Cosora, this time with the profile system.
This commit is contained in:
parent
920c1aad0f
commit
34ca7de946
|
@ -1,6 +1,9 @@
|
||||||
/public/trumbowyg/*
|
/public/trumbowyg/*
|
||||||
/public/jquery-emojiarea/*
|
/public/jquery-emojiarea/*
|
||||||
/public/font-awesome-4.7.0/*
|
/public/font-awesome-4.7.0/*
|
||||||
|
/public/jquery-3.1.1.min.js
|
||||||
|
/public/EQCSS.min.js
|
||||||
|
/public/EQCSS.js
|
||||||
/schema/*
|
/schema/*
|
||||||
|
|
||||||
template_list.go
|
template_list.go
|
||||||
|
|
|
@ -142,6 +142,18 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = NoBanned(w,req,user)
|
||||||
|
if err != nil {
|
||||||
|
router.handleError(err,w,req,user)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = NoSessionMismatch(w,req,user)
|
||||||
|
if err != nil {
|
||||||
|
router.handleError(err,w,req,user)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
switch(req.URL.Path) {
|
switch(req.URL.Path) {
|
||||||
case "/report/submit/":
|
case "/report/submit/":
|
||||||
err = routeReportSubmit(w,req,user,extra_data)
|
err = routeReportSubmit(w,req,user,extra_data)
|
||||||
|
|
|
@ -238,6 +238,7 @@ func routeTopicCreateSubmit(w http.ResponseWriter, r *http.Request, user User) R
|
||||||
|
|
||||||
func routeCreateReply(w http.ResponseWriter, r *http.Request, user User) RouteError {
|
func routeCreateReply(w http.ResponseWriter, r *http.Request, user User) RouteError {
|
||||||
// TODO: Reduce this to 1MB for attachments for each file?
|
// TODO: Reduce this to 1MB for attachments for each file?
|
||||||
|
// TODO: Reuse this code more
|
||||||
if r.ContentLength > int64(config.MaxRequestSize) {
|
if r.ContentLength > int64(config.MaxRequestSize) {
|
||||||
size, unit := convertByteUnit(float64(config.MaxRequestSize))
|
size, unit := convertByteUnit(float64(config.MaxRequestSize))
|
||||||
return CustomError("Your attachments are too big. Your files need to be smaller than "+strconv.Itoa(int(size))+unit+".", http.StatusExpectationFailed, "Error", w, r, user)
|
return CustomError("Your attachments are too big. Your files need to be smaller than "+strconv.Itoa(int(size))+unit+".", http.StatusExpectationFailed, "Error", w, r, user)
|
||||||
|
@ -343,7 +344,7 @@ func routeCreateReply(w http.ResponseWriter, r *http.Request, user User) RouteEr
|
||||||
return LocalError("Bad IP", w, r, user)
|
return LocalError("Bad IP", w, r, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = rstore.Create(tid, content, ipaddress, topic.ParentID, user.ID)
|
_, err = rstore.Create(topic, content, ipaddress, user.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return InternalError(err, w, r)
|
return InternalError(err, w, r)
|
||||||
}
|
}
|
||||||
|
@ -409,18 +410,10 @@ func routeLikeTopic(w http.ResponseWriter, r *http.Request, user User) RouteErro
|
||||||
if !user.Perms.ViewTopic || !user.Perms.LikeItem {
|
if !user.Perms.ViewTopic || !user.Perms.LikeItem {
|
||||||
return NoPermissions(w, r, user)
|
return NoPermissions(w, r, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
if topic.CreatedBy == user.ID {
|
if topic.CreatedBy == user.ID {
|
||||||
return LocalError("You can't like your own topics", w, r, user)
|
return LocalError("You can't like your own topics", w, r, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = stmts.hasLikedTopic.QueryRow(user.ID, tid).Scan(&tid)
|
|
||||||
if err != nil && err != ErrNoRows {
|
|
||||||
return InternalError(err, w, r)
|
|
||||||
} else if err != ErrNoRows {
|
|
||||||
return LocalError("You already liked this!", w, r, user)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = users.Get(topic.CreatedBy)
|
_, err = users.Get(topic.CreatedBy)
|
||||||
if err != nil && err == ErrNoRows {
|
if err != nil && err == ErrNoRows {
|
||||||
return LocalError("The target user doesn't exist", w, r, user)
|
return LocalError("The target user doesn't exist", w, r, user)
|
||||||
|
@ -429,13 +422,10 @@ func routeLikeTopic(w http.ResponseWriter, r *http.Request, user User) RouteErro
|
||||||
}
|
}
|
||||||
|
|
||||||
score := 1
|
score := 1
|
||||||
_, err = stmts.createLike.Exec(score, tid, "topics", user.ID)
|
err = topic.Like(score, user.ID)
|
||||||
if err != nil {
|
if err == ErrAlreadyLiked {
|
||||||
return InternalError(err, w, r)
|
return LocalError("You already liked this", w, r, user)
|
||||||
}
|
} else if err != nil {
|
||||||
|
|
||||||
_, err = stmts.addLikesToTopic.Exec(1, tid)
|
|
||||||
if err != nil {
|
|
||||||
return InternalError(err, w, r)
|
return InternalError(err, w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -456,11 +446,6 @@ func routeLikeTopic(w http.ResponseWriter, r *http.Request, user User) RouteErro
|
||||||
// Live alerts, if the poster is online and WebSockets is enabled
|
// Live alerts, if the poster is online and WebSockets is enabled
|
||||||
_ = wsHub.pushAlert(topic.CreatedBy, int(lastID), "like", "topic", user.ID, topic.CreatedBy, tid)
|
_ = wsHub.pushAlert(topic.CreatedBy, int(lastID), "like", "topic", user.ID, topic.CreatedBy, tid)
|
||||||
|
|
||||||
// Flush the topic out of the cache
|
|
||||||
tcache, ok := topics.(TopicCache)
|
|
||||||
if ok {
|
|
||||||
tcache.CacheRemove(tid)
|
|
||||||
}
|
|
||||||
http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther)
|
http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -499,7 +484,6 @@ func routeReplyLikeSubmit(w http.ResponseWriter, r *http.Request, user User) Rou
|
||||||
if !user.Perms.ViewTopic || !user.Perms.LikeItem {
|
if !user.Perms.ViewTopic || !user.Perms.LikeItem {
|
||||||
return NoPermissions(w, r, user)
|
return NoPermissions(w, r, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
if reply.CreatedBy == user.ID {
|
if reply.CreatedBy == user.ID {
|
||||||
return LocalError("You can't like your own replies", w, r, user)
|
return LocalError("You can't like your own replies", w, r, user)
|
||||||
}
|
}
|
||||||
|
@ -573,26 +557,10 @@ func routeProfileReplyCreate(w http.ResponseWriter, r *http.Request, user User)
|
||||||
}
|
}
|
||||||
|
|
||||||
func routeReportSubmit(w http.ResponseWriter, r *http.Request, user User, sitemID string) RouteError {
|
func routeReportSubmit(w http.ResponseWriter, r *http.Request, user User, sitemID string) RouteError {
|
||||||
if !user.Loggedin {
|
|
||||||
return LoginRequired(w, r, user)
|
|
||||||
}
|
|
||||||
if user.IsBanned {
|
|
||||||
return Banned(w, r, user)
|
|
||||||
}
|
|
||||||
|
|
||||||
err := r.ParseForm()
|
|
||||||
if err != nil {
|
|
||||||
return LocalError("Bad Form", w, r, user)
|
|
||||||
}
|
|
||||||
if r.FormValue("session") != user.Session {
|
|
||||||
return SecurityError(w, r, user)
|
|
||||||
}
|
|
||||||
|
|
||||||
itemID, err := strconv.Atoi(sitemID)
|
itemID, err := strconv.Atoi(sitemID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return LocalError("Bad ID", w, r, user)
|
return LocalError("Bad ID", w, r, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
itemType := r.FormValue("type")
|
itemType := r.FormValue("type")
|
||||||
|
|
||||||
var fid = 1
|
var fid = 1
|
||||||
|
@ -649,23 +617,16 @@ func routeReportSubmit(w http.ResponseWriter, r *http.Request, user User, sitemI
|
||||||
}
|
}
|
||||||
|
|
||||||
var count int
|
var count int
|
||||||
rows, err := stmts.reportExists.Query(itemType + "_" + strconv.Itoa(itemID))
|
err = stmts.reportExists.QueryRow(itemType + "_" + strconv.Itoa(itemID)).Scan(&count)
|
||||||
if err != nil && err != ErrNoRows {
|
if err != nil && err != ErrNoRows {
|
||||||
return InternalError(err, w, r)
|
return InternalError(err, w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
for rows.Next() {
|
|
||||||
err = rows.Scan(&count)
|
|
||||||
if err != nil {
|
|
||||||
return InternalError(err, w, r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if count != 0 {
|
if count != 0 {
|
||||||
return LocalError("Someone has already reported this!", w, r, user)
|
return LocalError("Someone has already reported this!", w, r, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Repost attachments in the reports forum, so that the mods can see them
|
// TODO: Repost attachments in the reports forum, so that the mods can see them
|
||||||
// ? - Can we do this via the TopicStore?
|
// ? - Can we do this via the TopicStore? Should we do a ReportStore?
|
||||||
res, err := stmts.createReport.Exec(title, content, parseMessage(content, 0, ""), user.ID, user.ID, itemType+"_"+strconv.Itoa(itemID))
|
res, err := stmts.createReport.Exec(title, content, parseMessage(content, 0, ""), user.ID, user.ID, itemType+"_"+strconv.Itoa(itemID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return InternalError(err, w, r)
|
return InternalError(err, w, r)
|
||||||
|
|
|
@ -750,7 +750,9 @@ func TestReplyStore(t *testing.T) {
|
||||||
|
|
||||||
// TODO: Test Create and Get
|
// TODO: Test Create and Get
|
||||||
//Create(tid int, content string, ipaddress string, fid int, uid int) (id int, err error)
|
//Create(tid int, content string, ipaddress string, fid int, uid int) (id int, err error)
|
||||||
rid, err := rstore.Create(1, "Fofofo", "::1", 2, 1)
|
topic, err := topics.Get(1)
|
||||||
|
expectNilErr(t, err)
|
||||||
|
rid, err := rstore.Create(topic, "Fofofo", "::1", 1)
|
||||||
expectNilErr(t, err)
|
expectNilErr(t, err)
|
||||||
expect(t, rid == 2, fmt.Sprintf("The next reply ID should be 2 not %d", rid))
|
expect(t, rid == 2, fmt.Sprintf("The next reply ID should be 2 not %d", rid))
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ var rstore ReplyStore
|
||||||
|
|
||||||
type ReplyStore interface {
|
type ReplyStore interface {
|
||||||
Get(id int) (*Reply, error)
|
Get(id int) (*Reply, error)
|
||||||
Create(tid int, content string, ipaddress string, fid int, uid int) (id int, err error)
|
Create(topic *Topic, content string, ipaddress string, uid int) (id int, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type SQLReplyStore struct {
|
type SQLReplyStore struct {
|
||||||
|
@ -30,24 +30,16 @@ func (store *SQLReplyStore) Get(id int) (*Reply, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Write a test for this
|
// TODO: Write a test for this
|
||||||
func (store *SQLReplyStore) Create(tid int, content string, ipaddress string, fid int, uid int) (id int, err error) {
|
func (store *SQLReplyStore) Create(topic *Topic, content string, ipaddress string, uid int) (id int, err error) {
|
||||||
wcount := wordCount(content)
|
wcount := wordCount(content)
|
||||||
res, err := store.create.Exec(tid, content, parseMessage(content, fid, "forums"), ipaddress, wcount, uid)
|
res, err := store.create.Exec(topic.ID, content, parseMessage(content, topic.ParentID, "forums"), ipaddress, wcount, uid)
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
lastID, err := res.LastInsertId()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = stmts.addRepliesToTopic.Exec(1, uid, tid)
|
lastID, err := res.LastInsertId()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return int(lastID), err
|
return 0, err
|
||||||
}
|
}
|
||||||
tcache, ok := topics.(TopicCache)
|
return int(lastID), topic.AddReply(uid)
|
||||||
if ok {
|
|
||||||
tcache.CacheRemove(tid)
|
|
||||||
}
|
|
||||||
return int(lastID), err
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
type RouteGroup struct {
|
||||||
|
Path string
|
||||||
|
RouteList []*RouteImpl
|
||||||
|
RunBefore []Runnable
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRouteGroup(path string, routes ...*RouteImpl) *RouteGroup {
|
||||||
|
return &RouteGroup{path, routes, []Runnable{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (group *RouteGroup) Not(path ...string) *RouteSubset {
|
||||||
|
routes := make([]*RouteImpl, len(group.RouteList))
|
||||||
|
copy(routes, group.RouteList)
|
||||||
|
for i, route := range routes {
|
||||||
|
if inStringList(route.Path, path) {
|
||||||
|
routes = append(routes[:i], routes[i+1:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &RouteSubset{routes}
|
||||||
|
}
|
||||||
|
|
||||||
|
func inStringList(needle string, list []string) bool {
|
||||||
|
for _, item := range list {
|
||||||
|
if item == needle {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (group *RouteGroup) Before(line string, literal ...bool) *RouteGroup {
|
||||||
|
var litItem bool
|
||||||
|
if len(literal) > 0 {
|
||||||
|
litItem = literal[0]
|
||||||
|
}
|
||||||
|
group.RunBefore = append(group.RunBefore, Runnable{line, litItem})
|
||||||
|
return group
|
||||||
|
}
|
||||||
|
|
||||||
|
func (group *RouteGroup) Routes(routes ...*RouteImpl) *RouteGroup {
|
||||||
|
group.RouteList = append(group.RouteList, routes...)
|
||||||
|
return group
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
type RouteSubset struct {
|
||||||
|
RouteList []*RouteImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
func (set *RouteSubset) Before(line string, literal ...bool) *RouteSubset {
|
||||||
|
var litItem bool
|
||||||
|
if len(literal) > 0 {
|
||||||
|
litItem = literal[0]
|
||||||
|
}
|
||||||
|
for _, route := range set.RouteList {
|
||||||
|
route.RunBefore = append(route.RunBefore, Runnable{line, litItem})
|
||||||
|
}
|
||||||
|
return set
|
||||||
|
}
|
||||||
|
|
||||||
|
func (set *RouteSubset) Not(path ...string) *RouteSubset {
|
||||||
|
for i, route := range set.RouteList {
|
||||||
|
if inStringList(route.Path, path) {
|
||||||
|
set.RouteList = append(set.RouteList[:i], set.RouteList[i+1:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return set
|
||||||
|
}
|
|
@ -7,12 +7,6 @@ type RouteImpl struct {
|
||||||
RunBefore []Runnable
|
RunBefore []Runnable
|
||||||
}
|
}
|
||||||
|
|
||||||
type RouteGroup struct {
|
|
||||||
Path string
|
|
||||||
RouteList []*RouteImpl
|
|
||||||
RunBefore []Runnable
|
|
||||||
}
|
|
||||||
|
|
||||||
type Runnable struct {
|
type Runnable struct {
|
||||||
Contents string
|
Contents string
|
||||||
Literal bool
|
Literal bool
|
||||||
|
@ -31,27 +25,10 @@ func (route *RouteImpl) Before(item string, literal ...bool) *RouteImpl {
|
||||||
return route
|
return route
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRouteGroup(path string, routes ...*RouteImpl) *RouteGroup {
|
|
||||||
return &RouteGroup{path, routes, []Runnable{}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func addRouteGroup(routeGroup *RouteGroup) {
|
func addRouteGroup(routeGroup *RouteGroup) {
|
||||||
routeGroups = append(routeGroups, routeGroup)
|
routeGroups = append(routeGroups, routeGroup)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (group *RouteGroup) Before(line string, literal ...bool) *RouteGroup {
|
|
||||||
var litItem bool
|
|
||||||
if len(literal) > 0 {
|
|
||||||
litItem = literal[0]
|
|
||||||
}
|
|
||||||
group.RunBefore = append(group.RunBefore, Runnable{line, litItem})
|
|
||||||
return group
|
|
||||||
}
|
|
||||||
|
|
||||||
func (group *RouteGroup) Routes(routes ...*RouteImpl) {
|
|
||||||
group.RouteList = append(group.RouteList, routes...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func blankRoute() *RouteImpl {
|
func blankRoute() *RouteImpl {
|
||||||
return &RouteImpl{"", "", []string{}, []Runnable{}}
|
return &RouteImpl{"", "", []string{}, []Runnable{}}
|
||||||
}
|
}
|
||||||
|
@ -74,9 +51,10 @@ func routes() {
|
||||||
addRoute(Route("routeChangeTheme", "/theme/"))
|
addRoute(Route("routeChangeTheme", "/theme/"))
|
||||||
addRoute(Route("routeShowAttachment", "/attachs/", "extra_data"))
|
addRoute(Route("routeShowAttachment", "/attachs/", "extra_data"))
|
||||||
|
|
||||||
|
// TODO: Reduce the number of Befores. With a new method, perhaps?
|
||||||
reportGroup := newRouteGroup("/report/",
|
reportGroup := newRouteGroup("/report/",
|
||||||
Route("routeReportSubmit", "/report/submit/", "extra_data"),
|
Route("routeReportSubmit", "/report/submit/", "extra_data"),
|
||||||
).Before("MemberOnly")
|
).Before("MemberOnly").Before("NoBanned").Before("NoSessionMismatch")
|
||||||
addRouteGroup(reportGroup)
|
addRouteGroup(reportGroup)
|
||||||
|
|
||||||
topicGroup := newRouteGroup("/topics/",
|
topicGroup := newRouteGroup("/topics/",
|
||||||
|
@ -90,20 +68,19 @@ func routes() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Test the email token route
|
// TODO: Test the email token route
|
||||||
// TODO: Add a BeforeExcept method?
|
|
||||||
func buildUserRoutes() {
|
func buildUserRoutes() {
|
||||||
userGroup := newRouteGroup("/user/") //.Before("MemberOnly")
|
userGroup := newRouteGroup("/user/")
|
||||||
userGroup.Routes(
|
userGroup.Routes(
|
||||||
Route("routeProfile", "/user/").Before("req.URL.Path += extra_data", true),
|
Route("routeProfile", "/user/").Before("req.URL.Path += extra_data", true),
|
||||||
Route("routeAccountOwnEditCritical", "/user/edit/critical/").Before("MemberOnly"),
|
Route("routeAccountOwnEditCritical", "/user/edit/critical/"),
|
||||||
Route("routeAccountOwnEditCriticalSubmit", "/user/edit/critical/submit/").Before("MemberOnly"),
|
Route("routeAccountOwnEditCriticalSubmit", "/user/edit/critical/submit/"),
|
||||||
Route("routeAccountOwnEditAvatar", "/user/edit/avatar/").Before("MemberOnly"),
|
Route("routeAccountOwnEditAvatar", "/user/edit/avatar/"),
|
||||||
Route("routeAccountOwnEditAvatarSubmit", "/user/edit/avatar/submit/").Before("MemberOnly"),
|
Route("routeAccountOwnEditAvatarSubmit", "/user/edit/avatar/submit/"),
|
||||||
Route("routeAccountOwnEditUsername", "/user/edit/username/").Before("MemberOnly"),
|
Route("routeAccountOwnEditUsername", "/user/edit/username/"),
|
||||||
Route("routeAccountOwnEditUsernameSubmit", "/user/edit/username/submit/").Before("MemberOnly"),
|
Route("routeAccountOwnEditUsernameSubmit", "/user/edit/username/submit/"),
|
||||||
Route("routeAccountOwnEditEmail", "/user/edit/email/").Before("MemberOnly"),
|
Route("routeAccountOwnEditEmail", "/user/edit/email/"),
|
||||||
Route("routeAccountOwnEditEmailTokenSubmit", "/user/edit/token/", "extra_data").Before("MemberOnly"),
|
Route("routeAccountOwnEditEmailTokenSubmit", "/user/edit/token/", "extra_data"),
|
||||||
)
|
).Not("/user/").Before("MemberOnly")
|
||||||
addRouteGroup(userGroup)
|
addRouteGroup(userGroup)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -310,7 +310,34 @@ func SuperModOnly(w http.ResponseWriter, r *http.Request, user User) RouteError
|
||||||
// MemberOnly makes sure that only logged in users can access this route
|
// MemberOnly makes sure that only logged in users can access this route
|
||||||
func MemberOnly(w http.ResponseWriter, r *http.Request, user User) RouteError {
|
func MemberOnly(w http.ResponseWriter, r *http.Request, user User) RouteError {
|
||||||
if !user.Loggedin {
|
if !user.Loggedin {
|
||||||
return NoPermissions(w, r, user) // TODO: Do an error telling them to login instead?
|
return LoginRequired(w, r, user)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NoBanned stops any banned users from accessing this route
|
||||||
|
func NoBanned(w http.ResponseWriter, r *http.Request, user User) RouteError {
|
||||||
|
if user.IsBanned {
|
||||||
|
return Banned(w, r, user)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseForm(w http.ResponseWriter, r *http.Request, user User) RouteError {
|
||||||
|
err := r.ParseForm()
|
||||||
|
if err != nil {
|
||||||
|
return LocalError("Bad Form", w, r, user)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NoSessionMismatch(w http.ResponseWriter, r *http.Request, user User) RouteError {
|
||||||
|
err := r.ParseForm()
|
||||||
|
if err != nil {
|
||||||
|
return LocalError("Bad Form", w, r, user)
|
||||||
|
}
|
||||||
|
if r.FormValue("session") != user.Session {
|
||||||
|
return SecurityError(w, r, user)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,11 +77,8 @@ func (sBox SettingBox) ParseSetting(sname string, scontent string, stype string,
|
||||||
}
|
}
|
||||||
|
|
||||||
con1, err := strconv.Atoi(cons[0])
|
con1, err := strconv.Atoi(cons[0])
|
||||||
if err != nil {
|
con2, err2 := strconv.Atoi(cons[1])
|
||||||
return "Invalid contraint! The constraint field wasn't an integer!"
|
if err != nil || err2 != nil {
|
||||||
}
|
|
||||||
con2, err := strconv.Atoi(cons[1])
|
|
||||||
if err != nil {
|
|
||||||
return "Invalid contraint! The constraint field wasn't an integer!"
|
return "Invalid contraint! The constraint field wasn't an integer!"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -505,11 +505,11 @@ var profile_0 = []byte(`
|
||||||
<div class="rowitem"><h1>Profile</h1></div>
|
<div class="rowitem"><h1>Profile</h1></div>
|
||||||
</header>-->
|
</header>-->
|
||||||
<div id="profile_left_pane" class="rowmenu">
|
<div id="profile_left_pane" class="rowmenu">
|
||||||
<div class="rowitem avatarRow" style="padding: 0;">
|
<div class="rowitem avatarRow">
|
||||||
<img src="`)
|
<img src="`)
|
||||||
var profile_1 = []byte(`" class="avatar" />
|
var profile_1 = []byte(`" class="avatar" />
|
||||||
</div>
|
</div>
|
||||||
<div class="rowitem">`)
|
<div class="rowitem nameRow">`)
|
||||||
var profile_2 = []byte(`
|
var profile_2 = []byte(`
|
||||||
<span class="profileName">`)
|
<span class="profileName">`)
|
||||||
var profile_3 = []byte(`</span>`)
|
var profile_3 = []byte(`</span>`)
|
||||||
|
|
|
@ -7,10 +7,10 @@
|
||||||
<div class="rowitem"><h1>Profile</h1></div>
|
<div class="rowitem"><h1>Profile</h1></div>
|
||||||
</header>-->
|
</header>-->
|
||||||
<div id="profile_left_pane" class="rowmenu">
|
<div id="profile_left_pane" class="rowmenu">
|
||||||
<div class="rowitem avatarRow" style="padding: 0;">
|
<div class="rowitem avatarRow">
|
||||||
<img src="{{.ProfileOwner.Avatar}}" class="avatar" />
|
<img src="{{.ProfileOwner.Avatar}}" class="avatar" />
|
||||||
</div>
|
</div>
|
||||||
<div class="rowitem">{{/** TODO: Stop inlining this CSS **/}}
|
<div class="rowitem nameRow">{{/** TODO: Stop inlining this CSS **/}}
|
||||||
<span class="profileName">{{.ProfileOwner.Name}}</span>{{if .ProfileOwner.Tag}}<span class="username" style="float: right;font-weight: normal;">{{.ProfileOwner.Tag}}</span>{{end}}
|
<span class="profileName">{{.ProfileOwner.Name}}</span>{{if .ProfileOwner.Tag}}<span class="username" style="float: right;font-weight: normal;">{{.ProfileOwner.Tag}}</span>{{end}}
|
||||||
</div>
|
</div>
|
||||||
<div class="rowitem passive">
|
<div class="rowitem passive">
|
||||||
|
|
|
@ -703,11 +703,14 @@ select, input, textarea {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
#profile_left_lane {
|
#profile_left_lane {
|
||||||
|
margin-left: 8px;
|
||||||
|
margin-right: 16px;
|
||||||
border: 1px solid var(--element-border-color);
|
border: 1px solid var(--element-border-color);
|
||||||
border-bottom: 2px solid var(--element-border-color);
|
border-bottom: 2px solid var(--element-border-color);
|
||||||
}
|
}
|
||||||
#profile_left_pane {
|
#profile_left_pane {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
padding-bottom: 18px;
|
||||||
}
|
}
|
||||||
#profile_left_pane .avatarRow {
|
#profile_left_pane .avatarRow {
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
|
@ -715,6 +718,16 @@ select, input, textarea {
|
||||||
#profile_left_pane .avatar {
|
#profile_left_pane .avatar {
|
||||||
border-radius: 80px;
|
border-radius: 80px;
|
||||||
}
|
}
|
||||||
|
#profile_left_pane .nameRow {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
#profile_right_lane {
|
||||||
|
width: 100%;
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
#profile_right_lane .colstack_item {
|
#profile_right_lane .colstack_item {
|
||||||
border: 1px solid var(--element-border-color);
|
border: 1px solid var(--element-border-color);
|
||||||
border-bottom: 2px solid var(--element-border-color);
|
border-bottom: 2px solid var(--element-border-color);
|
||||||
|
|
|
@ -655,6 +655,7 @@ input, select, textarea {
|
||||||
#profile_left_lane .avatarRow {
|
#profile_left_lane .avatarRow {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
max-height: 220px;
|
max-height: 220px;
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
#profile_left_lane .avatar {
|
#profile_left_lane .avatar {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
@ -703,6 +703,7 @@ button.username {
|
||||||
#profile_left_lane .avatarRow {
|
#profile_left_lane .avatarRow {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
max-height: 220px;
|
max-height: 220px;
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
#profile_left_lane .avatar {
|
#profile_left_lane .avatar {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
@ -543,6 +543,10 @@ button.username {
|
||||||
padding-left: 136px;
|
padding-left: 136px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#profile_left_lane .avatarRow {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Media Queries */
|
/* Media Queries */
|
||||||
|
|
||||||
@media(min-width: 881px) {
|
@media(min-width: 881px) {
|
||||||
|
|
|
@ -755,6 +755,7 @@ button.username {
|
||||||
#profile_left_lane .avatarRow {
|
#profile_left_lane .avatarRow {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
max-height: 220px;
|
max-height: 220px;
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
#profile_left_lane .avatar {
|
#profile_left_lane .avatar {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
79
topic.go
79
topic.go
|
@ -14,6 +14,9 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// This is also in reply.go
|
||||||
|
//var ErrAlreadyLiked = errors.New("This item was already liked by this user")
|
||||||
|
|
||||||
// ? - Add a TopicMeta struct for *Forums?
|
// ? - Add a TopicMeta struct for *Forums?
|
||||||
|
|
||||||
type Topic struct {
|
type Topic struct {
|
||||||
|
@ -102,51 +105,70 @@ type TopicsRow struct {
|
||||||
ForumLink string
|
ForumLink string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (topic *Topic) Lock() (err error) {
|
// Flush the topic out of the cache
|
||||||
_, err = stmts.lockTopic.Exec(topic.ID)
|
// ? - We do a CacheRemove() here instead of mutating the pointer to avoid creating a race condition
|
||||||
|
func (topic *Topic) cacheRemove() {
|
||||||
tcache, ok := topics.(TopicCache)
|
tcache, ok := topics.(TopicCache)
|
||||||
if ok {
|
if ok {
|
||||||
tcache.CacheRemove(topic.ID)
|
tcache.CacheRemove(topic.ID)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Write a test for this
|
||||||
|
func (topic *Topic) AddReply(uid int) (err error) {
|
||||||
|
_, err = stmts.addRepliesToTopic.Exec(1, uid, topic.ID)
|
||||||
|
topic.cacheRemove()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (topic *Topic) Lock() (err error) {
|
||||||
|
_, err = stmts.lockTopic.Exec(topic.ID)
|
||||||
|
topic.cacheRemove()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (topic *Topic) Unlock() (err error) {
|
func (topic *Topic) Unlock() (err error) {
|
||||||
_, err = stmts.unlockTopic.Exec(topic.ID)
|
_, err = stmts.unlockTopic.Exec(topic.ID)
|
||||||
tcache, ok := topics.(TopicCache)
|
topic.cacheRemove()
|
||||||
if ok {
|
|
||||||
tcache.CacheRemove(topic.ID)
|
|
||||||
}
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: We might want more consistent terminology rather than using stick in some places and pin in others. If you don't understand the difference, there is none, they are one and the same.
|
// TODO: We might want more consistent terminology rather than using stick in some places and pin in others. If you don't understand the difference, there is none, they are one and the same.
|
||||||
// ? - We do a CacheDelete() here instead of mutating the pointer to avoid creating a race condition
|
|
||||||
func (topic *Topic) Stick() (err error) {
|
func (topic *Topic) Stick() (err error) {
|
||||||
_, err = stmts.stickTopic.Exec(topic.ID)
|
_, err = stmts.stickTopic.Exec(topic.ID)
|
||||||
tcache, ok := topics.(TopicCache)
|
topic.cacheRemove()
|
||||||
if ok {
|
|
||||||
tcache.CacheRemove(topic.ID)
|
|
||||||
}
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (topic *Topic) Unstick() (err error) {
|
func (topic *Topic) Unstick() (err error) {
|
||||||
_, err = stmts.unstickTopic.Exec(topic.ID)
|
_, err = stmts.unstickTopic.Exec(topic.ID)
|
||||||
tcache, ok := topics.(TopicCache)
|
topic.cacheRemove()
|
||||||
if ok {
|
return err
|
||||||
tcache.CacheRemove(topic.ID)
|
}
|
||||||
|
|
||||||
|
// TODO: Test this
|
||||||
|
// TODO: Use a transaction for this
|
||||||
|
func (topic *Topic) Like(score int, uid int) (err error) {
|
||||||
|
var tid int // Unused
|
||||||
|
err = stmts.hasLikedTopic.QueryRow(uid, topic.ID).Scan(&tid)
|
||||||
|
if err != nil && err != ErrNoRows {
|
||||||
|
return err
|
||||||
|
} else if err != ErrNoRows {
|
||||||
|
return ErrAlreadyLiked
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, err = stmts.createLike.Exec(score, tid, "topics", uid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = stmts.addLikesToTopic.Exec(1, tid)
|
||||||
|
topic.cacheRemove()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implement this
|
// TODO: Implement this
|
||||||
func (topic *Topic) AddLike(uid int) error {
|
func (topic *Topic) Unlike(uid int) error {
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Implement this
|
|
||||||
func (topic *Topic) RemoveLike(uid int) error {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,22 +191,16 @@ func (topic *Topic) Delete() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = stmts.deleteTopic.Exec(topic.ID)
|
_, err = stmts.deleteTopic.Exec(topic.ID)
|
||||||
tcache, ok := topics.(TopicCache)
|
topic.cacheRemove()
|
||||||
if ok {
|
|
||||||
tcache.CacheRemove(topic.ID)
|
|
||||||
}
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (topic *Topic) Update(name string, content string) error {
|
func (topic *Topic) Update(name string, content string) error {
|
||||||
content = preparseMessage(content)
|
content = preparseMessage(content)
|
||||||
parsed_content := parseMessage(html.EscapeString(content), topic.ParentID, "forums")
|
parsed_content := parseMessage(html.EscapeString(content), topic.ParentID, "forums")
|
||||||
_, err := stmts.editTopic.Exec(name, content, parsed_content, topic.ID)
|
|
||||||
|
|
||||||
tcache, ok := topics.(TopicCache)
|
_, err := stmts.editTopic.Exec(name, content, parsed_content, topic.ID)
|
||||||
if ok {
|
topic.cacheRemove()
|
||||||
tcache.CacheRemove(topic.ID)
|
|
||||||
}
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,10 +210,7 @@ func (topic *Topic) CreateActionReply(action string, ipaddress string, user User
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = stmts.addRepliesToTopic.Exec(1, user.ID, topic.ID)
|
_, err = stmts.addRepliesToTopic.Exec(1, user.ID, topic.ID)
|
||||||
tcache, ok := topics.(TopicCache)
|
topic.cacheRemove()
|
||||||
if ok {
|
|
||||||
tcache.CacheRemove(topic.ID)
|
|
||||||
}
|
|
||||||
// ? - Update the last topic cache for the parent forum?
|
// ? - Update the last topic cache for the parent forum?
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
19
utils.go
19
utils.go
|
@ -199,7 +199,8 @@ func nameToSlug(name string) (slug string) {
|
||||||
return slug
|
return slug
|
||||||
}
|
}
|
||||||
|
|
||||||
func SendEmail(email string, subject string, msg string) (res bool) {
|
// TODO: Refactor this
|
||||||
|
func SendEmail(email string, subject string, msg string) bool {
|
||||||
// This hook is useful for plugin_sendmail or for testing tools. Possibly to hook it into some sort of mail server?
|
// This hook is useful for plugin_sendmail or for testing tools. Possibly to hook it into some sort of mail server?
|
||||||
if vhooks["email_send_intercept"] != nil {
|
if vhooks["email_send_intercept"] != nil {
|
||||||
return vhooks["email_send_intercept"](email, subject, msg).(bool)
|
return vhooks["email_send_intercept"](email, subject, msg).(bool)
|
||||||
|
@ -208,42 +209,42 @@ func SendEmail(email string, subject string, msg string) (res bool) {
|
||||||
|
|
||||||
con, err := smtp.Dial(config.SMTPServer + ":" + config.SMTPPort)
|
con, err := smtp.Dial(config.SMTPServer + ":" + config.SMTPPort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.SMTPUsername != "" {
|
if config.SMTPUsername != "" {
|
||||||
auth := smtp.PlainAuth("", config.SMTPUsername, config.SMTPPassword, config.SMTPServer)
|
auth := smtp.PlainAuth("", config.SMTPUsername, config.SMTPPassword, config.SMTPServer)
|
||||||
err = con.Auth(auth)
|
err = con.Auth(auth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = con.Mail(site.Email)
|
err = con.Mail(site.Email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
err = con.Rcpt(email)
|
err = con.Rcpt(email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
emailData, err := con.Data()
|
emailData, err := con.Data()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
_, err = fmt.Fprintf(emailData, body)
|
_, err = fmt.Fprintf(emailData, body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
err = emailData.Close()
|
err = emailData.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
err = con.Quit()
|
err = con.Quit()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue