Began work on the Nox Theme.
Removed the Tempra Cursive Theme. You can now do bulk moderation actions with Shadow. Added: Argon2 as a dependency. The EmailStore. The ReportStore. The Copy method to *Setting. The AddColumn method to the query builder and adapters. The textarea setting type. More logging to better debug issues. The GetOffset method to the UserStore. Removed: Sortable from Code Climate's Analysis. MemberCheck and memberCheck as they're obsolete now. The obsolete url_tags setting. The BcryptGeneratePasswordNoSalt function. Some redundant fields from some of the page structs. Revamped: The Control Panel Setting List and Editor. Refactored: The password hashing logic to make it more amenable to multiple hashing algorithms. The email portion of the Account Manager. The Control Panel User List. The report system. simplePanelUserCheck and simpleUserCheck to remove the duplicated logic as the two do the exact same thing. Fixed: Missing slugs in the profile links in the User Manager. A few template initialisers potentially reducing the number of odd template edge cases. Some problems with the footer. Custom selection colour not applying to images on Shadow. The avatars of the bottom row of the topic list on Conflux leaking out. Other: Moved the startTime variable into package common and exported it. Moved the password hashing logic from user.go to auth.go Split common/themes.go into common/theme.go and common/theme_list.go Replaced the SettingLabels phrase category with the more generic SettingPhrases category. Moved a load of routes, including panel ones into the routes and panel packages. Hid the notifications link from the Account Menu. Moved more inline CSS into the CSS files and made things a little more flexible here and there. Continued work on PgSQL, still a ways away. Guests now have a default avatar like everyone else. Tweaked some of the font sizes on Cosora to make the text look a little nicer. Partially implemented the theme dock override logic. Partially implemented a "symlink" like feature for theme directories. ... And a bunch of other things I might have missed. You will need to run this update script / patcher for this commit. Warning: This is an "unstable commit", therefore some things may be a little less stable than I'd like. For instance, the Shadow Theme is a little broken in this commit.
This commit is contained in:
parent
8ff8ce8e51
commit
ca80d0dd6f
|
@ -8,10 +8,4 @@ exclude_patterns:
|
|||
- "public/jquery-3.1.1.min.js"
|
||||
- "public/EQCSS.min.js"
|
||||
- "public/EQCSS.js"
|
||||
- "template_list.go"
|
||||
- "template_forum.go"
|
||||
- "template_forums.go"
|
||||
- "template_topic.go"
|
||||
- "template_topic_alt.go"
|
||||
- "template_topics.go"
|
||||
- "template_profile.go"
|
||||
- "public/Sortable-1.4.0/*"
|
10
README.md
10
README.md
|
@ -108,6 +108,8 @@ go get -u github.com/go-sql-driver/mysql
|
|||
|
||||
go get -u golang.org/x/crypto/bcrypt
|
||||
|
||||
go get -u golang.org/x/crypto/argon2
|
||||
|
||||
go get -u github.com/StackExchange/wmi
|
||||
|
||||
go get -u github.com/Azareal/gopsutil
|
||||
|
@ -189,8 +191,6 @@ We're looking for ways to clean-up the plugin system so that all of them (except
|
|||
|
||||
![Tempra Simple Mobile](https://github.com/Azareal/Gosora/blob/master/images/tempra-simple-mobile-375px.png)
|
||||
|
||||
![Tempra Cursive Theme](https://github.com/Azareal/Gosora/blob/master/images/tempra-cursive.png)
|
||||
|
||||
![Tempra Conflux Theme](https://github.com/Azareal/Gosora/blob/master/images/tempra-conflux.png)
|
||||
|
||||
![Tempra Conflux Mobile](https://github.com/Azareal/Gosora/blob/master/images/tempra-conflux-mobile-320px.png)
|
||||
|
@ -207,7 +207,7 @@ More images in the /images/ folder. Beware though, some of them are *really* out
|
|||
|
||||
* github.com/go-sql-driver/mysql For interfacing with MariaDB.
|
||||
|
||||
* golang.org/x/crypto/bcrypt For hashing passwords.
|
||||
* golang.org/x/crypto/bcrypt and go get -u golang.org/x/crypto/argon2 For hashing passwords.
|
||||
|
||||
* github.com/Azareal/gopsutil For pulling information on CPU and memory usage. I've temporarily forked this, as we were having stability issues with the latest build.
|
||||
|
||||
|
@ -229,6 +229,8 @@ More images in the /images/ folder. Beware though, some of them are *really* out
|
|||
|
||||
* github.com/fsnotify/fsnotify A library for watching events on the file system.
|
||||
|
||||
* More items to come here, our dependencies are going through a lot of changes, and I'll be documenting those soon ;)
|
||||
|
||||
# Bundled Plugins
|
||||
|
||||
There are several plugins which are bundled with the software by default. These cover various common tasks which aren't common enough to clutter the core with or which have competing implementation methods (E.g. plugin_markdown vs plugin_bbcode for post mark-up).
|
||||
|
@ -239,7 +241,7 @@ There are several plugins which are bundled with the software by default. These
|
|||
|
||||
* Markdown - An extremely simple plugin for converting Markdown into HTML.
|
||||
|
||||
* Social Groups - A WIP plugin which lets users create their own little discussion areas which they can administrate / moderate on their own.
|
||||
* Social Groups - An extremely unstable WIP plugin which lets users create their own little discussion areas which they can administrate / moderate on their own.
|
||||
|
||||
# Developers
|
||||
|
||||
|
|
114
common/auth.go
114
common/auth.go
|
@ -1,30 +1,58 @@
|
|||
/*
|
||||
*
|
||||
* Gosora Authentication Interface
|
||||
* Copyright Azareal 2017 - 2018
|
||||
* Copyright Azareal 2017 - 2019
|
||||
*
|
||||
*/
|
||||
package common
|
||||
|
||||
import "errors"
|
||||
import "strconv"
|
||||
import "net/http"
|
||||
import "database/sql"
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
import "golang.org/x/crypto/bcrypt"
|
||||
import "../query_gen/lib"
|
||||
"../query_gen/lib"
|
||||
//"golang.org/x/crypto/argon2"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
var Auth AuthInt
|
||||
|
||||
const SaltLength int = 32
|
||||
const SessionLength int = 80
|
||||
|
||||
// ErrMismatchedHashAndPassword is thrown whenever a hash doesn't match it's unhashed password
|
||||
var ErrMismatchedHashAndPassword = bcrypt.ErrMismatchedHashAndPassword
|
||||
|
||||
// nolint
|
||||
var ErrHashNotExist = errors.New("We don't recognise that hashing algorithm")
|
||||
var ErrTooFewHashParams = errors.New("You haven't provided enough hash parameters")
|
||||
|
||||
// ErrPasswordTooLong is silly, but we don't want bcrypt to bork on us
|
||||
var ErrPasswordTooLong = errors.New("The password you selected is too long")
|
||||
var ErrWrongPassword = errors.New("That's not the correct password.")
|
||||
var ErrSecretError = errors.New("There was a glitch in the system. Please contact your local administrator.")
|
||||
var ErrNoUserByName = errors.New("We couldn't find an account with that username.")
|
||||
var DefaultHashAlgo = "bcrypt" // Override this in the configuration file, not here
|
||||
|
||||
//func(realPassword string, password string, salt string) (err error)
|
||||
var CheckPasswordFuncs = map[string]func(string, string, string) error{
|
||||
"bcrypt": BcryptCheckPassword,
|
||||
//"argon2": Argon2CheckPassword,
|
||||
}
|
||||
|
||||
//func(password string) (hashedPassword string, salt string, err error)
|
||||
var GeneratePasswordFuncs = map[string]func(string) (string, string, error){
|
||||
"bcrypt": BcryptGeneratePassword,
|
||||
//"argon2": Argon2GeneratePassword,
|
||||
}
|
||||
|
||||
var HashPrefixes = map[string]string{
|
||||
"$2a$": "bcrypt",
|
||||
//"argon2$": "argon2",
|
||||
}
|
||||
|
||||
// AuthInt is the main authentication interface.
|
||||
type AuthInt interface {
|
||||
|
@ -176,3 +204,75 @@ func (auth *DefaultAuth) CreateSession(uid int) (session string, err error) {
|
|||
}
|
||||
return session, nil
|
||||
}
|
||||
|
||||
func CheckPassword(realPassword string, password string, salt string) (err error) {
|
||||
blasted := strings.Split(realPassword, "$")
|
||||
prefix := blasted[0]
|
||||
if len(blasted) > 1 {
|
||||
prefix += blasted[1]
|
||||
}
|
||||
algo, ok := HashPrefixes[prefix]
|
||||
if !ok {
|
||||
return ErrHashNotExist
|
||||
}
|
||||
checker := CheckPasswordFuncs[algo]
|
||||
return checker(realPassword, password, salt)
|
||||
}
|
||||
|
||||
func GeneratePassword(password string) (hash string, salt string, err error) {
|
||||
gen, ok := GeneratePasswordFuncs[DefaultHashAlgo]
|
||||
if !ok {
|
||||
return "", "", ErrHashNotExist
|
||||
}
|
||||
return gen(password)
|
||||
}
|
||||
|
||||
func BcryptCheckPassword(realPassword string, password string, salt string) (err error) {
|
||||
return bcrypt.CompareHashAndPassword([]byte(realPassword), []byte(password+salt))
|
||||
}
|
||||
|
||||
// Note: The salt is in the hash, therefore the salt parameter is blank
|
||||
func BcryptGeneratePassword(password string) (hash string, salt string, err error) {
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return string(hashedPassword), salt, nil
|
||||
}
|
||||
|
||||
/*const (
|
||||
argon2Time uint32 = 3
|
||||
argon2Memory uint32 = 32 * 1024
|
||||
argon2Threads uint8 = 4
|
||||
argon2KeyLen uint32 = 32
|
||||
)
|
||||
|
||||
func Argon2CheckPassword(realPassword string, password string, salt string) (err error) {
|
||||
split := strings.Split(realPassword, "$")
|
||||
// TODO: Better validation
|
||||
if len(split) < 5 {
|
||||
return ErrTooFewHashParams
|
||||
}
|
||||
realKey, _ := base64.StdEncoding.DecodeString(split[len(split)-1])
|
||||
time, _ := strconv.Atoi(split[1])
|
||||
memory, _ := strconv.Atoi(split[2])
|
||||
threads, _ := strconv.Atoi(split[3])
|
||||
keyLen, _ := strconv.Atoi(split[4])
|
||||
key := argon2.Key([]byte(password), []byte(salt), uint32(time), uint32(memory), uint8(threads), uint32(keyLen))
|
||||
if subtle.ConstantTimeCompare(realKey, key) != 1 {
|
||||
return ErrMismatchedHashAndPassword
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Argon2GeneratePassword(password string) (hash string, salt string, err error) {
|
||||
sbytes := make([]byte, SaltLength)
|
||||
_, err = rand.Read(sbytes)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
key := argon2.Key([]byte(password), sbytes, argon2Time, argon2Memory, argon2Threads, argon2KeyLen)
|
||||
hash = base64.StdEncoding.EncodeToString(key)
|
||||
return fmt.Sprintf("argon2$%d%d%d%d%s%s", argon2Time, argon2Memory, argon2Threads, argon2KeyLen, salt, hash), string(sbytes), nil
|
||||
}
|
||||
*/
|
||||
|
|
|
@ -3,6 +3,7 @@ package common
|
|||
import (
|
||||
"database/sql"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"../query_gen/lib"
|
||||
)
|
||||
|
@ -19,9 +20,7 @@ const Gigabyte int = Megabyte * 1024
|
|||
const Terabyte int = Gigabyte * 1024
|
||||
const Petabyte int = Terabyte * 1024
|
||||
|
||||
const SaltLength int = 32
|
||||
const SessionLength int = 80
|
||||
|
||||
var StartTime time.Time
|
||||
var TmplPtrMap = make(map[string]interface{})
|
||||
|
||||
// ErrNoRows is an alias of sql.ErrNoRows, just in case we end up with non-database/sql datastores
|
||||
|
|
|
@ -19,8 +19,7 @@ type DefaultViewCounter struct {
|
|||
insert *sql.Stmt
|
||||
}
|
||||
|
||||
func NewGlobalViewCounter() (*DefaultViewCounter, error) {
|
||||
acc := qgen.Builder.Accumulator()
|
||||
func NewGlobalViewCounter(acc *qgen.Accumulator) (*DefaultViewCounter, error) {
|
||||
counter := &DefaultViewCounter{
|
||||
currentBucket: 0,
|
||||
insert: acc.Insert("viewchunks").Columns("count, createdAt").Fields("?,UTC_TIMESTAMP()").Prepare(),
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
package common
|
||||
|
||||
import "database/sql"
|
||||
import "../query_gen/lib"
|
||||
|
||||
var Emails EmailStore
|
||||
|
||||
type EmailStore interface {
|
||||
GetEmailsByUser(user *User) (emails []Email, err error)
|
||||
VerifyEmail(email string) error
|
||||
}
|
||||
|
||||
type DefaultEmailStore struct {
|
||||
getEmailsByUser *sql.Stmt
|
||||
verifyEmail *sql.Stmt
|
||||
}
|
||||
|
||||
func NewDefaultEmailStore(acc *qgen.Accumulator) (*DefaultEmailStore, error) {
|
||||
return &DefaultEmailStore{
|
||||
getEmailsByUser: acc.Select("emails").Columns("email, validated, token").Where("uid = ?").Prepare(),
|
||||
|
||||
// Need to fix this: Empty string isn't working, it gets set to 1 instead x.x -- Has this been fixed?
|
||||
verifyEmail: acc.Update("emails").Set("validated = 1, token = ''").Where("email = ?").Prepare(),
|
||||
}, acc.FirstError()
|
||||
}
|
||||
|
||||
func (store *DefaultEmailStore) GetEmailsByUser(user *User) (emails []Email, err error) {
|
||||
email := Email{UserID: user.ID}
|
||||
rows, err := store.getEmailsByUser.Query(user.ID)
|
||||
if err != nil {
|
||||
return emails, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
err := rows.Scan(&email.Email, &email.Validated, &email.Token)
|
||||
if err != nil {
|
||||
return emails, err
|
||||
}
|
||||
|
||||
if email.Email == user.Email {
|
||||
email.Primary = true
|
||||
}
|
||||
emails = append(emails, email)
|
||||
}
|
||||
return emails, rows.Err()
|
||||
}
|
||||
|
||||
func (store *DefaultEmailStore) VerifyEmail(email string) error {
|
||||
_, err := store.verifyEmail.Exec(email)
|
||||
return err
|
||||
}
|
|
@ -314,6 +314,7 @@ func (mgs *MemoryGroupStore) GetRange(lower int, higher int) (groups []*Group, e
|
|||
return mgs.GetAll()
|
||||
}
|
||||
|
||||
// TODO: Simplify these four conditionals into two
|
||||
if lower == 0 {
|
||||
if higher < 0 {
|
||||
return nil, errors.New("higher may not be lower than 0")
|
||||
|
|
|
@ -74,14 +74,12 @@ type Paginator struct {
|
|||
}
|
||||
|
||||
type TopicPage struct {
|
||||
Title string
|
||||
CurrentUser User
|
||||
Header *Header
|
||||
ItemList []ReplyUser
|
||||
Topic TopicUser
|
||||
Poll Poll
|
||||
Page int
|
||||
LastPage int
|
||||
*Header
|
||||
ItemList []ReplyUser
|
||||
Topic TopicUser
|
||||
Poll Poll
|
||||
Page int
|
||||
LastPage int
|
||||
}
|
||||
|
||||
type TopicListPage struct {
|
||||
|
@ -93,11 +91,9 @@ type TopicListPage struct {
|
|||
}
|
||||
|
||||
type ForumPage struct {
|
||||
Title string
|
||||
CurrentUser User
|
||||
Header *Header
|
||||
ItemList []*TopicsRow
|
||||
Forum *Forum
|
||||
*Header
|
||||
ItemList []*TopicsRow
|
||||
Forum *Forum
|
||||
Paginator
|
||||
}
|
||||
|
||||
|
@ -132,6 +128,14 @@ type IPSearchPage struct {
|
|||
IP string
|
||||
}
|
||||
|
||||
type EmailListPage struct {
|
||||
Title string
|
||||
CurrentUser User
|
||||
Header *Header
|
||||
ItemList []Email
|
||||
Something interface{}
|
||||
}
|
||||
|
||||
type PanelStats struct {
|
||||
Users int
|
||||
Groups int
|
||||
|
@ -169,6 +173,19 @@ type PanelDashboardPage struct {
|
|||
GridItems []GridElement
|
||||
}
|
||||
|
||||
type PanelSetting struct {
|
||||
*Setting
|
||||
FriendlyName string
|
||||
}
|
||||
|
||||
type PanelSettingPage struct {
|
||||
*Header
|
||||
Stats PanelStats
|
||||
Zone string
|
||||
ItemList []OptionLabel
|
||||
Setting *PanelSetting
|
||||
}
|
||||
|
||||
type PanelTimeGraph struct {
|
||||
Series []int64 // The counts on the left
|
||||
Labels []int64 // unixtimes for the bottom, gets converted into 1:00, 2:00, etc. with JS
|
||||
|
@ -282,12 +299,10 @@ type PanelMenuItemPage struct {
|
|||
}
|
||||
|
||||
type PanelUserPage struct {
|
||||
Title string
|
||||
CurrentUser User
|
||||
Header *Header
|
||||
Stats PanelStats
|
||||
Zone string
|
||||
ItemList []User
|
||||
*Header
|
||||
Stats PanelStats
|
||||
Zone string
|
||||
ItemList []*User
|
||||
Paginator
|
||||
}
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ type LanguagePack struct {
|
|||
Levels LevelPhrases
|
||||
GlobalPerms map[string]string
|
||||
LocalPerms map[string]string
|
||||
SettingLabels map[string]string
|
||||
SettingPhrases map[string]string
|
||||
PermPresets map[string]string
|
||||
Accounts map[string]string // TODO: Apply these phrases in the software proper
|
||||
UserAgents map[string]string
|
||||
|
@ -148,16 +148,16 @@ func GetLocalPermPhrase(name string) string {
|
|||
return res
|
||||
}
|
||||
|
||||
func GetSettingLabel(name string) string {
|
||||
res, ok := currentLangPack.Load().(*LanguagePack).SettingLabels[name]
|
||||
func GetSettingPhrase(name string) string {
|
||||
res, ok := currentLangPack.Load().(*LanguagePack).SettingPhrases[name]
|
||||
if !ok {
|
||||
return getPhrasePlaceholder("settings", name)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func GetAllSettingLabels() map[string]string {
|
||||
return currentLangPack.Load().(*LanguagePack).SettingLabels
|
||||
func GetAllSettingPhrases() map[string]string {
|
||||
return currentLangPack.Load().(*LanguagePack).SettingPhrases
|
||||
}
|
||||
|
||||
func GetAllPermPresets() map[string]string {
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
"../query_gen/lib"
|
||||
)
|
||||
|
||||
var Reports ReportStore
|
||||
var ErrAlreadyReported = errors.New("This item has already been reported")
|
||||
|
||||
// The report system mostly wraps around the topic system for simplicty
|
||||
type ReportStore interface {
|
||||
Create(title string, content string, user *User, itemType string, itemID int) (int, error)
|
||||
}
|
||||
|
||||
type DefaultReportStore struct {
|
||||
create *sql.Stmt
|
||||
exists *sql.Stmt
|
||||
}
|
||||
|
||||
func NewDefaultReportStore(acc *qgen.Accumulator) (*DefaultReportStore, error) {
|
||||
return &DefaultReportStore{
|
||||
create: acc.Insert("topics").Columns("title, content, parsed_content, ipaddress, createdAt, lastReplyAt, createdBy, lastReplyBy, data, parentID, css_class").Fields("?,?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),?,?,?,1,'report'").Prepare(),
|
||||
exists: acc.Count("topics").Where("data = ? AND data != '' AND parentID = 1").Prepare(),
|
||||
}, acc.FirstError()
|
||||
}
|
||||
|
||||
// ! There's a data race in this. If two users report one item at the exact same time, then both reports will go through
|
||||
func (store *DefaultReportStore) Create(title string, content string, user *User, itemType string, itemID int) (int, error) {
|
||||
var count int
|
||||
err := store.exists.QueryRow(itemType + "_" + strconv.Itoa(itemID)).Scan(&count)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return 0, err
|
||||
}
|
||||
if count != 0 {
|
||||
return 0, ErrAlreadyReported
|
||||
}
|
||||
|
||||
res, err := store.create.Exec(title, content, ParseMessage(content, 0, ""), user.LastIP, user.ID, user.ID, itemType+"_"+strconv.Itoa(itemID))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
lastID, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return int(lastID), Forums.AddTopic(int(lastID), user.ID, 1)
|
||||
}
|
|
@ -17,7 +17,6 @@ var PanelUserCheck func(http.ResponseWriter, *http.Request, *User) (*Header, Pan
|
|||
var SimplePanelUserCheck func(http.ResponseWriter, *http.Request, *User) (*HeaderLite, RouteError) = simplePanelUserCheck
|
||||
var SimpleForumUserCheck func(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerLite *HeaderLite, err RouteError) = simpleForumUserCheck
|
||||
var ForumUserCheck func(w http.ResponseWriter, r *http.Request, user *User, fid int) (header *Header, err RouteError) = forumUserCheck
|
||||
var MemberCheck func(w http.ResponseWriter, r *http.Request, user *User) (header *Header, err RouteError) = memberCheck
|
||||
var SimpleUserCheck func(w http.ResponseWriter, r *http.Request, user *User) (headerLite *HeaderLite, err RouteError) = simpleUserCheck
|
||||
var UserCheck func(w http.ResponseWriter, r *http.Request, user *User) (header *Header, err RouteError) = userCheck
|
||||
|
||||
|
@ -166,28 +165,15 @@ func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (header
|
|||
}
|
||||
|
||||
func simplePanelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (headerLite *HeaderLite, rerr RouteError) {
|
||||
return &HeaderLite{
|
||||
Site: Site,
|
||||
Settings: SettingBox.Load().(SettingMap),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TODO: Add this to the member routes
|
||||
func memberCheck(w http.ResponseWriter, r *http.Request, user *User) (header *Header, rerr RouteError) {
|
||||
header, rerr = UserCheck(w, r, user)
|
||||
if !user.Loggedin {
|
||||
return header, NoPermissions(w, r, *user)
|
||||
}
|
||||
return header, rerr
|
||||
return simpleUserCheck(w, r, user)
|
||||
}
|
||||
|
||||
// SimpleUserCheck is back from the grave, yay :D
|
||||
func simpleUserCheck(w http.ResponseWriter, r *http.Request, user *User) (headerLite *HeaderLite, rerr RouteError) {
|
||||
headerLite = &HeaderLite{
|
||||
return &HeaderLite{
|
||||
Site: Site,
|
||||
Settings: SettingBox.Load().(SettingMap),
|
||||
}
|
||||
return headerLite, nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TODO: Add the ability for admins to restrict certain themes to certain groups?
|
||||
|
|
|
@ -54,6 +54,12 @@ func init() {
|
|||
})
|
||||
}
|
||||
|
||||
func (setting *Setting) Copy() (out *Setting) {
|
||||
out = &Setting{Name: ""}
|
||||
*out = *setting
|
||||
return out
|
||||
}
|
||||
|
||||
func LoadSettings() error {
|
||||
var sBox = SettingMap(make(map[string]interface{}))
|
||||
settings, err := sBox.BypassGetAll()
|
||||
|
|
|
@ -51,6 +51,7 @@ type dbConfig struct {
|
|||
type config struct {
|
||||
SslPrivkey string
|
||||
SslFullchain string
|
||||
HashAlgo string // Defaults to bcrypt, and in the future, possibly something stronger
|
||||
|
||||
MaxRequestSize int
|
||||
CacheTopicUser int
|
||||
|
@ -103,6 +104,11 @@ func ProcessConfig() error {
|
|||
if Config.MaxUsernameLength == 0 {
|
||||
Config.MaxUsernameLength = 100
|
||||
}
|
||||
GuestUser.Avatar = BuildAvatar(0, "")
|
||||
|
||||
if Config.HashAlgo != "" {
|
||||
// TODO: Set the alternate hash algo, e.g. argon2
|
||||
}
|
||||
|
||||
// We need this in here rather than verifyConfig as switchToTestDB() currently overwrites the values it verifies
|
||||
if DbConfig.TestDbname == DbConfig.Dbname {
|
||||
|
|
|
@ -155,6 +155,14 @@ func CompileTemplates() error {
|
|||
},
|
||||
}
|
||||
|
||||
var header2 = &Header{Site: Site}
|
||||
*header2 = *header
|
||||
header2.CurrentUser = user2
|
||||
|
||||
var header3 = &Header{Site: Site}
|
||||
*header3 = *header
|
||||
header3.CurrentUser = user3
|
||||
|
||||
log.Print("Compiling the templates")
|
||||
|
||||
var now = time.Now()
|
||||
|
@ -167,7 +175,8 @@ func CompileTemplates() error {
|
|||
replyList = append(replyList, ReplyUser{0, 0, "Yo!", "Yo!", 0, "alice", "Alice", Config.DefaultGroup, now, RelativeTime(now), 0, 0, "", "", 0, "", "", "", "", 0, "127.0.0.1", false, 1, "", ""})
|
||||
|
||||
var varList = make(map[string]tmpl.VarItem)
|
||||
tpage := TopicPage{"Title", user, header, replyList, topic, poll, 1, 1}
|
||||
header.Title = "Topic Name"
|
||||
tpage := TopicPage{header, replyList, topic, poll, 1, 1}
|
||||
topicIDTmpl, err := c.Compile("topic.html", "templates/", "common.TopicPage", tpage, varList)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -203,17 +212,16 @@ func CompileTemplates() error {
|
|||
|
||||
var topicsList []*TopicsRow
|
||||
topicsList = append(topicsList, &TopicsRow{1, "topic-title", "Topic Title", "The topic content.", 1, false, false, "Date", time.Now(), "Date", user3.ID, 1, "", "127.0.0.1", 0, 1, "classname", "", &user2, "", 0, &user3, "General", "/forum/general.2"})
|
||||
header.Title = "Topic List"
|
||||
header2.Title = "Topic List"
|
||||
topicListPage := TopicListPage{header, topicsList, forumList, Config.DefaultForum, Paginator{[]int{1}, 1, 1}}
|
||||
topicListTmpl, err := c.Compile("topics.html", "templates/", "common.TopicListPage", topicListPage, varList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//var topicList []TopicUser
|
||||
//topicList = append(topicList,TopicUser{1,"topic-title","Topic Title","The topic content.",1,false,false,"Date","Date",1,"","127.0.0.1",0,1,"classname","","admin-fred","Admin Fred",config.DefaultGroup,"",0,"","","","",58,false})
|
||||
forumItem := BlankForum(1, "general-forum.1", "General Forum", "Where the general stuff happens", true, "all", 0, "", 0)
|
||||
forumPage := ForumPage{"General Forum", user, header, topicsList, forumItem, Paginator{[]int{1}, 1, 1}}
|
||||
header.Title = "General Forum"
|
||||
forumPage := ForumPage{header, topicsList, forumItem, Paginator{[]int{1}, 1, 1}}
|
||||
forumTmpl, err := c.Compile("forum.html", "templates/", "common.ForumPage", forumPage, varList)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -0,0 +1,239 @@
|
|||
/* Copyright Azareal 2016 - 2019 */
|
||||
package common
|
||||
|
||||
import (
|
||||
//"fmt"
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"mime"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
type Theme struct {
|
||||
Path string // Redirect this file to another folder
|
||||
|
||||
Name string
|
||||
FriendlyName string
|
||||
Version string
|
||||
Creator string
|
||||
FullImage string
|
||||
MobileFriendly bool
|
||||
Disabled bool
|
||||
HideFromThemes bool
|
||||
BgAvatars bool // For profiles, at the moment
|
||||
ForkOf string
|
||||
Tag string
|
||||
URL string
|
||||
Docks []string // Allowed Values: leftSidebar, rightSidebar, footer
|
||||
Settings map[string]ThemeSetting
|
||||
Templates []TemplateMapping
|
||||
TemplatesMap map[string]string
|
||||
TmplPtr map[string]interface{}
|
||||
Resources []ThemeResource
|
||||
ResourceTemplates *template.Template
|
||||
|
||||
// Dock intercepters
|
||||
// TODO: Implement this
|
||||
MapTmplToDock map[string]ThemeMapTmplToDock // map[dockName]data
|
||||
RunOnDock func(string) string //(dock string) (sbody string)
|
||||
|
||||
// This variable should only be set and unset by the system, not the theme meta file
|
||||
Active bool
|
||||
}
|
||||
|
||||
type ThemeSetting struct {
|
||||
FriendlyName string
|
||||
Options []string
|
||||
}
|
||||
|
||||
type TemplateMapping struct {
|
||||
Name string
|
||||
Source string
|
||||
//When string
|
||||
}
|
||||
|
||||
type ThemeResource struct {
|
||||
Name string
|
||||
Location string
|
||||
Loggedin bool // Only serve this resource to logged in users
|
||||
}
|
||||
|
||||
type ThemeMapTmplToDock struct {
|
||||
//Name string
|
||||
File string
|
||||
}
|
||||
|
||||
// TODO: It might be unsafe to call the template parsing functions with fsnotify, do something more concurrent
|
||||
func (theme *Theme) LoadStaticFiles() error {
|
||||
theme.ResourceTemplates = template.New("")
|
||||
template.Must(theme.ResourceTemplates.ParseGlob("./themes/" + theme.Name + "/public/*.css"))
|
||||
|
||||
// It should be safe for us to load the files for all the themes in memory, as-long as the admin hasn't setup a ridiculous number of themes
|
||||
return theme.AddThemeStaticFiles()
|
||||
}
|
||||
|
||||
func (theme *Theme) AddThemeStaticFiles() error {
|
||||
phraseMap := GetTmplPhrases()
|
||||
// TODO: Use a function instead of a closure to make this more testable? What about a function call inside the closure to take the theme variable into account?
|
||||
return filepath.Walk("./themes/"+theme.Name+"/public", func(path string, f os.FileInfo, err error) error {
|
||||
DebugLog("Attempting to add static file '" + path + "' for default theme '" + theme.Name + "'")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if f.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
path = strings.Replace(path, "\\", "/", -1)
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var ext = filepath.Ext(path)
|
||||
if ext == ".css" && len(data) != 0 {
|
||||
var b bytes.Buffer
|
||||
var pieces = strings.Split(path, "/")
|
||||
var filename = pieces[len(pieces)-1]
|
||||
err = theme.ResourceTemplates.ExecuteTemplate(&b, filename, CSSData{Phrases: phraseMap})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data = b.Bytes()
|
||||
}
|
||||
|
||||
path = strings.TrimPrefix(path, "themes/"+theme.Name+"/public")
|
||||
gzipData := compressBytesGzip(data)
|
||||
StaticFiles.Set("/static/"+theme.Name+path, SFile{data, gzipData, 0, int64(len(data)), int64(len(gzipData)), mime.TypeByExtension(ext), f, f.ModTime().UTC().Format(http.TimeFormat)})
|
||||
|
||||
DebugLog("Added the '/" + theme.Name + path + "' static file for theme " + theme.Name + ".")
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (theme *Theme) MapTemplates() {
|
||||
if theme.Templates != nil {
|
||||
for _, themeTmpl := range theme.Templates {
|
||||
if themeTmpl.Name == "" {
|
||||
LogError(errors.New("Invalid destination template name"))
|
||||
}
|
||||
if themeTmpl.Source == "" {
|
||||
LogError(errors.New("Invalid source template name"))
|
||||
}
|
||||
|
||||
// `go generate` is one possibility for letting plugins inject custom page structs, but it would simply add another step of compilation. It might be simpler than the current build process from the perspective of the administrator?
|
||||
|
||||
destTmplPtr, ok := TmplPtrMap[themeTmpl.Name]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
sourceTmplPtr, ok := TmplPtrMap[themeTmpl.Source]
|
||||
if !ok {
|
||||
LogError(errors.New("The source template doesn't exist!"))
|
||||
}
|
||||
|
||||
switch dTmplPtr := destTmplPtr.(type) {
|
||||
case *func(TopicPage, io.Writer) error:
|
||||
switch sTmplPtr := sourceTmplPtr.(type) {
|
||||
case *func(TopicPage, io.Writer) error:
|
||||
//overridenTemplates[themeTmpl.Name] = d_tmpl_ptr
|
||||
overridenTemplates[themeTmpl.Name] = true
|
||||
*dTmplPtr = *sTmplPtr
|
||||
default:
|
||||
LogError(errors.New("The source and destination templates are incompatible"))
|
||||
}
|
||||
case *func(TopicListPage, io.Writer) error:
|
||||
switch sTmplPtr := sourceTmplPtr.(type) {
|
||||
case *func(TopicListPage, io.Writer) error:
|
||||
//overridenTemplates[themeTmpl.Name] = d_tmpl_ptr
|
||||
overridenTemplates[themeTmpl.Name] = true
|
||||
*dTmplPtr = *sTmplPtr
|
||||
default:
|
||||
LogError(errors.New("The source and destination templates are incompatible"))
|
||||
}
|
||||
case *func(ForumPage, io.Writer) error:
|
||||
switch sTmplPtr := sourceTmplPtr.(type) {
|
||||
case *func(ForumPage, io.Writer) error:
|
||||
//overridenTemplates[themeTmpl.Name] = d_tmpl_ptr
|
||||
overridenTemplates[themeTmpl.Name] = true
|
||||
*dTmplPtr = *sTmplPtr
|
||||
default:
|
||||
LogError(errors.New("The source and destination templates are incompatible"))
|
||||
}
|
||||
case *func(ForumsPage, io.Writer) error:
|
||||
switch sTmplPtr := sourceTmplPtr.(type) {
|
||||
case *func(ForumsPage, io.Writer) error:
|
||||
//overridenTemplates[themeTmpl.Name] = d_tmpl_ptr
|
||||
overridenTemplates[themeTmpl.Name] = true
|
||||
*dTmplPtr = *sTmplPtr
|
||||
default:
|
||||
LogError(errors.New("The source and destination templates are incompatible"))
|
||||
}
|
||||
case *func(ProfilePage, io.Writer) error:
|
||||
switch sTmplPtr := sourceTmplPtr.(type) {
|
||||
case *func(ProfilePage, io.Writer) error:
|
||||
//overridenTemplates[themeTmpl.Name] = d_tmpl_ptr
|
||||
overridenTemplates[themeTmpl.Name] = true
|
||||
*dTmplPtr = *sTmplPtr
|
||||
default:
|
||||
LogError(errors.New("The source and destination templates are incompatible"))
|
||||
}
|
||||
case *func(CreateTopicPage, io.Writer) error:
|
||||
switch sTmplPtr := sourceTmplPtr.(type) {
|
||||
case *func(CreateTopicPage, io.Writer) error:
|
||||
//overridenTemplates[themeTmpl.Name] = d_tmpl_ptr
|
||||
overridenTemplates[themeTmpl.Name] = true
|
||||
*dTmplPtr = *sTmplPtr
|
||||
default:
|
||||
LogError(errors.New("The source and destination templates are incompatible"))
|
||||
}
|
||||
case *func(IPSearchPage, io.Writer) error:
|
||||
switch sTmplPtr := sourceTmplPtr.(type) {
|
||||
case *func(IPSearchPage, io.Writer) error:
|
||||
//overridenTemplates[themeTmpl.Name] = d_tmpl_ptr
|
||||
overridenTemplates[themeTmpl.Name] = true
|
||||
*dTmplPtr = *sTmplPtr
|
||||
default:
|
||||
LogError(errors.New("The source and destination templates are incompatible"))
|
||||
}
|
||||
case *func(Page, io.Writer) error:
|
||||
switch sTmplPtr := sourceTmplPtr.(type) {
|
||||
case *func(Page, io.Writer) error:
|
||||
//overridenTemplates[themeTmpl.Name] = d_tmpl_ptr
|
||||
overridenTemplates[themeTmpl.Name] = true
|
||||
*dTmplPtr = *sTmplPtr
|
||||
default:
|
||||
LogError(errors.New("The source and destination templates are incompatible"))
|
||||
}
|
||||
default:
|
||||
log.Print("themeTmpl.Name: ", themeTmpl.Name)
|
||||
log.Print("themeTmpl.Source: ", themeTmpl.Source)
|
||||
LogError(errors.New("Unknown destination template type!"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (theme Theme) HasDock(name string) bool {
|
||||
for _, dock := range theme.Docks {
|
||||
if dock == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (theme Theme) BuildDock(dock string) (sbody string) {
|
||||
runOnDock := theme.RunOnDock
|
||||
if runOnDock != nil {
|
||||
return runOnDock(dock)
|
||||
}
|
||||
return ""
|
||||
}
|
|
@ -1,79 +1,30 @@
|
|||
/* Copyright Azareal 2016 - 2018 */
|
||||
package common
|
||||
|
||||
import (
|
||||
//"fmt"
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"mime"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"text/template"
|
||||
|
||||
"../query_gen/lib"
|
||||
)
|
||||
|
||||
type ThemeList map[string]*Theme
|
||||
|
||||
var Themes ThemeList = make(map[string]*Theme)
|
||||
var Themes ThemeList = make(map[string]*Theme) // ? Refactor this into a store?
|
||||
var DefaultThemeBox atomic.Value
|
||||
var ChangeDefaultThemeMutex sync.Mutex
|
||||
|
||||
// TODO: Use this when the default theme doesn't exist
|
||||
var fallbackTheme = "cosora"
|
||||
var overridenTemplates = make(map[string]bool)
|
||||
|
||||
type Theme struct {
|
||||
Name string
|
||||
FriendlyName string
|
||||
Version string
|
||||
Creator string
|
||||
FullImage string
|
||||
MobileFriendly bool
|
||||
Disabled bool
|
||||
HideFromThemes bool
|
||||
BgAvatars bool // For profiles, at the moment
|
||||
ForkOf string
|
||||
Tag string
|
||||
URL string
|
||||
Docks []string // Allowed Values: leftSidebar, rightSidebar, footer
|
||||
Settings map[string]ThemeSetting
|
||||
Templates []TemplateMapping
|
||||
TemplatesMap map[string]string
|
||||
TmplPtr map[string]interface{}
|
||||
Resources []ThemeResource
|
||||
ResourceTemplates *template.Template
|
||||
|
||||
// This variable should only be set and unset by the system, not the theme meta file
|
||||
Active bool
|
||||
}
|
||||
|
||||
type ThemeSetting struct {
|
||||
FriendlyName string
|
||||
Options []string
|
||||
}
|
||||
|
||||
type TemplateMapping struct {
|
||||
Name string
|
||||
Source string
|
||||
//When string
|
||||
}
|
||||
|
||||
type ThemeResource struct {
|
||||
Name string
|
||||
Location string
|
||||
Loggedin bool // Only serve this resource to logged in users
|
||||
}
|
||||
var overridenTemplates = make(map[string]bool) // ? What is this used for?
|
||||
|
||||
type ThemeStmts struct {
|
||||
getThemes *sql.Stmt
|
||||
|
@ -91,6 +42,89 @@ func init() {
|
|||
})
|
||||
}
|
||||
|
||||
func NewThemeList() (themes ThemeList, err error) {
|
||||
themes = make(map[string]*Theme)
|
||||
|
||||
themeFiles, err := ioutil.ReadDir("./themes")
|
||||
if err != nil {
|
||||
return themes, err
|
||||
}
|
||||
|
||||
for _, themeFile := range themeFiles {
|
||||
if !themeFile.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
themeName := themeFile.Name()
|
||||
log.Printf("Adding theme '%s'", themeName)
|
||||
themePath := "./themes/" + themeName
|
||||
themeFile, err := ioutil.ReadFile(themePath + "/theme.json")
|
||||
if err != nil {
|
||||
return themes, err
|
||||
}
|
||||
|
||||
var theme = &Theme{Name: ""}
|
||||
err = json.Unmarshal(themeFile, theme)
|
||||
if err != nil {
|
||||
return themes, err
|
||||
}
|
||||
|
||||
// TODO: Implement the static file part of this and fsnotify
|
||||
if theme.Path != "" {
|
||||
log.Print("Resolving redirect to " + theme.Path)
|
||||
themeFile, err := ioutil.ReadFile(theme.Path + "/theme.json")
|
||||
if err != nil {
|
||||
return themes, err
|
||||
}
|
||||
theme = &Theme{Name: "", Path: theme.Path}
|
||||
err = json.Unmarshal(themeFile, theme)
|
||||
if err != nil {
|
||||
return themes, err
|
||||
}
|
||||
} else {
|
||||
theme.Path = themePath
|
||||
}
|
||||
|
||||
theme.Active = false // Set this to false, just in case someone explicitly overrode this value in the JSON file
|
||||
|
||||
// TODO: Let the theme specify where it's resources are via the JSON file?
|
||||
// TODO: Let the theme inherit CSS from another theme?
|
||||
// ? - This might not be too helpful, as it only searches for /public/ and not if /public/ is empty. Still, it might help some people with a slightly less cryptic error
|
||||
log.Print(theme.Path + "/public/")
|
||||
_, err = os.Stat(theme.Path + "/public/")
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return themes, errors.New("We couldn't find this theme's resources. E.g. the /public/ folder.")
|
||||
} else {
|
||||
log.Print("We weren't able to access this theme's resources due to a permissions issue or some other problem")
|
||||
return themes, err
|
||||
}
|
||||
}
|
||||
|
||||
if theme.FullImage != "" {
|
||||
DebugLog("Adding theme image")
|
||||
err = StaticFiles.Add(theme.Path+"/"+theme.FullImage, themePath)
|
||||
if err != nil {
|
||||
return themes, err
|
||||
}
|
||||
}
|
||||
|
||||
theme.TemplatesMap = make(map[string]string)
|
||||
theme.TmplPtr = make(map[string]interface{})
|
||||
if theme.Templates != nil {
|
||||
for _, themeTmpl := range theme.Templates {
|
||||
theme.TemplatesMap[themeTmpl.Name] = themeTmpl.Source
|
||||
theme.TmplPtr[themeTmpl.Name] = TmplPtrMap["o_"+themeTmpl.Source]
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Bind the built template, or an interpreted one for any dock overrides this theme has
|
||||
|
||||
themes[theme.Name] = theme
|
||||
}
|
||||
return themes, nil
|
||||
}
|
||||
|
||||
// TODO: Make the initThemes and LoadThemes functions less confusing
|
||||
// ? - Delete themes which no longer exist in the themes folder from the database?
|
||||
func (themes ThemeList) LoadActiveStatus() error {
|
||||
|
@ -141,221 +175,8 @@ func (themes ThemeList) LoadStaticFiles() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func InitThemes() error {
|
||||
themeFiles, err := ioutil.ReadDir("./themes")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, themeFile := range themeFiles {
|
||||
if !themeFile.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
themeName := themeFile.Name()
|
||||
log.Printf("Adding theme '%s'", themeName)
|
||||
themeFile, err := ioutil.ReadFile("./themes/" + themeName + "/theme.json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var theme = &Theme{Name: ""}
|
||||
err = json.Unmarshal(themeFile, theme)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
theme.Active = false // Set this to false, just in case someone explicitly overrode this value in the JSON file
|
||||
|
||||
// TODO: Let the theme specify where it's resources are via the JSON file?
|
||||
// TODO: Let the theme inherit CSS from another theme?
|
||||
// ? - This might not be too helpful, as it only searches for /public/ and not if /public/ is empty. Still, it might help some people with a slightly less cryptic error
|
||||
_, err = os.Stat("./themes/" + theme.Name + "/public/")
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return errors.New("We couldn't find this theme's resources. E.g. the /public/ folder.")
|
||||
} else {
|
||||
log.Print("We weren't able to access this theme's resources due to a permissions issue or some other problem")
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if theme.FullImage != "" {
|
||||
DebugLog("Adding theme image")
|
||||
err = StaticFiles.Add("./themes/"+themeName+"/"+theme.FullImage, "./themes/"+themeName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
theme.TemplatesMap = make(map[string]string)
|
||||
theme.TmplPtr = make(map[string]interface{})
|
||||
if theme.Templates != nil {
|
||||
for _, themeTmpl := range theme.Templates {
|
||||
theme.TemplatesMap[themeTmpl.Name] = themeTmpl.Source
|
||||
theme.TmplPtr[themeTmpl.Name] = TmplPtrMap["o_"+themeTmpl.Source]
|
||||
}
|
||||
}
|
||||
|
||||
Themes[theme.Name] = theme
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: It might be unsafe to call the template parsing functions with fsnotify, do something more concurrent
|
||||
func (theme *Theme) LoadStaticFiles() error {
|
||||
theme.ResourceTemplates = template.New("")
|
||||
template.Must(theme.ResourceTemplates.ParseGlob("./themes/" + theme.Name + "/public/*.css"))
|
||||
|
||||
// It should be safe for us to load the files for all the themes in memory, as-long as the admin hasn't setup a ridiculous number of themes
|
||||
return theme.AddThemeStaticFiles()
|
||||
}
|
||||
|
||||
func (theme *Theme) AddThemeStaticFiles() error {
|
||||
phraseMap := GetTmplPhrases()
|
||||
// TODO: Use a function instead of a closure to make this more testable? What about a function call inside the closure to take the theme variable into account?
|
||||
return filepath.Walk("./themes/"+theme.Name+"/public", func(path string, f os.FileInfo, err error) error {
|
||||
DebugLog("Attempting to add static file '" + path + "' for default theme '" + theme.Name + "'")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if f.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
path = strings.Replace(path, "\\", "/", -1)
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var ext = filepath.Ext(path)
|
||||
if ext == ".css" && len(data) != 0 {
|
||||
var b bytes.Buffer
|
||||
var pieces = strings.Split(path, "/")
|
||||
var filename = pieces[len(pieces)-1]
|
||||
err = theme.ResourceTemplates.ExecuteTemplate(&b, filename, CSSData{Phrases: phraseMap})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data = b.Bytes()
|
||||
}
|
||||
|
||||
path = strings.TrimPrefix(path, "themes/"+theme.Name+"/public")
|
||||
gzipData := compressBytesGzip(data)
|
||||
StaticFiles.Set("/static/"+theme.Name+path, SFile{data, gzipData, 0, int64(len(data)), int64(len(gzipData)), mime.TypeByExtension(ext), f, f.ModTime().UTC().Format(http.TimeFormat)})
|
||||
|
||||
DebugLog("Added the '/" + theme.Name + path + "' static file for theme " + theme.Name + ".")
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (theme *Theme) MapTemplates() {
|
||||
if theme.Templates != nil {
|
||||
for _, themeTmpl := range theme.Templates {
|
||||
if themeTmpl.Name == "" {
|
||||
LogError(errors.New("Invalid destination template name"))
|
||||
}
|
||||
if themeTmpl.Source == "" {
|
||||
LogError(errors.New("Invalid source template name"))
|
||||
}
|
||||
|
||||
// `go generate` is one possibility for letting plugins inject custom page structs, but it would simply add another step of compilation. It might be simpler than the current build process from the perspective of the administrator?
|
||||
|
||||
destTmplPtr, ok := TmplPtrMap[themeTmpl.Name]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
sourceTmplPtr, ok := TmplPtrMap[themeTmpl.Source]
|
||||
if !ok {
|
||||
LogError(errors.New("The source template doesn't exist!"))
|
||||
}
|
||||
|
||||
switch dTmplPtr := destTmplPtr.(type) {
|
||||
case *func(TopicPage, io.Writer) error:
|
||||
switch sTmplPtr := sourceTmplPtr.(type) {
|
||||
case *func(TopicPage, io.Writer) error:
|
||||
//overridenTemplates[themeTmpl.Name] = d_tmpl_ptr
|
||||
overridenTemplates[themeTmpl.Name] = true
|
||||
*dTmplPtr = *sTmplPtr
|
||||
default:
|
||||
LogError(errors.New("The source and destination templates are incompatible"))
|
||||
}
|
||||
case *func(TopicListPage, io.Writer) error:
|
||||
switch sTmplPtr := sourceTmplPtr.(type) {
|
||||
case *func(TopicListPage, io.Writer) error:
|
||||
//overridenTemplates[themeTmpl.Name] = d_tmpl_ptr
|
||||
overridenTemplates[themeTmpl.Name] = true
|
||||
*dTmplPtr = *sTmplPtr
|
||||
default:
|
||||
LogError(errors.New("The source and destination templates are incompatible"))
|
||||
}
|
||||
case *func(ForumPage, io.Writer) error:
|
||||
switch sTmplPtr := sourceTmplPtr.(type) {
|
||||
case *func(ForumPage, io.Writer) error:
|
||||
//overridenTemplates[themeTmpl.Name] = d_tmpl_ptr
|
||||
overridenTemplates[themeTmpl.Name] = true
|
||||
*dTmplPtr = *sTmplPtr
|
||||
default:
|
||||
LogError(errors.New("The source and destination templates are incompatible"))
|
||||
}
|
||||
case *func(ForumsPage, io.Writer) error:
|
||||
switch sTmplPtr := sourceTmplPtr.(type) {
|
||||
case *func(ForumsPage, io.Writer) error:
|
||||
//overridenTemplates[themeTmpl.Name] = d_tmpl_ptr
|
||||
overridenTemplates[themeTmpl.Name] = true
|
||||
*dTmplPtr = *sTmplPtr
|
||||
default:
|
||||
LogError(errors.New("The source and destination templates are incompatible"))
|
||||
}
|
||||
case *func(ProfilePage, io.Writer) error:
|
||||
switch sTmplPtr := sourceTmplPtr.(type) {
|
||||
case *func(ProfilePage, io.Writer) error:
|
||||
//overridenTemplates[themeTmpl.Name] = d_tmpl_ptr
|
||||
overridenTemplates[themeTmpl.Name] = true
|
||||
*dTmplPtr = *sTmplPtr
|
||||
default:
|
||||
LogError(errors.New("The source and destination templates are incompatible"))
|
||||
}
|
||||
case *func(CreateTopicPage, io.Writer) error:
|
||||
switch sTmplPtr := sourceTmplPtr.(type) {
|
||||
case *func(CreateTopicPage, io.Writer) error:
|
||||
//overridenTemplates[themeTmpl.Name] = d_tmpl_ptr
|
||||
overridenTemplates[themeTmpl.Name] = true
|
||||
*dTmplPtr = *sTmplPtr
|
||||
default:
|
||||
LogError(errors.New("The source and destination templates are incompatible"))
|
||||
}
|
||||
case *func(IPSearchPage, io.Writer) error:
|
||||
switch sTmplPtr := sourceTmplPtr.(type) {
|
||||
case *func(IPSearchPage, io.Writer) error:
|
||||
//overridenTemplates[themeTmpl.Name] = d_tmpl_ptr
|
||||
overridenTemplates[themeTmpl.Name] = true
|
||||
*dTmplPtr = *sTmplPtr
|
||||
default:
|
||||
LogError(errors.New("The source and destination templates are incompatible"))
|
||||
}
|
||||
case *func(Page, io.Writer) error:
|
||||
switch sTmplPtr := sourceTmplPtr.(type) {
|
||||
case *func(Page, io.Writer) error:
|
||||
//overridenTemplates[themeTmpl.Name] = d_tmpl_ptr
|
||||
overridenTemplates[themeTmpl.Name] = true
|
||||
*dTmplPtr = *sTmplPtr
|
||||
default:
|
||||
LogError(errors.New("The source and destination templates are incompatible"))
|
||||
}
|
||||
default:
|
||||
log.Print("themeTmpl.Name: ", themeTmpl.Name)
|
||||
log.Print("themeTmpl.Source: ", themeTmpl.Source)
|
||||
LogError(errors.New("Unknown destination template type!"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ResetTemplateOverrides() {
|
||||
log.Print("Resetting the template overrides")
|
||||
|
||||
for name := range overridenTemplates {
|
||||
log.Print("Resetting '" + name + "' template override")
|
||||
|
||||
|
@ -542,17 +363,3 @@ func GetDefaultThemeName() string {
|
|||
func SetDefaultThemeName(name string) {
|
||||
DefaultThemeBox.Store(name)
|
||||
}
|
||||
|
||||
func (theme Theme) HasDock(name string) bool {
|
||||
for _, dock := range theme.Docks {
|
||||
if dock == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// TODO: Implement this
|
||||
func (theme Theme) BuildDock(dock string) (sbody string) {
|
||||
return ""
|
||||
}
|
|
@ -14,20 +14,14 @@ import (
|
|||
"time"
|
||||
|
||||
"../query_gen/lib"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// TODO: Replace any literals with this
|
||||
var BanGroup = 4
|
||||
|
||||
// TODO: Use something else as the guest avatar, maybe a question mark of some sort?
|
||||
// GuestUser is an instance of user which holds guest data to avoid having to initialise a guest every time
|
||||
var GuestUser = User{ID: 0, Link: "#", Group: 6, Perms: GuestPerms}
|
||||
|
||||
//func(real_password string, password string, salt string) (err error)
|
||||
var CheckPassword = BcryptCheckPassword
|
||||
|
||||
//func(password string) (hashed_password string, salt string, err error)
|
||||
var GeneratePassword = BcryptGeneratePassword
|
||||
var GuestUser = User{ID: 0, Name: "Guest", Link: "#", Group: 6, Perms: GuestPerms} // BuildAvatar is done in site.go to make sure it's done after init
|
||||
var ErrNoTempGroup = errors.New("We couldn't find a temporary group for this user")
|
||||
|
||||
type User struct {
|
||||
|
@ -369,33 +363,6 @@ func BuildAvatar(uid int, avatar string) string {
|
|||
return strings.Replace(Config.Noavatar, "{id}", strconv.Itoa(uid), 1)
|
||||
}
|
||||
|
||||
func BcryptCheckPassword(realPassword string, password string, salt string) (err error) {
|
||||
return bcrypt.CompareHashAndPassword([]byte(realPassword), []byte(password+salt))
|
||||
}
|
||||
|
||||
// Investigate. Do we need the extra salt?
|
||||
func BcryptGeneratePassword(password string) (hashedPassword string, salt string, err error) {
|
||||
salt, err = GenerateSafeString(SaltLength)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
password = password + salt
|
||||
hashedPassword, err = BcryptGeneratePasswordNoSalt(password)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return hashedPassword, salt, nil
|
||||
}
|
||||
|
||||
func BcryptGeneratePasswordNoSalt(password string) (hash string, err error) {
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(hashedPassword), nil
|
||||
}
|
||||
|
||||
// TODO: Move this to *User
|
||||
func SetPassword(uid int, password string) error {
|
||||
hashedPassword, salt, err := GeneratePassword(password)
|
||||
|
|
|
@ -20,6 +20,7 @@ type UserStore interface {
|
|||
DirtyGet(id int) *User
|
||||
Get(id int) (*User, error)
|
||||
Exists(id int) bool
|
||||
GetOffset(offset int, perPage int) (users []*User, err error)
|
||||
//BulkGet(ids []int) ([]*User, error)
|
||||
BulkGetMap(ids []int) (map[int]*User, error)
|
||||
BypassGet(id int) (*User, error)
|
||||
|
@ -35,6 +36,7 @@ type DefaultUserStore struct {
|
|||
cache UserCache
|
||||
|
||||
get *sql.Stmt
|
||||
getOffset *sql.Stmt
|
||||
exists *sql.Stmt
|
||||
register *sql.Stmt
|
||||
usernameExists *sql.Stmt
|
||||
|
@ -51,6 +53,7 @@ func NewDefaultUserStore(cache UserCache) (*DefaultUserStore, error) {
|
|||
return &DefaultUserStore{
|
||||
cache: cache,
|
||||
get: acc.SimpleSelect("users", "name, group, active, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, liked, last_ip, temp_group", "uid = ?", "", ""),
|
||||
getOffset: acc.Select("users").Columns("uid, name, group, active, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, liked, last_ip, temp_group").Orderby("uid ASC").Limit("?,?").Prepare(),
|
||||
exists: acc.SimpleSelect("users", "uid", "uid = ?", "", ""),
|
||||
register: acc.SimpleInsert("users", "name, email, password, salt, group, is_super_admin, session, active, message, createdAt, lastActiveAt", "?,?,?,?,?,0,'',?,'',UTC_TIMESTAMP(),UTC_TIMESTAMP()"), // TODO: Implement user_count on users_groups here
|
||||
usernameExists: acc.SimpleSelect("users", "name", "name = ?", "", ""),
|
||||
|
@ -92,6 +95,29 @@ func (mus *DefaultUserStore) Get(id int) (*User, error) {
|
|||
return user, err
|
||||
}
|
||||
|
||||
// TODO: Optimise this, so we don't wind up hitting the database every-time for small gaps
|
||||
// TODO: Make this a little more consistent with DefaultGroupStore's GetRange method
|
||||
func (store *DefaultUserStore) GetOffset(offset int, perPage int) (users []*User, err error) {
|
||||
rows, err := store.getOffset.Query(offset, perPage)
|
||||
if err != nil {
|
||||
return users, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
user := &User{Loggedin: true}
|
||||
err := rows.Scan(&user.ID, &user.Name, &user.Group, &user.Active, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.Liked, &user.LastIP, &user.TempGroup)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user.Init()
|
||||
store.cache.Set(user)
|
||||
users = append(users, user)
|
||||
}
|
||||
return users, rows.Err()
|
||||
}
|
||||
|
||||
// TODO: Optimise the query to avoid preparing it on the spot? Maybe, use knowledge of the most common IN() parameter counts?
|
||||
// TODO: ID of 0 should always error?
|
||||
func (mus *DefaultUserStore) BulkGetMap(ids []int) (list map[int]*User, err error) {
|
||||
|
|
|
@ -48,7 +48,7 @@ func GenerateSafeString(length int) (string, error) {
|
|||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.URLEncoding.EncodeToString(rb), nil
|
||||
return base64.StdEncoding.EncodeToString(rb), nil
|
||||
}
|
||||
|
||||
// TODO: Write a test for this
|
||||
|
|
|
@ -10,6 +10,9 @@ go get -u github.com/denisenkom/go-mssqldb
|
|||
echo "Updating bcrypt"
|
||||
go get -u golang.org/x/crypto/bcrypt
|
||||
|
||||
echo "Updating Argon2"
|
||||
go get -u golang.org/x/crypto/argon2
|
||||
|
||||
echo "Updating gopsutil"
|
||||
go get -u github.com/Azareal/gopsutil
|
||||
|
||||
|
|
|
@ -29,6 +29,13 @@ if %errorlevel% neq 0 (
|
|||
exit /b %errorlevel%
|
||||
)
|
||||
|
||||
echo Updating the Argon2 library
|
||||
go get -u golang.org/x/crypto/argon2
|
||||
if %errorlevel% neq 0 (
|
||||
pause
|
||||
exit /b %errorlevel%
|
||||
)
|
||||
|
||||
echo Updating /x/sys/windows (dependency for gopsutil)
|
||||
go get -u golang.org/x/sys/windows
|
||||
if %errorlevel% neq 0 (
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
# Templates
|
||||
|
||||
Gosora uses a subset of [Go Templates](https://golang.org/pkg/text/template/) which are run on both the server side and client side with custom transpiler to wring out the most performance. Some more obscure features may not be available, although I am adding them in here and there.
|
||||
|
||||
The base templates are stored in `/templates/` and you can shadow them by placing modified duplicates in `/templates/overrides/`. The default themes all share the same set of templates present there.
|
||||
|
||||
More to come soon.
|
|
@ -29,6 +29,13 @@ if %errorlevel% neq 0 (
|
|||
exit /b %errorlevel%
|
||||
)
|
||||
|
||||
echo Updating the Argon2 library
|
||||
go get -u golang.org/x/crypto/argon2
|
||||
if %errorlevel% neq 0 (
|
||||
pause
|
||||
exit /b %errorlevel%
|
||||
)
|
||||
|
||||
echo Updating /x/sys/windows (dependency for gopsutil)
|
||||
go get -u golang.org/x/sys/windows
|
||||
if %errorlevel% neq 0 (
|
||||
|
|
54
gen_mssql.go
54
gen_mssql.go
|
@ -10,14 +10,10 @@ import "./common"
|
|||
// nolint
|
||||
type Stmts struct {
|
||||
isPluginActive *sql.Stmt
|
||||
getUsersOffset *sql.Stmt
|
||||
isThemeDefault *sql.Stmt
|
||||
getEmailsByUser *sql.Stmt
|
||||
getTopicBasic *sql.Stmt
|
||||
forumEntryExists *sql.Stmt
|
||||
groupEntryExists *sql.Stmt
|
||||
getForumTopics *sql.Stmt
|
||||
createReport *sql.Stmt
|
||||
addForumPermsToForum *sql.Stmt
|
||||
addPlugin *sql.Stmt
|
||||
addTheme *sql.Stmt
|
||||
|
@ -29,13 +25,11 @@ type Stmts struct {
|
|||
updateGroupPerms *sql.Stmt
|
||||
updateGroup *sql.Stmt
|
||||
updateEmail *sql.Stmt
|
||||
verifyEmail *sql.Stmt
|
||||
setTempGroup *sql.Stmt
|
||||
updateWordFilter *sql.Stmt
|
||||
bumpSync *sql.Stmt
|
||||
deleteActivityStreamMatch *sql.Stmt
|
||||
deleteWordFilter *sql.Stmt
|
||||
reportExists *sql.Stmt
|
||||
|
||||
getActivityFeedByWatcher *sql.Stmt
|
||||
getActivityCountByWatcher *sql.Stmt
|
||||
|
@ -59,14 +53,6 @@ func _gen_mssql() (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
common.DebugLog("Preparing getUsersOffset statement.")
|
||||
stmts.getUsersOffset, err = db.Prepare("SELECT [uid],[name],[group],[active],[is_super_admin],[avatar] FROM [users] ORDER BY uid ASC OFFSET ?1 ROWS FETCH NEXT ?2 ROWS ONLY")
|
||||
if err != nil {
|
||||
log.Print("Error in getUsersOffset statement.")
|
||||
log.Print("Bad Query: ","SELECT [uid],[name],[group],[active],[is_super_admin],[avatar] FROM [users] ORDER BY uid ASC OFFSET ?1 ROWS FETCH NEXT ?2 ROWS ONLY")
|
||||
return err
|
||||
}
|
||||
|
||||
common.DebugLog("Preparing isThemeDefault statement.")
|
||||
stmts.isThemeDefault, err = db.Prepare("SELECT [default] FROM [themes] WHERE [uname] = ?1")
|
||||
if err != nil {
|
||||
|
@ -75,22 +61,6 @@ func _gen_mssql() (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
common.DebugLog("Preparing getEmailsByUser statement.")
|
||||
stmts.getEmailsByUser, err = db.Prepare("SELECT [email],[validated],[token] FROM [emails] WHERE [uid] = ?1")
|
||||
if err != nil {
|
||||
log.Print("Error in getEmailsByUser statement.")
|
||||
log.Print("Bad Query: ","SELECT [email],[validated],[token] FROM [emails] WHERE [uid] = ?1")
|
||||
return err
|
||||
}
|
||||
|
||||
common.DebugLog("Preparing getTopicBasic statement.")
|
||||
stmts.getTopicBasic, err = db.Prepare("SELECT [title],[content] FROM [topics] WHERE [tid] = ?1")
|
||||
if err != nil {
|
||||
log.Print("Error in getTopicBasic statement.")
|
||||
log.Print("Bad Query: ","SELECT [title],[content] FROM [topics] WHERE [tid] = ?1")
|
||||
return err
|
||||
}
|
||||
|
||||
common.DebugLog("Preparing forumEntryExists statement.")
|
||||
stmts.forumEntryExists, err = db.Prepare("SELECT [fid] FROM [forums] WHERE [name] = '' ORDER BY fid ASC OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY")
|
||||
if err != nil {
|
||||
|
@ -115,14 +85,6 @@ func _gen_mssql() (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
common.DebugLog("Preparing createReport statement.")
|
||||
stmts.createReport, err = db.Prepare("INSERT INTO [topics] ([title],[content],[parsed_content],[createdAt],[lastReplyAt],[createdBy],[lastReplyBy],[data],[parentID],[css_class]) VALUES (?,?,?,GETUTCDATE(),GETUTCDATE(),?,?,?,1,'report')")
|
||||
if err != nil {
|
||||
log.Print("Error in createReport statement.")
|
||||
log.Print("Bad Query: ","INSERT INTO [topics] ([title],[content],[parsed_content],[createdAt],[lastReplyAt],[createdBy],[lastReplyBy],[data],[parentID],[css_class]) VALUES (?,?,?,GETUTCDATE(),GETUTCDATE(),?,?,?,1,'report')")
|
||||
return err
|
||||
}
|
||||
|
||||
common.DebugLog("Preparing addForumPermsToForum statement.")
|
||||
stmts.addForumPermsToForum, err = db.Prepare("INSERT INTO [forums_permissions] ([gid],[fid],[preset],[permissions]) VALUES (?,?,?,?)")
|
||||
if err != nil {
|
||||
|
@ -211,14 +173,6 @@ func _gen_mssql() (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
common.DebugLog("Preparing verifyEmail statement.")
|
||||
stmts.verifyEmail, err = db.Prepare("UPDATE [emails] SET [validated] = 1,[token] = '' WHERE [email] = ?")
|
||||
if err != nil {
|
||||
log.Print("Error in verifyEmail statement.")
|
||||
log.Print("Bad Query: ","UPDATE [emails] SET [validated] = 1,[token] = '' WHERE [email] = ?")
|
||||
return err
|
||||
}
|
||||
|
||||
common.DebugLog("Preparing setTempGroup statement.")
|
||||
stmts.setTempGroup, err = db.Prepare("UPDATE [users] SET [temp_group] = ? WHERE [uid] = ?")
|
||||
if err != nil {
|
||||
|
@ -258,14 +212,6 @@ func _gen_mssql() (err error) {
|
|||
log.Print("Bad Query: ","DELETE FROM [word_filters] WHERE [wfid] = ?")
|
||||
return err
|
||||
}
|
||||
|
||||
common.DebugLog("Preparing reportExists statement.")
|
||||
stmts.reportExists, err = db.Prepare("SELECT COUNT(*) AS [count] FROM [topics] WHERE [data] = ? AND [data] != '' AND [parentID] = 1")
|
||||
if err != nil {
|
||||
log.Print("Error in reportExists statement.")
|
||||
log.Print("Bad Query: ","SELECT COUNT(*) AS [count] FROM [topics] WHERE [data] = ? AND [data] != '' AND [parentID] = 1")
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
48
gen_mysql.go
48
gen_mysql.go
|
@ -12,14 +12,10 @@ import "./common"
|
|||
// nolint
|
||||
type Stmts struct {
|
||||
isPluginActive *sql.Stmt
|
||||
getUsersOffset *sql.Stmt
|
||||
isThemeDefault *sql.Stmt
|
||||
getEmailsByUser *sql.Stmt
|
||||
getTopicBasic *sql.Stmt
|
||||
forumEntryExists *sql.Stmt
|
||||
groupEntryExists *sql.Stmt
|
||||
getForumTopics *sql.Stmt
|
||||
createReport *sql.Stmt
|
||||
addForumPermsToForum *sql.Stmt
|
||||
addPlugin *sql.Stmt
|
||||
addTheme *sql.Stmt
|
||||
|
@ -31,13 +27,11 @@ type Stmts struct {
|
|||
updateGroupPerms *sql.Stmt
|
||||
updateGroup *sql.Stmt
|
||||
updateEmail *sql.Stmt
|
||||
verifyEmail *sql.Stmt
|
||||
setTempGroup *sql.Stmt
|
||||
updateWordFilter *sql.Stmt
|
||||
bumpSync *sql.Stmt
|
||||
deleteActivityStreamMatch *sql.Stmt
|
||||
deleteWordFilter *sql.Stmt
|
||||
reportExists *sql.Stmt
|
||||
|
||||
getActivityFeedByWatcher *sql.Stmt
|
||||
getActivityCountByWatcher *sql.Stmt
|
||||
|
@ -60,13 +54,6 @@ func _gen_mysql() (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
common.DebugLog("Preparing getUsersOffset statement.")
|
||||
stmts.getUsersOffset, err = db.Prepare("SELECT `uid`,`name`,`group`,`active`,`is_super_admin`,`avatar` FROM `users` ORDER BY `uid` ASC LIMIT ?,?")
|
||||
if err != nil {
|
||||
log.Print("Error in getUsersOffset statement.")
|
||||
return err
|
||||
}
|
||||
|
||||
common.DebugLog("Preparing isThemeDefault statement.")
|
||||
stmts.isThemeDefault, err = db.Prepare("SELECT `default` FROM `themes` WHERE `uname` = ?")
|
||||
if err != nil {
|
||||
|
@ -74,20 +61,6 @@ func _gen_mysql() (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
common.DebugLog("Preparing getEmailsByUser statement.")
|
||||
stmts.getEmailsByUser, err = db.Prepare("SELECT `email`,`validated`,`token` FROM `emails` WHERE `uid` = ?")
|
||||
if err != nil {
|
||||
log.Print("Error in getEmailsByUser statement.")
|
||||
return err
|
||||
}
|
||||
|
||||
common.DebugLog("Preparing getTopicBasic statement.")
|
||||
stmts.getTopicBasic, err = db.Prepare("SELECT `title`,`content` FROM `topics` WHERE `tid` = ?")
|
||||
if err != nil {
|
||||
log.Print("Error in getTopicBasic statement.")
|
||||
return err
|
||||
}
|
||||
|
||||
common.DebugLog("Preparing forumEntryExists statement.")
|
||||
stmts.forumEntryExists, err = db.Prepare("SELECT `fid` FROM `forums` WHERE `name` = '' ORDER BY `fid` ASC LIMIT 0,1")
|
||||
if err != nil {
|
||||
|
@ -109,13 +82,6 @@ func _gen_mysql() (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
common.DebugLog("Preparing createReport statement.")
|
||||
stmts.createReport, err = db.Prepare("INSERT INTO `topics`(`title`,`content`,`parsed_content`,`createdAt`,`lastReplyAt`,`createdBy`,`lastReplyBy`,`data`,`parentID`,`css_class`) VALUES (?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),?,?,?,1,'report')")
|
||||
if err != nil {
|
||||
log.Print("Error in createReport statement.")
|
||||
return err
|
||||
}
|
||||
|
||||
common.DebugLog("Preparing addForumPermsToForum statement.")
|
||||
stmts.addForumPermsToForum, err = db.Prepare("INSERT INTO `forums_permissions`(`gid`,`fid`,`preset`,`permissions`) VALUES (?,?,?,?)")
|
||||
if err != nil {
|
||||
|
@ -193,13 +159,6 @@ func _gen_mysql() (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
common.DebugLog("Preparing verifyEmail statement.")
|
||||
stmts.verifyEmail, err = db.Prepare("UPDATE `emails` SET `validated` = 1,`token` = '' WHERE `email` = ?")
|
||||
if err != nil {
|
||||
log.Print("Error in verifyEmail statement.")
|
||||
return err
|
||||
}
|
||||
|
||||
common.DebugLog("Preparing setTempGroup statement.")
|
||||
stmts.setTempGroup, err = db.Prepare("UPDATE `users` SET `temp_group` = ? WHERE `uid` = ?")
|
||||
if err != nil {
|
||||
|
@ -234,13 +193,6 @@ func _gen_mysql() (err error) {
|
|||
log.Print("Error in deleteWordFilter statement.")
|
||||
return err
|
||||
}
|
||||
|
||||
common.DebugLog("Preparing reportExists statement.")
|
||||
stmts.reportExists, err = db.Prepare("SELECT COUNT(*) AS `count` FROM `topics` WHERE `data` = ? AND `data` != '' AND `parentID` = 1")
|
||||
if err != nil {
|
||||
log.Print("Error in reportExists statement.")
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
40
gen_pgsql.go
40
gen_pgsql.go
|
@ -9,6 +9,10 @@ import "./common"
|
|||
|
||||
// nolint
|
||||
type Stmts struct {
|
||||
addForumPermsToForum *sql.Stmt
|
||||
addPlugin *sql.Stmt
|
||||
addTheme *sql.Stmt
|
||||
createWordFilter *sql.Stmt
|
||||
updatePlugin *sql.Stmt
|
||||
updatePluginInstall *sql.Stmt
|
||||
updateTheme *sql.Stmt
|
||||
|
@ -16,7 +20,6 @@ type Stmts struct {
|
|||
updateGroupPerms *sql.Stmt
|
||||
updateGroup *sql.Stmt
|
||||
updateEmail *sql.Stmt
|
||||
verifyEmail *sql.Stmt
|
||||
setTempGroup *sql.Stmt
|
||||
updateWordFilter *sql.Stmt
|
||||
bumpSync *sql.Stmt
|
||||
|
@ -35,6 +38,34 @@ type Stmts struct {
|
|||
func _gen_pgsql() (err error) {
|
||||
common.DebugLog("Building the generated statements")
|
||||
|
||||
common.DebugLog("Preparing addForumPermsToForum statement.")
|
||||
stmts.addForumPermsToForum, err = db.Prepare("INSERT INTO "forums_permissions"("gid","fid","preset","permissions") VALUES (?,?,?,?)")
|
||||
if err != nil {
|
||||
log.Print("Error in addForumPermsToForum statement.")
|
||||
return err
|
||||
}
|
||||
|
||||
common.DebugLog("Preparing addPlugin statement.")
|
||||
stmts.addPlugin, err = db.Prepare("INSERT INTO "plugins"("uname","active","installed") VALUES (?,?,?)")
|
||||
if err != nil {
|
||||
log.Print("Error in addPlugin statement.")
|
||||
return err
|
||||
}
|
||||
|
||||
common.DebugLog("Preparing addTheme statement.")
|
||||
stmts.addTheme, err = db.Prepare("INSERT INTO "themes"("uname","default") VALUES (?,?)")
|
||||
if err != nil {
|
||||
log.Print("Error in addTheme statement.")
|
||||
return err
|
||||
}
|
||||
|
||||
common.DebugLog("Preparing createWordFilter statement.")
|
||||
stmts.createWordFilter, err = db.Prepare("INSERT INTO "word_filters"("find","replacement") VALUES (?,?)")
|
||||
if err != nil {
|
||||
log.Print("Error in createWordFilter statement.")
|
||||
return err
|
||||
}
|
||||
|
||||
common.DebugLog("Preparing updatePlugin statement.")
|
||||
stmts.updatePlugin, err = db.Prepare("UPDATE `plugins` SET `active` = ? WHERE `uname` = ?")
|
||||
if err != nil {
|
||||
|
@ -84,13 +115,6 @@ func _gen_pgsql() (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
common.DebugLog("Preparing verifyEmail statement.")
|
||||
stmts.verifyEmail, err = db.Prepare("UPDATE `emails` SET `validated` = 1,`token` = '' WHERE `email` = ?")
|
||||
if err != nil {
|
||||
log.Print("Error in verifyEmail statement.")
|
||||
return err
|
||||
}
|
||||
|
||||
common.DebugLog("Preparing setTempGroup statement.")
|
||||
stmts.setTempGroup, err = db.Prepare("UPDATE `users` SET `temp_group` = ? WHERE `uid` = ?")
|
||||
if err != nil {
|
||||
|
|
273
gen_router.go
273
gen_router.go
|
@ -14,6 +14,7 @@ import (
|
|||
"./common"
|
||||
"./common/counters"
|
||||
"./routes"
|
||||
"./routes/panel"
|
||||
)
|
||||
|
||||
var ErrNoRoute = errors.New("That route doesn't exist.")
|
||||
|
@ -27,21 +28,21 @@ var RouteMap = map[string]interface{}{
|
|||
"routes.ChangeTheme": routes.ChangeTheme,
|
||||
"routes.ShowAttachment": routes.ShowAttachment,
|
||||
"common.RouteWebsockets": common.RouteWebsockets,
|
||||
"routeReportSubmit": routeReportSubmit,
|
||||
"routes.ReportSubmit": routes.ReportSubmit,
|
||||
"routes.CreateTopic": routes.CreateTopic,
|
||||
"routes.TopicList": routes.TopicList,
|
||||
"routePanelForums": routePanelForums,
|
||||
"routePanelForumsCreateSubmit": routePanelForumsCreateSubmit,
|
||||
"routePanelForumsDelete": routePanelForumsDelete,
|
||||
"routePanelForumsDeleteSubmit": routePanelForumsDeleteSubmit,
|
||||
"routePanelForumsEdit": routePanelForumsEdit,
|
||||
"routePanelForumsEditSubmit": routePanelForumsEditSubmit,
|
||||
"routePanelForumsEditPermsSubmit": routePanelForumsEditPermsSubmit,
|
||||
"routePanelForumsEditPermsAdvance": routePanelForumsEditPermsAdvance,
|
||||
"routePanelForumsEditPermsAdvanceSubmit": routePanelForumsEditPermsAdvanceSubmit,
|
||||
"routePanelSettings": routePanelSettings,
|
||||
"routePanelSettingEdit": routePanelSettingEdit,
|
||||
"routePanelSettingEditSubmit": routePanelSettingEditSubmit,
|
||||
"panel.Forums": panel.Forums,
|
||||
"panel.ForumsCreateSubmit": panel.ForumsCreateSubmit,
|
||||
"panel.ForumsDelete": panel.ForumsDelete,
|
||||
"panel.ForumsDeleteSubmit": panel.ForumsDeleteSubmit,
|
||||
"panel.ForumsEdit": panel.ForumsEdit,
|
||||
"panel.ForumsEditSubmit": panel.ForumsEditSubmit,
|
||||
"panel.ForumsEditPermsSubmit": panel.ForumsEditPermsSubmit,
|
||||
"panel.ForumsEditPermsAdvance": panel.ForumsEditPermsAdvance,
|
||||
"panel.ForumsEditPermsAdvanceSubmit": panel.ForumsEditPermsAdvanceSubmit,
|
||||
"panel.Settings": panel.Settings,
|
||||
"panel.SettingEdit": panel.SettingEdit,
|
||||
"panel.SettingEditSubmit": panel.SettingEditSubmit,
|
||||
"routePanelWordFilters": routePanelWordFilters,
|
||||
"routePanelWordFiltersCreateSubmit": routePanelWordFiltersCreateSubmit,
|
||||
"routePanelWordFiltersEdit": routePanelWordFiltersEdit,
|
||||
|
@ -63,31 +64,31 @@ var RouteMap = map[string]interface{}{
|
|||
"routePanelUsers": routePanelUsers,
|
||||
"routePanelUsersEdit": routePanelUsersEdit,
|
||||
"routePanelUsersEditSubmit": routePanelUsersEditSubmit,
|
||||
"routePanelAnalyticsViews": routePanelAnalyticsViews,
|
||||
"routePanelAnalyticsRoutes": routePanelAnalyticsRoutes,
|
||||
"routePanelAnalyticsAgents": routePanelAnalyticsAgents,
|
||||
"routePanelAnalyticsSystems": routePanelAnalyticsSystems,
|
||||
"routePanelAnalyticsLanguages": routePanelAnalyticsLanguages,
|
||||
"routePanelAnalyticsReferrers": routePanelAnalyticsReferrers,
|
||||
"routePanelAnalyticsRouteViews": routePanelAnalyticsRouteViews,
|
||||
"routePanelAnalyticsAgentViews": routePanelAnalyticsAgentViews,
|
||||
"routePanelAnalyticsForumViews": routePanelAnalyticsForumViews,
|
||||
"routePanelAnalyticsSystemViews": routePanelAnalyticsSystemViews,
|
||||
"routePanelAnalyticsLanguageViews": routePanelAnalyticsLanguageViews,
|
||||
"routePanelAnalyticsReferrerViews": routePanelAnalyticsReferrerViews,
|
||||
"routePanelAnalyticsPosts": routePanelAnalyticsPosts,
|
||||
"routePanelAnalyticsTopics": routePanelAnalyticsTopics,
|
||||
"routePanelAnalyticsForums": routePanelAnalyticsForums,
|
||||
"panel.AnalyticsViews": panel.AnalyticsViews,
|
||||
"panel.AnalyticsRoutes": panel.AnalyticsRoutes,
|
||||
"panel.AnalyticsAgents": panel.AnalyticsAgents,
|
||||
"panel.AnalyticsSystems": panel.AnalyticsSystems,
|
||||
"panel.AnalyticsLanguages": panel.AnalyticsLanguages,
|
||||
"panel.AnalyticsReferrers": panel.AnalyticsReferrers,
|
||||
"panel.AnalyticsRouteViews": panel.AnalyticsRouteViews,
|
||||
"panel.AnalyticsAgentViews": panel.AnalyticsAgentViews,
|
||||
"panel.AnalyticsForumViews": panel.AnalyticsForumViews,
|
||||
"panel.AnalyticsSystemViews": panel.AnalyticsSystemViews,
|
||||
"panel.AnalyticsLanguageViews": panel.AnalyticsLanguageViews,
|
||||
"panel.AnalyticsReferrerViews": panel.AnalyticsReferrerViews,
|
||||
"panel.AnalyticsPosts": panel.AnalyticsPosts,
|
||||
"panel.AnalyticsTopics": panel.AnalyticsTopics,
|
||||
"panel.AnalyticsForums": panel.AnalyticsForums,
|
||||
"routePanelGroups": routePanelGroups,
|
||||
"routePanelGroupsEdit": routePanelGroupsEdit,
|
||||
"routePanelGroupsEditPerms": routePanelGroupsEditPerms,
|
||||
"routePanelGroupsEditSubmit": routePanelGroupsEditSubmit,
|
||||
"routePanelGroupsEditPermsSubmit": routePanelGroupsEditPermsSubmit,
|
||||
"routePanelGroupsCreateSubmit": routePanelGroupsCreateSubmit,
|
||||
"routePanelBackups": routePanelBackups,
|
||||
"routePanelLogsRegs": routePanelLogsRegs,
|
||||
"routePanelLogsMod": routePanelLogsMod,
|
||||
"routePanelDebug": routePanelDebug,
|
||||
"panel.Backups": panel.Backups,
|
||||
"panel.LogsRegs": panel.LogsRegs,
|
||||
"panel.LogsMod": panel.LogsMod,
|
||||
"panel.Debug": panel.Debug,
|
||||
"routePanelDashboard": routePanelDashboard,
|
||||
"routes.AccountEditCritical": routes.AccountEditCritical,
|
||||
"routes.AccountEditCriticalSubmit": routes.AccountEditCriticalSubmit,
|
||||
|
@ -95,8 +96,8 @@ var RouteMap = map[string]interface{}{
|
|||
"routes.AccountEditAvatarSubmit": routes.AccountEditAvatarSubmit,
|
||||
"routes.AccountEditUsername": routes.AccountEditUsername,
|
||||
"routes.AccountEditUsernameSubmit": routes.AccountEditUsernameSubmit,
|
||||
"routeAccountEditEmail": routeAccountEditEmail,
|
||||
"routeAccountEditEmailTokenSubmit": routeAccountEditEmailTokenSubmit,
|
||||
"routes.AccountEditEmail": routes.AccountEditEmail,
|
||||
"routes.AccountEditEmailTokenSubmit": routes.AccountEditEmailTokenSubmit,
|
||||
"routes.ViewProfile": routes.ViewProfile,
|
||||
"routes.BanUserSubmit": routes.BanUserSubmit,
|
||||
"routes.UnbanUser": routes.UnbanUser,
|
||||
|
@ -144,21 +145,21 @@ var routeMapEnum = map[string]int{
|
|||
"routes.ChangeTheme": 5,
|
||||
"routes.ShowAttachment": 6,
|
||||
"common.RouteWebsockets": 7,
|
||||
"routeReportSubmit": 8,
|
||||
"routes.ReportSubmit": 8,
|
||||
"routes.CreateTopic": 9,
|
||||
"routes.TopicList": 10,
|
||||
"routePanelForums": 11,
|
||||
"routePanelForumsCreateSubmit": 12,
|
||||
"routePanelForumsDelete": 13,
|
||||
"routePanelForumsDeleteSubmit": 14,
|
||||
"routePanelForumsEdit": 15,
|
||||
"routePanelForumsEditSubmit": 16,
|
||||
"routePanelForumsEditPermsSubmit": 17,
|
||||
"routePanelForumsEditPermsAdvance": 18,
|
||||
"routePanelForumsEditPermsAdvanceSubmit": 19,
|
||||
"routePanelSettings": 20,
|
||||
"routePanelSettingEdit": 21,
|
||||
"routePanelSettingEditSubmit": 22,
|
||||
"panel.Forums": 11,
|
||||
"panel.ForumsCreateSubmit": 12,
|
||||
"panel.ForumsDelete": 13,
|
||||
"panel.ForumsDeleteSubmit": 14,
|
||||
"panel.ForumsEdit": 15,
|
||||
"panel.ForumsEditSubmit": 16,
|
||||
"panel.ForumsEditPermsSubmit": 17,
|
||||
"panel.ForumsEditPermsAdvance": 18,
|
||||
"panel.ForumsEditPermsAdvanceSubmit": 19,
|
||||
"panel.Settings": 20,
|
||||
"panel.SettingEdit": 21,
|
||||
"panel.SettingEditSubmit": 22,
|
||||
"routePanelWordFilters": 23,
|
||||
"routePanelWordFiltersCreateSubmit": 24,
|
||||
"routePanelWordFiltersEdit": 25,
|
||||
|
@ -180,31 +181,31 @@ var routeMapEnum = map[string]int{
|
|||
"routePanelUsers": 41,
|
||||
"routePanelUsersEdit": 42,
|
||||
"routePanelUsersEditSubmit": 43,
|
||||
"routePanelAnalyticsViews": 44,
|
||||
"routePanelAnalyticsRoutes": 45,
|
||||
"routePanelAnalyticsAgents": 46,
|
||||
"routePanelAnalyticsSystems": 47,
|
||||
"routePanelAnalyticsLanguages": 48,
|
||||
"routePanelAnalyticsReferrers": 49,
|
||||
"routePanelAnalyticsRouteViews": 50,
|
||||
"routePanelAnalyticsAgentViews": 51,
|
||||
"routePanelAnalyticsForumViews": 52,
|
||||
"routePanelAnalyticsSystemViews": 53,
|
||||
"routePanelAnalyticsLanguageViews": 54,
|
||||
"routePanelAnalyticsReferrerViews": 55,
|
||||
"routePanelAnalyticsPosts": 56,
|
||||
"routePanelAnalyticsTopics": 57,
|
||||
"routePanelAnalyticsForums": 58,
|
||||
"panel.AnalyticsViews": 44,
|
||||
"panel.AnalyticsRoutes": 45,
|
||||
"panel.AnalyticsAgents": 46,
|
||||
"panel.AnalyticsSystems": 47,
|
||||
"panel.AnalyticsLanguages": 48,
|
||||
"panel.AnalyticsReferrers": 49,
|
||||
"panel.AnalyticsRouteViews": 50,
|
||||
"panel.AnalyticsAgentViews": 51,
|
||||
"panel.AnalyticsForumViews": 52,
|
||||
"panel.AnalyticsSystemViews": 53,
|
||||
"panel.AnalyticsLanguageViews": 54,
|
||||
"panel.AnalyticsReferrerViews": 55,
|
||||
"panel.AnalyticsPosts": 56,
|
||||
"panel.AnalyticsTopics": 57,
|
||||
"panel.AnalyticsForums": 58,
|
||||
"routePanelGroups": 59,
|
||||
"routePanelGroupsEdit": 60,
|
||||
"routePanelGroupsEditPerms": 61,
|
||||
"routePanelGroupsEditSubmit": 62,
|
||||
"routePanelGroupsEditPermsSubmit": 63,
|
||||
"routePanelGroupsCreateSubmit": 64,
|
||||
"routePanelBackups": 65,
|
||||
"routePanelLogsRegs": 66,
|
||||
"routePanelLogsMod": 67,
|
||||
"routePanelDebug": 68,
|
||||
"panel.Backups": 65,
|
||||
"panel.LogsRegs": 66,
|
||||
"panel.LogsMod": 67,
|
||||
"panel.Debug": 68,
|
||||
"routePanelDashboard": 69,
|
||||
"routes.AccountEditCritical": 70,
|
||||
"routes.AccountEditCriticalSubmit": 71,
|
||||
|
@ -212,8 +213,8 @@ var routeMapEnum = map[string]int{
|
|||
"routes.AccountEditAvatarSubmit": 73,
|
||||
"routes.AccountEditUsername": 74,
|
||||
"routes.AccountEditUsernameSubmit": 75,
|
||||
"routeAccountEditEmail": 76,
|
||||
"routeAccountEditEmailTokenSubmit": 77,
|
||||
"routes.AccountEditEmail": 76,
|
||||
"routes.AccountEditEmailTokenSubmit": 77,
|
||||
"routes.ViewProfile": 78,
|
||||
"routes.BanUserSubmit": 79,
|
||||
"routes.UnbanUser": 80,
|
||||
|
@ -259,21 +260,21 @@ var reverseRouteMapEnum = map[int]string{
|
|||
5: "routes.ChangeTheme",
|
||||
6: "routes.ShowAttachment",
|
||||
7: "common.RouteWebsockets",
|
||||
8: "routeReportSubmit",
|
||||
8: "routes.ReportSubmit",
|
||||
9: "routes.CreateTopic",
|
||||
10: "routes.TopicList",
|
||||
11: "routePanelForums",
|
||||
12: "routePanelForumsCreateSubmit",
|
||||
13: "routePanelForumsDelete",
|
||||
14: "routePanelForumsDeleteSubmit",
|
||||
15: "routePanelForumsEdit",
|
||||
16: "routePanelForumsEditSubmit",
|
||||
17: "routePanelForumsEditPermsSubmit",
|
||||
18: "routePanelForumsEditPermsAdvance",
|
||||
19: "routePanelForumsEditPermsAdvanceSubmit",
|
||||
20: "routePanelSettings",
|
||||
21: "routePanelSettingEdit",
|
||||
22: "routePanelSettingEditSubmit",
|
||||
11: "panel.Forums",
|
||||
12: "panel.ForumsCreateSubmit",
|
||||
13: "panel.ForumsDelete",
|
||||
14: "panel.ForumsDeleteSubmit",
|
||||
15: "panel.ForumsEdit",
|
||||
16: "panel.ForumsEditSubmit",
|
||||
17: "panel.ForumsEditPermsSubmit",
|
||||
18: "panel.ForumsEditPermsAdvance",
|
||||
19: "panel.ForumsEditPermsAdvanceSubmit",
|
||||
20: "panel.Settings",
|
||||
21: "panel.SettingEdit",
|
||||
22: "panel.SettingEditSubmit",
|
||||
23: "routePanelWordFilters",
|
||||
24: "routePanelWordFiltersCreateSubmit",
|
||||
25: "routePanelWordFiltersEdit",
|
||||
|
@ -295,31 +296,31 @@ var reverseRouteMapEnum = map[int]string{
|
|||
41: "routePanelUsers",
|
||||
42: "routePanelUsersEdit",
|
||||
43: "routePanelUsersEditSubmit",
|
||||
44: "routePanelAnalyticsViews",
|
||||
45: "routePanelAnalyticsRoutes",
|
||||
46: "routePanelAnalyticsAgents",
|
||||
47: "routePanelAnalyticsSystems",
|
||||
48: "routePanelAnalyticsLanguages",
|
||||
49: "routePanelAnalyticsReferrers",
|
||||
50: "routePanelAnalyticsRouteViews",
|
||||
51: "routePanelAnalyticsAgentViews",
|
||||
52: "routePanelAnalyticsForumViews",
|
||||
53: "routePanelAnalyticsSystemViews",
|
||||
54: "routePanelAnalyticsLanguageViews",
|
||||
55: "routePanelAnalyticsReferrerViews",
|
||||
56: "routePanelAnalyticsPosts",
|
||||
57: "routePanelAnalyticsTopics",
|
||||
58: "routePanelAnalyticsForums",
|
||||
44: "panel.AnalyticsViews",
|
||||
45: "panel.AnalyticsRoutes",
|
||||
46: "panel.AnalyticsAgents",
|
||||
47: "panel.AnalyticsSystems",
|
||||
48: "panel.AnalyticsLanguages",
|
||||
49: "panel.AnalyticsReferrers",
|
||||
50: "panel.AnalyticsRouteViews",
|
||||
51: "panel.AnalyticsAgentViews",
|
||||
52: "panel.AnalyticsForumViews",
|
||||
53: "panel.AnalyticsSystemViews",
|
||||
54: "panel.AnalyticsLanguageViews",
|
||||
55: "panel.AnalyticsReferrerViews",
|
||||
56: "panel.AnalyticsPosts",
|
||||
57: "panel.AnalyticsTopics",
|
||||
58: "panel.AnalyticsForums",
|
||||
59: "routePanelGroups",
|
||||
60: "routePanelGroupsEdit",
|
||||
61: "routePanelGroupsEditPerms",
|
||||
62: "routePanelGroupsEditSubmit",
|
||||
63: "routePanelGroupsEditPermsSubmit",
|
||||
64: "routePanelGroupsCreateSubmit",
|
||||
65: "routePanelBackups",
|
||||
66: "routePanelLogsRegs",
|
||||
67: "routePanelLogsMod",
|
||||
68: "routePanelDebug",
|
||||
65: "panel.Backups",
|
||||
66: "panel.LogsRegs",
|
||||
67: "panel.LogsMod",
|
||||
68: "panel.Debug",
|
||||
69: "routePanelDashboard",
|
||||
70: "routes.AccountEditCritical",
|
||||
71: "routes.AccountEditCriticalSubmit",
|
||||
|
@ -327,8 +328,8 @@ var reverseRouteMapEnum = map[int]string{
|
|||
73: "routes.AccountEditAvatarSubmit",
|
||||
74: "routes.AccountEditUsername",
|
||||
75: "routes.AccountEditUsernameSubmit",
|
||||
76: "routeAccountEditEmail",
|
||||
77: "routeAccountEditEmailTokenSubmit",
|
||||
76: "routes.AccountEditEmail",
|
||||
77: "routes.AccountEditEmailTokenSubmit",
|
||||
78: "routes.ViewProfile",
|
||||
79: "routes.BanUserSubmit",
|
||||
80: "routes.UnbanUser",
|
||||
|
@ -908,7 +909,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
|
||||
counters.RouteViewCounter.Bump(8)
|
||||
err = routeReportSubmit(w,req,user,extraData)
|
||||
err = routes.ReportSubmit(w,req,user,extraData)
|
||||
}
|
||||
if err != nil {
|
||||
router.handleError(err,w,req,user)
|
||||
|
@ -941,7 +942,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
switch(req.URL.Path) {
|
||||
case "/panel/forums/":
|
||||
counters.RouteViewCounter.Bump(11)
|
||||
err = routePanelForums(w,req,user)
|
||||
err = panel.Forums(w,req,user)
|
||||
case "/panel/forums/create/":
|
||||
err = common.NoSessionMismatch(w,req,user)
|
||||
if err != nil {
|
||||
|
@ -950,7 +951,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
|
||||
counters.RouteViewCounter.Bump(12)
|
||||
err = routePanelForumsCreateSubmit(w,req,user)
|
||||
err = panel.ForumsCreateSubmit(w,req,user)
|
||||
case "/panel/forums/delete/":
|
||||
err = common.NoSessionMismatch(w,req,user)
|
||||
if err != nil {
|
||||
|
@ -959,7 +960,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
|
||||
counters.RouteViewCounter.Bump(13)
|
||||
err = routePanelForumsDelete(w,req,user,extraData)
|
||||
err = panel.ForumsDelete(w,req,user,extraData)
|
||||
case "/panel/forums/delete/submit/":
|
||||
err = common.NoSessionMismatch(w,req,user)
|
||||
if err != nil {
|
||||
|
@ -968,10 +969,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
|
||||
counters.RouteViewCounter.Bump(14)
|
||||
err = routePanelForumsDeleteSubmit(w,req,user,extraData)
|
||||
err = panel.ForumsDeleteSubmit(w,req,user,extraData)
|
||||
case "/panel/forums/edit/":
|
||||
counters.RouteViewCounter.Bump(15)
|
||||
err = routePanelForumsEdit(w,req,user,extraData)
|
||||
err = panel.ForumsEdit(w,req,user,extraData)
|
||||
case "/panel/forums/edit/submit/":
|
||||
err = common.NoSessionMismatch(w,req,user)
|
||||
if err != nil {
|
||||
|
@ -980,7 +981,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
|
||||
counters.RouteViewCounter.Bump(16)
|
||||
err = routePanelForumsEditSubmit(w,req,user,extraData)
|
||||
err = panel.ForumsEditSubmit(w,req,user,extraData)
|
||||
case "/panel/forums/edit/perms/submit/":
|
||||
err = common.NoSessionMismatch(w,req,user)
|
||||
if err != nil {
|
||||
|
@ -989,10 +990,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
|
||||
counters.RouteViewCounter.Bump(17)
|
||||
err = routePanelForumsEditPermsSubmit(w,req,user,extraData)
|
||||
err = panel.ForumsEditPermsSubmit(w,req,user,extraData)
|
||||
case "/panel/forums/edit/perms/":
|
||||
counters.RouteViewCounter.Bump(18)
|
||||
err = routePanelForumsEditPermsAdvance(w,req,user,extraData)
|
||||
err = panel.ForumsEditPermsAdvance(w,req,user,extraData)
|
||||
case "/panel/forums/edit/perms/adv/submit/":
|
||||
err = common.NoSessionMismatch(w,req,user)
|
||||
if err != nil {
|
||||
|
@ -1001,13 +1002,13 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
|
||||
counters.RouteViewCounter.Bump(19)
|
||||
err = routePanelForumsEditPermsAdvanceSubmit(w,req,user,extraData)
|
||||
err = panel.ForumsEditPermsAdvanceSubmit(w,req,user,extraData)
|
||||
case "/panel/settings/":
|
||||
counters.RouteViewCounter.Bump(20)
|
||||
err = routePanelSettings(w,req,user)
|
||||
err = panel.Settings(w,req,user)
|
||||
case "/panel/settings/edit/":
|
||||
counters.RouteViewCounter.Bump(21)
|
||||
err = routePanelSettingEdit(w,req,user,extraData)
|
||||
err = panel.SettingEdit(w,req,user,extraData)
|
||||
case "/panel/settings/edit/submit/":
|
||||
err = common.NoSessionMismatch(w,req,user)
|
||||
if err != nil {
|
||||
|
@ -1016,7 +1017,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
|
||||
counters.RouteViewCounter.Bump(22)
|
||||
err = routePanelSettingEditSubmit(w,req,user,extraData)
|
||||
err = panel.SettingEditSubmit(w,req,user,extraData)
|
||||
case "/panel/settings/word-filters/":
|
||||
counters.RouteViewCounter.Bump(23)
|
||||
err = routePanelWordFilters(w,req,user)
|
||||
|
@ -1160,7 +1161,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
|
||||
counters.RouteViewCounter.Bump(44)
|
||||
err = routePanelAnalyticsViews(w,req,user)
|
||||
err = panel.AnalyticsViews(w,req,user)
|
||||
case "/panel/analytics/routes/":
|
||||
err = common.ParseForm(w,req,user)
|
||||
if err != nil {
|
||||
|
@ -1169,7 +1170,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
|
||||
counters.RouteViewCounter.Bump(45)
|
||||
err = routePanelAnalyticsRoutes(w,req,user)
|
||||
err = panel.AnalyticsRoutes(w,req,user)
|
||||
case "/panel/analytics/agents/":
|
||||
err = common.ParseForm(w,req,user)
|
||||
if err != nil {
|
||||
|
@ -1178,7 +1179,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
|
||||
counters.RouteViewCounter.Bump(46)
|
||||
err = routePanelAnalyticsAgents(w,req,user)
|
||||
err = panel.AnalyticsAgents(w,req,user)
|
||||
case "/panel/analytics/systems/":
|
||||
err = common.ParseForm(w,req,user)
|
||||
if err != nil {
|
||||
|
@ -1187,7 +1188,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
|
||||
counters.RouteViewCounter.Bump(47)
|
||||
err = routePanelAnalyticsSystems(w,req,user)
|
||||
err = panel.AnalyticsSystems(w,req,user)
|
||||
case "/panel/analytics/langs/":
|
||||
err = common.ParseForm(w,req,user)
|
||||
if err != nil {
|
||||
|
@ -1196,7 +1197,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
|
||||
counters.RouteViewCounter.Bump(48)
|
||||
err = routePanelAnalyticsLanguages(w,req,user)
|
||||
err = panel.AnalyticsLanguages(w,req,user)
|
||||
case "/panel/analytics/referrers/":
|
||||
err = common.ParseForm(w,req,user)
|
||||
if err != nil {
|
||||
|
@ -1205,25 +1206,25 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
|
||||
counters.RouteViewCounter.Bump(49)
|
||||
err = routePanelAnalyticsReferrers(w,req,user)
|
||||
err = panel.AnalyticsReferrers(w,req,user)
|
||||
case "/panel/analytics/route/":
|
||||
counters.RouteViewCounter.Bump(50)
|
||||
err = routePanelAnalyticsRouteViews(w,req,user,extraData)
|
||||
err = panel.AnalyticsRouteViews(w,req,user,extraData)
|
||||
case "/panel/analytics/agent/":
|
||||
counters.RouteViewCounter.Bump(51)
|
||||
err = routePanelAnalyticsAgentViews(w,req,user,extraData)
|
||||
err = panel.AnalyticsAgentViews(w,req,user,extraData)
|
||||
case "/panel/analytics/forum/":
|
||||
counters.RouteViewCounter.Bump(52)
|
||||
err = routePanelAnalyticsForumViews(w,req,user,extraData)
|
||||
err = panel.AnalyticsForumViews(w,req,user,extraData)
|
||||
case "/panel/analytics/system/":
|
||||
counters.RouteViewCounter.Bump(53)
|
||||
err = routePanelAnalyticsSystemViews(w,req,user,extraData)
|
||||
err = panel.AnalyticsSystemViews(w,req,user,extraData)
|
||||
case "/panel/analytics/lang/":
|
||||
counters.RouteViewCounter.Bump(54)
|
||||
err = routePanelAnalyticsLanguageViews(w,req,user,extraData)
|
||||
err = panel.AnalyticsLanguageViews(w,req,user,extraData)
|
||||
case "/panel/analytics/referrer/":
|
||||
counters.RouteViewCounter.Bump(55)
|
||||
err = routePanelAnalyticsReferrerViews(w,req,user,extraData)
|
||||
err = panel.AnalyticsReferrerViews(w,req,user,extraData)
|
||||
case "/panel/analytics/posts/":
|
||||
err = common.ParseForm(w,req,user)
|
||||
if err != nil {
|
||||
|
@ -1232,7 +1233,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
|
||||
counters.RouteViewCounter.Bump(56)
|
||||
err = routePanelAnalyticsPosts(w,req,user)
|
||||
err = panel.AnalyticsPosts(w,req,user)
|
||||
case "/panel/analytics/topics/":
|
||||
err = common.ParseForm(w,req,user)
|
||||
if err != nil {
|
||||
|
@ -1241,7 +1242,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
|
||||
counters.RouteViewCounter.Bump(57)
|
||||
err = routePanelAnalyticsTopics(w,req,user)
|
||||
err = panel.AnalyticsTopics(w,req,user)
|
||||
case "/panel/analytics/forums/":
|
||||
err = common.ParseForm(w,req,user)
|
||||
if err != nil {
|
||||
|
@ -1250,7 +1251,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
|
||||
counters.RouteViewCounter.Bump(58)
|
||||
err = routePanelAnalyticsForums(w,req,user)
|
||||
err = panel.AnalyticsForums(w,req,user)
|
||||
case "/panel/groups/":
|
||||
counters.RouteViewCounter.Bump(59)
|
||||
err = routePanelGroups(w,req,user)
|
||||
|
@ -1295,13 +1296,13 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
|
||||
counters.RouteViewCounter.Bump(65)
|
||||
err = routePanelBackups(w,req,user,extraData)
|
||||
err = panel.Backups(w,req,user,extraData)
|
||||
case "/panel/logs/regs/":
|
||||
counters.RouteViewCounter.Bump(66)
|
||||
err = routePanelLogsRegs(w,req,user)
|
||||
err = panel.LogsRegs(w,req,user)
|
||||
case "/panel/logs/mod/":
|
||||
counters.RouteViewCounter.Bump(67)
|
||||
err = routePanelLogsMod(w,req,user)
|
||||
err = panel.LogsMod(w,req,user)
|
||||
case "/panel/debug/":
|
||||
err = common.AdminOnly(w,req,user)
|
||||
if err != nil {
|
||||
|
@ -1310,7 +1311,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
|
||||
counters.RouteViewCounter.Bump(68)
|
||||
err = routePanelDebug(w,req,user)
|
||||
err = panel.Debug(w,req,user)
|
||||
default:
|
||||
counters.RouteViewCounter.Bump(69)
|
||||
err = routePanelDashboard(w,req,user)
|
||||
|
@ -1405,7 +1406,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
|
||||
counters.RouteViewCounter.Bump(76)
|
||||
err = routeAccountEditEmail(w,req,user)
|
||||
err = routes.AccountEditEmail(w,req,user)
|
||||
case "/user/edit/token/":
|
||||
err = common.NoSessionMismatch(w,req,user)
|
||||
if err != nil {
|
||||
|
@ -1420,7 +1421,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
|
||||
counters.RouteViewCounter.Bump(77)
|
||||
err = routeAccountEditEmailTokenSubmit(w,req,user,extraData)
|
||||
err = routes.AccountEditEmailTokenSubmit(w,req,user,extraData)
|
||||
default:
|
||||
req.URL.Path += extraData
|
||||
counters.RouteViewCounter.Bump(78)
|
||||
|
|
|
@ -2,18 +2,20 @@
|
|||
package main
|
||||
|
||||
var dbTablePrimaryKeys = map[string]string{
|
||||
"topics":"tid",
|
||||
"attachments":"attachID",
|
||||
"menus":"mid",
|
||||
"users_groups":"gid",
|
||||
"users_groups_scheduler":"uid",
|
||||
"registration_logs":"rlid",
|
||||
"word_filters":"wfid",
|
||||
"menu_items":"miid",
|
||||
"polls":"pollID",
|
||||
"users_replies":"rid",
|
||||
"topics":"tid",
|
||||
"activity_stream":"asid",
|
||||
"pages":"pid",
|
||||
"replies":"rid",
|
||||
"revisions":"reviseID",
|
||||
"activity_stream":"asid",
|
||||
"word_filters":"wfid",
|
||||
"menus":"mid",
|
||||
"users":"uid",
|
||||
"menu_items":"miid",
|
||||
"forums":"fid",
|
||||
"attachments":"attachID",
|
||||
"polls":"pollID",
|
||||
}
|
||||
|
|
|
@ -53,7 +53,7 @@ func gloinit() (err error) {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = common.InitThemes()
|
||||
common.Themes, err = common.NewThemeList()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -10,6 +10,9 @@ go get -u github.com/denisenkom/go-mssqldb
|
|||
echo "Installing bcrypt"
|
||||
go get -u golang.org/x/crypto/bcrypt
|
||||
|
||||
echo "Installing Argon2"
|
||||
go get -u golang.org/x/crypto/argon2
|
||||
|
||||
echo "Installing gopsutil"
|
||||
go get -u github.com/Azareal/gopsutil
|
||||
|
||||
|
|
|
@ -29,6 +29,13 @@ if %errorlevel% neq 0 (
|
|||
exit /b %errorlevel%
|
||||
)
|
||||
|
||||
echo Installing the Argon2 library
|
||||
go get -u golang.org/x/crypto/argon2
|
||||
if %errorlevel% neq 0 (
|
||||
pause
|
||||
exit /b %errorlevel%
|
||||
)
|
||||
|
||||
echo Installing /x/sys/windows (dependency for gopsutil)
|
||||
go get -u golang.org/x/sys/windows
|
||||
if %errorlevel% neq 0 (
|
||||
|
|
|
@ -13,28 +13,15 @@ func GenerateSafeString(length int) (string, error) {
|
|||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.URLEncoding.EncodeToString(rb), nil
|
||||
return base64.StdEncoding.EncodeToString(rb), nil
|
||||
}
|
||||
|
||||
// Generate a bcrypt hash from a password and a salt
|
||||
func BcryptGeneratePassword(password string) (hashedPassword string, salt string, err error) {
|
||||
salt, err = GenerateSafeString(saltLength)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
password = password + salt
|
||||
hashedPassword, err = bcryptGeneratePasswordNoSalt(password)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return hashedPassword, salt, nil
|
||||
}
|
||||
|
||||
func bcryptGeneratePasswordNoSalt(password string) (hash string, err error) {
|
||||
// Generate a bcrypt hash
|
||||
// Note: The salt is in the hash, therefore the salt value is blank
|
||||
func bcryptGeneratePassword(password string) (hash string, salt string, err error) {
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", err
|
||||
}
|
||||
return string(hashedPassword), nil
|
||||
return string(hashedPassword), salt, nil
|
||||
}
|
||||
|
|
|
@ -44,8 +44,12 @@
|
|||
"MoveTopic": "Can move topics in or out"
|
||||
},
|
||||
|
||||
"SettingLabels": {
|
||||
"activation_type": "Activate All,Email Activation,Admin Approval"
|
||||
"SettingPhrases": {
|
||||
"activation_type":"Activation Type",
|
||||
"activation_type_label": "Activate All,Email Activation,Admin Approval",
|
||||
"bigpost_min_words":"Big Post Minimum Words",
|
||||
"megapost_min_words":"Mega Post Minimum Words",
|
||||
"meta_desc":"Meta Description"
|
||||
},
|
||||
|
||||
"PermPresets": {
|
||||
|
@ -263,6 +267,7 @@
|
|||
"topics_click_topics_to_select":"Click the topics to select them",
|
||||
"topics_new_topic":"New Topic",
|
||||
"forum_locked":"Locked",
|
||||
"topics_moderate":"Moderate",
|
||||
"topics_replies_suffix":" replies",
|
||||
"forums_topics_suffix":" topics",
|
||||
"topics_gap_likes_suffix":" likes",
|
||||
|
|
39
main.go
39
main.go
|
@ -29,7 +29,6 @@ import (
|
|||
|
||||
var version = common.Version{Major: 0, Minor: 1, Patch: 0, Tag: "dev"}
|
||||
var router *GenRouter
|
||||
var startTime time.Time
|
||||
var logWriter = io.MultiWriter(os.Stderr)
|
||||
|
||||
// TODO: Wrap the globals in here so we can pass pointers to them to subpackages
|
||||
|
@ -107,6 +106,14 @@ func afterDBInit() (err error) {
|
|||
}
|
||||
|
||||
log.Print("Initialising the stores")
|
||||
common.Reports, err = common.NewDefaultReportStore(acc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
common.Emails, err = common.NewDefaultEmailStore(acc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
common.RegLogs, err = common.NewRegLogStore(acc)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -140,7 +147,8 @@ func afterDBInit() (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
counters.GlobalViewCounter, err = counters.NewGlobalViewCounter()
|
||||
log.Print("Initialising the view counters")
|
||||
counters.GlobalViewCounter, err = counters.NewGlobalViewCounter(acc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -195,18 +203,6 @@ func main() {
|
|||
return
|
||||
}
|
||||
}()*/
|
||||
|
||||
// WIP: Mango Test
|
||||
/*res, err := ioutil.ReadFile("./templates/topic.html")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
tagIndices, err := mangoParse(string(res))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Printf("tagIndices: %+v\n", tagIndices)
|
||||
log.Fatal("")*/
|
||||
config.Config()
|
||||
|
||||
// TODO: Have a file for each run with the time/date the server started as the file name?
|
||||
|
@ -217,18 +213,9 @@ func main() {
|
|||
}
|
||||
logWriter = io.MultiWriter(os.Stderr, f)
|
||||
log.SetOutput(logWriter)
|
||||
|
||||
//if profiling {
|
||||
// f, err := os.Create("startup_cpu.prof")
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
// pprof.StartCPUProfile(f)
|
||||
//}
|
||||
|
||||
log.Print("Running Gosora v" + version.String())
|
||||
fmt.Println("")
|
||||
startTime = time.Now()
|
||||
common.StartTime = time.Now()
|
||||
|
||||
log.Print("Processing configuration data")
|
||||
err = common.ProcessConfig()
|
||||
|
@ -236,7 +223,7 @@ func main() {
|
|||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = common.InitThemes()
|
||||
common.Themes, err = common.NewThemeList()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
@ -271,6 +258,7 @@ func main() {
|
|||
log.Fatal(err)
|
||||
}
|
||||
|
||||
log.Print("Initialising the file watcher")
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
|
@ -337,6 +325,7 @@ func main() {
|
|||
}
|
||||
}
|
||||
|
||||
log.Print("Initialising the task system")
|
||||
var runTasks = func(tasks []func() error) {
|
||||
for _, task := range tasks {
|
||||
if task() != nil {
|
||||
|
|
217
member_routes.go
217
member_routes.go
|
@ -1,217 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"./common"
|
||||
"./common/counters"
|
||||
)
|
||||
|
||||
func routeReportSubmit(w http.ResponseWriter, r *http.Request, user common.User, sitemID string) common.RouteError {
|
||||
itemID, err := strconv.Atoi(sitemID)
|
||||
if err != nil {
|
||||
return common.LocalError("Bad ID", w, r, user)
|
||||
}
|
||||
itemType := r.FormValue("type")
|
||||
|
||||
var fid = 1
|
||||
var title, content string
|
||||
if itemType == "reply" {
|
||||
reply, err := common.Rstore.Get(itemID)
|
||||
if err == ErrNoRows {
|
||||
return common.LocalError("We were unable to find the reported post", w, r, user)
|
||||
} else if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
topic, err := common.Topics.Get(reply.ParentID)
|
||||
if err == ErrNoRows {
|
||||
return common.LocalError("We weren't able to find the topic the reported post is supposed to be in", w, r, user)
|
||||
} else if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
title = "Reply: " + topic.Title
|
||||
content = reply.Content + "\n\nOriginal Post: #rid-" + strconv.Itoa(itemID)
|
||||
} else if itemType == "user-reply" {
|
||||
userReply, err := common.Prstore.Get(itemID)
|
||||
if err == ErrNoRows {
|
||||
return common.LocalError("We weren't able to find the reported post", w, r, user)
|
||||
} else if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
profileOwner, err := common.Users.Get(userReply.ParentID)
|
||||
if err == ErrNoRows {
|
||||
return common.LocalError("We weren't able to find the profile the reported post is supposed to be on", w, r, user)
|
||||
} else if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
title = "Profile: " + profileOwner.Name
|
||||
content = userReply.Content + "\n\nOriginal Post: @" + strconv.Itoa(userReply.ParentID)
|
||||
} else if itemType == "topic" {
|
||||
err = stmts.getTopicBasic.QueryRow(itemID).Scan(&title, &content)
|
||||
if err == ErrNoRows {
|
||||
return common.NotFound(w, r, nil)
|
||||
} else if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
title = "Topic: " + title
|
||||
content = content + "\n\nOriginal Post: #tid-" + strconv.Itoa(itemID)
|
||||
} else {
|
||||
_, hasHook := common.RunVhookNeedHook("report_preassign", &itemID, &itemType)
|
||||
if hasHook {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Don't try to guess the type
|
||||
return common.LocalError("Unknown type", w, r, user)
|
||||
}
|
||||
|
||||
var count int
|
||||
err = stmts.reportExists.QueryRow(itemType + "_" + strconv.Itoa(itemID)).Scan(&count)
|
||||
if err != nil && err != ErrNoRows {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
if count != 0 {
|
||||
return common.LocalError("Someone has already reported this!", w, r, user)
|
||||
}
|
||||
|
||||
// TODO: Repost attachments in the reports forum, so that the mods can see them
|
||||
// ? - Can we do this via the TopicStore? Should we do a ReportStore?
|
||||
res, err := stmts.createReport.Exec(title, content, common.ParseMessage(content, 0, ""), user.ID, user.ID, itemType+"_"+strconv.Itoa(itemID))
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
lastID, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
err = common.Forums.AddTopic(int(lastID), user.ID, fid)
|
||||
if err != nil && err != ErrNoRows {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
counters.PostCounter.Bump()
|
||||
|
||||
http.Redirect(w, r, "/topic/"+strconv.FormatInt(lastID, 10), http.StatusSeeOther)
|
||||
return nil
|
||||
}
|
||||
|
||||
func routeAccountEditEmail(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
|
||||
headerVars, ferr := common.UserCheck(w, r, &user)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
|
||||
email := common.Email{UserID: user.ID}
|
||||
var emailList []interface{}
|
||||
rows, err := stmts.getEmailsByUser.Query(user.ID)
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
err := rows.Scan(&email.Email, &email.Validated, &email.Token)
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
if email.Email == user.Email {
|
||||
email.Primary = true
|
||||
}
|
||||
emailList = append(emailList, email)
|
||||
}
|
||||
err = rows.Err()
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
// Was this site migrated from another forum software? Most of them don't have multiple emails for a single user.
|
||||
// This also applies when the admin switches site.EnableEmails on after having it off for a while.
|
||||
if len(emailList) == 0 {
|
||||
email.Email = user.Email
|
||||
email.Validated = false
|
||||
email.Primary = true
|
||||
emailList = append(emailList, email)
|
||||
}
|
||||
|
||||
if !common.Site.EnableEmails {
|
||||
headerVars.NoticeList = append(headerVars.NoticeList, common.GetNoticePhrase("account_mail_disabled"))
|
||||
}
|
||||
if r.FormValue("verified") == "1" {
|
||||
headerVars.NoticeList = append(headerVars.NoticeList, common.GetNoticePhrase("account_mail_verify_success"))
|
||||
}
|
||||
|
||||
pi := common.Page{"Email Manager", user, headerVars, emailList, nil}
|
||||
if common.RunPreRenderHook("pre_render_account_own_edit_email", w, r, &user, &pi) {
|
||||
return nil
|
||||
}
|
||||
err = common.Templates.ExecuteTemplate(w, "account_own_edit_email.html", pi)
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: Do a session check on this?
|
||||
func routeAccountEditEmailTokenSubmit(w http.ResponseWriter, r *http.Request, user common.User, token string) common.RouteError {
|
||||
headerVars, ferr := common.UserCheck(w, r, &user)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
|
||||
email := common.Email{UserID: user.ID}
|
||||
targetEmail := common.Email{UserID: user.ID}
|
||||
var emailList []interface{}
|
||||
rows, err := stmts.getEmailsByUser.Query(user.ID)
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
err := rows.Scan(&email.Email, &email.Validated, &email.Token)
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
if email.Email == user.Email {
|
||||
email.Primary = true
|
||||
}
|
||||
if email.Token == token {
|
||||
targetEmail = email
|
||||
}
|
||||
emailList = append(emailList, email)
|
||||
}
|
||||
err = rows.Err()
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
if len(emailList) == 0 {
|
||||
return common.LocalError("A verification email was never sent for you!", w, r, user)
|
||||
}
|
||||
if targetEmail.Token == "" {
|
||||
return common.LocalError("That's not a valid token!", w, r, user)
|
||||
}
|
||||
|
||||
_, err = stmts.verifyEmail.Exec(user.Email)
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
// If Email Activation is on, then activate the account while we're here
|
||||
if headerVars.Settings["activation_type"] == 2 {
|
||||
err = user.Activate()
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
}
|
||||
http.Redirect(w, r, "/user/edit/email/?verified=1", http.StatusSeeOther)
|
||||
|
||||
return nil
|
||||
}
|
|
@ -866,7 +866,7 @@ func TestAuth(t *testing.T) {
|
|||
realPassword = "Madame Cassandra's Mystic Orb"
|
||||
t.Logf("Set realPassword to '%s'", realPassword)
|
||||
t.Log("Hashing the real password")
|
||||
hashedPassword, err = common.BcryptGeneratePasswordNoSalt(realPassword)
|
||||
hashedPassword, err = common.BcryptGeneratePassword(realPassword)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
|
1493
panel_routes.go
1493
panel_routes.go
File diff suppressed because it is too large
Load Diff
|
@ -12,6 +12,7 @@ func init() {
|
|||
addPatch(1, patch1)
|
||||
addPatch(2, patch2)
|
||||
addPatch(3, patch3)
|
||||
addPatch(4, patch4)
|
||||
}
|
||||
|
||||
func patch0(scanner *bufio.Scanner) (err error) {
|
||||
|
@ -235,3 +236,214 @@ func patch3(scanner *bufio.Scanner) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func patch4(scanner *bufio.Scanner) error {
|
||||
// ! Don't reuse this function blindly, it doesn't escape apostrophes
|
||||
var replaceTextWhere = func(replaceThis string, withThis string) error {
|
||||
return execStmt(qgen.Builder.SimpleUpdate("viewchunks", "route = '"+withThis+"'", "route = '"+replaceThis+"'"))
|
||||
}
|
||||
|
||||
err := replaceTextWhere("routeReportSubmit", "routes.ReportSubmit")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = replaceTextWhere("routeAccountEditEmail", "routes.AccountEditEmail")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = replaceTextWhere("routeAccountEditEmailTokenSubmit", "routes.AccountEditEmailTokenSubmit")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = replaceTextWhere("routePanelLogsRegs", "panel.LogsRegs")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = replaceTextWhere("routePanelLogsMod", "panel.LogsMod")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = replaceTextWhere("routePanelLogsAdmin", "panel.LogsAdmin")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = replaceTextWhere("routePanelDebug", "panel.Debug")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = replaceTextWhere("routePanelAnalyticsViews", "panel.AnalyticsViews")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = replaceTextWhere("routePanelAnalyticsRouteViews", "panel.AnalyticsRouteViews")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = replaceTextWhere("routePanelAnalyticsRouteViews", "panel.AnalyticsRouteViews")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = replaceTextWhere("routePanelAnalyticsAgentViews", "panel.AnalyticsAgentViews")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = replaceTextWhere("routePanelAnalyticsForumViews", "panel.AnalyticsForumViews")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = replaceTextWhere("routePanelAnalyticsSystemViews", "panel.AnalyticsSystemViews")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = replaceTextWhere("routePanelAnalyticsLanguageViews", "panel.AnalyticsLanguageViews")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = replaceTextWhere("routePanelAnalyticsReferrerViews", "panel.AnalyticsReferrerViews")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = replaceTextWhere("routePanelAnalyticsTopics", "panel.AnalyticsTopics")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = replaceTextWhere("routePanelAnalyticsPosts", "panel.AnalyticsPosts")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = replaceTextWhere("routePanelAnalyticsForums", "panel.AnalyticsForums")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = replaceTextWhere("routePanelAnalyticsRoutes", "panel.AnalyticsRoutes")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = replaceTextWhere("routePanelAnalyticsAgents", "panel.AnalyticsAgents")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = replaceTextWhere("routePanelAnalyticsSystems", "panel.AnalyticsSystems")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = replaceTextWhere("routePanelAnalyticsLanguages", "panel.AnalyticsLanguages")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = replaceTextWhere("routePanelAnalyticsReferrers", "panel.AnalyticsReferrers")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = replaceTextWhere("routePanelSettings", "panel.Settings")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = replaceTextWhere("routePanelSettingEdit", "panel.SettingEdit")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = replaceTextWhere("routePanelSettingEditSubmit", "panel.SettingEditSubmit")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = replaceTextWhere("routePanelForums", "panel.Forums")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = replaceTextWhere("routePanelForumsCreateSubmit", "panel.ForumsCreateSubmit")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = replaceTextWhere("routePanelForumsDelete", "panel.ForumsDelete")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = replaceTextWhere("routePanelForumsDeleteSubmit", "panel.ForumsDeleteSubmit")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = replaceTextWhere("routePanelForumsEdit", "panel.ForumsEdit")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = replaceTextWhere("routePanelForumsEditSubmit", "panel.ForumsEditSubmit")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = replaceTextWhere("routePanelForumsEditPermsSubmit", "panel.ForumsEditPermsSubmit")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = replaceTextWhere("routePanelForumsEditPermsAdvance", "panel.ForumsEditPermsAdvance")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = replaceTextWhere("routePanelForumsEditPermsAdvanceSubmit", "panel.ForumsEditPermsAdvanceSubmit")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = replaceTextWhere("routePanelBackups", "panel.Backups")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = execStmt(qgen.Builder.SimpleDelete("settings", "name='url_tags'"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = execStmt(qgen.Builder.CreateTable("pages", "utf8mb4", "utf8mb4_general_ci",
|
||||
[]qgen.DBTableColumn{
|
||||
qgen.DBTableColumn{"pid", "int", 0, false, true, ""},
|
||||
qgen.DBTableColumn{"name", "varchar", 200, false, false, ""},
|
||||
qgen.DBTableColumn{"title", "varchar", 200, false, false, ""},
|
||||
qgen.DBTableColumn{"body", "text", 0, false, false, ""},
|
||||
qgen.DBTableColumn{"allowedGroups", "text", 0, false, false, ""},
|
||||
qgen.DBTableColumn{"menuID", "int", 0, false, false, "-1"},
|
||||
},
|
||||
[]qgen.DBTableKey{
|
||||
qgen.DBTableKey{"pid", "primary"},
|
||||
},
|
||||
))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -547,9 +547,10 @@ $(document).ready(function(){
|
|||
uploadFiles.addEventListener("change", uploadFileHandler, false);
|
||||
}
|
||||
|
||||
$(".moderate_link").click(function(event) {
|
||||
$(".moderate_link").click((event) => {
|
||||
event.preventDefault();
|
||||
$(".pre_opt").removeClass("auto_hide");
|
||||
$(".moderate_link").addClass("moderate_open");
|
||||
$(".topic_row").each(function(){
|
||||
$(this).click(function(){
|
||||
selectedTopics.push(parseInt($(this).attr("data-tid"),10));
|
||||
|
|
|
@ -104,6 +104,10 @@ func (build *builder) CreateTable(table string, charset string, collation string
|
|||
return build.prepare(build.adapter.CreateTable("_builder", table, charset, collation, columns, keys))
|
||||
}
|
||||
|
||||
func (build *builder) AddColumn(table string, column DBTableColumn) (stmt *sql.Stmt, err error) {
|
||||
return build.prepare(build.adapter.AddColumn("_builder", table, column))
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
|
|
@ -71,53 +71,7 @@ func (adapter *MssqlAdapter) CreateTable(name string, table string, charset stri
|
|||
|
||||
var querystr = "CREATE TABLE [" + table + "] ("
|
||||
for _, column := range columns {
|
||||
var max bool
|
||||
var createdAt bool
|
||||
switch column.Type {
|
||||
case "createdAt":
|
||||
column.Type = "datetime"
|
||||
createdAt = true
|
||||
case "varchar":
|
||||
column.Type = "nvarchar"
|
||||
case "text":
|
||||
column.Type = "nvarchar"
|
||||
max = true
|
||||
case "json":
|
||||
column.Type = "nvarchar"
|
||||
max = true
|
||||
case "boolean":
|
||||
column.Type = "bit"
|
||||
}
|
||||
|
||||
var size string
|
||||
if column.Size > 0 {
|
||||
size = " (" + strconv.Itoa(column.Size) + ")"
|
||||
}
|
||||
if max {
|
||||
size = " (MAX)"
|
||||
}
|
||||
|
||||
var end string
|
||||
if column.Default != "" {
|
||||
end = " DEFAULT "
|
||||
if createdAt {
|
||||
end += "GETUTCDATE()" // TODO: Use GETUTCDATE() in updates instead of the neutral format
|
||||
} else if adapter.stringyType(column.Type) && column.Default != "''" {
|
||||
end += "'" + column.Default + "'"
|
||||
} else {
|
||||
end += column.Default
|
||||
}
|
||||
}
|
||||
|
||||
if !column.Null {
|
||||
end += " not null"
|
||||
}
|
||||
|
||||
// ! Not exactly the meaning of auto increment...
|
||||
if column.AutoIncrement {
|
||||
end += " IDENTITY"
|
||||
}
|
||||
|
||||
column, size, end := adapter.parseColumn(column)
|
||||
querystr += "\n\t[" + column.Name + "] " + column.Type + size + end + ","
|
||||
}
|
||||
|
||||
|
@ -140,6 +94,67 @@ func (adapter *MssqlAdapter) CreateTable(name string, table string, charset stri
|
|||
return querystr, nil
|
||||
}
|
||||
|
||||
func (adapter *MssqlAdapter) parseColumn(column DBTableColumn) (col DBTableColumn, size string, end string) {
|
||||
var max, createdAt bool
|
||||
switch column.Type {
|
||||
case "createdAt":
|
||||
column.Type = "datetime"
|
||||
createdAt = true
|
||||
case "varchar":
|
||||
column.Type = "nvarchar"
|
||||
case "text":
|
||||
column.Type = "nvarchar"
|
||||
max = true
|
||||
case "json":
|
||||
column.Type = "nvarchar"
|
||||
max = true
|
||||
case "boolean":
|
||||
column.Type = "bit"
|
||||
}
|
||||
|
||||
if column.Size > 0 {
|
||||
size = " (" + strconv.Itoa(column.Size) + ")"
|
||||
}
|
||||
if max {
|
||||
size = " (MAX)"
|
||||
}
|
||||
|
||||
if column.Default != "" {
|
||||
end = " DEFAULT "
|
||||
if createdAt {
|
||||
end += "GETUTCDATE()" // TODO: Use GETUTCDATE() in updates instead of the neutral format
|
||||
} else if adapter.stringyType(column.Type) && column.Default != "''" {
|
||||
end += "'" + column.Default + "'"
|
||||
} else {
|
||||
end += column.Default
|
||||
}
|
||||
}
|
||||
if !column.Null {
|
||||
end += " not null"
|
||||
}
|
||||
|
||||
// ! Not exactly the meaning of auto increment...
|
||||
if column.AutoIncrement {
|
||||
end += " IDENTITY"
|
||||
}
|
||||
return column, size, end
|
||||
}
|
||||
|
||||
// TODO: Test this, not sure if some things work
|
||||
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 == "" {
|
||||
return "", errors.New("You need a name for this table")
|
||||
}
|
||||
|
||||
column, size, end := adapter.parseColumn(column)
|
||||
querystr := "ALTER TABLE [" + table + "] ADD [" + column.Name + "] " + column.Type + size + end + ";"
|
||||
adapter.pushStatement(name, "add-column", querystr)
|
||||
return querystr, nil
|
||||
}
|
||||
|
||||
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")
|
||||
|
|
|
@ -86,39 +86,7 @@ func (adapter *MysqlAdapter) CreateTable(name string, table string, charset stri
|
|||
|
||||
var querystr = "CREATE TABLE `" + table + "` ("
|
||||
for _, column := range columns {
|
||||
// Make it easier to support Cassandra in the future
|
||||
if column.Type == "createdAt" {
|
||||
column.Type = "datetime"
|
||||
} else if column.Type == "json" {
|
||||
column.Type = "text"
|
||||
}
|
||||
|
||||
var size string
|
||||
if column.Size > 0 {
|
||||
size = "(" + strconv.Itoa(column.Size) + ")"
|
||||
}
|
||||
|
||||
var end string
|
||||
// TODO: Exclude the other variants of text like mediumtext and longtext too
|
||||
if column.Default != "" && column.Type != "text" {
|
||||
end = " DEFAULT "
|
||||
if adapter.stringyType(column.Type) && column.Default != "''" {
|
||||
end += "'" + column.Default + "'"
|
||||
} else {
|
||||
end += column.Default
|
||||
}
|
||||
}
|
||||
|
||||
if column.Null {
|
||||
end += " null"
|
||||
} else {
|
||||
end += " not null"
|
||||
}
|
||||
|
||||
if column.AutoIncrement {
|
||||
end += " AUTO_INCREMENT"
|
||||
}
|
||||
|
||||
column, size, end := adapter.parseColumn(column)
|
||||
querystr += "\n\t`" + column.Name + "` " + column.Type + size + end + ","
|
||||
}
|
||||
|
||||
|
@ -148,6 +116,54 @@ func (adapter *MysqlAdapter) CreateTable(name string, table string, charset stri
|
|||
return querystr + ";", nil
|
||||
}
|
||||
|
||||
func (adapter *MysqlAdapter) parseColumn(column DBTableColumn) (col DBTableColumn, size string, end string) {
|
||||
// Make it easier to support Cassandra in the future
|
||||
if column.Type == "createdAt" {
|
||||
column.Type = "datetime"
|
||||
} else if column.Type == "json" {
|
||||
column.Type = "text"
|
||||
}
|
||||
if column.Size > 0 {
|
||||
size = "(" + strconv.Itoa(column.Size) + ")"
|
||||
}
|
||||
|
||||
// TODO: Exclude the other variants of text like mediumtext and longtext too
|
||||
if column.Default != "" && column.Type != "text" {
|
||||
end = " DEFAULT "
|
||||
if adapter.stringyType(column.Type) && column.Default != "''" {
|
||||
end += "'" + column.Default + "'"
|
||||
} else {
|
||||
end += column.Default
|
||||
}
|
||||
}
|
||||
|
||||
if column.Null {
|
||||
end += " null"
|
||||
} else {
|
||||
end += " not null"
|
||||
}
|
||||
if column.AutoIncrement {
|
||||
end += " AUTO_INCREMENT"
|
||||
}
|
||||
return column, size, end
|
||||
}
|
||||
|
||||
// TODO: Support AFTER column
|
||||
// TODO: Test to make sure everything works here
|
||||
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 == "" {
|
||||
return "", errors.New("You need a name for this table")
|
||||
}
|
||||
|
||||
column, size, end := adapter.parseColumn(column)
|
||||
querystr := "ALTER TABLE `" + table + "` ADD COLUMN " + "`" + column.Name + "` " + column.Type + size + end + ";"
|
||||
adapter.pushStatement(name, "add-column", querystr)
|
||||
return querystr, nil
|
||||
}
|
||||
|
||||
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")
|
||||
|
|
|
@ -119,7 +119,7 @@ func (adapter *PgsqlAdapter) CreateTable(name string, table string, charset stri
|
|||
}
|
||||
|
||||
// TODO: Implement this
|
||||
func (adapter *PgsqlAdapter) SimpleInsert(name string, table string, columns string, fields string) (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")
|
||||
}
|
||||
|
@ -129,6 +129,54 @@ func (adapter *PgsqlAdapter) SimpleInsert(name string, table string, columns str
|
|||
return "", nil
|
||||
}
|
||||
|
||||
// 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
|
||||
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 == "" {
|
||||
return "", errors.New("You need a name for this table")
|
||||
}
|
||||
|
||||
var querystr = "INSERT INTO \"" + table + "\"("
|
||||
if columns != "" {
|
||||
querystr += adapter.buildColumns(columns) + ") VALUES ("
|
||||
for _, field := range processFields(fields) {
|
||||
nameLen := len(field.Name)
|
||||
if field.Name[0] == '"' && field.Name[nameLen-1] == '"' && nameLen >= 3 {
|
||||
field.Name = "'" + field.Name[1:nameLen-1] + "'"
|
||||
}
|
||||
if field.Name[0] == '\'' && field.Name[nameLen-1] == '\'' && nameLen >= 3 {
|
||||
field.Name = "'" + strings.Replace(field.Name[1:nameLen-1], "'", "''", -1) + "'"
|
||||
}
|
||||
querystr += field.Name + ","
|
||||
}
|
||||
querystr = querystr[0 : len(querystr)-1]
|
||||
} else {
|
||||
querystr += ") VALUES ("
|
||||
}
|
||||
querystr += ")"
|
||||
|
||||
adapter.pushStatement(name, "insert", querystr)
|
||||
return querystr, nil
|
||||
}
|
||||
|
||||
func (adapter *PgsqlAdapter) buildColumns(columns string) (querystr string) {
|
||||
if columns == "" {
|
||||
return ""
|
||||
}
|
||||
// Escape the column names, just in case we've used a reserved keyword
|
||||
for _, column := range processColumns(columns) {
|
||||
if column.Type == "function" {
|
||||
querystr += column.Left + ","
|
||||
} else {
|
||||
querystr += "\"" + column.Left + "\","
|
||||
}
|
||||
}
|
||||
return querystr[0 : len(querystr)-1]
|
||||
}
|
||||
|
||||
// TODO: Implement this
|
||||
func (adapter *PgsqlAdapter) SimpleReplace(name string, table string, columns string, fields string) (string, error) {
|
||||
if name == "" {
|
||||
|
|
|
@ -106,6 +106,9 @@ type Adapter interface {
|
|||
|
||||
DropTable(name string, table string) (string, error)
|
||||
CreateTable(name string, table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) (string, error)
|
||||
// TODO: Some way to add indices and keys
|
||||
// TODO: Test this
|
||||
AddColumn(name string, table string, column DBTableColumn) (string, error)
|
||||
SimpleInsert(name string, table string, columns string, fields string) (string, error)
|
||||
SimpleUpdate(name string, table string, set string, where string) (string, error)
|
||||
SimpleDelete(name string, table string, where string) (string, error)
|
||||
|
|
|
@ -111,8 +111,6 @@ func writeStatements(adapter qgen.Adapter) error {
|
|||
|
||||
func seedTables(adapter qgen.Adapter) error {
|
||||
qgen.Install.SimpleInsert("sync", "last_update", "UTC_TIMESTAMP()")
|
||||
|
||||
qgen.Install.SimpleInsert("settings", "name, content, type", "'url_tags','1','bool'")
|
||||
qgen.Install.SimpleInsert("settings", "name, content, type, constraints", "'activation_type','1','list','1-3'")
|
||||
qgen.Install.SimpleInsert("settings", "name, content, type", "'bigpost_min_words','250','int'")
|
||||
qgen.Install.SimpleInsert("settings", "name, content, type", "'megapost_min_words','1000','int'")
|
||||
|
@ -261,14 +259,8 @@ func writeSelects(adapter qgen.Adapter) error {
|
|||
|
||||
//build.Select("isPluginInstalled").Table("plugins").Columns("installed").Where("uname = ?").Parse()
|
||||
|
||||
build.Select("getUsersOffset").Table("users").Columns("uid, name, group, active, is_super_admin, avatar").Orderby("uid ASC").Limit("?,?").Parse()
|
||||
|
||||
build.Select("isThemeDefault").Table("themes").Columns("default").Where("uname = ?").Parse()
|
||||
|
||||
build.Select("getEmailsByUser").Table("emails").Columns("email, validated, token").Where("uid = ?").Parse()
|
||||
|
||||
build.Select("getTopicBasic").Table("topics").Columns("title, content").Where("tid = ?").Parse() // TODO: Comment this out and see if anything breaks
|
||||
|
||||
build.Select("forumEntryExists").Table("forums").Columns("fid").Where("name = ''").Orderby("fid ASC").Limit("0,1").Parse()
|
||||
|
||||
build.Select("groupEntryExists").Table("users_groups").Columns("gid").Where("name = ''").Orderby("gid ASC").Limit("0,1").Parse()
|
||||
|
@ -289,8 +281,6 @@ func writeInnerJoins(adapter qgen.Adapter) (err error) {
|
|||
func writeInserts(adapter qgen.Adapter) error {
|
||||
build := adapter.Builder()
|
||||
|
||||
build.Insert("createReport").Table("topics").Columns("title, content, parsed_content, createdAt, lastReplyAt, createdBy, lastReplyBy, data, parentID, css_class").Fields("?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),?,?,?,1,'report'").Parse()
|
||||
|
||||
build.Insert("addForumPermsToForum").Table("forums_permissions").Columns("gid,fid,preset,permissions").Fields("?,?,?,?").Parse()
|
||||
|
||||
build.Insert("addPlugin").Table("plugins").Columns("uname, active, installed").Fields("?,?,?").Parse()
|
||||
|
@ -319,8 +309,6 @@ func writeUpdates(adapter qgen.Adapter) error {
|
|||
|
||||
build.Update("updateEmail").Table("emails").Set("email = ?, uid = ?, validated = ?, token = ?").Where("email = ?").Parse()
|
||||
|
||||
build.Update("verifyEmail").Table("emails").Set("validated = 1, token = ''").Where("email = ?").Parse() // Need to fix this: Empty string isn't working, it gets set to 1 instead x.x -- Has this been fixed?
|
||||
|
||||
build.Update("setTempGroup").Table("users").Set("temp_group = ?").Where("uid = ?").Parse()
|
||||
|
||||
build.Update("updateWordFilter").Table("word_filters").Set("find = ?, replacement = ?").Where("wfid = ?").Parse()
|
||||
|
@ -344,8 +332,6 @@ func writeDeletes(adapter qgen.Adapter) error {
|
|||
}
|
||||
|
||||
func writeSimpleCounts(adapter qgen.Adapter) error {
|
||||
adapter.SimpleCount("reportExists", "topics", "data = ? AND data != '' AND parentID = 1", "")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -412,6 +412,22 @@ func createTables(adapter qgen.Adapter) error {
|
|||
},
|
||||
)
|
||||
|
||||
qgen.Install.CreateTable("pages", "utf8mb4", "utf8mb4_general_ci",
|
||||
[]qgen.DBTableColumn{
|
||||
qgen.DBTableColumn{"pid", "int", 0, false, true, ""},
|
||||
//qgen.DBTableColumn{"path", "varchar", 200, false, false, ""},
|
||||
qgen.DBTableColumn{"name", "varchar", 200, false, false, ""},
|
||||
qgen.DBTableColumn{"title", "varchar", 200, false, false, ""},
|
||||
qgen.DBTableColumn{"body", "text", 0, false, false, ""},
|
||||
// TODO: Make this a table?
|
||||
qgen.DBTableColumn{"allowedGroups", "text", 0, false, false, ""},
|
||||
qgen.DBTableColumn{"menuID", "int", 0, false, false, "-1"}, // simple sidebar menu
|
||||
},
|
||||
[]qgen.DBTableKey{
|
||||
qgen.DBTableKey{"pid", "primary"},
|
||||
},
|
||||
)
|
||||
|
||||
qgen.Install.CreateTable("registration_logs", "", "",
|
||||
[]qgen.DBTableColumn{
|
||||
qgen.DBTableColumn{"rlid", "int", 0, false, true, ""},
|
||||
|
|
|
@ -232,6 +232,7 @@ import (
|
|||
"./common"
|
||||
"./common/counters"
|
||||
"./routes"
|
||||
"./routes/panel"
|
||||
)
|
||||
|
||||
var ErrNoRoute = errors.New("That route doesn't exist.")
|
||||
|
|
|
@ -14,7 +14,7 @@ func routes() {
|
|||
|
||||
// TODO: Reduce the number of Befores. With a new method, perhaps?
|
||||
reportGroup := newRouteGroup("/report/",
|
||||
Action("routeReportSubmit", "/report/submit/", "extraData"),
|
||||
Action("routes.ReportSubmit", "/report/submit/", "extraData"),
|
||||
).Before("NoBanned")
|
||||
addRouteGroup(reportGroup)
|
||||
|
||||
|
@ -46,8 +46,8 @@ func buildUserRoutes() {
|
|||
UploadAction("routes.AccountEditAvatarSubmit", "/user/edit/avatar/submit/").MaxSizeVar("int(common.Config.MaxRequestSize)"),
|
||||
MemberView("routes.AccountEditUsername", "/user/edit/username/"),
|
||||
Action("routes.AccountEditUsernameSubmit", "/user/edit/username/submit/"), // TODO: Full test this
|
||||
MemberView("routeAccountEditEmail", "/user/edit/email/"),
|
||||
Action("routeAccountEditEmailTokenSubmit", "/user/edit/token/", "extraData"),
|
||||
MemberView("routes.AccountEditEmail", "/user/edit/email/"),
|
||||
Action("routes.AccountEditEmailTokenSubmit", "/user/edit/token/", "extraData"),
|
||||
)
|
||||
addRouteGroup(userGroup)
|
||||
|
||||
|
@ -131,19 +131,19 @@ func buildPanelRoutes() {
|
|||
panelGroup := newRouteGroup("/panel/").Before("SuperModOnly")
|
||||
panelGroup.Routes(
|
||||
View("routePanelDashboard", "/panel/"),
|
||||
View("routePanelForums", "/panel/forums/"),
|
||||
Action("routePanelForumsCreateSubmit", "/panel/forums/create/"),
|
||||
Action("routePanelForumsDelete", "/panel/forums/delete/", "extraData"),
|
||||
Action("routePanelForumsDeleteSubmit", "/panel/forums/delete/submit/", "extraData"),
|
||||
View("routePanelForumsEdit", "/panel/forums/edit/", "extraData"),
|
||||
Action("routePanelForumsEditSubmit", "/panel/forums/edit/submit/", "extraData"),
|
||||
Action("routePanelForumsEditPermsSubmit", "/panel/forums/edit/perms/submit/", "extraData"),
|
||||
View("routePanelForumsEditPermsAdvance", "/panel/forums/edit/perms/", "extraData"),
|
||||
Action("routePanelForumsEditPermsAdvanceSubmit", "/panel/forums/edit/perms/adv/submit/", "extraData"),
|
||||
View("panel.Forums", "/panel/forums/"),
|
||||
Action("panel.ForumsCreateSubmit", "/panel/forums/create/"),
|
||||
Action("panel.ForumsDelete", "/panel/forums/delete/", "extraData"),
|
||||
Action("panel.ForumsDeleteSubmit", "/panel/forums/delete/submit/", "extraData"),
|
||||
View("panel.ForumsEdit", "/panel/forums/edit/", "extraData"),
|
||||
Action("panel.ForumsEditSubmit", "/panel/forums/edit/submit/", "extraData"),
|
||||
Action("panel.ForumsEditPermsSubmit", "/panel/forums/edit/perms/submit/", "extraData"),
|
||||
View("panel.ForumsEditPermsAdvance", "/panel/forums/edit/perms/", "extraData"),
|
||||
Action("panel.ForumsEditPermsAdvanceSubmit", "/panel/forums/edit/perms/adv/submit/", "extraData"),
|
||||
|
||||
View("routePanelSettings", "/panel/settings/"),
|
||||
View("routePanelSettingEdit", "/panel/settings/edit/", "extraData"),
|
||||
Action("routePanelSettingEditSubmit", "/panel/settings/edit/submit/", "extraData"),
|
||||
View("panel.Settings", "/panel/settings/"),
|
||||
View("panel.SettingEdit", "/panel/settings/edit/", "extraData"),
|
||||
Action("panel.SettingEditSubmit", "/panel/settings/edit/submit/", "extraData"),
|
||||
|
||||
View("routePanelWordFilters", "/panel/settings/word-filters/"),
|
||||
Action("routePanelWordFiltersCreateSubmit", "/panel/settings/word-filters/create/"),
|
||||
|
@ -170,21 +170,21 @@ func buildPanelRoutes() {
|
|||
View("routePanelUsersEdit", "/panel/users/edit/", "extraData"),
|
||||
Action("routePanelUsersEditSubmit", "/panel/users/edit/submit/", "extraData"),
|
||||
|
||||
View("routePanelAnalyticsViews", "/panel/analytics/views/").Before("ParseForm"),
|
||||
View("routePanelAnalyticsRoutes", "/panel/analytics/routes/").Before("ParseForm"),
|
||||
View("routePanelAnalyticsAgents", "/panel/analytics/agents/").Before("ParseForm"),
|
||||
View("routePanelAnalyticsSystems", "/panel/analytics/systems/").Before("ParseForm"),
|
||||
View("routePanelAnalyticsLanguages", "/panel/analytics/langs/").Before("ParseForm"),
|
||||
View("routePanelAnalyticsReferrers", "/panel/analytics/referrers/").Before("ParseForm"),
|
||||
View("routePanelAnalyticsRouteViews", "/panel/analytics/route/", "extraData"),
|
||||
View("routePanelAnalyticsAgentViews", "/panel/analytics/agent/", "extraData"),
|
||||
View("routePanelAnalyticsForumViews", "/panel/analytics/forum/", "extraData"),
|
||||
View("routePanelAnalyticsSystemViews", "/panel/analytics/system/", "extraData"),
|
||||
View("routePanelAnalyticsLanguageViews", "/panel/analytics/lang/", "extraData"),
|
||||
View("routePanelAnalyticsReferrerViews", "/panel/analytics/referrer/", "extraData"),
|
||||
View("routePanelAnalyticsPosts", "/panel/analytics/posts/").Before("ParseForm"),
|
||||
View("routePanelAnalyticsTopics", "/panel/analytics/topics/").Before("ParseForm"),
|
||||
View("routePanelAnalyticsForums", "/panel/analytics/forums/").Before("ParseForm"),
|
||||
View("panel.AnalyticsViews", "/panel/analytics/views/").Before("ParseForm"),
|
||||
View("panel.AnalyticsRoutes", "/panel/analytics/routes/").Before("ParseForm"),
|
||||
View("panel.AnalyticsAgents", "/panel/analytics/agents/").Before("ParseForm"),
|
||||
View("panel.AnalyticsSystems", "/panel/analytics/systems/").Before("ParseForm"),
|
||||
View("panel.AnalyticsLanguages", "/panel/analytics/langs/").Before("ParseForm"),
|
||||
View("panel.AnalyticsReferrers", "/panel/analytics/referrers/").Before("ParseForm"),
|
||||
View("panel.AnalyticsRouteViews", "/panel/analytics/route/", "extraData"),
|
||||
View("panel.AnalyticsAgentViews", "/panel/analytics/agent/", "extraData"),
|
||||
View("panel.AnalyticsForumViews", "/panel/analytics/forum/", "extraData"),
|
||||
View("panel.AnalyticsSystemViews", "/panel/analytics/system/", "extraData"),
|
||||
View("panel.AnalyticsLanguageViews", "/panel/analytics/lang/", "extraData"),
|
||||
View("panel.AnalyticsReferrerViews", "/panel/analytics/referrer/", "extraData"),
|
||||
View("panel.AnalyticsPosts", "/panel/analytics/posts/").Before("ParseForm"),
|
||||
View("panel.AnalyticsTopics", "/panel/analytics/topics/").Before("ParseForm"),
|
||||
View("panel.AnalyticsForums", "/panel/analytics/forums/").Before("ParseForm"),
|
||||
|
||||
View("routePanelGroups", "/panel/groups/"),
|
||||
View("routePanelGroupsEdit", "/panel/groups/edit/", "extraData"),
|
||||
|
@ -193,10 +193,10 @@ func buildPanelRoutes() {
|
|||
Action("routePanelGroupsEditPermsSubmit", "/panel/groups/edit/perms/submit/", "extraData"),
|
||||
Action("routePanelGroupsCreateSubmit", "/panel/groups/create/"),
|
||||
|
||||
View("routePanelBackups", "/panel/backups/", "extraData").Before("SuperAdminOnly"), // TODO: Test
|
||||
View("routePanelLogsRegs", "/panel/logs/regs/"),
|
||||
View("routePanelLogsMod", "/panel/logs/mod/"),
|
||||
View("routePanelDebug", "/panel/debug/").Before("AdminOnly"),
|
||||
View("panel.Backups", "/panel/backups/", "extraData").Before("SuperAdminOnly"), // TODO: Tests for this
|
||||
View("panel.LogsRegs", "/panel/logs/regs/"),
|
||||
View("panel.LogsMod", "/panel/logs/mod/"),
|
||||
View("panel.Debug", "/panel/debug/").Before("AdminOnly"),
|
||||
)
|
||||
addRouteGroup(panelGroup)
|
||||
}
|
||||
|
|
|
@ -402,3 +402,88 @@ func AccountEditUsernameSubmit(w http.ResponseWriter, r *http.Request, user comm
|
|||
http.Redirect(w, r, "/user/edit/username/?updated=1", http.StatusSeeOther)
|
||||
return nil
|
||||
}
|
||||
|
||||
func AccountEditEmail(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
|
||||
headerVars, ferr := common.UserCheck(w, r, &user)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
|
||||
emails, err := common.Emails.GetEmailsByUser(&user)
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
// Was this site migrated from another forum software? Most of them don't have multiple emails for a single user.
|
||||
// This also applies when the admin switches site.EnableEmails on after having it off for a while.
|
||||
if len(emails) == 0 {
|
||||
email := common.Email{UserID: user.ID}
|
||||
email.Email = user.Email
|
||||
email.Validated = false
|
||||
email.Primary = true
|
||||
emails = append(emails, email)
|
||||
}
|
||||
|
||||
if !common.Site.EnableEmails {
|
||||
headerVars.NoticeList = append(headerVars.NoticeList, common.GetNoticePhrase("account_mail_disabled"))
|
||||
}
|
||||
if r.FormValue("verified") == "1" {
|
||||
headerVars.NoticeList = append(headerVars.NoticeList, common.GetNoticePhrase("account_mail_verify_success"))
|
||||
}
|
||||
|
||||
pi := common.EmailListPage{"Email Manager", user, headerVars, emails, nil}
|
||||
if common.RunPreRenderHook("pre_render_account_own_edit_email", w, r, &user, &pi) {
|
||||
return nil
|
||||
}
|
||||
err = common.Templates.ExecuteTemplate(w, "account_own_edit_email.html", pi)
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: Do a session check on this?
|
||||
func AccountEditEmailTokenSubmit(w http.ResponseWriter, r *http.Request, user common.User, token string) common.RouteError {
|
||||
headerVars, ferr := common.UserCheck(w, r, &user)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
if !common.Site.EnableEmails {
|
||||
http.Redirect(w, r, "/user/edit/email/", http.StatusSeeOther)
|
||||
return nil
|
||||
}
|
||||
|
||||
targetEmail := common.Email{UserID: user.ID}
|
||||
emails, err := common.Emails.GetEmailsByUser(&user)
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
for _, email := range emails {
|
||||
if email.Token == token {
|
||||
targetEmail = email
|
||||
}
|
||||
}
|
||||
|
||||
if len(emails) == 0 {
|
||||
return common.LocalError("A verification email was never sent for you!", w, r, user)
|
||||
}
|
||||
if targetEmail.Token == "" {
|
||||
return common.LocalError("That's not a valid token!", w, r, user)
|
||||
}
|
||||
|
||||
err = common.Emails.VerifyEmail(user.Email)
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
// If Email Activation is on, then activate the account while we're here
|
||||
if headerVars.Settings["activation_type"] == 2 {
|
||||
err = user.Activate()
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
}
|
||||
http.Redirect(w, r, "/user/edit/email/?verified=1", http.StatusSeeOther)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
package routes
|
||||
|
||||
var successJSONBytes = []byte(`{"success":"1"}`)
|
|
@ -56,6 +56,7 @@ func ViewForum(w http.ResponseWriter, r *http.Request, user common.User, sfid st
|
|||
} else if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
header.Title = forum.Name
|
||||
|
||||
// TODO: Does forum.TopicCount take the deleted items into consideration for guests? We don't have soft-delete yet, only hard-delete
|
||||
offset, page, lastPage := common.PageOffset(forum.TopicCount, page, common.Config.ItemsPerPage)
|
||||
|
@ -112,7 +113,7 @@ func ViewForum(w http.ResponseWriter, r *http.Request, user common.User, sfid st
|
|||
}
|
||||
|
||||
pageList := common.Paginate(forum.TopicCount, common.Config.ItemsPerPage, 5)
|
||||
pi := common.ForumPage{forum.Name, user, header, topicList, forum, common.Paginator{pageList, page, lastPage}}
|
||||
pi := common.ForumPage{header, topicList, forum, common.Paginator{pageList, page, lastPage}}
|
||||
if common.RunPreRenderHook("pre_render_forum", w, r, &user, &pi) {
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,746 @@
|
|||
package panel
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"html"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"../../common"
|
||||
"../../query_gen/lib"
|
||||
)
|
||||
|
||||
// TODO: Move this to another file, probably common/pages.go
|
||||
type AnalyticsTimeRange struct {
|
||||
Quantity int
|
||||
Unit string
|
||||
Slices int
|
||||
SliceWidth int
|
||||
Range string
|
||||
}
|
||||
|
||||
func analyticsTimeRange(rawTimeRange string) (timeRange AnalyticsTimeRange, err error) {
|
||||
timeRange.Quantity = 6
|
||||
timeRange.Unit = "hour"
|
||||
timeRange.Slices = 12
|
||||
timeRange.SliceWidth = 60 * 30
|
||||
timeRange.Range = "six-hours"
|
||||
|
||||
switch rawTimeRange {
|
||||
case "one-month":
|
||||
timeRange.Quantity = 30
|
||||
timeRange.Unit = "day"
|
||||
timeRange.Slices = 30
|
||||
timeRange.SliceWidth = 60 * 60 * 24
|
||||
timeRange.Range = "one-month"
|
||||
case "one-week":
|
||||
timeRange.Quantity = 7
|
||||
timeRange.Unit = "day"
|
||||
timeRange.Slices = 14
|
||||
timeRange.SliceWidth = 60 * 60 * 12
|
||||
timeRange.Range = "one-week"
|
||||
case "two-days": // Two days is experimental
|
||||
timeRange.Quantity = 2
|
||||
timeRange.Unit = "day"
|
||||
timeRange.Slices = 24
|
||||
timeRange.SliceWidth = 60 * 60 * 2
|
||||
timeRange.Range = "two-days"
|
||||
case "one-day":
|
||||
timeRange.Quantity = 1
|
||||
timeRange.Unit = "day"
|
||||
timeRange.Slices = 24
|
||||
timeRange.SliceWidth = 60 * 60
|
||||
timeRange.Range = "one-day"
|
||||
case "twelve-hours":
|
||||
timeRange.Quantity = 12
|
||||
timeRange.Slices = 24
|
||||
timeRange.Range = "twelve-hours"
|
||||
case "six-hours", "":
|
||||
timeRange.Range = "six-hours"
|
||||
default:
|
||||
return timeRange, errors.New("Unknown time range")
|
||||
}
|
||||
return timeRange, nil
|
||||
}
|
||||
|
||||
func analyticsTimeRangeToLabelList(timeRange AnalyticsTimeRange) (revLabelList []int64, labelList []int64, viewMap map[int64]int64) {
|
||||
viewMap = make(map[int64]int64)
|
||||
var currentTime = time.Now().Unix()
|
||||
for i := 1; i <= timeRange.Slices; i++ {
|
||||
var label = currentTime - int64(i*timeRange.SliceWidth)
|
||||
revLabelList = append(revLabelList, label)
|
||||
viewMap[label] = 0
|
||||
}
|
||||
for _, value := range revLabelList {
|
||||
labelList = append(labelList, value)
|
||||
}
|
||||
return revLabelList, labelList, viewMap
|
||||
}
|
||||
|
||||
func analyticsRowsToViewMap(rows *sql.Rows, labelList []int64, viewMap map[int64]int64) (map[int64]int64, error) {
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var count int64
|
||||
var createdAt time.Time
|
||||
err := rows.Scan(&count, &createdAt)
|
||||
if err != nil {
|
||||
return viewMap, err
|
||||
}
|
||||
|
||||
var unixCreatedAt = createdAt.Unix()
|
||||
// TODO: Bulk log this
|
||||
if common.Dev.SuperDebug {
|
||||
log.Print("count: ", count)
|
||||
log.Print("createdAt: ", createdAt)
|
||||
log.Print("unixCreatedAt: ", unixCreatedAt)
|
||||
}
|
||||
|
||||
for _, value := range labelList {
|
||||
if unixCreatedAt > value {
|
||||
viewMap[value] += count
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return viewMap, rows.Err()
|
||||
}
|
||||
|
||||
func AnalyticsViews(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
|
||||
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
headerVars.AddSheet("chartist/chartist.min.css")
|
||||
headerVars.AddScript("chartist/chartist.min.js")
|
||||
headerVars.AddScript("analytics.js")
|
||||
|
||||
timeRange, err := analyticsTimeRange(r.FormValue("timeRange"))
|
||||
if err != nil {
|
||||
return common.LocalError(err.Error(), w, r, user)
|
||||
}
|
||||
revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange)
|
||||
|
||||
common.DebugLog("in panel.AnalyticsViews")
|
||||
acc := qgen.Builder.Accumulator()
|
||||
rows, err := acc.Select("viewchunks").Columns("count, createdAt").Where("route = ''").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
viewMap, err = analyticsRowsToViewMap(rows, labelList, viewMap)
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
var viewList []int64
|
||||
var viewItems []common.PanelAnalyticsItem
|
||||
for _, value := range revLabelList {
|
||||
viewList = append(viewList, viewMap[value])
|
||||
viewItems = append(viewItems, common.PanelAnalyticsItem{Time: value, Count: viewMap[value]})
|
||||
}
|
||||
graph := common.PanelTimeGraph{Series: viewList, Labels: labelList}
|
||||
common.DebugLogf("graph: %+v\n", graph)
|
||||
|
||||
pi := common.PanelAnalyticsPage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", graph, viewItems, timeRange.Range}
|
||||
return panelRenderTemplate("panel_analytics_views", w, r, user, &pi)
|
||||
}
|
||||
|
||||
func AnalyticsRouteViews(w http.ResponseWriter, r *http.Request, user common.User, route string) common.RouteError {
|
||||
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
headerVars.AddSheet("chartist/chartist.min.css")
|
||||
headerVars.AddScript("chartist/chartist.min.js")
|
||||
headerVars.AddScript("analytics.js")
|
||||
|
||||
timeRange, err := analyticsTimeRange(r.FormValue("timeRange"))
|
||||
if err != nil {
|
||||
return common.LocalError(err.Error(), w, r, user)
|
||||
}
|
||||
revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange)
|
||||
|
||||
common.DebugLog("in panel.AnalyticsRouteViews")
|
||||
acc := qgen.Builder.Accumulator()
|
||||
// TODO: Validate the route is valid
|
||||
rows, err := acc.Select("viewchunks").Columns("count, createdAt").Where("route = ?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(route)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
viewMap, err = analyticsRowsToViewMap(rows, labelList, viewMap)
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
var viewList []int64
|
||||
var viewItems []common.PanelAnalyticsItem
|
||||
for _, value := range revLabelList {
|
||||
viewList = append(viewList, viewMap[value])
|
||||
viewItems = append(viewItems, common.PanelAnalyticsItem{Time: value, Count: viewMap[value]})
|
||||
}
|
||||
graph := common.PanelTimeGraph{Series: viewList, Labels: labelList}
|
||||
common.DebugLogf("graph: %+v\n", graph)
|
||||
|
||||
pi := common.PanelAnalyticsRoutePage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", html.EscapeString(route), graph, viewItems, timeRange.Range}
|
||||
return panelRenderTemplate("panel_analytics_route_views", w, r, user, &pi)
|
||||
}
|
||||
|
||||
func AnalyticsAgentViews(w http.ResponseWriter, r *http.Request, user common.User, agent string) common.RouteError {
|
||||
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
headerVars.AddSheet("chartist/chartist.min.css")
|
||||
headerVars.AddScript("chartist/chartist.min.js")
|
||||
headerVars.AddScript("analytics.js")
|
||||
|
||||
timeRange, err := analyticsTimeRange(r.FormValue("timeRange"))
|
||||
if err != nil {
|
||||
return common.LocalError(err.Error(), w, r, user)
|
||||
}
|
||||
revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange)
|
||||
|
||||
// ? Only allow valid agents? The problem with this is that agents wind up getting renamed and it would take a migration to get them all up to snuff
|
||||
agent = html.EscapeString(agent)
|
||||
|
||||
common.DebugLog("in panel.AnalyticsAgentViews")
|
||||
acc := qgen.Builder.Accumulator()
|
||||
// TODO: Verify the agent is valid
|
||||
rows, err := acc.Select("viewchunks_agents").Columns("count, createdAt").Where("browser = ?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(agent)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
viewMap, err = analyticsRowsToViewMap(rows, labelList, viewMap)
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
var viewList []int64
|
||||
for _, value := range revLabelList {
|
||||
viewList = append(viewList, viewMap[value])
|
||||
}
|
||||
graph := common.PanelTimeGraph{Series: viewList, Labels: labelList}
|
||||
common.DebugLogf("graph: %+v\n", graph)
|
||||
|
||||
friendlyAgent, ok := common.GetUserAgentPhrase(agent)
|
||||
if !ok {
|
||||
friendlyAgent = agent
|
||||
}
|
||||
|
||||
pi := common.PanelAnalyticsAgentPage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", agent, friendlyAgent, graph, timeRange.Range}
|
||||
return panelRenderTemplate("panel_analytics_agent_views", w, r, user, &pi)
|
||||
}
|
||||
|
||||
func AnalyticsForumViews(w http.ResponseWriter, r *http.Request, user common.User, sfid string) common.RouteError {
|
||||
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
headerVars.AddSheet("chartist/chartist.min.css")
|
||||
headerVars.AddScript("chartist/chartist.min.js")
|
||||
headerVars.AddScript("analytics.js")
|
||||
|
||||
timeRange, err := analyticsTimeRange(r.FormValue("timeRange"))
|
||||
if err != nil {
|
||||
return common.LocalError(err.Error(), w, r, user)
|
||||
}
|
||||
revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange)
|
||||
|
||||
fid, err := strconv.Atoi(sfid)
|
||||
if err != nil {
|
||||
return common.LocalError("Invalid integer", w, r, user)
|
||||
}
|
||||
|
||||
common.DebugLog("in panel.AnalyticsForumViews")
|
||||
acc := qgen.Builder.Accumulator()
|
||||
// TODO: Verify the agent is valid
|
||||
rows, err := acc.Select("viewchunks_forums").Columns("count, createdAt").Where("forum = ?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(fid)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
viewMap, err = analyticsRowsToViewMap(rows, labelList, viewMap)
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
var viewList []int64
|
||||
for _, value := range revLabelList {
|
||||
viewList = append(viewList, viewMap[value])
|
||||
}
|
||||
graph := common.PanelTimeGraph{Series: viewList, Labels: labelList}
|
||||
common.DebugLogf("graph: %+v\n", graph)
|
||||
|
||||
forum, err := common.Forums.Get(fid)
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
pi := common.PanelAnalyticsAgentPage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", sfid, forum.Name, graph, timeRange.Range}
|
||||
return panelRenderTemplate("panel_analytics_forum_views", w, r, user, &pi)
|
||||
}
|
||||
|
||||
func AnalyticsSystemViews(w http.ResponseWriter, r *http.Request, user common.User, system string) common.RouteError {
|
||||
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
headerVars.AddSheet("chartist/chartist.min.css")
|
||||
headerVars.AddScript("chartist/chartist.min.js")
|
||||
headerVars.AddScript("analytics.js")
|
||||
|
||||
timeRange, err := analyticsTimeRange(r.FormValue("timeRange"))
|
||||
if err != nil {
|
||||
return common.LocalError(err.Error(), w, r, user)
|
||||
}
|
||||
revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange)
|
||||
system = html.EscapeString(system)
|
||||
|
||||
common.DebugLog("in panel.AnalyticsSystemViews")
|
||||
acc := qgen.Builder.Accumulator()
|
||||
// TODO: Verify the OS name is valid
|
||||
rows, err := acc.Select("viewchunks_systems").Columns("count, createdAt").Where("system = ?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(system)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
viewMap, err = analyticsRowsToViewMap(rows, labelList, viewMap)
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
var viewList []int64
|
||||
for _, value := range revLabelList {
|
||||
viewList = append(viewList, viewMap[value])
|
||||
}
|
||||
graph := common.PanelTimeGraph{Series: viewList, Labels: labelList}
|
||||
common.DebugLogf("graph: %+v\n", graph)
|
||||
|
||||
friendlySystem, ok := common.GetOSPhrase(system)
|
||||
if !ok {
|
||||
friendlySystem = system
|
||||
}
|
||||
|
||||
pi := common.PanelAnalyticsAgentPage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", system, friendlySystem, graph, timeRange.Range}
|
||||
return panelRenderTemplate("panel_analytics_system_views", w, r, user, &pi)
|
||||
}
|
||||
|
||||
func AnalyticsLanguageViews(w http.ResponseWriter, r *http.Request, user common.User, lang string) common.RouteError {
|
||||
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
headerVars.AddSheet("chartist/chartist.min.css")
|
||||
headerVars.AddScript("chartist/chartist.min.js")
|
||||
headerVars.AddScript("analytics.js")
|
||||
|
||||
timeRange, err := analyticsTimeRange(r.FormValue("timeRange"))
|
||||
if err != nil {
|
||||
return common.LocalError(err.Error(), w, r, user)
|
||||
}
|
||||
revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange)
|
||||
lang = html.EscapeString(lang)
|
||||
|
||||
common.DebugLog("in panel.AnalyticsLanguageViews")
|
||||
acc := qgen.Builder.Accumulator()
|
||||
// TODO: Verify the language code is valid
|
||||
rows, err := acc.Select("viewchunks_langs").Columns("count, createdAt").Where("lang = ?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(lang)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
viewMap, err = analyticsRowsToViewMap(rows, labelList, viewMap)
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
var viewList []int64
|
||||
for _, value := range revLabelList {
|
||||
viewList = append(viewList, viewMap[value])
|
||||
}
|
||||
graph := common.PanelTimeGraph{Series: viewList, Labels: labelList}
|
||||
common.DebugLogf("graph: %+v\n", graph)
|
||||
|
||||
friendlyLang, ok := common.GetHumanLangPhrase(lang)
|
||||
if !ok {
|
||||
friendlyLang = lang
|
||||
}
|
||||
|
||||
pi := common.PanelAnalyticsAgentPage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", lang, friendlyLang, graph, timeRange.Range}
|
||||
return panelRenderTemplate("panel_analytics_lang_views", w, r, user, &pi)
|
||||
}
|
||||
|
||||
func AnalyticsReferrerViews(w http.ResponseWriter, r *http.Request, user common.User, domain string) common.RouteError {
|
||||
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
headerVars.AddSheet("chartist/chartist.min.css")
|
||||
headerVars.AddScript("chartist/chartist.min.js")
|
||||
headerVars.AddScript("analytics.js")
|
||||
|
||||
timeRange, err := analyticsTimeRange(r.FormValue("timeRange"))
|
||||
if err != nil {
|
||||
return common.LocalError(err.Error(), w, r, user)
|
||||
}
|
||||
revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange)
|
||||
|
||||
common.DebugLog("in panel.AnalyticsReferrerViews")
|
||||
acc := qgen.Builder.Accumulator()
|
||||
// TODO: Verify the agent is valid
|
||||
rows, err := acc.Select("viewchunks_referrers").Columns("count, createdAt").Where("domain = ?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(domain)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
viewMap, err = analyticsRowsToViewMap(rows, labelList, viewMap)
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
var viewList []int64
|
||||
for _, value := range revLabelList {
|
||||
viewList = append(viewList, viewMap[value])
|
||||
}
|
||||
graph := common.PanelTimeGraph{Series: viewList, Labels: labelList}
|
||||
common.DebugLogf("graph: %+v\n", graph)
|
||||
|
||||
pi := common.PanelAnalyticsAgentPage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", html.EscapeString(domain), "", graph, timeRange.Range}
|
||||
return panelRenderTemplate("panel_analytics_referrer_views", w, r, user, &pi)
|
||||
}
|
||||
|
||||
func AnalyticsTopics(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
|
||||
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
headerVars.AddSheet("chartist/chartist.min.css")
|
||||
headerVars.AddScript("chartist/chartist.min.js")
|
||||
headerVars.AddScript("analytics.js")
|
||||
|
||||
timeRange, err := analyticsTimeRange(r.FormValue("timeRange"))
|
||||
if err != nil {
|
||||
return common.LocalError(err.Error(), w, r, user)
|
||||
}
|
||||
revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange)
|
||||
|
||||
common.DebugLog("in panel.AnalyticsTopics")
|
||||
acc := qgen.Builder.Accumulator()
|
||||
rows, err := acc.Select("topicchunks").Columns("count, createdAt").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
viewMap, err = analyticsRowsToViewMap(rows, labelList, viewMap)
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
var viewList []int64
|
||||
var viewItems []common.PanelAnalyticsItem
|
||||
for _, value := range revLabelList {
|
||||
viewList = append(viewList, viewMap[value])
|
||||
viewItems = append(viewItems, common.PanelAnalyticsItem{Time: value, Count: viewMap[value]})
|
||||
}
|
||||
graph := common.PanelTimeGraph{Series: viewList, Labels: labelList}
|
||||
common.DebugLogf("graph: %+v\n", graph)
|
||||
|
||||
pi := common.PanelAnalyticsPage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", graph, viewItems, timeRange.Range}
|
||||
return panelRenderTemplate("panel_analytics_topics", w, r, user, &pi)
|
||||
}
|
||||
|
||||
func AnalyticsPosts(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
|
||||
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
headerVars.AddSheet("chartist/chartist.min.css")
|
||||
headerVars.AddScript("chartist/chartist.min.js")
|
||||
headerVars.AddScript("analytics.js")
|
||||
|
||||
timeRange, err := analyticsTimeRange(r.FormValue("timeRange"))
|
||||
if err != nil {
|
||||
return common.LocalError(err.Error(), w, r, user)
|
||||
}
|
||||
revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange)
|
||||
|
||||
common.DebugLog("in panel.AnalyticsPosts")
|
||||
acc := qgen.Builder.Accumulator()
|
||||
rows, err := acc.Select("postchunks").Columns("count, createdAt").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
viewMap, err = analyticsRowsToViewMap(rows, labelList, viewMap)
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
var viewList []int64
|
||||
var viewItems []common.PanelAnalyticsItem
|
||||
for _, value := range revLabelList {
|
||||
viewList = append(viewList, viewMap[value])
|
||||
viewItems = append(viewItems, common.PanelAnalyticsItem{Time: value, Count: viewMap[value]})
|
||||
}
|
||||
graph := common.PanelTimeGraph{Series: viewList, Labels: labelList}
|
||||
common.DebugLogf("graph: %+v\n", graph)
|
||||
|
||||
pi := common.PanelAnalyticsPage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", graph, viewItems, timeRange.Range}
|
||||
return panelRenderTemplate("panel_analytics_posts", w, r, user, &pi)
|
||||
}
|
||||
|
||||
func analyticsRowsToNameMap(rows *sql.Rows) (map[string]int, error) {
|
||||
nameMap := make(map[string]int)
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var count int
|
||||
var name string
|
||||
err := rows.Scan(&count, &name)
|
||||
if err != nil {
|
||||
return nameMap, err
|
||||
}
|
||||
|
||||
// TODO: Bulk log this
|
||||
if common.Dev.SuperDebug {
|
||||
log.Print("count: ", count)
|
||||
log.Print("name: ", name)
|
||||
}
|
||||
nameMap[name] += count
|
||||
}
|
||||
return nameMap, rows.Err()
|
||||
}
|
||||
|
||||
func AnalyticsForums(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
|
||||
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
|
||||
timeRange, err := analyticsTimeRange(r.FormValue("timeRange"))
|
||||
if err != nil {
|
||||
return common.LocalError(err.Error(), w, r, user)
|
||||
}
|
||||
|
||||
acc := qgen.Builder.Accumulator()
|
||||
rows, err := acc.Select("viewchunks_forums").Columns("count, forum").Where("forum != ''").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
forumMap, err := analyticsRowsToNameMap(rows)
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
// TODO: Sort this slice
|
||||
var forumItems []common.PanelAnalyticsAgentsItem
|
||||
for sfid, count := range forumMap {
|
||||
fid, err := strconv.Atoi(sfid)
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
forum, err := common.Forums.Get(fid)
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
forumItems = append(forumItems, common.PanelAnalyticsAgentsItem{
|
||||
Agent: sfid,
|
||||
FriendlyAgent: forum.Name,
|
||||
Count: count,
|
||||
})
|
||||
}
|
||||
|
||||
pi := common.PanelAnalyticsAgentsPage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", forumItems, timeRange.Range}
|
||||
return panelRenderTemplate("panel_analytics_forums", w, r, user, &pi)
|
||||
}
|
||||
|
||||
func AnalyticsRoutes(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
|
||||
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
timeRange, err := analyticsTimeRange(r.FormValue("timeRange"))
|
||||
if err != nil {
|
||||
return common.LocalError(err.Error(), w, r, user)
|
||||
}
|
||||
|
||||
acc := qgen.Builder.Accumulator()
|
||||
rows, err := acc.Select("viewchunks").Columns("count, route").Where("route != ''").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
routeMap, err := analyticsRowsToNameMap(rows)
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
// TODO: Sort this slice
|
||||
var routeItems []common.PanelAnalyticsRoutesItem
|
||||
for route, count := range routeMap {
|
||||
routeItems = append(routeItems, common.PanelAnalyticsRoutesItem{
|
||||
Route: route,
|
||||
Count: count,
|
||||
})
|
||||
}
|
||||
|
||||
pi := common.PanelAnalyticsRoutesPage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", routeItems, timeRange.Range}
|
||||
return panelRenderTemplate("panel_analytics_routes", w, r, user, &pi)
|
||||
}
|
||||
|
||||
func AnalyticsAgents(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
|
||||
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
timeRange, err := analyticsTimeRange(r.FormValue("timeRange"))
|
||||
if err != nil {
|
||||
return common.LocalError(err.Error(), w, r, user)
|
||||
}
|
||||
|
||||
acc := qgen.Builder.Accumulator()
|
||||
rows, err := acc.Select("viewchunks_agents").Columns("count, browser").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
agentMap, err := analyticsRowsToNameMap(rows)
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
// TODO: Sort this slice
|
||||
var agentItems []common.PanelAnalyticsAgentsItem
|
||||
for agent, count := range agentMap {
|
||||
aAgent, ok := common.GetUserAgentPhrase(agent)
|
||||
if !ok {
|
||||
aAgent = agent
|
||||
}
|
||||
agentItems = append(agentItems, common.PanelAnalyticsAgentsItem{
|
||||
Agent: agent,
|
||||
FriendlyAgent: aAgent,
|
||||
Count: count,
|
||||
})
|
||||
}
|
||||
|
||||
pi := common.PanelAnalyticsAgentsPage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", agentItems, timeRange.Range}
|
||||
return panelRenderTemplate("panel_analytics_agents", w, r, user, &pi)
|
||||
}
|
||||
|
||||
func AnalyticsSystems(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
|
||||
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
timeRange, err := analyticsTimeRange(r.FormValue("timeRange"))
|
||||
if err != nil {
|
||||
return common.LocalError(err.Error(), w, r, user)
|
||||
}
|
||||
|
||||
acc := qgen.Builder.Accumulator()
|
||||
rows, err := acc.Select("viewchunks_systems").Columns("count, system").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
osMap, err := analyticsRowsToNameMap(rows)
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
// TODO: Sort this slice
|
||||
var systemItems []common.PanelAnalyticsAgentsItem
|
||||
for system, count := range osMap {
|
||||
sSystem, ok := common.GetOSPhrase(system)
|
||||
if !ok {
|
||||
sSystem = system
|
||||
}
|
||||
systemItems = append(systemItems, common.PanelAnalyticsAgentsItem{
|
||||
Agent: system,
|
||||
FriendlyAgent: sSystem,
|
||||
Count: count,
|
||||
})
|
||||
}
|
||||
|
||||
pi := common.PanelAnalyticsAgentsPage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", systemItems, timeRange.Range}
|
||||
return panelRenderTemplate("panel_analytics_systems", w, r, user, &pi)
|
||||
}
|
||||
|
||||
func AnalyticsLanguages(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
|
||||
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
timeRange, err := analyticsTimeRange(r.FormValue("timeRange"))
|
||||
if err != nil {
|
||||
return common.LocalError(err.Error(), w, r, user)
|
||||
}
|
||||
|
||||
acc := qgen.Builder.Accumulator()
|
||||
rows, err := acc.Select("viewchunks_langs").Columns("count, lang").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
langMap, err := analyticsRowsToNameMap(rows)
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
// TODO: Can we de-duplicate these analytics functions further?
|
||||
// TODO: Sort this slice
|
||||
var langItems []common.PanelAnalyticsAgentsItem
|
||||
for lang, count := range langMap {
|
||||
lLang, ok := common.GetHumanLangPhrase(lang)
|
||||
if !ok {
|
||||
lLang = lang
|
||||
}
|
||||
langItems = append(langItems, common.PanelAnalyticsAgentsItem{
|
||||
Agent: lang,
|
||||
FriendlyAgent: lLang,
|
||||
Count: count,
|
||||
})
|
||||
}
|
||||
|
||||
pi := common.PanelAnalyticsAgentsPage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", langItems, timeRange.Range}
|
||||
return panelRenderTemplate("panel_analytics_langs", w, r, user, &pi)
|
||||
}
|
||||
|
||||
func AnalyticsReferrers(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
|
||||
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
timeRange, err := analyticsTimeRange(r.FormValue("timeRange"))
|
||||
if err != nil {
|
||||
return common.LocalError(err.Error(), w, r, user)
|
||||
}
|
||||
|
||||
acc := qgen.Builder.Accumulator()
|
||||
rows, err := acc.Select("viewchunks_referrers").Columns("count, domain").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
refMap, err := analyticsRowsToNameMap(rows)
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
// TODO: Sort this slice
|
||||
var refItems []common.PanelAnalyticsAgentsItem
|
||||
for domain, count := range refMap {
|
||||
refItems = append(refItems, common.PanelAnalyticsAgentsItem{
|
||||
Agent: html.EscapeString(domain),
|
||||
Count: count,
|
||||
})
|
||||
}
|
||||
|
||||
pi := common.PanelAnalyticsAgentsPage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", refItems, timeRange.Range}
|
||||
return panelRenderTemplate("panel_analytics_referrers", w, r, user, &pi)
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package panel
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"../../common"
|
||||
)
|
||||
|
||||
func Backups(w http.ResponseWriter, r *http.Request, user common.User, backupURL string) common.RouteError {
|
||||
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
|
||||
if backupURL != "" {
|
||||
// We don't want them trying to break out of this directory, it shouldn't hurt since it's a super admin, but it's always good to practice good security hygiene, especially if this is one of many instances on a managed server not controlled by the superadmin/s
|
||||
backupURL = common.Stripslashes(backupURL)
|
||||
|
||||
var ext = filepath.Ext("./backups/" + backupURL)
|
||||
if ext == ".sql" {
|
||||
info, err := os.Stat("./backups/" + backupURL)
|
||||
if err != nil {
|
||||
return common.NotFound(w, r, headerVars)
|
||||
}
|
||||
// TODO: Change the served filename to gosora_backup_%timestamp%.sql, the time the file was generated, not when it was modified aka what the name of it should be
|
||||
w.Header().Set("Content-Disposition", "attachment; filename=gosora_backup.sql")
|
||||
w.Header().Set("Content-Length", strconv.FormatInt(info.Size(), 10))
|
||||
// TODO: Fix the problem where non-existent files aren't greeted with custom 404s on ServeFile()'s side
|
||||
http.ServeFile(w, r, "./backups/"+backupURL)
|
||||
return nil
|
||||
}
|
||||
return common.NotFound(w, r, headerVars)
|
||||
}
|
||||
|
||||
var backupList []common.BackupItem
|
||||
backupFiles, err := ioutil.ReadDir("./backups")
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
for _, backupFile := range backupFiles {
|
||||
var ext = filepath.Ext(backupFile.Name())
|
||||
if ext != ".sql" {
|
||||
continue
|
||||
}
|
||||
backupList = append(backupList, common.BackupItem{backupFile.Name(), backupFile.ModTime()})
|
||||
}
|
||||
|
||||
pi := common.PanelBackupPage{common.GetTitlePhrase("panel_backups"), user, headerVars, stats, "backups", backupList}
|
||||
return panelRenderTemplate("panel_backups", w, r, user, &pi)
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package panel
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"../../common"
|
||||
)
|
||||
|
||||
// A blank list to fill out that parameter in Page for routes which don't use it
|
||||
var tList []interface{}
|
||||
var successJSONBytes = []byte(`{"success":"1"}`)
|
||||
|
||||
// We're trying to reduce the amount of boilerplate in here, so I added these two functions, they might wind up circulating outside this file in the future
|
||||
func panelSuccessRedirect(dest string, w http.ResponseWriter, r *http.Request, isJs bool) common.RouteError {
|
||||
if !isJs {
|
||||
http.Redirect(w, r, dest, http.StatusSeeOther)
|
||||
} else {
|
||||
w.Write(successJSONBytes)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func panelRenderTemplate(tmplName string, w http.ResponseWriter, r *http.Request, user common.User, pi interface{}) common.RouteError {
|
||||
if common.RunPreRenderHook("pre_render_"+tmplName, w, r, &user, pi) {
|
||||
return nil
|
||||
}
|
||||
err := common.Templates.ExecuteTemplate(w, tmplName+".html", pi)
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package panel
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"../../common"
|
||||
"../../query_gen/lib"
|
||||
)
|
||||
|
||||
func Debug(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
|
||||
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
|
||||
goVersion := runtime.Version()
|
||||
dbVersion := qgen.Builder.DbVersion()
|
||||
var uptime string
|
||||
upDuration := time.Since(common.StartTime)
|
||||
hours := int(upDuration.Hours())
|
||||
minutes := int(upDuration.Minutes())
|
||||
if hours > 24 {
|
||||
days := hours / 24
|
||||
hours -= days * 24
|
||||
uptime += strconv.Itoa(days) + "d"
|
||||
uptime += strconv.Itoa(hours) + "h"
|
||||
} else if hours >= 1 {
|
||||
uptime += strconv.Itoa(hours) + "h"
|
||||
}
|
||||
uptime += strconv.Itoa(minutes) + "m"
|
||||
|
||||
dbStats := qgen.Builder.GetConn().Stats()
|
||||
openConnCount := dbStats.OpenConnections
|
||||
// Disk I/O?
|
||||
// TODO: Fetch the adapter from Builder rather than getting it from a global?
|
||||
|
||||
pi := common.PanelDebugPage{common.GetTitlePhrase("panel_debug"), user, headerVars, stats, "debug", goVersion, dbVersion, uptime, openConnCount, qgen.Builder.GetAdapter().GetName()}
|
||||
return panelRenderTemplate("panel_debug", w, r, user, &pi)
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
This file is here so that Git will include this folder in the repository.
|
|
@ -0,0 +1,417 @@
|
|||
package panel
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"../../common"
|
||||
)
|
||||
|
||||
func Forums(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
|
||||
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
if !user.Perms.ManageForums {
|
||||
return common.NoPermissions(w, r, user)
|
||||
}
|
||||
|
||||
// TODO: Paginate this?
|
||||
var forumList []interface{}
|
||||
forums, err := common.Forums.GetAll()
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
// ? - Should we generate something similar to the forumView? It might be a little overkill for a page which is rarely loaded in comparison to /forums/
|
||||
for _, forum := range forums {
|
||||
if forum.Name != "" && forum.ParentID == 0 {
|
||||
fadmin := common.ForumAdmin{forum.ID, forum.Name, forum.Desc, forum.Active, forum.Preset, forum.TopicCount, common.PresetToLang(forum.Preset)}
|
||||
if fadmin.Preset == "" {
|
||||
fadmin.Preset = "custom"
|
||||
}
|
||||
forumList = append(forumList, fadmin)
|
||||
}
|
||||
}
|
||||
|
||||
if r.FormValue("created") == "1" {
|
||||
headerVars.NoticeList = append(headerVars.NoticeList, common.GetNoticePhrase("panel_forum_created"))
|
||||
} else if r.FormValue("deleted") == "1" {
|
||||
headerVars.NoticeList = append(headerVars.NoticeList, common.GetNoticePhrase("panel_forum_deleted"))
|
||||
} else if r.FormValue("updated") == "1" {
|
||||
headerVars.NoticeList = append(headerVars.NoticeList, common.GetNoticePhrase("panel_forum_updated"))
|
||||
}
|
||||
|
||||
pi := common.PanelPage{common.GetTitlePhrase("panel_forums"), user, headerVars, stats, "forums", forumList, nil}
|
||||
return panelRenderTemplate("panel_forums", w, r, user, &pi)
|
||||
}
|
||||
|
||||
func ForumsCreateSubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
|
||||
_, ferr := common.SimplePanelUserCheck(w, r, &user)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
if !user.Perms.ManageForums {
|
||||
return common.NoPermissions(w, r, user)
|
||||
}
|
||||
|
||||
fname := r.PostFormValue("forum-name")
|
||||
fdesc := r.PostFormValue("forum-desc")
|
||||
fpreset := common.StripInvalidPreset(r.PostFormValue("forum-preset"))
|
||||
factive := r.PostFormValue("forum-active")
|
||||
active := (factive == "on" || factive == "1")
|
||||
|
||||
_, err := common.Forums.Create(fname, fdesc, active, fpreset)
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/panel/forums/?created=1", http.StatusSeeOther)
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: Revamp this
|
||||
func ForumsDelete(w http.ResponseWriter, r *http.Request, user common.User, sfid string) common.RouteError {
|
||||
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
if !user.Perms.ManageForums {
|
||||
return common.NoPermissions(w, r, user)
|
||||
}
|
||||
|
||||
fid, err := strconv.Atoi(sfid)
|
||||
if err != nil {
|
||||
return common.LocalError("The provided Forum ID is not a valid number.", w, r, user)
|
||||
}
|
||||
|
||||
forum, err := common.Forums.Get(fid)
|
||||
if err == sql.ErrNoRows {
|
||||
return common.LocalError("The forum you're trying to delete doesn't exist.", w, r, user)
|
||||
} else if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
// TODO: Make this a phrase
|
||||
confirmMsg := "Are you sure you want to delete the '" + forum.Name + "' forum?"
|
||||
yousure := common.AreYouSure{"/panel/forums/delete/submit/" + strconv.Itoa(fid), confirmMsg}
|
||||
|
||||
pi := common.PanelPage{common.GetTitlePhrase("panel_delete_forum"), user, headerVars, stats, "forums", tList, yousure}
|
||||
if common.RunPreRenderHook("pre_render_panel_delete_forum", w, r, &user, &pi) {
|
||||
return nil
|
||||
}
|
||||
err = common.Templates.ExecuteTemplate(w, "are_you_sure.html", pi)
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ForumsDeleteSubmit(w http.ResponseWriter, r *http.Request, user common.User, sfid string) common.RouteError {
|
||||
_, ferr := common.SimplePanelUserCheck(w, r, &user)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
if !user.Perms.ManageForums {
|
||||
return common.NoPermissions(w, r, user)
|
||||
}
|
||||
|
||||
fid, err := strconv.Atoi(sfid)
|
||||
if err != nil {
|
||||
return common.LocalError("The provided Forum ID is not a valid number.", w, r, user)
|
||||
}
|
||||
|
||||
err = common.Forums.Delete(fid)
|
||||
if err == sql.ErrNoRows {
|
||||
return common.LocalError("The forum you're trying to delete doesn't exist.", w, r, user)
|
||||
} else if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/panel/forums/?deleted=1", http.StatusSeeOther)
|
||||
return nil
|
||||
}
|
||||
|
||||
func ForumsEdit(w http.ResponseWriter, r *http.Request, user common.User, sfid string) common.RouteError {
|
||||
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
if !user.Perms.ManageForums {
|
||||
return common.NoPermissions(w, r, user)
|
||||
}
|
||||
|
||||
fid, err := strconv.Atoi(sfid)
|
||||
if err != nil {
|
||||
return common.LocalError("The provided Forum ID is not a valid number.", w, r, user)
|
||||
}
|
||||
|
||||
forum, err := common.Forums.Get(fid)
|
||||
if err == sql.ErrNoRows {
|
||||
return common.LocalError("The forum you're trying to edit doesn't exist.", w, r, user)
|
||||
} else if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
if forum.Preset == "" {
|
||||
forum.Preset = "custom"
|
||||
}
|
||||
|
||||
glist, err := common.Groups.GetAll()
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
var gplist []common.GroupForumPermPreset
|
||||
for gid, group := range glist {
|
||||
if gid == 0 {
|
||||
continue
|
||||
}
|
||||
forumPerms, err := common.FPStore.Get(fid, group.ID)
|
||||
if err == sql.ErrNoRows {
|
||||
forumPerms = common.BlankForumPerms()
|
||||
} else if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
gplist = append(gplist, common.GroupForumPermPreset{group, common.ForumPermsToGroupForumPreset(forumPerms)})
|
||||
}
|
||||
|
||||
if r.FormValue("updated") == "1" {
|
||||
headerVars.NoticeList = append(headerVars.NoticeList, common.GetNoticePhrase("panel_forum_updated"))
|
||||
}
|
||||
|
||||
pi := common.PanelEditForumPage{common.GetTitlePhrase("panel_edit_forum"), user, headerVars, stats, "forums", forum.ID, forum.Name, forum.Desc, forum.Active, forum.Preset, gplist}
|
||||
if common.RunPreRenderHook("pre_render_panel_edit_forum", w, r, &user, &pi) {
|
||||
return nil
|
||||
}
|
||||
err = common.Templates.ExecuteTemplate(w, "panel-forum-edit.html", pi)
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ForumsEditSubmit(w http.ResponseWriter, r *http.Request, user common.User, sfid string) common.RouteError {
|
||||
_, ferr := common.SimplePanelUserCheck(w, r, &user)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
if !user.Perms.ManageForums {
|
||||
return common.NoPermissions(w, r, user)
|
||||
}
|
||||
isJs := (r.PostFormValue("js") == "1")
|
||||
|
||||
fid, err := strconv.Atoi(sfid)
|
||||
if err != nil {
|
||||
return common.LocalErrorJSQ("The provided Forum ID is not a valid number.", w, r, user, isJs)
|
||||
}
|
||||
|
||||
forum, err := common.Forums.Get(fid)
|
||||
if err == sql.ErrNoRows {
|
||||
return common.LocalErrorJSQ("The forum you're trying to edit doesn't exist.", w, r, user, isJs)
|
||||
} else if err != nil {
|
||||
return common.InternalErrorJSQ(err, w, r, isJs)
|
||||
}
|
||||
|
||||
forumName := r.PostFormValue("forum_name")
|
||||
forumDesc := r.PostFormValue("forum_desc")
|
||||
forumPreset := common.StripInvalidPreset(r.PostFormValue("forum_preset"))
|
||||
forumActive := r.PostFormValue("forum_active")
|
||||
|
||||
var active = false
|
||||
if forumActive == "" {
|
||||
active = forum.Active
|
||||
} else if forumActive == "1" || forumActive == "Show" {
|
||||
active = true
|
||||
}
|
||||
|
||||
err = forum.Update(forumName, forumDesc, active, forumPreset)
|
||||
if err != nil {
|
||||
return common.InternalErrorJSQ(err, w, r, isJs)
|
||||
}
|
||||
// ? Should we redirect to the forum editor instead?
|
||||
return panelSuccessRedirect("/panel/forums/", w, r, isJs)
|
||||
}
|
||||
|
||||
func ForumsEditPermsSubmit(w http.ResponseWriter, r *http.Request, user common.User, sfid string) common.RouteError {
|
||||
_, ferr := common.SimplePanelUserCheck(w, r, &user)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
if !user.Perms.ManageForums {
|
||||
return common.NoPermissions(w, r, user)
|
||||
}
|
||||
isJs := (r.PostFormValue("js") == "1")
|
||||
|
||||
fid, err := strconv.Atoi(sfid)
|
||||
if err != nil {
|
||||
return common.LocalErrorJSQ("The provided Forum ID is not a valid number.", w, r, user, isJs)
|
||||
}
|
||||
|
||||
gid, err := strconv.Atoi(r.PostFormValue("gid"))
|
||||
if err != nil {
|
||||
return common.LocalErrorJSQ("Invalid Group ID", w, r, user, isJs)
|
||||
}
|
||||
|
||||
forum, err := common.Forums.Get(fid)
|
||||
if err == sql.ErrNoRows {
|
||||
return common.LocalErrorJSQ("This forum doesn't exist", w, r, user, isJs)
|
||||
} else if err != nil {
|
||||
return common.InternalErrorJSQ(err, w, r, isJs)
|
||||
}
|
||||
|
||||
permPreset := common.StripInvalidGroupForumPreset(r.PostFormValue("perm_preset"))
|
||||
err = forum.SetPreset(permPreset, gid)
|
||||
if err != nil {
|
||||
return common.LocalErrorJSQ(err.Error(), w, r, user, isJs)
|
||||
}
|
||||
|
||||
return panelSuccessRedirect("/panel/forums/edit/"+strconv.Itoa(fid)+"?updated=1", w, r, isJs)
|
||||
}
|
||||
|
||||
// A helper function for the Advanced portion of the Forum Perms Editor
|
||||
func forumPermsExtractDash(paramList string) (fid int, gid int, err error) {
|
||||
params := strings.Split(paramList, "-")
|
||||
if len(params) != 2 {
|
||||
return fid, gid, errors.New("Parameter count mismatch")
|
||||
}
|
||||
|
||||
fid, err = strconv.Atoi(params[0])
|
||||
if err != nil {
|
||||
return fid, gid, errors.New("The provided Forum ID is not a valid number.")
|
||||
}
|
||||
|
||||
gid, err = strconv.Atoi(params[1])
|
||||
if err != nil {
|
||||
err = errors.New("The provided Group ID is not a valid number.")
|
||||
}
|
||||
|
||||
return fid, gid, err
|
||||
}
|
||||
|
||||
func ForumsEditPermsAdvance(w http.ResponseWriter, r *http.Request, user common.User, paramList string) common.RouteError {
|
||||
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
if !user.Perms.ManageForums {
|
||||
return common.NoPermissions(w, r, user)
|
||||
}
|
||||
|
||||
fid, gid, err := forumPermsExtractDash(paramList)
|
||||
if err != nil {
|
||||
return common.LocalError(err.Error(), w, r, user)
|
||||
}
|
||||
|
||||
forum, err := common.Forums.Get(fid)
|
||||
if err == sql.ErrNoRows {
|
||||
return common.LocalError("The forum you're trying to edit doesn't exist.", w, r, user)
|
||||
} else if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
if forum.Preset == "" {
|
||||
forum.Preset = "custom"
|
||||
}
|
||||
|
||||
forumPerms, err := common.FPStore.Get(fid, gid)
|
||||
if err == sql.ErrNoRows {
|
||||
forumPerms = common.BlankForumPerms()
|
||||
} else if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
var formattedPermList []common.NameLangToggle
|
||||
|
||||
// TODO: Load the phrases in bulk for efficiency?
|
||||
// TODO: Reduce the amount of code duplication between this and the group editor. Also, can we grind this down into one line or use a code generator to stay current more easily?
|
||||
var addNameLangToggle = func(permStr string, perm bool) {
|
||||
formattedPermList = append(formattedPermList, common.NameLangToggle{permStr, common.GetLocalPermPhrase(permStr), perm})
|
||||
}
|
||||
addNameLangToggle("ViewTopic", forumPerms.ViewTopic)
|
||||
addNameLangToggle("LikeItem", forumPerms.LikeItem)
|
||||
addNameLangToggle("CreateTopic", forumPerms.CreateTopic)
|
||||
//<--
|
||||
addNameLangToggle("EditTopic", forumPerms.EditTopic)
|
||||
addNameLangToggle("DeleteTopic", forumPerms.DeleteTopic)
|
||||
addNameLangToggle("CreateReply", forumPerms.CreateReply)
|
||||
addNameLangToggle("EditReply", forumPerms.EditReply)
|
||||
addNameLangToggle("DeleteReply", forumPerms.DeleteReply)
|
||||
addNameLangToggle("PinTopic", forumPerms.PinTopic)
|
||||
addNameLangToggle("CloseTopic", forumPerms.CloseTopic)
|
||||
addNameLangToggle("MoveTopic", forumPerms.MoveTopic)
|
||||
|
||||
if r.FormValue("updated") == "1" {
|
||||
headerVars.NoticeList = append(headerVars.NoticeList, common.GetNoticePhrase("panel_forums_perms_updated"))
|
||||
}
|
||||
|
||||
pi := common.PanelEditForumGroupPage{common.GetTitlePhrase("panel_edit_forum"), user, headerVars, stats, "forums", forum.ID, gid, forum.Name, forum.Desc, forum.Active, forum.Preset, formattedPermList}
|
||||
if common.RunPreRenderHook("pre_render_panel_edit_forum", w, r, &user, &pi) {
|
||||
return nil
|
||||
}
|
||||
err = common.Templates.ExecuteTemplate(w, "panel-forum-edit-perms.html", pi)
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ForumsEditPermsAdvanceSubmit(w http.ResponseWriter, r *http.Request, user common.User, paramList string) common.RouteError {
|
||||
_, ferr := common.SimplePanelUserCheck(w, r, &user)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
if !user.Perms.ManageForums {
|
||||
return common.NoPermissions(w, r, user)
|
||||
}
|
||||
isJs := (r.PostFormValue("js") == "1")
|
||||
|
||||
fid, gid, err := forumPermsExtractDash(paramList)
|
||||
if err != nil {
|
||||
return common.LocalError(err.Error(), w, r, user)
|
||||
}
|
||||
|
||||
forum, err := common.Forums.Get(fid)
|
||||
if err == sql.ErrNoRows {
|
||||
return common.LocalError("The forum you're trying to edit doesn't exist.", w, r, user)
|
||||
} else if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
forumPerms, err := common.FPStore.GetCopy(fid, gid)
|
||||
if err == sql.ErrNoRows {
|
||||
forumPerms = *common.BlankForumPerms()
|
||||
} else if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
var extractPerm = func(name string) bool {
|
||||
pvalue := r.PostFormValue("forum-perm-" + name)
|
||||
return (pvalue == "1")
|
||||
}
|
||||
|
||||
// TODO: Generate this code?
|
||||
forumPerms.ViewTopic = extractPerm("ViewTopic")
|
||||
forumPerms.LikeItem = extractPerm("LikeItem")
|
||||
forumPerms.CreateTopic = extractPerm("CreateTopic")
|
||||
forumPerms.EditTopic = extractPerm("EditTopic")
|
||||
forumPerms.DeleteTopic = extractPerm("DeleteTopic")
|
||||
forumPerms.CreateReply = extractPerm("CreateReply")
|
||||
forumPerms.EditReply = extractPerm("EditReply")
|
||||
forumPerms.DeleteReply = extractPerm("DeleteReply")
|
||||
forumPerms.PinTopic = extractPerm("PinTopic")
|
||||
forumPerms.CloseTopic = extractPerm("CloseTopic")
|
||||
forumPerms.MoveTopic = extractPerm("MoveTopic")
|
||||
|
||||
err = forum.SetPerms(&forumPerms, "custom", gid)
|
||||
if err != nil {
|
||||
return common.LocalErrorJSQ(err.Error(), w, r, user, isJs)
|
||||
}
|
||||
|
||||
return panelSuccessRedirect("/panel/forums/edit/perms/"+strconv.Itoa(fid)+"-"+strconv.Itoa(gid)+"?updated=1", w, r, isJs)
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
package panel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"../../common"
|
||||
)
|
||||
|
||||
func LogsRegs(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
|
||||
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
|
||||
logCount := common.RegLogs.GlobalCount()
|
||||
page, _ := strconv.Atoi(r.FormValue("page"))
|
||||
perPage := 10
|
||||
offset, page, lastPage := common.PageOffset(logCount, page, perPage)
|
||||
|
||||
logs, err := common.RegLogs.GetOffset(offset, perPage)
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
var llist = make([]common.PageRegLogItem, len(logs))
|
||||
for index, log := range logs {
|
||||
llist[index] = common.PageRegLogItem{log, strings.Replace(strings.TrimSuffix(log.FailureReason, "|"), "|", " | ", -1)}
|
||||
}
|
||||
|
||||
pageList := common.Paginate(logCount, perPage, 5)
|
||||
pi := common.PanelRegLogsPage{common.GetTitlePhrase("panel_registration_logs"), user, headerVars, stats, "logs", llist, common.Paginator{pageList, page, lastPage}}
|
||||
return panelRenderTemplate("panel_reglogs", w, r, user, &pi)
|
||||
}
|
||||
|
||||
// TODO: Log errors when something really screwy is going on?
|
||||
func handleUnknownUser(user *common.User, err error) *common.User {
|
||||
if err != nil {
|
||||
return &common.User{Name: "Unknown", Link: common.BuildProfileURL("unknown", 0)}
|
||||
}
|
||||
return user
|
||||
}
|
||||
func handleUnknownTopic(topic *common.Topic, err error) *common.Topic {
|
||||
if err != nil {
|
||||
return &common.Topic{Title: "Unknown", Link: common.BuildProfileURL("unknown", 0)}
|
||||
}
|
||||
return topic
|
||||
}
|
||||
|
||||
// TODO: Move the log building logic into /common/ and it's own abstraction
|
||||
func topicElementTypeAction(action string, elementType string, elementID int, actor *common.User, topic *common.Topic) (out string) {
|
||||
if action == "delete" {
|
||||
return fmt.Sprintf("Topic #%d was deleted by <a href='%s'>%s</a>", elementID, actor.Link, actor.Name)
|
||||
}
|
||||
switch action {
|
||||
case "lock":
|
||||
out = "<a href='%s'>%s</a> was locked by <a href='%s'>%s</a>"
|
||||
case "unlock":
|
||||
out = "<a href='%s'>%s</a> was reopened by <a href='%s'>%s</a>"
|
||||
case "stick":
|
||||
out = "<a href='%s'>%s</a> was pinned by <a href='%s'>%s</a>"
|
||||
case "unstick":
|
||||
out = "<a href='%s'>%s</a> was unpinned by <a href='%s'>%s</a>"
|
||||
case "move":
|
||||
out = "<a href='%s'>%s</a> was moved by <a href='%s'>%s</a>" // TODO: Add where it was moved to, we'll have to change the source data for that, most likely? Investigate that and try to work this in
|
||||
default:
|
||||
return fmt.Sprintf("Unknown action '%s' on elementType '%s' by <a href='%s'>%s</a>", action, elementType, actor.Link, actor.Name)
|
||||
}
|
||||
return fmt.Sprintf(out, topic.Link, topic.Title, actor.Link, actor.Name)
|
||||
}
|
||||
|
||||
func modlogsElementType(action string, elementType string, elementID int, actor *common.User) (out string) {
|
||||
switch elementType {
|
||||
case "topic":
|
||||
topic := handleUnknownTopic(common.Topics.Get(elementID))
|
||||
out = topicElementTypeAction(action, elementType, elementID, actor, topic)
|
||||
case "user":
|
||||
targetUser := handleUnknownUser(common.Users.Get(elementID))
|
||||
switch action {
|
||||
case "ban":
|
||||
out = "<a href='%s'>%s</a> was banned by <a href='%s'>%s</a>"
|
||||
case "unban":
|
||||
out = "<a href='%s'>%s</a> was unbanned by <a href='%s'>%s</a>"
|
||||
case "activate":
|
||||
out = "<a href='%s'>%s</a> was activated by <a href='%s'>%s</a>"
|
||||
}
|
||||
out = fmt.Sprintf(out, targetUser.Link, targetUser.Name, actor.Link, actor.Name)
|
||||
case "reply":
|
||||
if action == "delete" {
|
||||
topic := handleUnknownTopic(common.TopicByReplyID(elementID))
|
||||
out = fmt.Sprintf("A reply in <a href='%s'>%s</a> was deleted by <a href='%s'>%s</a>", topic.Link, topic.Title, actor.Link, actor.Name)
|
||||
}
|
||||
}
|
||||
|
||||
if out == "" {
|
||||
out = fmt.Sprintf("Unknown action '%s' on elementType '%s' by <a href='%s'>%s</a>", action, elementType, actor.Link, actor.Name)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func LogsMod(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
|
||||
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
|
||||
logCount := common.ModLogs.GlobalCount()
|
||||
page, _ := strconv.Atoi(r.FormValue("page"))
|
||||
perPage := 10
|
||||
offset, page, lastPage := common.PageOffset(logCount, page, perPage)
|
||||
|
||||
logs, err := common.ModLogs.GetOffset(offset, perPage)
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
var llist = make([]common.PageLogItem, len(logs))
|
||||
for index, log := range logs {
|
||||
actor := handleUnknownUser(common.Users.Get(log.ActorID))
|
||||
action := modlogsElementType(log.Action, log.ElementType, log.ElementID, actor)
|
||||
llist[index] = common.PageLogItem{Action: template.HTML(action), IPAddress: log.IPAddress, DoneAt: log.DoneAt}
|
||||
}
|
||||
|
||||
pageList := common.Paginate(logCount, perPage, 5)
|
||||
pi := common.PanelLogsPage{common.GetTitlePhrase("panel_mod_logs"), user, headerVars, stats, "logs", llist, common.Paginator{pageList, page, lastPage}}
|
||||
return panelRenderTemplate("panel_modlogs", w, r, user, &pi)
|
||||
}
|
||||
|
||||
func LogsAdmin(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
|
||||
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
|
||||
logCount := common.ModLogs.GlobalCount()
|
||||
page, _ := strconv.Atoi(r.FormValue("page"))
|
||||
perPage := 10
|
||||
offset, page, lastPage := common.PageOffset(logCount, page, perPage)
|
||||
|
||||
logs, err := common.AdminLogs.GetOffset(offset, perPage)
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
var llist = make([]common.PageLogItem, len(logs))
|
||||
for index, log := range logs {
|
||||
actor := handleUnknownUser(common.Users.Get(log.ActorID))
|
||||
action := modlogsElementType(log.Action, log.ElementType, log.ElementID, actor)
|
||||
llist[index] = common.PageLogItem{Action: template.HTML(action), IPAddress: log.IPAddress, DoneAt: log.DoneAt}
|
||||
}
|
||||
|
||||
pageList := common.Paginate(logCount, perPage, 5)
|
||||
pi := common.PanelLogsPage{common.GetTitlePhrase("panel_admin_logs"), user, headerVars, stats, "logs", llist, common.Paginator{pageList, page, lastPage}}
|
||||
return panelRenderTemplate("panel_adminlogs", w, r, user, &pi)
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
package panel
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"html"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"../../common"
|
||||
)
|
||||
|
||||
func Settings(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
|
||||
header, stats, ferr := common.PanelUserCheck(w, r, &user)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
if !user.Perms.EditSettings {
|
||||
return common.NoPermissions(w, r, user)
|
||||
}
|
||||
|
||||
settings, err := header.Settings.BypassGetAll()
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
settingPhrases := common.GetAllSettingPhrases()
|
||||
|
||||
var settingList []*common.PanelSetting
|
||||
for _, settingPtr := range settings {
|
||||
setting := settingPtr.Copy()
|
||||
if setting.Type == "list" {
|
||||
llist := settingPhrases[setting.Name+"_label"]
|
||||
labels := strings.Split(llist, ",")
|
||||
conv, err := strconv.Atoi(setting.Content)
|
||||
if err != nil {
|
||||
return common.LocalError("The setting '"+setting.Name+"' can't be converted to an integer", w, r, user)
|
||||
}
|
||||
setting.Content = labels[conv-1]
|
||||
} else if setting.Type == "bool" {
|
||||
if setting.Content == "1" {
|
||||
setting.Content = "Yes"
|
||||
} else {
|
||||
setting.Content = "No"
|
||||
}
|
||||
} else if setting.Type == "html-attribute" {
|
||||
setting.Type = "textarea"
|
||||
}
|
||||
settingList = append(settingList, &common.PanelSetting{setting, common.GetSettingPhrase(setting.Name)})
|
||||
}
|
||||
|
||||
pi := common.PanelPage{common.GetTitlePhrase("panel_settings"), user, header, stats, "settings", tList, settingList}
|
||||
return panelRenderTemplate("panel_settings", w, r, user, &pi)
|
||||
}
|
||||
|
||||
func SettingEdit(w http.ResponseWriter, r *http.Request, user common.User, sname string) common.RouteError {
|
||||
header, stats, ferr := common.PanelUserCheck(w, r, &user)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
if !user.Perms.EditSettings {
|
||||
return common.NoPermissions(w, r, user)
|
||||
}
|
||||
header.Title = common.GetTitlePhrase("panel_edit_setting")
|
||||
|
||||
setting, err := header.Settings.BypassGet(sname)
|
||||
if err == sql.ErrNoRows {
|
||||
return common.LocalError("The setting you want to edit doesn't exist.", w, r, user)
|
||||
} else if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
var itemList []common.OptionLabel
|
||||
if setting.Type == "list" {
|
||||
llist := common.GetSettingPhrase(setting.Name + "_label")
|
||||
conv, err := strconv.Atoi(setting.Content)
|
||||
if err != nil {
|
||||
return common.LocalError("The value of this setting couldn't be converted to an integer", w, r, user)
|
||||
}
|
||||
fmt.Println("llist: ", llist)
|
||||
|
||||
for index, label := range strings.Split(llist, ",") {
|
||||
itemList = append(itemList, common.OptionLabel{
|
||||
Label: label,
|
||||
Value: index + 1,
|
||||
Selected: conv == (index + 1),
|
||||
})
|
||||
}
|
||||
} else if setting.Type == "html-attribute" {
|
||||
setting.Type = "textarea"
|
||||
}
|
||||
|
||||
pSetting := &common.PanelSetting{setting, common.GetSettingPhrase(setting.Name)}
|
||||
pi := common.PanelSettingPage{header, stats, "settings", itemList, pSetting}
|
||||
return panelRenderTemplate("panel_setting", w, r, user, &pi)
|
||||
}
|
||||
|
||||
func SettingEditSubmit(w http.ResponseWriter, r *http.Request, user common.User, sname string) common.RouteError {
|
||||
headerLite, ferr := common.SimplePanelUserCheck(w, r, &user)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
if !user.Perms.EditSettings {
|
||||
return common.NoPermissions(w, r, user)
|
||||
}
|
||||
|
||||
scontent := html.EscapeString(r.PostFormValue("setting-value"))
|
||||
err := headerLite.Settings.Update(sname, scontent)
|
||||
if err != nil {
|
||||
if common.SafeSettingError(err) {
|
||||
return common.LocalError(err.Error(), w, r, user)
|
||||
}
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/panel/settings/", http.StatusSeeOther)
|
||||
return nil
|
||||
}
|
|
@ -32,6 +32,8 @@ func ViewProfile(w http.ResponseWriter, r *http.Request, user common.User) commo
|
|||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
// TODO: Preload this?
|
||||
header.AddScript("profile.css")
|
||||
|
||||
var err error
|
||||
var replyCreatedAt time.Time
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
package routes
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"../common"
|
||||
"../common/counters"
|
||||
)
|
||||
|
||||
func ReportSubmit(w http.ResponseWriter, r *http.Request, user common.User, sitemID string) common.RouteError {
|
||||
_, ferr := common.SimpleUserCheck(w, r, &user)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
isJs := (r.PostFormValue("isJs") == "1")
|
||||
|
||||
itemID, err := strconv.Atoi(sitemID)
|
||||
if err != nil {
|
||||
return common.LocalError("Bad ID", w, r, user)
|
||||
}
|
||||
itemType := r.FormValue("type")
|
||||
|
||||
var title, content string
|
||||
if itemType == "reply" {
|
||||
reply, err := common.Rstore.Get(itemID)
|
||||
if err == sql.ErrNoRows {
|
||||
return common.LocalError("We were unable to find the reported post", w, r, user)
|
||||
} else if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
topic, err := common.Topics.Get(reply.ParentID)
|
||||
if err == sql.ErrNoRows {
|
||||
return common.LocalError("We weren't able to find the topic the reported post is supposed to be in", w, r, user)
|
||||
} else if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
title = "Reply: " + topic.Title
|
||||
content = reply.Content + "\n\nOriginal Post: #rid-" + strconv.Itoa(itemID)
|
||||
} else if itemType == "user-reply" {
|
||||
userReply, err := common.Prstore.Get(itemID)
|
||||
if err == sql.ErrNoRows {
|
||||
return common.LocalError("We weren't able to find the reported post", w, r, user)
|
||||
} else if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
profileOwner, err := common.Users.Get(userReply.ParentID)
|
||||
if err == sql.ErrNoRows {
|
||||
return common.LocalError("We weren't able to find the profile the reported post is supposed to be on", w, r, user)
|
||||
} else if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
title = "Profile: " + profileOwner.Name
|
||||
content = userReply.Content + "\n\nOriginal Post: @" + strconv.Itoa(userReply.ParentID)
|
||||
} else if itemType == "topic" {
|
||||
topic, err := common.Topics.Get(itemID)
|
||||
if err == sql.ErrNoRows {
|
||||
return common.NotFound(w, r, nil)
|
||||
} else if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
title = "Topic: " + topic.Title
|
||||
content = topic.Content + "\n\nOriginal Post: #tid-" + strconv.Itoa(itemID)
|
||||
} else {
|
||||
_, hasHook := common.RunVhookNeedHook("report_preassign", &itemID, &itemType)
|
||||
if hasHook {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Don't try to guess the type
|
||||
return common.LocalError("Unknown type", w, r, user)
|
||||
}
|
||||
|
||||
// TODO: Repost attachments in the reports forum, so that the mods can see them
|
||||
_, err = common.Reports.Create(title, content, &user, itemType, itemID)
|
||||
if err == common.ErrAlreadyReported {
|
||||
return common.LocalError("Someone has already reported this!", w, r, user)
|
||||
}
|
||||
counters.PostCounter.Bump()
|
||||
|
||||
if !isJs {
|
||||
// TODO: Redirect back to where we came from
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
} else {
|
||||
_, _ = w.Write(successJSONBytes)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -37,8 +37,6 @@ func init() {
|
|||
})
|
||||
}
|
||||
|
||||
var successJSONBytes = []byte(`{"success":"1"}`)
|
||||
|
||||
func ViewTopic(w http.ResponseWriter, r *http.Request, user common.User, urlBit string) common.RouteError {
|
||||
page, _ := strconv.Atoi(r.FormValue("page"))
|
||||
|
||||
|
@ -64,7 +62,7 @@ func ViewTopic(w http.ResponseWriter, r *http.Request, user common.User, urlBit
|
|||
topic.ClassName = ""
|
||||
//log.Printf("topic: %+v\n", topic)
|
||||
|
||||
headerVars, ferr := common.ForumUserCheck(w, r, &user, topic.ParentID)
|
||||
header, ferr := common.ForumUserCheck(w, r, &user, topic.ParentID)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
|
@ -72,10 +70,11 @@ func ViewTopic(w http.ResponseWriter, r *http.Request, user common.User, urlBit
|
|||
//log.Printf("user.Perms: %+v\n", user.Perms)
|
||||
return common.NoPermissions(w, r, user)
|
||||
}
|
||||
headerVars.Zone = "view_topic"
|
||||
header.Title = topic.Title
|
||||
header.Zone = "view_topic"
|
||||
// TODO: Only include these on pages with polls
|
||||
headerVars.AddSheet("chartist/chartist.min.css")
|
||||
headerVars.AddScript("chartist/chartist.min.js")
|
||||
header.AddSheet("chartist/chartist.min.css")
|
||||
header.AddScript("chartist/chartist.min.js")
|
||||
|
||||
topic.ContentHTML = common.ParseMessage(topic.Content, topic.ParentID, "forums")
|
||||
topic.ContentLines = strings.Count(topic.Content, "\n")
|
||||
|
@ -121,7 +120,7 @@ func ViewTopic(w http.ResponseWriter, r *http.Request, user common.User, urlBit
|
|||
|
||||
// Calculate the offset
|
||||
offset, page, lastPage := common.PageOffset(topic.PostCount, page, common.Config.ItemsPerPage)
|
||||
tpage := common.TopicPage{topic.Title, user, headerVars, []common.ReplyUser{}, topic, poll, page, lastPage}
|
||||
tpage := common.TopicPage{header, []common.ReplyUser{}, topic, poll, page, lastPage}
|
||||
|
||||
// Get the replies if we have any...
|
||||
if topic.PostCount > 0 {
|
||||
|
@ -227,7 +226,7 @@ func ViewTopic(w http.ResponseWriter, r *http.Request, user common.User, urlBit
|
|||
if common.RunPreRenderHook("pre_render_view_topic", w, r, &user, &tpage) {
|
||||
return nil
|
||||
}
|
||||
err = common.RunThemeTemplate(headerVars.Theme.Name, "topic", tpage, w)
|
||||
err = common.RunThemeTemplate(header.Theme.Name, "topic", tpage, w)
|
||||
if err != nil {
|
||||
return common.InternalError(err, w, r)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
INSERT INTO [sync] ([last_update]) VALUES (GETUTCDATE());
|
||||
INSERT INTO [settings] ([name],[content],[type]) VALUES ('url_tags','1','bool');
|
||||
INSERT INTO [settings] ([name],[content],[type],[constraints]) VALUES ('activation_type','1','list','1-3');
|
||||
INSERT INTO [settings] ([name],[content],[type]) VALUES ('bigpost_min_words','250','int');
|
||||
INSERT INTO [settings] ([name],[content],[type]) VALUES ('megapost_min_words','1000','int');
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
CREATE TABLE [pages] (
|
||||
[pid] int not null IDENTITY,
|
||||
[name] nvarchar (200) not null,
|
||||
[title] nvarchar (200) not null,
|
||||
[body] nvarchar (MAX) not null,
|
||||
[allowedGroups] nvarchar (MAX) not null,
|
||||
[menuID] int DEFAULT -1 not null,
|
||||
primary key([pid])
|
||||
);
|
|
@ -1,5 +1,4 @@
|
|||
INSERT INTO `sync`(`last_update`) VALUES (UTC_TIMESTAMP());
|
||||
INSERT INTO `settings`(`name`,`content`,`type`) VALUES ('url_tags','1','bool');
|
||||
INSERT INTO `settings`(`name`,`content`,`type`,`constraints`) VALUES ('activation_type','1','list','1-3');
|
||||
INSERT INTO `settings`(`name`,`content`,`type`) VALUES ('bigpost_min_words','250','int');
|
||||
INSERT INTO `settings`(`name`,`content`,`type`) VALUES ('megapost_min_words','1000','int');
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
CREATE TABLE `pages` (
|
||||
`pid` int not null AUTO_INCREMENT,
|
||||
`name` varchar(200) not null,
|
||||
`title` varchar(200) not null,
|
||||
`body` text not null,
|
||||
`allowedGroups` text not null,
|
||||
`menuID` int DEFAULT -1 not null,
|
||||
primary key(`pid`)
|
||||
) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;
|
|
@ -1,40 +1,39 @@
|
|||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
INSERT INTO "sync"("last_update") VALUES (UTC_TIMESTAMP());
|
||||
INSERT INTO "settings"("name","content","type","constraints") VALUES ('activation_type','1','list','1-3');
|
||||
INSERT INTO "settings"("name","content","type") VALUES ('bigpost_min_words','250','int');
|
||||
INSERT INTO "settings"("name","content","type") VALUES ('megapost_min_words','1000','int');
|
||||
INSERT INTO "settings"("name","content","type") VALUES ('meta_desc','','html-attribute');
|
||||
INSERT INTO "themes"("uname","default") VALUES ('cosora',1);
|
||||
INSERT INTO "emails"("email","uid","validated") VALUES ('admin@localhost',1,1);
|
||||
INSERT INTO "users_groups"("name","permissions","plugin_perms","is_mod","is_admin","tag") VALUES ('Administrator','{"BanUsers":true,"ActivateUsers":true,"EditUser":true,"EditUserEmail":true,"EditUserPassword":true,"EditUserGroup":true,"EditUserGroupSuperMod":true,"EditUserGroupAdmin":false,"EditGroup":true,"EditGroupLocalPerms":true,"EditGroupGlobalPerms":true,"EditGroupSuperMod":true,"EditGroupAdmin":false,"ManageForums":true,"EditSettings":true,"ManageThemes":true,"ManagePlugins":true,"ViewAdminLogs":true,"ViewIPs":true,"UploadFiles":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"EditTopic":true,"DeleteTopic":true,"CreateReply":true,"EditReply":true,"DeleteReply":true,"PinTopic":true,"CloseTopic":true,"MoveTopic":true}','{}',1,1,'Admin');
|
||||
INSERT INTO "users_groups"("name","permissions","plugin_perms","is_mod","tag") VALUES ('Moderator','{"BanUsers":true,"ActivateUsers":false,"EditUser":true,"EditUserEmail":false,"EditUserGroup":true,"ViewIPs":true,"UploadFiles":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"EditTopic":true,"DeleteTopic":true,"CreateReply":true,"EditReply":true,"DeleteReply":true,"PinTopic":true,"CloseTopic":true,"MoveTopic":true}','{}',1,'Mod');
|
||||
INSERT INTO "users_groups"("name","permissions","plugin_perms") VALUES ('Member','{"UploadFiles":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"CreateReply":true}','{}');
|
||||
INSERT INTO "users_groups"("name","permissions","plugin_perms","is_banned") VALUES ('Banned','{"ViewTopic":true}','{}',1);
|
||||
INSERT INTO "users_groups"("name","permissions","plugin_perms") VALUES ('Awaiting Activation','{"ViewTopic":true}','{}');
|
||||
INSERT INTO "users_groups"("name","permissions","plugin_perms","tag") VALUES ('Not Loggedin','{"ViewTopic":true}','{}','Guest');
|
||||
INSERT INTO "forums"("name","active","desc") VALUES ('Reports',0,'All the reports go here');
|
||||
INSERT INTO "forums"("name","lastTopicID","lastReplyerID","desc") VALUES ('General',1,1,'A place for general discussions which don''t fit elsewhere');
|
||||
INSERT INTO "forums_permissions"("gid","fid","permissions") VALUES (1,1,'{"ViewTopic":true,"CreateReply":true,"CreateTopic":true,"PinTopic":true,"CloseTopic":true}');
|
||||
INSERT INTO "forums_permissions"("gid","fid","permissions") VALUES (2,1,'{"ViewTopic":true,"CreateReply":true,"CloseTopic":true}');
|
||||
INSERT INTO "forums_permissions"("gid","fid","permissions") VALUES (3,1,'{}');
|
||||
INSERT INTO "forums_permissions"("gid","fid","permissions") VALUES (4,1,'{}');
|
||||
INSERT INTO "forums_permissions"("gid","fid","permissions") VALUES (5,1,'{}');
|
||||
INSERT INTO "forums_permissions"("gid","fid","permissions") VALUES (6,1,'{}');
|
||||
INSERT INTO "forums_permissions"("gid","fid","permissions") VALUES (1,2,'{"ViewTopic":true,"CreateReply":true,"CreateTopic":true,"LikeItem":true,"EditTopic":true,"DeleteTopic":true,"EditReply":true,"DeleteReply":true,"PinTopic":true,"CloseTopic":true,"MoveTopic":true}');
|
||||
INSERT INTO "forums_permissions"("gid","fid","permissions") VALUES (2,2,'{"ViewTopic":true,"CreateReply":true,"CreateTopic":true,"LikeItem":true,"EditTopic":true,"DeleteTopic":true,"EditReply":true,"DeleteReply":true,"PinTopic":true,"CloseTopic":true,"MoveTopic":true}');
|
||||
INSERT INTO "forums_permissions"("gid","fid","permissions") VALUES (3,2,'{"ViewTopic":true,"CreateReply":true,"CreateTopic":true,"LikeItem":true}');
|
||||
INSERT INTO "forums_permissions"("gid","fid","permissions") VALUES (4,2,'{"ViewTopic":true}');
|
||||
INSERT INTO "forums_permissions"("gid","fid","permissions") VALUES (5,2,'{"ViewTopic":true}');
|
||||
INSERT INTO "forums_permissions"("gid","fid","permissions") VALUES (6,2,'{"ViewTopic":true}');
|
||||
INSERT INTO "topics"("title","content","parsed_content","createdAt","lastReplyAt","lastReplyBy","createdBy","parentID","ipaddress") VALUES ('Test Topic','A topic automatically generated by the software.','A topic automatically generated by the software.',UTC_TIMESTAMP(),UTC_TIMESTAMP(),1,1,2,'::1');
|
||||
INSERT INTO "replies"("tid","content","parsed_content","createdAt","createdBy","lastUpdated","lastEdit","lastEditBy","ipaddress") VALUES (1,'A reply!','A reply!',UTC_TIMESTAMP(),1,UTC_TIMESTAMP(),0,0,'::1');
|
||||
INSERT INTO "menus"() VALUES ();
|
||||
INSERT INTO "menu_items"("mid","name","htmlID","position","path","aria","tooltip","order") VALUES (1,'{lang.menu_forums}','menu_forums','left','/forums/','{lang.menu_forums_aria}','{lang.menu_forums_tooltip}',0);
|
||||
INSERT INTO "menu_items"("mid","name","htmlID","cssClass","position","path","aria","tooltip","order") VALUES (1,'{lang.menu_topics}','menu_topics','menu_topics','left','/topics/','{lang.menu_topics_aria}','{lang.menu_topics_tooltip}',1);
|
||||
INSERT INTO "menu_items"("mid","htmlID","cssClass","position","tmplName","order") VALUES (1,'general_alerts','menu_alerts','right','menu_alerts',2);
|
||||
INSERT INTO "menu_items"("mid","name","cssClass","position","path","aria","tooltip","memberOnly","order") VALUES (1,'{lang.menu_account}','menu_account','left','/user/edit/critical/','{lang.menu_account_aria}','{lang.menu_account_tooltip}',1,3);
|
||||
INSERT INTO "menu_items"("mid","name","cssClass","position","path","aria","tooltip","memberOnly","order") VALUES (1,'{lang.menu_profile}','menu_profile','left','{me.Link}','{lang.menu_profile_aria}','{lang.menu_profile_tooltip}',1,4);
|
||||
INSERT INTO "menu_items"("mid","name","cssClass","position","path","aria","tooltip","memberOnly","staffOnly","order") VALUES (1,'{lang.menu_panel}','menu_panel menu_account','left','/panel/','{lang.menu_panel_aria}','{lang.menu_panel_tooltip}',1,1,5);
|
||||
INSERT INTO "menu_items"("mid","name","cssClass","position","path","aria","tooltip","memberOnly","order") VALUES (1,'{lang.menu_logout}','menu_logout','left','/accounts/logout/?session={me.Session}','{lang.menu_logout_aria}','{lang.menu_logout_tooltip}',1,6);
|
||||
INSERT INTO "menu_items"("mid","name","cssClass","position","path","aria","tooltip","guestOnly","order") VALUES (1,'{lang.menu_register}','menu_register','left','/accounts/create/','{lang.menu_register_aria}','{lang.menu_register_tooltip}',1,7);
|
||||
INSERT INTO "menu_items"("mid","name","cssClass","position","path","aria","tooltip","guestOnly","order") VALUES (1,'{lang.menu_login}','menu_login','left','/accounts/login/','{lang.menu_login_aria}','{lang.menu_login_tooltip}',1,8);
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
CREATE TABLE `pages` (
|
||||
`pid` serial not null,
|
||||
`name` varchar (200) not null,
|
||||
`title` varchar (200) not null,
|
||||
`body` text not null,
|
||||
`allowedGroups` text not null,
|
||||
`menuID` int DEFAULT -1 not null,
|
||||
primary key(`pid`)
|
||||
);
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"DBVersion":"4",
|
||||
"DBVersion":"5",
|
||||
"DynamicFileVersion":"0",
|
||||
"MinGoVersion":"1.10",
|
||||
"MinVersion":""
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<div class="rowitem passive"><a href="/user/edit/username/">{{lang "account_menu_username"}}</a></div>
|
||||
<div class="rowitem passive"><a href="/user/edit/critical/">{{lang "account_menu_password"}}</a></div>
|
||||
<div class="rowitem passive"><a href="/user/edit/email/">{{lang "account_menu_email"}}</a></div>
|
||||
<div class="rowitem passive"><a href="/user/edit/notifications/">{{lang "account_menu_notifications"}}</a></div>
|
||||
<!--<div class="rowitem passive"><a href="/user/edit/notifications/">{{lang "account_menu_notifications"}}</a></div>-->
|
||||
{{/** TODO: Add an alerts page with pagination to go through alerts which either don't fit in the alerts drop-down or which have already been dismissed. Bear in mind though that dismissed alerts older than two weeks might be purged to save space and to speed up the database **/}}
|
||||
</div>
|
||||
</nav>
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
</div>
|
||||
<aside class="midRight sidebar">{{dock "rightSidebar" .Header }}</aside>
|
||||
</div>
|
||||
<div class="footBlock">
|
||||
<div class="footLeft"></div>
|
||||
<div class="footer">
|
||||
{{dock "footer" .Header }}
|
||||
<div id="poweredByHolder" class="footerBit">
|
||||
|
@ -15,8 +20,8 @@
|
|||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<aside class="sidebar">{{dock "rightSidebar" .Header }}</aside>
|
||||
<div class="footRight"></div>
|
||||
</div>
|
||||
<div style="clear: both;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -88,10 +88,17 @@
|
|||
{{if .IsClosed}}<span class="rowsmall topic_status_e topic_status_closed" title="{{lang "status_closed_tooltip"}}"> | 🔒︎</span>{{end}}
|
||||
{{if .Sticky}}<span class="rowsmall topic_status_e topic_status_sticky" title="{{lang "status_pinned_tooltip"}}"> | 📍︎</span>{{end}}
|
||||
</span>
|
||||
<span class="topic_inner_right rowsmall" style="float: right;">
|
||||
{{/** TODO: Phase this out of Cosora and remove it **/}}
|
||||
<div class="topic_inner_right rowsmall">
|
||||
<span class="replyCount">{{.PostCount}}</span><br />
|
||||
<span class="likeCount">{{.LikeCount}}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="topic_middle">
|
||||
<div class="topic_inner_middle rowsmall">
|
||||
<span class="replyCount">{{.PostCount}}</span><br />
|
||||
<span class="likeCount">{{.LikeCount}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rowitem topic_right passive datarow {{if .Sticky}}topic_sticky{{else if .IsClosed}}topic_closed{{end}}">
|
||||
<a href="{{.LastUser.Link}}"><img src="{{.LastUser.Avatar}}" height="64" alt="{{.LastUser.Name}}'s Avatar" title="{{.LastUser.Name}}'s Avatar" /></a>
|
||||
|
|
|
@ -12,14 +12,13 @@
|
|||
{{if .Desc}}
|
||||
<br /><span class="rowsmall" itemprop="description">{{.Desc}}</span>
|
||||
{{else}}
|
||||
<br /><span class="rowsmall" style="font-style: italic;">{{lang "forums_no_description"}}</span>
|
||||
<br /><span class="rowsmall forum_nodesc">{{lang "forums_no_description"}}</span>
|
||||
{{end}}
|
||||
</span>
|
||||
|
||||
<span class="forum_right shift_right">
|
||||
{{if .LastReplyer.Avatar}}<img class="extra_little_row_avatar" src="{{.LastReplyer.Avatar}}" height=64 width=64 alt="{{.LastReplyer.Name}}'s Avatar" title="{{.LastReplyer.Name}}'s Avatar" />{{end}}
|
||||
<span>
|
||||
<a href="{{.LastTopic.Link}}">{{if .LastTopic.Title}}{{.LastTopic.Title}}{{else}}{{lang "forums_none"}}{{end}}</a>
|
||||
<a {{if .LastTopic.Link}}href="{{.LastTopic.Link}}"{{else}}class="forum_no_poster"{{end}}>{{if .LastTopic.Title}}{{.LastTopic.Title}}{{else}}{{lang "forums_none"}}{{end}}</a>
|
||||
{{if .LastTopicTime}}<br /><span class="rowsmall">{{.LastTopicTime}}</span>{{end}}
|
||||
</span>
|
||||
</span>
|
||||
|
|
|
@ -22,12 +22,13 @@
|
|||
<body>
|
||||
{{if not .CurrentUser.IsSuperMod}}<style>.supermod_only { display: none !important; }</style>{{end}}
|
||||
<div class="container">
|
||||
<!--<div class="navrow">-->
|
||||
<div class="left_of_nav">{{dock "leftOfNav" .Header }}</div>
|
||||
<nav class="nav">
|
||||
<div class="move_left">
|
||||
<div class="move_right">
|
||||
<ul>
|
||||
<li id="menu_overview" class="menu_left"><a href="/" rel="home">{{.Header.Site.ShortName}}</a></li>
|
||||
<ul>{{/** TODO: Have the theme control whether the long or short form of the name is used **/}}
|
||||
<li id="menu_overview" class="menu_left"><a href="/" rel="home">{{if eq .Header.Theme.Name "nox"}}{{.Header.Site.Name}}{{else}}{{.Header.Site.ShortName}}{{end}}</a></li>
|
||||
{{dock "topMenu" .Header }}
|
||||
<li class="menu_left menu_hamburger" title="{{lang "menu_hamburger_tooltip"}}"><a></a></li>
|
||||
</ul>
|
||||
|
@ -35,8 +36,23 @@
|
|||
</div>
|
||||
<div style="clear: both;"></div>
|
||||
</nav>
|
||||
<div class="right_of_nav">{{dock "rightOfNav" .Header }}</div>
|
||||
<div id="back"><div id="main" {{if .Header.Widgets.RightSidebar}}class="shrink_main"{{end}}>
|
||||
<div class="alertbox">{{range .Header.NoticeList}}
|
||||
{{template "notice.html" . }}{{end}}
|
||||
<div class="right_of_nav"><!--{{dock "rightOfNav" .Header }}-->
|
||||
{{/** TODO: Make this a separate template and load it via the theme docks, here for now so we can rapidly prototype the Nox theme **/}}
|
||||
{{if eq .Header.Theme.Name "nox"}}
|
||||
<div class="user_box">
|
||||
<img src="{{.CurrentUser.Avatar}}" />
|
||||
<div class="option_box">
|
||||
<span class="username">{{.CurrentUser.Name}}</span>
|
||||
<span class="alerts">21 new alerts</span>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
<!--</div>-->
|
||||
<div class="midRow">
|
||||
<div class="midLeft"></div>
|
||||
<div id="back" class="zone_{{.Header.Zone}}{{if .Header.Widgets.RightSidebar}} shrink_main{{end}}">
|
||||
<div id="main" >
|
||||
<div class="alertbox">{{range .Header.NoticeList}}
|
||||
{{template "notice.html" . }}{{end}}
|
||||
</div>
|
|
@ -4,15 +4,11 @@
|
|||
{{template "panel-menu.html" . }}
|
||||
<main class="colstack_right">
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem"><h1>{{lang "panel_setting_head"}}</h1></div>
|
||||
<div class="rowitem"><h1>{{.Setting.FriendlyName}}</h1></div>
|
||||
</div>
|
||||
<div id="panel_setting" class="colstack_item">
|
||||
<form action="/panel/settings/edit/submit/{{.Something.Name}}?session={{.CurrentUser.Session}}" method="post">
|
||||
<div class="formrow">
|
||||
<div class="formitem formlabel"><a>{{lang "panel_setting_name"}}</a></div>
|
||||
<div class="formitem formlabel">{{.Something.Name}}</div>
|
||||
</div>
|
||||
{{if eq .Something.Type "list"}}
|
||||
<form action="/panel/settings/edit/submit/{{.Setting.Name}}?session={{.CurrentUser.Session}}" method="post">
|
||||
{{if eq .Setting.Type "list"}}
|
||||
<div class="formrow">
|
||||
<div class="formitem formlabel"><a>{{lang "panel_setting_value"}}</a></div>
|
||||
<div class="formitem">
|
||||
|
@ -21,19 +17,23 @@
|
|||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{{else if eq .Something.Type "bool"}}
|
||||
{{else if eq .Setting.Type "bool"}}
|
||||
<div class="formrow">
|
||||
<div class="formitem formlabel"><a>{{lang "panel_setting_value"}}</a></div>
|
||||
<div class="formitem">
|
||||
<select name="setting-value">
|
||||
<option{{if eq .Something.Content "1"}} selected{{end}} value="1">{{lang "option_yes"}}</option>
|
||||
<option{{if eq .Something.Content "0"}} selected{{end}} value="0">{{lang "option_no"}}</option>
|
||||
<option{{if eq .Setting.Content "1"}} selected{{end}} value="1">{{lang "option_yes"}}</option>
|
||||
<option{{if eq .Setting.Content "0"}} selected{{end}} value="0">{{lang "option_no"}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{{else if eq .Setting.Type "textarea"}}
|
||||
<div class="formrow">
|
||||
<div class="formitem"><textarea name="setting-value">{{.Setting.Content}}</textarea></div>
|
||||
</div>
|
||||
{{else}}<div class="formrow">
|
||||
<div class="formitem formlabel"><a>{{lang "panel_setting_value"}}</a></div>
|
||||
<div class="formitem"><input name="setting-value" type="text" value="{{.Something.Content}}" /></div>
|
||||
<div class="formitem"><input name="setting-value" type="text" value="{{.Setting.Content}}" /></div>
|
||||
</div>{{end}}
|
||||
<div class="formrow">
|
||||
<div class="formitem"><button name="panel-button" class="formbutton">{{lang "panel_setting_update_button"}}</button></div>
|
||||
|
|
|
@ -6,10 +6,10 @@
|
|||
<div class="rowitem"><h1>{{lang "panel_settings_head"}}</h1></div>
|
||||
</div>
|
||||
<div id="panel_settings" class="colstack_item rowlist">
|
||||
{{range $key, $value := .Something}}
|
||||
{{range .Something}}
|
||||
<div class="rowitem panel_compactrow editable_parent">
|
||||
<a href="/panel/settings/edit/{{$key}}" class="editable_block panel_upshift">{{$key}}</a>
|
||||
<a class="panel_compacttext to_right">{{$value}}</a>
|
||||
<a href="/panel/settings/edit/{{.Name}}" class="editable_block panel_upshift">{{.FriendlyName}}</a>
|
||||
<a class="panel_compacttext to_right">{{.Content}}</a>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
|
|
|
@ -11,9 +11,8 @@
|
|||
<div class="rowitem editable_parent" style="background-image: url('{{.Avatar}}');">
|
||||
<img class="bgsub" src="{{.Avatar}}" alt="{{.Name}}'s Avatar" />
|
||||
<a class="rowTitle editable_block"{{if $.CurrentUser.Perms.EditUser}} href="/panel/users/edit/{{.ID}}?session={{$.CurrentUser.Session}}"{{end}}>{{.Name}}</a>
|
||||
<a href="/user/{{.ID}}" class="tag-mini profile_url">{{lang "panel_users_profile"}}</a>
|
||||
<a href="{{.Link}}" class="tag-mini profile_url">{{lang "panel_users_profile"}}</a>
|
||||
{{if (.Tag) and (.IsSuperMod)}}<span style="float: right;"><span class="panel_tag" style="margin-left 4px;">{{.Tag}}</span></span>{{end}}
|
||||
|
||||
<span class="panel_floater">
|
||||
{{if .IsBanned}}<a href="/users/unban/{{.ID}}?session={{$.CurrentUser.Session}}" class="panel_tag panel_right_button ban_button">{{lang "panel_users_unban"}}</a>{{else if not .IsSuperMod}}<a href="/user/{{.ID}}#ban_user" class="panel_tag panel_right_button ban_button">{{lang "panel_users_ban"}}</a>{{end}}
|
||||
{{if not .Active}}<a href="/users/activate/{{.ID}}?session={{$.CurrentUser.Session}}" class="panel_tag panel_right_button">{{lang "panel_users_activate"}}</a>{{end}}
|
||||
|
|
|
@ -113,17 +113,26 @@
|
|||
{{if .IsClosed}}<span class="rowsmall topic_status_e topic_status_closed" title="{{lang "status_closed_tooltip"}}"> | 🔒︎</span>{{end}}
|
||||
{{if .Sticky}}<span class="rowsmall topic_status_e topic_status_sticky" title="{{lang "status_pinned_tooltip"}}"> | 📍︎</span>{{end}}
|
||||
</span>
|
||||
<span class="topic_inner_right rowsmall" style="float: right;">
|
||||
{{/** TODO: Phase this out of Cosora and remove it **/}}
|
||||
<div class="topic_inner_right rowsmall">
|
||||
<span class="replyCount">{{.PostCount}}</span><br />
|
||||
<span class="likeCount">{{.LikeCount}}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rowitem topic_right passive datarow {{if .Sticky}}topic_sticky{{else if .IsClosed}}topic_closed{{end}}">
|
||||
<a href="{{.LastUser.Link}}"><img src="{{.LastUser.Avatar}}" height="64" alt="{{.LastUser.Name}}'s Avatar" title="{{.LastUser.Name}}'s Avatar" /></a>
|
||||
<span>
|
||||
<a href="{{.LastUser.Link}}" class="lastName" style="font-size: 14px;" title="{{.LastUser.Name}}">{{.LastUser.Name}}</a><br>
|
||||
<span class="rowsmall lastReplyAt">{{.RelativeLastReplyAt}}</span>
|
||||
</span>
|
||||
<div class="topic_middle">
|
||||
<div class="topic_middle_inside rowsmall">
|
||||
<span class="replyCount">{{.PostCount}}</span><br />
|
||||
<span class="likeCount">{{.LikeCount}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rowitem topic_right passive datarow {{if .Sticky}}topic_sticky{{else if .IsClosed}} topic_closed{{end}}">
|
||||
<div class="topic_right_inside">
|
||||
<a href="{{.LastUser.Link}}"><img src="{{.LastUser.Avatar}}" height="64" alt="{{.LastUser.Name}}'s Avatar" title="{{.LastUser.Name}}'s Avatar" /></a>
|
||||
<span>
|
||||
<a href="{{.LastUser.Link}}" class="lastName" style="font-size: 14px;" title="{{.LastUser.Name}}">{{.LastUser.Name}}</a><br>
|
||||
<span class="rowsmall lastReplyAt">{{.RelativeLastReplyAt}}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>{{else}}<div class="rowitem passive rowmsg">{{lang "topics_no_topics"}}{{if .CurrentUser.Perms.CreateTopic}} <a href="/topics/create/">{{lang "topics_start_one"}}</a>{{end}}</div>{{end}}
|
||||
</div>
|
||||
|
|
|
@ -53,6 +53,10 @@ body, #main {
|
|||
padding-right: 0px;
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
.footBlock {
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
.container {
|
||||
background-color: var(--element-background-color);
|
||||
}
|
||||
|
@ -62,6 +66,7 @@ body, #main {
|
|||
padding-top: 14px;
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
padding-bottom: 14px;
|
||||
}
|
||||
.sidebar {
|
||||
width: 200px;
|
||||
|
@ -386,10 +391,10 @@ h1, h3 {
|
|||
}
|
||||
.topic_list_title_block .pre_opt {
|
||||
border-left: 1px solid var(--element-border-color);
|
||||
padding-left: 12px;
|
||||
padding-left: 11px;
|
||||
height: 20px;
|
||||
color: var(--light-text-color);
|
||||
margin-right: 10px;
|
||||
margin-right: 9px;
|
||||
}
|
||||
.topic_list_title_block .pre_opt:before {
|
||||
content: "{{index .Phrases "topics_click_topics_to_select"}}";
|
||||
|
@ -413,6 +418,9 @@ h1, h3 {
|
|||
font: normal normal normal 14px/1 FontAwesome;
|
||||
font-size: 18px;
|
||||
}
|
||||
.mod_opt .moderate_open {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.topic_create_form {
|
||||
display: flex !important;
|
||||
|
@ -686,6 +694,9 @@ textarea {
|
|||
border: 1px solid var(--element-border-color);
|
||||
border-bottom: 2px solid var(--element-border-color);
|
||||
}
|
||||
.topic_middle {
|
||||
display: none;
|
||||
}
|
||||
.rowlist .rowitem {
|
||||
background-color: var(--element-background-color);
|
||||
padding: 12px;
|
||||
|
@ -816,6 +827,9 @@ textarea {
|
|||
flex: 1 1 0px;
|
||||
border-left: none;
|
||||
}
|
||||
.topic_right_inside {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.topic_left img {
|
||||
border-radius: 30px;
|
||||
|
@ -824,7 +838,7 @@ textarea {
|
|||
margin-top: 8px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
.topic_right img {
|
||||
.topic_right_inside img {
|
||||
border-radius: 30px;
|
||||
height: 42px;
|
||||
width: 42px;
|
||||
|
@ -837,7 +851,7 @@ textarea {
|
|||
margin-bottom: 14px;
|
||||
width: 220px;
|
||||
}
|
||||
.topic_right > span {
|
||||
.topic_right_inside > span {
|
||||
margin-top: 12px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
@ -885,6 +899,9 @@ textarea {
|
|||
border-bottom: 2px solid var(--element-border-color);
|
||||
padding: 14px;
|
||||
}
|
||||
.forum_list .forum_nodesc {
|
||||
font-style: italic;
|
||||
}
|
||||
.forum_right {
|
||||
display: flex;
|
||||
}
|
||||
|
@ -1325,9 +1342,6 @@ textarea {
|
|||
}
|
||||
|
||||
/* TODO: Make widget_about's CSS less footer centric */
|
||||
.footer {
|
||||
margin-top: 14px;
|
||||
}
|
||||
.footerBit, .footer .widget {
|
||||
border-top: 1px solid var(--element-border-color);
|
||||
padding: 12px;
|
||||
|
@ -1456,15 +1470,29 @@ textarea {
|
|||
max-width: 1000px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
.footer {
|
||||
max-width: 1000px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
#main {
|
||||
padding-top: 18px;
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
border-left: 1px solid hsl(20,0%,95%);
|
||||
border-right: 1px solid hsl(20,0%,95%);
|
||||
}
|
||||
#back {
|
||||
.footer {
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
#back, .footer, .footBlock {
|
||||
background-color: hsl(0,0%,95%);
|
||||
}
|
||||
#back:not(.zone_panel) .footBlock {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@media(min-width: 721px) {
|
||||
|
@ -1591,12 +1619,17 @@ textarea {
|
|||
font-size: 18px;
|
||||
}
|
||||
main > .rowhead, #main > .rowhead {
|
||||
margin-left: 0px;
|
||||
margin-right: 0px;
|
||||
border: none;
|
||||
border-bottom: 2px solid var(--header-border-color);
|
||||
}
|
||||
|
||||
#main {
|
||||
padding-top: 0px;
|
||||
}
|
||||
main > .rowhead, #main > .rowhead, main > .opthead, #main > .opthead {
|
||||
margin-left: -3px;
|
||||
margin-right: -3px;
|
||||
}
|
||||
|
||||
.topic_list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
@ -1622,13 +1655,13 @@ textarea {
|
|||
border-left: 1px solid var(--element-border-color);
|
||||
background-color: hsl(0,0%,95%);
|
||||
}
|
||||
.topic_right br, .topic_right img {
|
||||
.topic_right_inside br, .topic_right_inside img {
|
||||
display: none;
|
||||
}
|
||||
.topic_right.topic_sticky {
|
||||
border-bottom: 2px solid var(--element-border-color);
|
||||
}
|
||||
.topic_right > span {
|
||||
.topic_right_inside > span {
|
||||
margin-top: 6px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
@ -1729,13 +1762,6 @@ textarea {
|
|||
}
|
||||
}
|
||||
@media(max-width: 520px) {
|
||||
#main {
|
||||
padding-top: 0px;
|
||||
}
|
||||
main > .rowhead, #main > .rowhead, main > .opthead, #main > .opthead {
|
||||
margin-left: -3px;
|
||||
margin-right: -3px;
|
||||
}
|
||||
.edit_item, .button_container .open_edit, .delete_item, .pin_item, .unpin_item, .lock_item, .unlock_item, .ip_item_button, .report_item:not(.profile_menu_item) {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
border-left: none;
|
||||
border-right: none;
|
||||
padding-left: 0px;
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
#back {
|
||||
background-color: inherit;
|
||||
|
@ -147,6 +148,10 @@
|
|||
padding-bottom: 4px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
#panel_setting textarea {
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
#forum_quick_perms .formitem {
|
||||
display: flex;
|
||||
|
@ -256,4 +261,17 @@
|
|||
.colstack_left {
|
||||
margin-top: -14.5px;
|
||||
}
|
||||
}
|
||||
|
||||
@media(min-width: 1000px) {
|
||||
.footBlock {
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
}
|
||||
.footer {
|
||||
max-width: none;
|
||||
width: 100%;
|
||||
margin-left: 0px;
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,320 @@
|
|||
:root {
|
||||
--darkest-background: #222222;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
color: #AAAAAA;
|
||||
font-family: "Segoe UI";
|
||||
}
|
||||
a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
nav.nav {
|
||||
background: var(--darkest-background);
|
||||
border-bottom: 1px solid #444444;
|
||||
width: calc(100% - 200px);
|
||||
float: left;
|
||||
}
|
||||
ul {
|
||||
list-style-type: none;
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
clear: both;
|
||||
}
|
||||
li {
|
||||
float: left;
|
||||
margin-right: 12px;
|
||||
}
|
||||
li a {
|
||||
padding-top: 35px;
|
||||
padding-bottom: 22px;
|
||||
font-size: 18px;
|
||||
display: inline-block;
|
||||
color: #aaaaaa;
|
||||
}
|
||||
#menu_overview {
|
||||
margin-right: 24px;
|
||||
}
|
||||
#menu_overview a {
|
||||
font-size: 22px;
|
||||
padding-bottom: 21px;
|
||||
color: rgb(221,221,221);
|
||||
padding-top: 31px;
|
||||
}
|
||||
.menu_topics a {
|
||||
border-bottom: 2px solid #777777;
|
||||
padding-bottom: 21px;
|
||||
color: #dddddd;
|
||||
}
|
||||
.menu_alerts {
|
||||
display: none;
|
||||
}
|
||||
.right_of_nav {
|
||||
float: left;
|
||||
width: 200px;
|
||||
background-color: var(--darkest-background);
|
||||
border-bottom: 1px solid #444444;
|
||||
padding-top: 12px;
|
||||
padding-bottom: 12px;
|
||||
padding-right: 12px;
|
||||
}
|
||||
.user_box {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background-color: #333333;
|
||||
border: 1px solid #444444;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
.user_box img {
|
||||
display: block;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 32px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
.user_box .username {
|
||||
display: block;
|
||||
font-size: 16px;
|
||||
padding-top: 4px;
|
||||
line-height: 10px;
|
||||
}
|
||||
.user_box .alerts {
|
||||
font-size: 12px;
|
||||
line-height: 12px;
|
||||
}
|
||||
.container {
|
||||
clear: both;
|
||||
}
|
||||
#back {
|
||||
background: #333333;
|
||||
padding: 24px;
|
||||
padding-top: 12px;
|
||||
clear: both;
|
||||
display: flex;
|
||||
}
|
||||
#main, #main .rowblock {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 320px;
|
||||
}
|
||||
.rowblock:not(.topic_list):not(.rowhead):not(.opthead) .rowitem {
|
||||
background-color: #444444;
|
||||
border-color: #555555;
|
||||
display: flex;
|
||||
padding: 12px;
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
h1, h3 {
|
||||
-webkit-margin-before: 0;
|
||||
-webkit-margin-after: 0;
|
||||
margin-block-start: 0;
|
||||
margin-block-end: 0;
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.mod_floater, .modal_pane {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.rowhead, .opthead {
|
||||
margin-left: 18px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.rowhead h1, .opthead h1 {
|
||||
font-size: 23px;
|
||||
}
|
||||
.sidebar .rowhead {
|
||||
margin-top: 4px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.sidebar .rowhead h1 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.topic_row:not(:last-child) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.topic_row {
|
||||
background-color: #444444;
|
||||
border-color: #555555;
|
||||
display: flex;
|
||||
}
|
||||
.topic_left, .topic_right, .topic_middle {
|
||||
padding: 16px;
|
||||
padding-bottom: 12px;
|
||||
display: flex;
|
||||
width: 33%;
|
||||
}
|
||||
.topic_middle {
|
||||
padding-top: 15px;
|
||||
}
|
||||
.topic_left {
|
||||
margin-right: auto;
|
||||
}
|
||||
.topic_left img, .topic_right img {
|
||||
border-radius: 24px;
|
||||
height: 38px;
|
||||
width: 38px;
|
||||
margin-right: 8px;
|
||||
margin-top: 1px;
|
||||
}
|
||||
.topic_inner_left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.topic_inner_left .parent_forum {
|
||||
display: none; /* Comment this until we figure out how to make it work */
|
||||
}
|
||||
.topic_right_inside {
|
||||
display: flex;
|
||||
margin-left: auto;
|
||||
width: 180px;
|
||||
}
|
||||
.topic_right_inside .lastName, .topic_left .rowtopic {
|
||||
font-size: 15px !important;
|
||||
line-height: 22px;
|
||||
}
|
||||
.topic_right_inside .lastReplyAt, .topic_left .starter {
|
||||
font-size: 14px;
|
||||
line-height: 14px;
|
||||
}
|
||||
.topic_right_inside span {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.topic_inner_left br, .topic_right_inside br, .topic_inner_right {
|
||||
display: none;
|
||||
}
|
||||
.topic_middle .replyCount:after {
|
||||
content: "replies";
|
||||
margin-left: 3px;
|
||||
}
|
||||
.topic_middle .likeCount:after {
|
||||
content: "likes";
|
||||
margin-left: 3px;
|
||||
}
|
||||
.topic_middle_inside {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
width: 80px;
|
||||
}
|
||||
.topic_status_e {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.pageset {
|
||||
display: flex;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.pageitem {
|
||||
background-color: #444444;
|
||||
padding: 6px;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
#prevFloat, #nextFloat {
|
||||
display: none;
|
||||
}
|
||||
.forum_list .rowitem {
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
}
|
||||
.forum_list .forum_left {
|
||||
margin-left: 8px;
|
||||
}
|
||||
.forum_list .forum_nodesc {
|
||||
font-style: italic;
|
||||
}
|
||||
.forum_list .forum_right {
|
||||
display: flex;
|
||||
margin-left: auto;
|
||||
margin-right: 8px;
|
||||
padding-top: 2px;
|
||||
width: 140px;
|
||||
}
|
||||
.forum_list .forum_right img {
|
||||
margin-right: 10px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
.forum_list .forum_right span {
|
||||
line-height: 19px;
|
||||
}
|
||||
.extra_little_row_avatar {
|
||||
border-radius: 24px;
|
||||
height: 36px;
|
||||
width: 36px;
|
||||
}
|
||||
|
||||
.footer .widget {
|
||||
padding: 12px;
|
||||
}
|
||||
#poweredByHolder {
|
||||
display: flex;
|
||||
padding: 12px;
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
}
|
||||
#poweredBy {
|
||||
margin-right: auto;
|
||||
}
|
||||
.footer .widget, #poweredByHolder {
|
||||
background-color: #444444;
|
||||
border-top: 1px solid #555555;
|
||||
}
|
||||
|
||||
@media(min-width: 1010px) {
|
||||
.container {
|
||||
background-color: #292929;
|
||||
}
|
||||
#back {
|
||||
width: 1000px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
border-left: 1px solid #444444;
|
||||
border-right: 1px solid #444444;
|
||||
}
|
||||
.footBlock {
|
||||
display: flex;
|
||||
}
|
||||
.footer {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
width: 1000px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.footer .widget, #poweredByHolder {
|
||||
border-left: 1px solid #555555;
|
||||
border-right: 1px solid #555555;
|
||||
}
|
||||
}
|
||||
|
||||
@media(min-width: 1330px) {
|
||||
nav.nav {
|
||||
width: calc(85% - 200px)
|
||||
}
|
||||
ul {
|
||||
margin-left: 205px;
|
||||
}
|
||||
.right_of_nav {
|
||||
width: calc(15% + 200px);
|
||||
}
|
||||
.user_box {
|
||||
width: 200px;
|
||||
}
|
||||
}
|
|
@ -21,7 +21,7 @@ body {
|
|||
background-color: var(--main-background-color);
|
||||
margin: 0;
|
||||
}
|
||||
p::selection, span::selection, a::selection {
|
||||
*::selection {
|
||||
background-color: hsl(0,0%,75%);
|
||||
color: hsl(0,0%,20%);
|
||||
font-weight: 100;
|
||||
|
@ -226,6 +226,9 @@ a {
|
|||
}
|
||||
|
||||
/* TODO: Add the avatars to the forum list */
|
||||
.forum_list .forum_nodesc {
|
||||
font-style: italic;
|
||||
}
|
||||
.extra_little_row_avatar {
|
||||
display: none;
|
||||
}
|
||||
|
@ -377,7 +380,7 @@ textarea.large {
|
|||
display: block;
|
||||
}
|
||||
|
||||
.formitem button, .formbutton {
|
||||
.formitem button, .formbutton, .mod_floater_submit, .pane_buttons button {
|
||||
background-color: var(--input-background-color);
|
||||
border: 1px solid var(--input-border-color);
|
||||
color: var(--input-text-color);
|
||||
|
@ -385,6 +388,15 @@ textarea.large {
|
|||
padding-bottom: 6px;
|
||||
font-size: 13px;
|
||||
}
|
||||
.mod_floater_submit {
|
||||
padding: 5px;
|
||||
padding-bottom: 4px;
|
||||
margin-left: 2px;
|
||||
}
|
||||
.pane_buttons button {
|
||||
padding: 5px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.formrow {
|
||||
flex-direction: row;
|
||||
|
@ -666,16 +678,97 @@ input[type=checkbox]:checked + label.poll_option_label .sel {
|
|||
font-size: 11px;
|
||||
}
|
||||
|
||||
.topic_list_title_block .pre_opt:before {
|
||||
content: "{{index .Phrases "topics_click_topics_to_select"}}";
|
||||
font-size: 14px;
|
||||
}
|
||||
.create_topic_opt a:before {
|
||||
content: "{{index .Phrases "topics_new_topic"}}";
|
||||
margin-left: 3px;
|
||||
}
|
||||
.locked_opt a:before {
|
||||
content: "{{index .Phrases "forum_locked"}}";
|
||||
}
|
||||
.mod_opt a {
|
||||
margin-left: 4px;
|
||||
}
|
||||
.mod_opt a:after {
|
||||
content: "{{index .Phrases "topics_moderate"}}";
|
||||
padding-left: 1px;
|
||||
}
|
||||
.create_topic_opt {
|
||||
order: 1;
|
||||
}
|
||||
.mod_opt {
|
||||
order: 2;
|
||||
}
|
||||
.pre_opt {
|
||||
order: 3;
|
||||
margin-left: auto;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
@keyframes fadein {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
.mod_floater {
|
||||
position: fixed;
|
||||
bottom: 15px;
|
||||
right: 15px;
|
||||
width: 150px;
|
||||
height: 65px;
|
||||
font-size: 14px;
|
||||
padding: 14px;
|
||||
z-index: 9999;
|
||||
animation: fadein 0.8s;
|
||||
background-color: var(--main-block-color);
|
||||
}
|
||||
.mod_floater_head {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.modal_pane {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--main-block-color);
|
||||
border: 2px solid #333333;
|
||||
padding-left: 24px;
|
||||
padding-right: 24px;
|
||||
z-index: 9999;
|
||||
animation: fadein 0.8s;
|
||||
}
|
||||
.pane_header {
|
||||
font-size: 15px;
|
||||
}
|
||||
.pane_header h3 {
|
||||
-webkit-margin-before: 0;
|
||||
-webkit-margin-after: 0;
|
||||
margin-block-start: 0;
|
||||
margin-block-end: 0;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
font-weight: normal;
|
||||
}
|
||||
.pane_row {
|
||||
font-size: 14px;
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
.pane_selected {
|
||||
font-weight: bold;
|
||||
}
|
||||
.pane_buttons {
|
||||
margin-top: 7px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.topic_list .topic_row {
|
||||
display: flex;
|
||||
}
|
||||
.topic_selected .rowitem {
|
||||
background-color: hsla(0, 0%, 29%, 1);
|
||||
}
|
||||
/* Temporary hack, so that I don't break the topic lists of the other themes */
|
||||
.topic_list .topic_inner_right {
|
||||
display: none;
|
||||
|
@ -706,10 +799,13 @@ input[type=checkbox]:checked + label.poll_option_label .sel {
|
|||
width: 284px;
|
||||
padding: 0px;
|
||||
}
|
||||
.topic_right_inside {
|
||||
display: flex;
|
||||
}
|
||||
.topic_list .topic_left img, .topic_list .topic_right img {
|
||||
width: 64px;
|
||||
}
|
||||
.topic_list .topic_inner_left, .topic_right > span {
|
||||
.topic_list .topic_inner_left, .topic_right_inside > span {
|
||||
margin-left: 8px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
@ -725,6 +821,9 @@ input[type=checkbox]:checked + label.poll_option_label .sel {
|
|||
.topic_list .starter:before {
|
||||
content: "{{index .Phrases "topics_starter"}}: ";
|
||||
}
|
||||
.topic_middle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.topic_name_input {
|
||||
width: 100%;
|
||||
|
|
|
@ -158,6 +158,10 @@ li a {
|
|||
#back {
|
||||
padding: 12px;
|
||||
padding-top: 0px;
|
||||
display: flex;
|
||||
}
|
||||
#main {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Explict declaring each border direction to fix a bug in Chrome where an override to .rowhead was also applying to .rowblock in some cases */
|
||||
|
@ -419,6 +423,9 @@ li a {
|
|||
}
|
||||
|
||||
/* TODO: Add the avatars to the forum list */
|
||||
.forum_list .forum_nodesc {
|
||||
font-style: italic;
|
||||
}
|
||||
.extra_little_row_avatar {
|
||||
display: none;
|
||||
}
|
||||
|
@ -434,6 +441,7 @@ li a {
|
|||
.topic_list .topic_row {
|
||||
display: grid;
|
||||
grid-template-columns: calc(100% - 204px) 204px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.topic_list .topic_inner_right {
|
||||
display: none;
|
||||
|
@ -466,14 +474,20 @@ li a {
|
|||
padding: 0px;
|
||||
height: 58px;
|
||||
}
|
||||
.topic_left img, .topic_right img {
|
||||
.topic_right_inside {
|
||||
display: flex;
|
||||
}
|
||||
.topic_left img, .topic_right_inside img {
|
||||
width: 64px;
|
||||
height: auto;
|
||||
}
|
||||
.topic_left .topic_inner_left, .topic_right > span {
|
||||
.topic_left .topic_inner_left, .topic_right_inside > span {
|
||||
margin-top: 10px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
.topic_middle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.postImage {
|
||||
max-width: 100%;
|
||||
|
@ -969,9 +983,13 @@ input[type=checkbox]:checked + label.poll_option_label .sel {
|
|||
content: "{{index .Phrases "topic_report_button_text"}}";
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-left: 12px;
|
||||
margin-right: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
#poweredByHolder {
|
||||
border: 1px solid var(--main-border-color);
|
||||
margin-top: 12px;
|
||||
clear: both;
|
||||
height: 40px;
|
||||
padding: 6px;
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
# Theme Notes
|
||||
|
||||
/public/post-avatar-bg.jpg is a solid rgb(255,255,255) white.
|
||||
|
|
@ -1,754 +0,0 @@
|
|||
* {
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: cursive;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
/* Patch for Edge, until they fix emojis in arial x.x */
|
||||
@supports (-ms-ime-align:auto) { .user_content { font-family: Segoe UI Emoji, arial; } }
|
||||
|
||||
ul {
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
height: 36px;
|
||||
list-style-type: none;
|
||||
border: 1px solid #ccc;
|
||||
background-color: white;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
li {
|
||||
height: 35px;
|
||||
padding-left: 10px;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
li:hover { background: rgb(250,250,250); }
|
||||
li a {
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
font-size: 17px;
|
||||
}
|
||||
.menu_left {
|
||||
float: left;
|
||||
border-right: 1px solid #ccc;
|
||||
padding-right: 10px;
|
||||
font-family: cursive;
|
||||
padding-top: 4px;
|
||||
}
|
||||
.menu_right {
|
||||
float: right;
|
||||
border-left: 1px solid #ccc;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
#menu_forums a:after {
|
||||
content: "Forums";
|
||||
}
|
||||
.menu_topics a:after {
|
||||
content: "Topics";
|
||||
}
|
||||
.menu_account a:after {
|
||||
content: "Account";
|
||||
}
|
||||
.menu_profile a:after {
|
||||
content: "Profile";
|
||||
}
|
||||
.menu_panel a:after {
|
||||
content: "Panel";
|
||||
}
|
||||
.menu_logout a:after {
|
||||
content: "Logout";
|
||||
}
|
||||
.menu_login a:after {
|
||||
content: "Login";
|
||||
}
|
||||
.menu_register a:after {
|
||||
content: "Register";
|
||||
}
|
||||
|
||||
.alert_bell:before {
|
||||
content: '🔔︎';
|
||||
}
|
||||
.menu_bell {
|
||||
cursor: default;
|
||||
}
|
||||
.menu_alerts {
|
||||
font-size: 20px;
|
||||
padding-top: 2px;
|
||||
color: rgb(80,80,80);
|
||||
z-index: 500;
|
||||
}
|
||||
.menu_alerts .alert_counter {
|
||||
position: relative;
|
||||
font-family: arial;
|
||||
font-size: 8px;
|
||||
top: -25px;
|
||||
background-color: rgb(190,0,0);
|
||||
color: white;
|
||||
width: 14px;
|
||||
left: 10px;
|
||||
line-height: 8px;
|
||||
padding-top: 2.5px;
|
||||
height: 14px;
|
||||
text-align: center;
|
||||
border: white solid 1px;
|
||||
}
|
||||
.menu_alerts .alert_counter:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.selectedAlert, .selectedAlert:hover {
|
||||
background: white;
|
||||
color: black;
|
||||
}
|
||||
.menu_alerts .alertList {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.selectedAlert .alertList {
|
||||
position: absolute;
|
||||
top: 51px;
|
||||
display: block;
|
||||
background: white;
|
||||
font-size: 10px;
|
||||
line-height: 16px;
|
||||
width: 300px;
|
||||
right: calc(5% + 7px);
|
||||
border: 1px solid #ccc;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.alertItem {
|
||||
padding: 8px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding-top: 15px;
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
.alertItem.withAvatar {
|
||||
background-size: 60px;
|
||||
background-repeat: no-repeat;
|
||||
padding-right: 12px;
|
||||
padding-left: 68px;
|
||||
height: 50px;
|
||||
}
|
||||
.alertItem.withAvatar:not(:last-child) {
|
||||
border-bottom: 1px solid rgb(230,230,230);
|
||||
}
|
||||
.alertItem.withAvatar .text {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
float: right;
|
||||
height: 40px;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.alertItem .text {
|
||||
font-size: 13px;
|
||||
font-weight: normal;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 90%;
|
||||
padding: 0px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.rowblock {
|
||||
border: 1px solid #ccc;
|
||||
width: 100%;
|
||||
padding: 0px;
|
||||
padding-top: 0px;
|
||||
}
|
||||
.rowblock:empty {
|
||||
display: none;
|
||||
}
|
||||
.rowsmall {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.bgsub {
|
||||
display: none;
|
||||
}
|
||||
.bgavatars .rowitem {
|
||||
background-repeat: no-repeat;
|
||||
background-size: 50px;
|
||||
padding-left: 58px;
|
||||
}
|
||||
|
||||
.colstack_left {
|
||||
float: left;
|
||||
width: 30%;
|
||||
margin-right: 8px;
|
||||
}
|
||||
.colstack_right {
|
||||
float: left;
|
||||
width: 65%;
|
||||
width: calc(70% - 15px);
|
||||
}
|
||||
.colstack_item {
|
||||
border: 1px solid #ccc;
|
||||
padding: 0px;
|
||||
padding-top: 0px;
|
||||
width: 100%;
|
||||
margin-bottom: 12px;
|
||||
overflow: hidden;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.colstack_head { margin-bottom: 0px; }
|
||||
.colstack_left:empty, .colstack_right:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.colstack_grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
/*grid-gap: 15px;*/
|
||||
grid-gap: 12px;
|
||||
margin-left: 5px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
.grid_item {
|
||||
border: 1px solid #ccc;
|
||||
word-wrap: break-word;
|
||||
background-color: white;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
.grid_stat, .grid_istat {
|
||||
/*padding-top: 15px;*/
|
||||
text-align: center;
|
||||
/*padding-bottom: 15px;
|
||||
font-size: 20px;*/
|
||||
padding-top: 12px;
|
||||
padding-bottom: 12px;
|
||||
font-size: 16px;
|
||||
}
|
||||
.grid_istat {
|
||||
/*margin-bottom: 10px;*/
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.stat_green {
|
||||
background-color: lightgreen;
|
||||
border-color: lightgreen;
|
||||
}
|
||||
.stat_orange {
|
||||
background-color: #ffe4b3;
|
||||
border-color: #ffe4b3;
|
||||
}
|
||||
.stat_red {
|
||||
background-color: #ffb2b2;
|
||||
border-color: #ffb2b2;
|
||||
}
|
||||
.stat_disabled {
|
||||
background-color: lightgray;
|
||||
border-color: lightgray;
|
||||
}
|
||||
|
||||
.rowitem {
|
||||
width: 100%;
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
padding-top: 12px;
|
||||
padding-bottom: 12px;
|
||||
background-color: white;
|
||||
font-family: cursive;
|
||||
}
|
||||
.rowitem:not(:last-child) {
|
||||
border-bottom: 1px dotted #ccc;
|
||||
}
|
||||
.rowitem a {
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
}
|
||||
.rowitem a:hover { color: silver; }
|
||||
.top_post { margin-bottom: 16px; }
|
||||
.opthead { display: none; }
|
||||
|
||||
.datarow {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.formrow {
|
||||
width: 100%;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
/* Clearfix */
|
||||
.formrow:before, .formrow:after {
|
||||
content: " ";
|
||||
display: table;
|
||||
}
|
||||
.formrow:after { clear: both; }
|
||||
.formrow:not(:last-child) { border-bottom: 1px dotted #ccc; }
|
||||
|
||||
.formitem {
|
||||
float: left;
|
||||
padding: 10px;
|
||||
min-width: 20%;
|
||||
/*font-size: 17px;*/
|
||||
font-weight: normal;
|
||||
}
|
||||
.formitem:not(:last-child) { border-right: 1px dotted #ccc; }
|
||||
.formitem.invisible_border { border: none; }
|
||||
|
||||
/* Mostly for textareas */
|
||||
.formitem:only-child { width: 100%; }
|
||||
.formitem textarea {
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
outline-color: #8e8e8e;
|
||||
}
|
||||
.formitem:has-child() {
|
||||
margin: 0 auto;
|
||||
float: none;
|
||||
}
|
||||
.formitem:not(:only-child) input, .formitem:not(:only-child) select {
|
||||
padding: 3px;/*5px;*/
|
||||
}
|
||||
.formitem:not(:only-child).formlabel {
|
||||
padding-top: 15px;/*18px;*/
|
||||
padding-bottom: 12px;/*16px;*/
|
||||
/*padding-left: 15px;*/
|
||||
}
|
||||
.formbutton {
|
||||
padding: 7px;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
font-size: 15px;
|
||||
border-color: #ccc;
|
||||
}
|
||||
|
||||
.dont_have_account {
|
||||
color: #505050;
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
float: right;
|
||||
}
|
||||
|
||||
button, input[type="submit"] {
|
||||
background: white;
|
||||
border: 1px solid #8e8e8e;
|
||||
}
|
||||
|
||||
/* TODO: Add the avatars to the forum list */
|
||||
.extra_little_row_avatar {
|
||||
display: none;
|
||||
}
|
||||
.shift_left {
|
||||
float: left;
|
||||
}
|
||||
.shift_right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
/* Topics */
|
||||
|
||||
.topic_list .starter:before {
|
||||
content: "Starter: ";
|
||||
}
|
||||
|
||||
.topic_sticky {
|
||||
background-color: rgb(255,255,234);
|
||||
}
|
||||
.topic_closed {
|
||||
background-color: rgb(248,248,248);
|
||||
}
|
||||
|
||||
.topic_status {
|
||||
text-transform: none;
|
||||
margin-left: 8px;
|
||||
padding-left: 2px;
|
||||
padding-right: 2px;
|
||||
padding-top: 2px;
|
||||
padding-bottom: 2px;
|
||||
background-color: #E8E8E8; /* 232,232,232. All three RGB colours being the same seems to create a shade of gray */
|
||||
color: #505050; /* 80,80,80 */
|
||||
border-radius: 2px;
|
||||
}
|
||||
.topic_status:empty { display: none; }
|
||||
|
||||
.username, .panel_tag {
|
||||
text-transform: none;
|
||||
margin-left: 0px;
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
padding-top: 2px;
|
||||
padding-bottom: 2px;
|
||||
color: #505050; /* 80,80,80 */
|
||||
font-size: 15px;
|
||||
background: none;
|
||||
}
|
||||
button.username {
|
||||
position: relative;
|
||||
top: -0.25px;
|
||||
}
|
||||
.username.level { color: #303030; }
|
||||
.username.real_username {
|
||||
color: #404040;
|
||||
font-size: 16px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
.username.real_username:hover { color: black; }
|
||||
|
||||
.tag-text {
|
||||
padding-top: 23px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.user_tag {
|
||||
float: right;
|
||||
color: #505050;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.post_item {
|
||||
background-size: 128px;
|
||||
padding-left: calc(128px + 12px);
|
||||
}
|
||||
|
||||
.controls {
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
/*margin-top: 20px;*/
|
||||
}
|
||||
|
||||
.controls > .username {
|
||||
display: inline-block;
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
|
||||
.real_username {
|
||||
margin-right: -8px;
|
||||
}
|
||||
|
||||
.mod_button > button {
|
||||
font-family: cursive;
|
||||
font-size: 12px;
|
||||
color: #202020;
|
||||
opacity: 0.7;
|
||||
border: none;
|
||||
}
|
||||
.post_item > .mod_button > button:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.mod_button:not(:last-child) {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.like_label:before {
|
||||
content: "+1";
|
||||
}
|
||||
.like_count_label:before {
|
||||
content: "likes";
|
||||
}
|
||||
.like_count_label {
|
||||
color: #202020;
|
||||
opacity: 0.7;
|
||||
font-size: 12px;
|
||||
}
|
||||
.like_count {
|
||||
color: #202020;
|
||||
opacity: 0.7;
|
||||
padding-left: 1px;
|
||||
padding-right: 2px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.like_count:before {
|
||||
content: "|";
|
||||
margin-right: 5px;
|
||||
}
|
||||
.edit_label:before { content: "Edit"; }
|
||||
.trash_label:before { content: "Delete"; }
|
||||
.pin_label:before { content: "Pin"; }
|
||||
.unpin_label:before { content: "Unpin"; }
|
||||
.flag_label:before { content: "Flag"; }
|
||||
.level_label { margin-right: 1px; color: #505050; }
|
||||
.level_label:before { content: "Level"; opacity:0.85; }
|
||||
|
||||
.controls {
|
||||
margin-top: 23px;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.action_item {
|
||||
padding: 14px;
|
||||
text-align: center;
|
||||
background-color: rgb(255,245,245);
|
||||
}
|
||||
|
||||
.postQuote {
|
||||
border: 1px solid #ccc;
|
||||
background: white;
|
||||
padding: 5px;
|
||||
margin: 0px;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.level {
|
||||
float: right;
|
||||
border-left: none;
|
||||
padding-left: 3px;
|
||||
padding-right: 5px;
|
||||
font-family: cursive;
|
||||
font-size: 15px;
|
||||
color: #202020;
|
||||
opacity: 0.7;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.mention {
|
||||
font-weight: bold;
|
||||
}
|
||||
.show_on_edit, .auto_hide, .hide_on_big, .show_on_mobile {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.alert {
|
||||
display: block;
|
||||
padding: 5px;
|
||||
margin-bottom: 10px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
.alert_success {
|
||||
display: block;
|
||||
padding: 5px;
|
||||
border: 1px solid A2FC00;
|
||||
margin-bottom: 10px;
|
||||
background-color: DAF7A6;
|
||||
}
|
||||
.alert_error {
|
||||
display: block;
|
||||
padding: 5px;
|
||||
border: 1px solid #FF004B;
|
||||
margin-bottom: 8px;
|
||||
background-color: #FEB7CC;
|
||||
}
|
||||
.prev_button, .next_button {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
font-size: 30px;
|
||||
border-width: 1px;
|
||||
background-color: #FFFFFF;
|
||||
border-style: dotted;
|
||||
border-color: #505050;
|
||||
padding: 0px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.prev_button a, .next_button a {
|
||||
line-height: 28px;
|
||||
margin-top: 2px;
|
||||
margin-bottom: 0px;
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
color: #505050;
|
||||
}
|
||||
.prev_button { left: 14px; }
|
||||
.next_button { right: 14px; }
|
||||
|
||||
.head_tag_upshift {
|
||||
color: #202020;
|
||||
opacity: 0.7;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
#profile_comments .rowitem {
|
||||
background-repeat: no-repeat, repeat-y;
|
||||
background-size: 128px;
|
||||
padding-left: 136px;
|
||||
}
|
||||
|
||||
#profile_left_lane .avatarRow {
|
||||
padding: 0;
|
||||
}
|
||||
#profile_left_pane .nameRow .username {
|
||||
float: right;
|
||||
font-weight: normal;
|
||||
}
|
||||
#profile_left_pane .report_item:after {
|
||||
content: "Report";
|
||||
}
|
||||
|
||||
/* Media Queries */
|
||||
|
||||
@media(min-width: 881px) {
|
||||
.shrink_main {
|
||||
float: left;
|
||||
width: calc(75% - 12px);
|
||||
}
|
||||
.sidebar {
|
||||
float: left;
|
||||
width: 25%;
|
||||
margin-left: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 880px) {
|
||||
li {
|
||||
height: 29px;
|
||||
font-size: 15px;
|
||||
padding-left: 9px;
|
||||
padding-top: 2px;
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
ul {
|
||||
height: 30px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.menu_left { padding-right: 9px; padding-top: 2px; }
|
||||
.menu_right { padding-right: 9px; }
|
||||
.menu_alerts {
|
||||
padding-left: 7px;
|
||||
padding-right: 7px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
body {
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
margin: 0px !important;
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.container { width: auto; }
|
||||
.sidebar { display: none; }
|
||||
.selectedAlert .alertList { top: 37px; right: 4px; }
|
||||
}
|
||||
|
||||
@media (max-width: 810px) {
|
||||
body { font-family: arial; }
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
li {
|
||||
padding-left: 5px;
|
||||
padding-top: 3px;
|
||||
padding-bottom: 2px;
|
||||
height: 25px;
|
||||
}
|
||||
li a { font-size: 14px; }
|
||||
ul { height: 26px; }
|
||||
.menu_left { padding-right: 5px; padding-top: 1px; }
|
||||
.menu_right { padding-right: 5px; }
|
||||
|
||||
.menu_alerts {
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
font-size: 16px;
|
||||
padding-top: 1px;
|
||||
}
|
||||
.menu_alerts .alert_counter {
|
||||
top: -23px;
|
||||
left: 8px;
|
||||
}
|
||||
.selectedAlert .alertList {
|
||||
top: 33px;
|
||||
}
|
||||
|
||||
.hide_on_mobile {
|
||||
display: none;
|
||||
}
|
||||
.prev_button, .next_button {
|
||||
top: auto;
|
||||
bottom: 5px;
|
||||
}
|
||||
.colstack_grid {
|
||||
grid-template-columns: none;
|
||||
grid-gap: 8px;
|
||||
}
|
||||
.grid_istat {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 350px) {
|
||||
.hide_on_micro { display: none !important; }
|
||||
}
|
||||
|
||||
@media (max-width: 470px) {
|
||||
#menu_overview, .menu_profile, .hide_on_micro { display: none; }
|
||||
.selectedAlert .alertList {
|
||||
width: 135px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.alertItem.withAvatar {
|
||||
background-size: 36px;
|
||||
text-align: right;
|
||||
padding-left: 10px;
|
||||
height: 46px;
|
||||
}
|
||||
.alertItem {
|
||||
padding: 8px;
|
||||
}
|
||||
.alertItem.withAvatar .text {
|
||||
width: calc(100% - 20px);
|
||||
height: 30px;
|
||||
white-space: normal;
|
||||
}
|
||||
.alertItem .text {
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
.post_container { overflow: visible !important; }
|
||||
.post_item {
|
||||
background-position: 0px 2px !important;
|
||||
background-size: 64px auto !important;
|
||||
padding-left: 2px !important;
|
||||
min-height: 96px;
|
||||
position: relative !important;
|
||||
}
|
||||
.post_item > .user_content {
|
||||
margin-left: 75px !important;
|
||||
width: 100% !important;
|
||||
min-height: 45px;
|
||||
}
|
||||
.post_item > .mod_button {
|
||||
float: right !important;
|
||||
margin-left: 2px !important;
|
||||
position: relative;
|
||||
top: -14px;
|
||||
}
|
||||
.post_item > .mod_button > button { opacity: 1; }
|
||||
.post_item > .real_username {
|
||||
position: absolute;
|
||||
top: 70px;
|
||||
float: left;
|
||||
margin-top: -2px;
|
||||
padding-top: 3px !important;
|
||||
margin-right: 2px;
|
||||
width: 60px;
|
||||
font-size: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
.post_item > .controls {
|
||||
margin-top: 0px;
|
||||
margin-left: 74px;
|
||||
width: calc(100% - 74px);
|
||||
}
|
||||
.container { width: 100% !important; }
|
||||
}
|
||||
|
||||
@media (max-width: 330px) {
|
||||
li { padding-left: 6px; }
|
||||
.menu_left { padding-right: 6px; }
|
||||
.menu_alerts { border-left: none; }
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
/* Control Panel */
|
||||
|
||||
.edit_button:before {
|
||||
content: "Edit";
|
||||
}
|
||||
.delete_button:after {
|
||||
content: "Delete";
|
||||
}
|
||||
|
||||
.tag-mini {
|
||||
margin-left: 0px;
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
padding-top: 2px;
|
||||
padding-bottom: 2px;
|
||||
|
||||
font-family: cursive;
|
||||
font-size: 12px;
|
||||
color: #202020;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.panel_floater {
|
||||
float: right;
|
||||
}
|
||||
#panel_groups > .rowitem > .panel_floater {
|
||||
float: none;
|
||||
}
|
||||
#panel_groups > .rowitem > .panel_floater > .panel_right_button {
|
||||
float: right;
|
||||
}
|
||||
#panel_forums > .rowitem > .panel_floater {
|
||||
float: none;
|
||||
}
|
||||
#panel_forums > .rowitem > .panel_floater > .panel_buttons {
|
||||
float: right;
|
||||
}
|
||||
#panel_forums > .rowitem > span > .forum_name {
|
||||
margin-right: 4px;
|
||||
}
|
||||
#panel_forums > .rowitem > .panel_floater > .panel_buttons > .panel_right_button {
|
||||
color: #505050;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#panel_word_filters .itemSeparator:before {
|
||||
content: " || ";
|
||||
padding-left: 2px;
|
||||
padding-right: 2px;
|
||||
}
|
||||
|
||||
.panel_rank_tag, .forum_preset, .forum_active {
|
||||
float: none;
|
||||
color: #202020;
|
||||
opacity: 0.7;
|
||||
font-size: 10px;
|
||||
}
|
||||
.panel_rank_tag_admin:before { content: "Admin Group"; }
|
||||
.panel_rank_tag_mod:before { content: "Mod Group"; }
|
||||
.panel_rank_tag_banned:before { content: "Banned Group"; }
|
||||
.panel_rank_tag_guest:before { content: "Guest Group"; }
|
||||
.panel_rank_tag_member:before { content: "Member Group"; }
|
||||
|
||||
.forum_preset_announce:after { content: "Announcements"; }
|
||||
.forum_preset_members:after { content: "Member Only"; }
|
||||
.forum_preset_staff:after { content: "Staff Only"; }
|
||||
.forum_preset_admins:after { content: "Admin Only"; }
|
||||
.forum_preset_archive:after { content: "Archive"; }
|
||||
.forum_preset_all:after { content: "Public"; }
|
||||
.forum_preset_custom, .forum_preset_ { display: none !important; }
|
||||
.forum_active_Hide:before { content: "Hidden"; }
|
||||
.forum_active_Hide + .forum_preset:before { content: " | "; }
|
||||
.forum_active_Show { display: none !important; }
|
||||
.forum_active_name { color: #707070; }
|
||||
.builtin_forum_divider { border-bottom-style: solid; }
|
||||
|
||||
.perm_preset_no_access:before { content: "No Access"; color: maroon; }
|
||||
.perm_preset_read_only:before { content: "Read Only"; color: green; }
|
||||
.perm_preset_can_post:before { content: "Can Post"; color: green; }
|
||||
.perm_preset_can_moderate:before { content: "Can Moderate"; color: darkblue; }
|
||||
.perm_preset_custom:before { content: "Custom"; color: black; }
|
||||
.perm_preset_default:before { content: "Default"; }
|
||||
|
||||
@media(max-width: 1300px) {
|
||||
.theme_row {
|
||||
background-image: none !important;
|
||||
}
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 539 B |
Binary file not shown.
Before Width: | Height: | Size: 240 KiB |
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"Name": "tempra-cursive",
|
||||
"FriendlyName": "Tempra Cursive",
|
||||
"Version": "0.1.0-dev",
|
||||
"Creator": "Azareal",
|
||||
"FullImage": "tempra-cursive.png",
|
||||
"ForkOf": "tempra-simple",
|
||||
"MobileFriendly": true,
|
||||
"HideFromThemes": true,
|
||||
"BgAvatars":true,
|
||||
"URL": "github.com/Azareal/Gosora",
|
||||
"Docks":["topMenu"],
|
||||
"Templates": [
|
||||
{
|
||||
"Name": "topic",
|
||||
"Source": "topic"
|
||||
}
|
||||
],
|
||||
"Docks":["rightSidebar"]
|
||||
}
|
|
@ -149,6 +149,12 @@ li a {
|
|||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
#back {
|
||||
display: flex;
|
||||
}
|
||||
#main {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.rowblock {
|
||||
border: 1px solid hsl(0, 0%, 80%);
|
||||
|
@ -420,6 +426,9 @@ input, select {
|
|||
}
|
||||
|
||||
/* TODO: Add the avatars to the forum list */
|
||||
.forum_list .forum_nodesc {
|
||||
font-style: italic;
|
||||
}
|
||||
.extra_little_row_avatar {
|
||||
display: none;
|
||||
}
|
||||
|
@ -474,14 +483,20 @@ input, select {
|
|||
height: 58px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.topic_left img, .topic_right img {
|
||||
.topic_right_inside {
|
||||
display: flex;
|
||||
}
|
||||
.topic_left img, .topic_right_inside img {
|
||||
width: 64px;
|
||||
height: auto;
|
||||
}
|
||||
.topic_left .topic_inner_left, .topic_right > span {
|
||||
.topic_left .topic_inner_left, .topic_right_inside > span {
|
||||
margin-top: 10px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
.topic_middle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.postImage {
|
||||
max-width: 100%;
|
||||
|
|
|
@ -10,6 +10,9 @@ go get -u github.com/denisenkom/go-mssqldb
|
|||
echo "Updating bcrypt"
|
||||
go get -u golang.org/x/crypto/bcrypt
|
||||
|
||||
echo "Updating Argon2"
|
||||
go get -u golang.org/x/crypto/argon2
|
||||
|
||||
echo "Updating gopsutil"
|
||||
go get -u github.com/Azareal/gopsutil
|
||||
|
||||
|
|
|
@ -26,6 +26,13 @@ if %errorlevel% neq 0 (
|
|||
exit /b %errorlevel%
|
||||
)
|
||||
|
||||
echo Updating Argon2
|
||||
go get -u golang.org/x/crypto/argon2
|
||||
if %errorlevel% neq 0 (
|
||||
pause
|
||||
exit /b %errorlevel%
|
||||
)
|
||||
|
||||
echo Updating /x/system/windows (dependency for gopsutil)
|
||||
go get -u golang.org/x/sys/windows
|
||||
if %errorlevel% neq 0 (
|
||||
|
|
Loading…
Reference in New Issue