Compare commits
50 Commits
Author | SHA1 | Date |
---|---|---|
Lars Hoogestraat | 25811479a8 | |
Lars Hoogestraat | becd502fbc | |
Lars Hoogestraat | a82a476f67 | |
Lars Hoogestraat | 0b4f8379b5 | |
Lars Hoogestraat | db4f41f1ac | |
Lars Hoogestraat | 8452e8bd52 | |
Lars Hoogestraat | 39d84c8edb | |
Lars Hoogestraat | e11d46d531 | |
Lars Hoogestraat | 4a58f2ed62 | |
Lars Hoogestraat | 9ce4aa4061 | |
Lars Hoogestraat | 4408371856 | |
Lars Hoogestraat | 82a94386fd | |
Lars Hoogestraat | 443693d17f | |
Lars Hoogestraat | 86a6ab3cd2 | |
Lars Hoogestraat | 85202d4b7d | |
Lars Hoogestraat | c66cf32cd7 | |
Lars Hoogestraat | ed0c47e094 | |
Lars Hoogestraat | aca5cd178c | |
Lars Hoogestraat | f36ab23580 | |
Lars Hoogestraat | d104b43b86 | |
Lars Hoogestraat | 6a3905f93e | |
Lars Hoogestraat | fca29a0819 | |
Lars Hoogestraat | 3d3352f5bd | |
Lars Hoogestraat | 44ae9fcb85 | |
Lars Hoogestraat | 563a00a9b9 | |
Lars Hoogestraat | 42ef2bf5cc | |
Lars Hoogestraat | bfa65a643b | |
Lars Hoogestraat | d7a432859d | |
Lars Hoogestraat | 21ed8c3a94 | |
Lars Hoogestraat | 0f1aea47ef | |
Lars Hoogestraat | 2a8e808ad9 | |
Lars Hoogestraat | 429ac762b2 | |
Lars Hoogestraat | 385c1207d3 | |
Lars Hoogestraat | dd32851030 | |
Lars Hoogestraat | c9a9d0f638 | |
Lars Hoogestraat | f416c8a55d | |
Lars Hoogestraat | f1dc6fa95a | |
Lars Hoogestraat | eb8beeeae6 | |
Lars Hoogestraat | 144cc03e91 | |
Lars Hoogestraat | aca7f56fa2 | |
Lars Hoogestraat | 193cb7c1c8 | |
Lars Hoogestraat | 574ebba89c | |
Lars Hoogestraat | e89c1bdb65 | |
Lars Hoogestraat | 3b5deff6a7 | |
Lars Hoogestraat | 95834ef9f1 | |
Lars Hoogestraat | b098acc6db | |
Lars Hoogestraat | 300d629680 | |
Lars Hoogestraat | ad9e4eaaff | |
Lars Hoogestraat | b25203d38b | |
Lars Hoogestraat | 7ebb9706c7 |
|
@ -32,9 +32,6 @@ releases
|
|||
|
||||
.idea
|
||||
.csrftoken
|
||||
/clt/createUser
|
||||
/clt/initDatabase
|
||||
/go-blog
|
||||
releases
|
||||
|
||||
db/
|
||||
|
|
8
Makefile
8
Makefile
|
@ -1,8 +1,14 @@
|
|||
BINARYNAME=go-blog
|
||||
TMP=tmp
|
||||
DIST=release
|
||||
GITHASH=$(shell git rev-parse HEAD)
|
||||
BUILD_VERSION=$(shell git describe --tags)
|
||||
|
||||
ifndef $(GOPATH)
|
||||
GOPATH=$(shell go env GOPATH)
|
||||
export GOPATH
|
||||
endif
|
||||
|
||||
RELEASE="releases"
|
||||
|
||||
LDFLAGS=-ldflags '-X main.BuildVersion=${BUILD_VERSION} -X main.GitHash=${GITHASH}'
|
||||
|
@ -24,7 +30,7 @@ install:
|
|||
package:
|
||||
-rm -r ${TMP}
|
||||
mkdir -p ${TMP}/clt
|
||||
-mkdir -p releases
|
||||
-mkdir -p releases/custom
|
||||
cp ${GOPATH}/bin/go-blog ${TMP}/
|
||||
cp ${GOPATH}/bin/create_user ${TMP}/clt
|
||||
cp ${GOPATH}/bin/init_database ${TMP}/clt
|
||||
|
|
|
@ -28,7 +28,7 @@ Create the initial database (switch to folder clt/):
|
|||
Change the config file to point to the correct sqlite database
|
||||
|
||||
~~~
|
||||
sqlite_file = /path/to/your/sqlite/database
|
||||
sqlite_file = /path/to/your/sqlite/database
|
||||
~~~
|
||||
|
||||
### Create user with administration rights ###
|
||||
|
|
|
@ -12,10 +12,9 @@ import (
|
|||
"os"
|
||||
"syscall"
|
||||
|
||||
"git.hoogi.eu/snafu/go-blog/components/database"
|
||||
"git.hoogi.eu/snafu/go-blog/components/logger"
|
||||
"git.hoogi.eu/snafu/go-blog/database"
|
||||
"git.hoogi.eu/snafu/go-blog/logger"
|
||||
"git.hoogi.eu/snafu/go-blog/models"
|
||||
"git.hoogi.eu/snafu/go-blog/utils"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
|
@ -38,14 +37,31 @@ func main() {
|
|||
|
||||
fmt.Printf("create_user version %s\n", BuildVersion)
|
||||
|
||||
username := flag.String("username", "", "Username for the admin user ")
|
||||
email := flag.String("email", "", "Email for the created user ")
|
||||
displayName := flag.String("displayname", "", "Display name for the admin user ")
|
||||
isAdmin := flag.Bool("admin", false, "If set a new administrator will be created; otherwise a non-admin is created")
|
||||
file := flag.String("sqlite", "", "Location to the sqlite3 database file")
|
||||
username := flag.String("username", "", "Username for the admin user. (required)")
|
||||
email := flag.String("email", "", "Email for the created user. (required)")
|
||||
displayName := flag.String("displayname", "", "Display name for the admin user. (required)")
|
||||
isAdmin := flag.Bool("admin", false, "If set a new user with admin permissions will be created; otherwise a non-admin is created.")
|
||||
file := flag.String("sqlite", "", "Location to the sqlite3 database file. (required)")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if *username == "" {
|
||||
fmt.Println("the username (-username) must be specified")
|
||||
os.Exit(1)
|
||||
}
|
||||
if *email == "" {
|
||||
fmt.Println("the email (-email) must be specified")
|
||||
os.Exit(1)
|
||||
}
|
||||
if *displayName == "" {
|
||||
fmt.Println("the display name (-displayname) must be specified")
|
||||
os.Exit(1)
|
||||
}
|
||||
if *file == "" {
|
||||
fmt.Println("the argument -sqlite is empty. Please specify the location of the sqlite3 database file")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if flag.Parsed() {
|
||||
initUser := createUserFlag{
|
||||
username: *username,
|
||||
|
@ -55,16 +71,13 @@ func main() {
|
|||
sqlite: *file,
|
||||
}
|
||||
|
||||
if err := initUser.validate(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("Password: ")
|
||||
pw, err := terminal.ReadPassword(int(syscall.Stdin))
|
||||
|
||||
fmt.Println("")
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("could not read password %v", err)
|
||||
fmt.Printf("could not read password %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
|
@ -85,28 +98,7 @@ func main() {
|
|||
}
|
||||
}
|
||||
|
||||
func (userFlags createUserFlag) validate() error {
|
||||
if utils.TrimmedStringIsEmpty(userFlags.username) {
|
||||
return fmt.Errorf("the username (-username) must be specified")
|
||||
}
|
||||
|
||||
if utils.TrimmedStringIsEmpty(userFlags.email) {
|
||||
return fmt.Errorf("the email (-email) must be specified")
|
||||
}
|
||||
if utils.TrimmedStringIsEmpty(userFlags.displayName) {
|
||||
return fmt.Errorf("the display name (-displayname) must be specified")
|
||||
}
|
||||
if utils.TrimmedStringIsEmpty(userFlags.sqlite) {
|
||||
return fmt.Errorf("the argument -sqlite is empty. Please specify the location of the sqlite3 database file")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (userFlags createUserFlag) CreateUser() error {
|
||||
|
||||
var userService models.UserService
|
||||
|
||||
dbConfig := database.SQLiteConfig{
|
||||
File: userFlags.sqlite,
|
||||
}
|
||||
|
@ -117,8 +109,8 @@ func (userFlags createUserFlag) CreateUser() error {
|
|||
return err
|
||||
}
|
||||
|
||||
userService = models.UserService{
|
||||
Datasource: models.SQLiteUserDatasource{
|
||||
userService := &models.UserService{
|
||||
Datasource: &models.SQLiteUserDatasource{
|
||||
SQLConn: db,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -12,8 +12,8 @@ import (
|
|||
"os"
|
||||
"strings"
|
||||
|
||||
"git.hoogi.eu/snafu/go-blog/components/database"
|
||||
"git.hoogi.eu/snafu/go-blog/components/logger"
|
||||
"git.hoogi.eu/snafu/go-blog/database"
|
||||
"git.hoogi.eu/snafu/go-blog/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
// Copyright 2018 Lars Hoogestraat
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package crypt provides utilities for creating hashes, random strings, password encryption
|
||||
package crypt
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha512"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
const bcryptRounds = 12
|
||||
|
||||
var (
|
||||
// AlphaUpper all upper alphas chars
|
||||
AlphaUpper = RandomSource("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||
// AlphaLower all lowers alphas chars
|
||||
AlphaLower = RandomSource("abcdefghijklmnopqrstuvwxyz")
|
||||
// AlphaUpperLower all upper and lowers aplhas chars
|
||||
AlphaUpperLower = RandomSource("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")
|
||||
// AlphaUpperLowerNumeric all upper lowers alphas and numerics
|
||||
AlphaUpperLowerNumeric = RandomSource("ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyz")
|
||||
// AlphaUpperLowerNumericSpecial all upper lowers alphas, numerics and special chars
|
||||
AlphaUpperLowerNumericSpecial = RandomSource("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz123456890" +
|
||||
"!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~")
|
||||
)
|
||||
|
||||
// RandomSource string containing which characters should be considered when generating random sequences
|
||||
type RandomSource string
|
||||
|
||||
// RandomSequence returns random character with given length;
|
||||
func (r RandomSource) RandomSequence(length int) []byte {
|
||||
result := make([]byte, length)
|
||||
|
||||
for i := 0; i < length; i++ {
|
||||
char, _ := rand.Int(rand.Reader, big.NewInt(int64(len(r))))
|
||||
result[i] = r[int(char.Int64())]
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// RandomSecureKey returns random character with given length
|
||||
func RandomSecureKey(length int) []byte {
|
||||
k := make([]byte, length)
|
||||
|
||||
if _, err := io.ReadFull(rand.Reader, k); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return k
|
||||
}
|
||||
|
||||
// CryptPassword hashes a password with bcrypt and a given cost
|
||||
func CryptPassword(password []byte) ([]byte, error) {
|
||||
return bcrypt.GenerateFromPassword(password, bcryptRounds)
|
||||
}
|
||||
|
||||
// GenerateSalt generates a random salt with alphanumerics and some special characters
|
||||
func GenerateSalt() []byte {
|
||||
return AlphaUpperLowerNumericSpecial.RandomSequence(32)
|
||||
}
|
||||
|
||||
// RandomHash returns a random SHA-512 hash
|
||||
func RandomHash(length int) string {
|
||||
hash := sha512.New()
|
||||
hash.Write(RandomSecureKey(length))
|
||||
|
||||
return hex.EncodeToString(hash.Sum(nil))
|
||||
}
|
|
@ -10,23 +10,17 @@ import (
|
|||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
//SQLiteConfig represents sqlite configuration type
|
||||
// SQLiteConfig represents sqlite configuration type
|
||||
type SQLiteConfig struct {
|
||||
File string
|
||||
}
|
||||
|
||||
//Open receives handle for sqlite database, returns an error if connection failed
|
||||
// Open receives handle for sqlite database, returns an error if connection failed
|
||||
func (d SQLiteConfig) Open() (*sql.DB, error) {
|
||||
db, err := sql.Open("sqlite3", d.File)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return db, nil
|
||||
return sql.Open("sqlite3", d.File)
|
||||
}
|
||||
|
||||
//InitTables creates the tables
|
||||
// InitTables creates the tables
|
||||
func InitTables(db *sql.DB) error {
|
||||
if _, err := db.Exec("CREATE TABLE user " +
|
||||
"(" +
|
||||
|
@ -148,5 +142,6 @@ func InitTables(db *sql.DB) error {
|
|||
");"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -60,7 +60,7 @@ application_overwrite_default_css = false
|
|||
# log levels: debug|info|warn|error|fatal|panic
|
||||
log_level = Error
|
||||
log_file = /var/log/go-blog/error.log
|
||||
log_access = false
|
||||
log_access = true
|
||||
log_access_file = /var/log/go-blog/access.log
|
||||
|
||||
########### BLOG SETTINGS ###########
|
||||
|
|
38
go.mod
38
go.mod
|
@ -1,20 +1,32 @@
|
|||
module git.hoogi.eu/snafu/go-blog
|
||||
|
||||
go 1.15
|
||||
go 1.21
|
||||
|
||||
toolchain go1.21.5
|
||||
|
||||
require (
|
||||
git.hoogi.eu/snafu/cfg v1.0.6
|
||||
git.hoogi.eu/snafu/session v1.1.2
|
||||
github.com/gorilla/csrf v1.7.0
|
||||
github.com/gorilla/handlers v1.5.1
|
||||
github.com/gorilla/mux v1.8.0
|
||||
git.hoogi.eu/snafu/session v1.3.0
|
||||
github.com/gorilla/csrf v1.7.2
|
||||
github.com/gorilla/handlers v1.5.2
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/justinas/alice v1.2.0
|
||||
github.com/mattn/go-sqlite3 v1.14.3
|
||||
github.com/microcosm-cc/bluemonday v1.0.4
|
||||
github.com/russross/blackfriday/v2 v2.0.1
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||
github.com/sirupsen/logrus v1.6.0
|
||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a
|
||||
golang.org/x/net v0.0.0-20200904194848-62affa334b73 // indirect
|
||||
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.19
|
||||
github.com/microcosm-cc/bluemonday v1.0.26
|
||||
github.com/russross/blackfriday/v2 v2.1.0
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
golang.org/x/crypto v0.17.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/gorilla/css v1.0.1 // indirect
|
||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/stretchr/testify v1.7.0 // indirect
|
||||
golang.org/x/net v0.19.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
golang.org/x/term v0.15.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
)
|
||||
|
|
192
go.sum
192
go.sum
|
@ -1,58 +1,182 @@
|
|||
git.hoogi.eu/snafu/cfg v1.0.6 h1:O34hYFqjwfnMjEwB4M8GaQQmBtf3H+AA0RFHb7PoMNs=
|
||||
git.hoogi.eu/snafu/cfg v1.0.6/go.mod h1:LQolv8bqH8ZPz7h9PSVswdBXj1BIM+kW79AVS/jpE7A=
|
||||
git.hoogi.eu/snafu/session v1.1.2 h1:MclTbSqD/9JodRUqFc4OwyJGk5AtonqB6BaG5RRJHf8=
|
||||
git.hoogi.eu/snafu/session v1.1.2/go.mod h1:kgRDrnHcKc9H18G9533BXy6qO+81eBf6e9gkUzBMDuA=
|
||||
git.hoogi.eu/snafu/session v1.3.0 h1:CzJQG7rseuerwBcLwxoJDtvWjXNP13bnWE90TsuTAls=
|
||||
git.hoogi.eu/snafu/session v1.3.0/go.mod h1:kgRDrnHcKc9H18G9533BXy6qO+81eBf6e9gkUzBMDuA=
|
||||
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
github.com/chris-ramon/douceur v0.2.0 h1:IDMEdxlEUUBYBKE4z/mJnFyVXox+MjuEVDJNN27glkU=
|
||||
github.com/chris-ramon/douceur v0.2.0/go.mod h1:wDW5xjJdeoMm1mRt4sD4c/LbF/mWdEpRXQKjTR8nIBE=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
|
||||
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/felixge/httpsnoop v1.0.2 h1:+nS9g82KMXccJ/wp0zyRW9ZBHFETmMGtkk+2CTTrW4o=
|
||||
github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
|
||||
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/gorilla/csrf v1.7.0 h1:mMPjV5/3Zd460xCavIkppUdvnl5fPXMpv2uz2Zyg7/Y=
|
||||
github.com/gorilla/csrf v1.7.0/go.mod h1:+a/4tCmqhG6/w4oafeAZ9pEa3/NZOWYVbD9fV0FwIQA=
|
||||
github.com/gorilla/csrf v1.7.1 h1:Ir3o2c1/Uzj6FBxMlAUB6SivgVMy1ONXwYgXn+/aHPE=
|
||||
github.com/gorilla/csrf v1.7.1/go.mod h1:+a/4tCmqhG6/w4oafeAZ9pEa3/NZOWYVbD9fV0FwIQA=
|
||||
github.com/gorilla/csrf v1.7.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI=
|
||||
github.com/gorilla/csrf v1.7.2/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk=
|
||||
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
||||
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
||||
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
|
||||
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
|
||||
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
|
||||
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
|
||||
github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
|
||||
github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
|
||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||
github.com/justinas/alice v1.2.0 h1:+MHSA/vccVCF4Uq37S42jwlkvI2Xzl7zTPCN5BnZNVo=
|
||||
github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/mattn/go-sqlite3 v1.14.3 h1:j7a/xn1U6TKA/PHHxqZuzh64CdtRc7rU9M+AvkOl5bA=
|
||||
github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
|
||||
github.com/microcosm-cc/bluemonday v1.0.4 h1:p0L+CTpo/PLFdkoPcJemLXG+fpMD7pYOoDEq1axMbGg=
|
||||
github.com/microcosm-cc/bluemonday v1.0.4/go.mod h1:8iwZnFn2CDDNZ0r6UXhF4xawGvzaqzCRa1n3/lO3W2w=
|
||||
github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxzIU=
|
||||
github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA=
|
||||
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v1.14.10 h1:MLn+5bFRlWMGoSRmJour3CL1w/qL96mvipqpwQW/Sfk=
|
||||
github.com/mattn/go-sqlite3 v1.14.10/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v1.14.12 h1:TJ1bhYJPV44phC+IMu1u2K/i5RriLTPe+yc68XDJ1Z0=
|
||||
github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v1.14.14 h1:qZgc/Rwetq+MtyE18WhzjokPD93dNqLGNT3QJuLvBGw=
|
||||
github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
|
||||
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
||||
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI=
|
||||
github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/microcosm-cc/bluemonday v1.0.15 h1:J4uN+qPng9rvkBZBoBb8YGR+ijuklIMpSOZZLjYpbeY=
|
||||
github.com/microcosm-cc/bluemonday v1.0.15/go.mod h1:ZLvAzeakRwrGnzQEvstVzVt3ZpqOF2+sdFr0Om+ce30=
|
||||
github.com/microcosm-cc/bluemonday v1.0.16 h1:kHmAq2t7WPWLjiGvzKa5o3HzSfahUKiOq7fAPUiMNIc=
|
||||
github.com/microcosm-cc/bluemonday v1.0.16/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM=
|
||||
github.com/microcosm-cc/bluemonday v1.0.17 h1:Z1a//hgsQ4yjC+8zEkV8IWySkXnsxmdSY642CTFQb5Y=
|
||||
github.com/microcosm-cc/bluemonday v1.0.17/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM=
|
||||
github.com/microcosm-cc/bluemonday v1.0.18 h1:6HcxvXDAi3ARt3slx6nTesbvorIc3QeTzBNRvWktHBo=
|
||||
github.com/microcosm-cc/bluemonday v1.0.18/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM=
|
||||
github.com/microcosm-cc/bluemonday v1.0.19 h1:OI7hoF5FY4pFz2VA//RN8TfM0YJ2dJcl4P4APrCWy6c=
|
||||
github.com/microcosm-cc/bluemonday v1.0.19/go.mod h1:QNzV2UbLK2/53oIIwTOyLUSABMkjZ4tqiyC1g/DyqxE=
|
||||
github.com/microcosm-cc/bluemonday v1.0.20 h1:flpzsq4KU3QIYAYGV/szUat7H+GPOXR0B2JU5A1Wp8Y=
|
||||
github.com/microcosm-cc/bluemonday v1.0.20/go.mod h1:yfBmMi8mxvaZut3Yytv+jTXRY8mxyjJ0/kQBTElld50=
|
||||
github.com/microcosm-cc/bluemonday v1.0.21 h1:dNH3e4PSyE4vNX+KlRGHT5KrSvjeUkoNPwEORjffHJg=
|
||||
github.com/microcosm-cc/bluemonday v1.0.21/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM=
|
||||
github.com/microcosm-cc/bluemonday v1.0.22 h1:p2tT7RNzRdCi0qmwxG+HbqD6ILkmwter1ZwVZn1oTxA=
|
||||
github.com/microcosm-cc/bluemonday v1.0.22/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM=
|
||||
github.com/microcosm-cc/bluemonday v1.0.25 h1:4NEwSfiJ+Wva0VxN5B8OwMicaJvD8r9tlJWm9rtloEg=
|
||||
github.com/microcosm-cc/bluemonday v1.0.25/go.mod h1:ZIOjCQp1OrzBBPIJmfX4qDYFuhU02nx4bn030ixfHLE=
|
||||
github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58=
|
||||
github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM=
|
||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA=
|
||||
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009 h1:W0lCpv29Hv0UaM1LXb9QlBHLNP8UFfcKjblhVCWftOM=
|
||||
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce h1:Roh6XWxHFKrPgC/EQhVubSAGQ6Ozk6IdxHSzt1mR0EI=
|
||||
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f h1:OeJjE6G4dgCY4PIXvIRQbE8+RX+uXZyGhUy/ksMGJoc=
|
||||
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be h1:fmw3UbQh+nxngCAHrDCCztao/kbYFnWjoqop8dHx05A=
|
||||
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8=
|
||||
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
|
||||
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
|
||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210716203947-853a461950ff h1:j2EK/QoxYNBsXI4R7fQkkRUk8y6wnOBI+6hgPdP/6Ds=
|
||||
golang.org/x/net v0.0.0-20210716203947-853a461950ff/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211029224645-99673261e6eb h1:pirldcYWx7rx7kE5r+9WsOXPXK0+WH5+uZ7uPmJ44uM=
|
||||
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d h1:1n1fc535VhN8SYtD4cDUyNlfpAF2ROMM9+11equK3hs=
|
||||
golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA=
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220728211354-c7608f3a8462 h1:UreQrH7DbFXSi9ZFox6FNT3WBooWmdANpU+IfkT1T4I=
|
||||
golang.org/x/net v0.0.0-20220728211354-c7608f3a8462/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.0.0-20221002022538-bcab6841153b h1:6e93nYa3hNqAvLr0pD4PN1fFS+gKzp2zAXqrnTCstqU=
|
||||
golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
|
||||
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
|
||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
||||
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211030160813-b3129d9d1021 h1:giLT+HuUP/gXYrG2Plg9WTjj4qhfgaW424ZIFog3rlk=
|
||||
golang.org/x/sys v0.0.0-20211030160813-b3129d9d1021/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60=
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220730100132-1609e554cd39 h1:aNCnH+Fiqs7ZDTFH6oEFjIfbX2HvgQXJ6uQuUbTobjk=
|
||||
golang.org/x/sys v0.0.0-20220730100132-1609e554cd39/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec h1:BkDtF2Ih9xZ7le9ndzTA7KJow28VbQW3odyk/8drmuI=
|
||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE=
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI=
|
||||
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
|
||||
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
||||
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
|
||||
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package controllers
|
||||
package handler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
@ -8,13 +8,13 @@ import (
|
|||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.hoogi.eu/snafu/go-blog/components/httperror"
|
||||
"git.hoogi.eu/snafu/go-blog/components/logger"
|
||||
"git.hoogi.eu/snafu/go-blog/httperror"
|
||||
"git.hoogi.eu/snafu/go-blog/logger"
|
||||
"git.hoogi.eu/snafu/go-blog/middleware"
|
||||
"git.hoogi.eu/snafu/go-blog/models"
|
||||
)
|
||||
|
||||
//AdminProfileHandler returns page for updating the profile of the currently logged-in user
|
||||
// AdminProfileHandler returns the page for updating the profile of the currently logged-in user
|
||||
func AdminProfileHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
user, _ := middleware.User(r)
|
||||
|
||||
|
@ -27,7 +27,7 @@ func AdminProfileHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *h
|
|||
}
|
||||
}
|
||||
|
||||
//AdminProfilePostHandler handles the updating of the user profile which is currently logged in
|
||||
// AdminProfilePostHandler handles the updating of the user profile which is currently logged in
|
||||
func AdminProfilePostHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
ctxUser, _ := middleware.User(r)
|
||||
ctxUser.PlainPassword = []byte(r.FormValue("current_password"))
|
||||
|
@ -57,7 +57,7 @@ func AdminProfilePostHandler(ctx *middleware.AppContext, w http.ResponseWriter,
|
|||
|
||||
if len(u.PlainPassword) > 0 {
|
||||
changePassword = true
|
||||
// Password change
|
||||
|
||||
u.PlainPassword = []byte(r.FormValue("password"))
|
||||
|
||||
if !bytes.Equal(u.PlainPassword, []byte(r.FormValue("retyped_password"))) {
|
||||
|
@ -81,11 +81,11 @@ func AdminProfilePostHandler(ctx *middleware.AppContext, w http.ResponseWriter,
|
|||
|
||||
session.SetValue("userid", u.ID)
|
||||
|
||||
sids := ctx.SessionService.SessionProvider.SessionIDsFromValues("userid", u.ID)
|
||||
sessions := ctx.SessionService.SessionProvider.FindByValue("userid", u.ID)
|
||||
|
||||
for _, sid := range sids {
|
||||
if sid != session.SessionID() {
|
||||
ctx.SessionService.SessionProvider.Remove(sid)
|
||||
for _, sid := range sessions {
|
||||
if sid.SessionID() != session.SessionID() {
|
||||
ctx.SessionService.SessionProvider.Remove(sid.SessionID())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -111,7 +111,7 @@ func AdminProfilePostHandler(ctx *middleware.AppContext, w http.ResponseWriter,
|
|||
}
|
||||
}
|
||||
|
||||
//ActivateAccountHandler shows the form to activate an account.
|
||||
// ActivateAccountHandler shows the form to activate an account
|
||||
func ActivateAccountHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
hash := getVar(r, "hash")
|
||||
|
||||
|
@ -139,9 +139,9 @@ func ActivateAccountHandler(ctx *middleware.AppContext, w http.ResponseWriter, r
|
|||
}
|
||||
}
|
||||
|
||||
//ActivateAccountPostHandler activates an user account
|
||||
// ActivateAccountPostHandler activates an user account
|
||||
func ActivateAccountPostHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
// Delete any cookies if an user is logged in
|
||||
// Delete cookie if the user is logged in
|
||||
ctx.SessionService.Remove(w, r)
|
||||
|
||||
password := r.FormValue("password")
|
||||
|
@ -196,11 +196,11 @@ func ActivateAccountPostHandler(ctx *middleware.AppContext, w http.ResponseWrite
|
|||
|
||||
return &middleware.Template{
|
||||
RedirectPath: "admin",
|
||||
SuccessMsg: "The account was successfully activated. You can now log in.",
|
||||
SuccessMsg: "The account was successfully activated. You can now login.",
|
||||
}
|
||||
}
|
||||
|
||||
//ResetPasswordHandler returns the form for resetting the password
|
||||
// ResetPasswordHandler returns the form to reset the password
|
||||
func ResetPasswordHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
hash := getVar(r, "hash")
|
||||
|
||||
|
@ -227,11 +227,11 @@ func ResetPasswordHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *
|
|||
}
|
||||
}
|
||||
|
||||
//ResetPasswordPostHandler handles the resetting of the password
|
||||
// ResetPasswordPostHandler handles a password reset
|
||||
func ResetPasswordPostHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
password := r.FormValue("password")
|
||||
repassword := r.FormValue("password_repeat")
|
||||
hash := getVar(r, "hash")
|
||||
password := r.FormValue("password")
|
||||
password2 := r.FormValue("password_repeat")
|
||||
|
||||
t, err := ctx.TokenService.Get(hash, models.PasswordReset, time.Duration(1)*time.Hour)
|
||||
|
||||
|
@ -251,7 +251,7 @@ func ResetPasswordPostHandler(ctx *middleware.AppContext, w http.ResponseWriter,
|
|||
}
|
||||
}
|
||||
|
||||
if password != repassword {
|
||||
if password != password2 {
|
||||
return &middleware.Template{
|
||||
Name: tplAdminResetPassword,
|
||||
Err: httperror.New(http.StatusUnprocessableEntity, "The passwords entered do not match.", errors.New("the password entered did not match")),
|
||||
|
@ -277,31 +277,43 @@ func ResetPasswordPostHandler(ctx *middleware.AppContext, w http.ResponseWriter,
|
|||
|
||||
ctx.Mailer.SendPasswordChangeConfirmation(u)
|
||||
|
||||
if !u.Active {
|
||||
logger.Log.Warnf("password reset for user '%s' was successful, but user is deactivated", u.Email)
|
||||
|
||||
return &middleware.Template{
|
||||
Name: tplAdminResetPassword,
|
||||
WarnMsg: "Your password reset was successful, but your account is deactivated.",
|
||||
}
|
||||
}
|
||||
|
||||
return &middleware.Template{
|
||||
RedirectPath: "admin",
|
||||
SuccessMsg: "Your password reset was successful.",
|
||||
}
|
||||
}
|
||||
|
||||
//ForgotPasswordHandler returns the form for the reset password form
|
||||
// ForgotPasswordHandler returns the form for the password reset
|
||||
func ForgotPasswordHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
return &middleware.Template{
|
||||
Name: tplAdminForgotPassword,
|
||||
}
|
||||
}
|
||||
|
||||
//ForgotPasswordPostHandler handles the processing of the reset password function
|
||||
// ForgotPasswordPostHandler handles the processing for the password reset
|
||||
func ForgotPasswordPostHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
email := r.FormValue("email")
|
||||
|
||||
u, err := ctx.UserService.GetByMail(email)
|
||||
|
||||
if err != nil {
|
||||
if httperror.Equals(err, sql.ErrNoRows) {
|
||||
logger.Log.Error(err)
|
||||
return &middleware.Template{
|
||||
RedirectPath: "admin",
|
||||
SuccessMsg: "An email with password reset instructions is on the way.",
|
||||
var e *httperror.Error
|
||||
if errors.As(err, &e) {
|
||||
if errors.Is(e.Err, sql.ErrNoRows) {
|
||||
logger.Log.Error(err)
|
||||
return &middleware.Template{
|
||||
Name: tplAdminForgotPassword,
|
||||
SuccessMsg: fmt.Sprintf("An email to '%s' with password reset instructions is on the way.", email),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return &middleware.Template{
|
||||
|
@ -316,31 +328,16 @@ func ForgotPasswordPostHandler(ctx *middleware.AppContext, w http.ResponseWriter
|
|||
}
|
||||
}
|
||||
|
||||
if !u.Active {
|
||||
return &middleware.Template{
|
||||
Name: tplAdminForgotPassword,
|
||||
Err: httperror.New(http.StatusUnauthorized, "Your account is deactivated.", err),
|
||||
Data: map[string]interface{}{
|
||||
"user": models.User{
|
||||
Email: email,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
t := &models.Token{
|
||||
Author: u,
|
||||
Type: models.PasswordReset,
|
||||
}
|
||||
|
||||
err = ctx.TokenService.RateLimit(u.ID, models.PasswordReset)
|
||||
if err != nil {
|
||||
if err = ctx.TokenService.RateLimit(u.ID, models.PasswordReset); err != nil {
|
||||
logger.Log.Error(err)
|
||||
}
|
||||
|
||||
err = ctx.TokenService.Create(t)
|
||||
|
||||
if err != nil {
|
||||
if err = ctx.TokenService.Create(t); err != nil {
|
||||
return &middleware.Template{
|
||||
Name: tplAdminForgotPassword,
|
||||
Err: err,
|
||||
|
@ -350,7 +347,7 @@ func ForgotPasswordPostHandler(ctx *middleware.AppContext, w http.ResponseWriter
|
|||
ctx.Mailer.SendPasswordResetLink(u, t)
|
||||
|
||||
return &middleware.Template{
|
||||
RedirectPath: "admin",
|
||||
SuccessMsg: "An email with password reset instructions is on the way.",
|
||||
Name: tplAdminForgotPassword,
|
||||
SuccessMsg: fmt.Sprintf("An email to '%s' with password reset instructions is on the way.", email),
|
||||
}
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
package controllers_test
|
||||
package handler_test
|
||||
|
||||
import (
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"git.hoogi.eu/snafu/go-blog/controllers"
|
||||
"git.hoogi.eu/snafu/go-blog/handler"
|
||||
"git.hoogi.eu/snafu/go-blog/models"
|
||||
)
|
||||
|
||||
|
@ -94,7 +94,7 @@ func doAdminProfileRequest(user reqUser, u *models.User, currentPassword string)
|
|||
|
||||
rw := httptest.NewRecorder()
|
||||
re := r.buildRequest()
|
||||
tpl := controllers.AdminProfilePostHandler(ctx, rw, re)
|
||||
tpl := handler.AdminProfilePostHandler(ctx, rw, re)
|
||||
|
||||
if tpl.Err != nil {
|
||||
return tpl.Err
|
||||
|
@ -122,7 +122,7 @@ func doActivateAccountRequest(user reqUser, password, passwordRepeat, hash strin
|
|||
}
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
tpl := controllers.ActivateAccountPostHandler(ctx, rw, r.buildRequest())
|
||||
tpl := handler.ActivateAccountPostHandler(ctx, rw, r.buildRequest())
|
||||
|
||||
if tpl.Err != nil {
|
||||
return tpl.Err
|
||||
|
@ -150,7 +150,7 @@ func doResetPasswordRequest(user reqUser, password, passwordRepeat, hash string)
|
|||
}
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
tpl := controllers.AdminSiteEditPostHandler(ctx, rw, r.buildRequest())
|
||||
tpl := handler.AdminSiteEditPostHandler(ctx, rw, r.buildRequest())
|
||||
|
||||
if tpl.Err != nil {
|
||||
return tpl.Err
|
|
@ -2,7 +2,7 @@
|
|||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package controllers
|
||||
package handler
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
@ -10,20 +10,21 @@ import (
|
|||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.hoogi.eu/snafu/go-blog/components/httperror"
|
||||
"git.hoogi.eu/snafu/go-blog/httperror"
|
||||
"git.hoogi.eu/snafu/go-blog/middleware"
|
||||
"git.hoogi.eu/snafu/go-blog/models"
|
||||
"git.hoogi.eu/snafu/go-blog/utils"
|
||||
)
|
||||
|
||||
//GetArticleHandler returns a specific article
|
||||
//Parameters in the url form 2016/3/my-headline are used for obtaining the article
|
||||
// GetArticleHandler returns a specific article
|
||||
// URL Parameters are in the form /year/month/slug e.g. 2016/3/my-
|
||||
func GetArticleHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
year := getVar(r, "year")
|
||||
month := getVar(r, "month")
|
||||
slug := getVar(r, "slug")
|
||||
headline := getVar(r, "slug")
|
||||
|
||||
a, err := ctx.ArticleService.GetBySlug(utils.AppendString(year, "/", month, "/", slug), nil, models.OnlyPublished)
|
||||
slug := year + "/" + month + "/" + headline
|
||||
|
||||
a, err := ctx.ArticleService.GetBySlug(slug, nil, models.OnlyPublished)
|
||||
|
||||
if err != nil {
|
||||
return &middleware.Template{
|
||||
|
@ -50,8 +51,7 @@ func GetArticleHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *htt
|
|||
}}
|
||||
}
|
||||
|
||||
//GetArticleHandler returns a specific article
|
||||
//Parameters in the url form 2016/03/my-headline are used for obtaining the article
|
||||
// GetArticleByIDHandler returns a specific article by the ID
|
||||
func GetArticleByIDHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
id, err := parseInt(getVar(r, "articleID"))
|
||||
|
||||
|
@ -89,7 +89,7 @@ func GetArticleByIDHandler(ctx *middleware.AppContext, w http.ResponseWriter, r
|
|||
}}
|
||||
}
|
||||
|
||||
//ListArticlesHandler returns the template which contains all published articles
|
||||
// ListArticlesHandler returns all published articles
|
||||
func ListArticlesHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
page := getPageParam(r)
|
||||
|
||||
|
@ -141,7 +141,7 @@ func ListArticlesHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *h
|
|||
}
|
||||
}
|
||||
|
||||
//ListArticlesHandler returns the template which contains all published articles
|
||||
// ListArticlesCategoryHandler returns all published articles in a category
|
||||
func ListArticlesCategoryHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
page := getPageParam(r)
|
||||
|
||||
|
@ -206,7 +206,39 @@ func ListArticlesCategoryHandler(ctx *middleware.AppContext, w http.ResponseWrit
|
|||
}
|
||||
}
|
||||
|
||||
//IndexArticlesCategoryHandler returns the template information for the index page grouped by categories
|
||||
// IndexArticlesHandler returns articles for the index page
|
||||
func IndexArticlesHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
a, err := ctx.ArticleService.Index(nil, nil, nil, models.OnlyPublished)
|
||||
|
||||
if err != nil {
|
||||
return &middleware.Template{
|
||||
Name: tplIndexArticles,
|
||||
Active: "index",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
c, err := ctx.CategoryService.List(models.CategoriesWithPublishedArticles)
|
||||
|
||||
if err != nil {
|
||||
return &middleware.Template{
|
||||
Name: tplIndexArticles,
|
||||
Active: "index",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
return &middleware.Template{
|
||||
Name: tplIndexArticles,
|
||||
Active: "index",
|
||||
Data: map[string]interface{}{
|
||||
"articles": a,
|
||||
"categories": c,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// IndexArticlesCategoryHandler returns articles for the index page grouped by categories
|
||||
func IndexArticlesCategoryHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
cs, err := ctx.CategoryService.List(models.CategoriesWithPublishedArticles)
|
||||
|
||||
|
@ -256,40 +288,7 @@ func IndexArticlesCategoryHandler(ctx *middleware.AppContext, w http.ResponseWri
|
|||
}
|
||||
}
|
||||
|
||||
//IndexArticlesHandler returns the template information for the index page
|
||||
func IndexArticlesHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
a, err := ctx.ArticleService.Index(nil, nil, nil, models.OnlyPublished)
|
||||
|
||||
if err != nil {
|
||||
return &middleware.Template{
|
||||
Name: tplIndexArticles,
|
||||
Active: "index",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
c, err := ctx.CategoryService.List(models.CategoriesWithPublishedArticles)
|
||||
|
||||
if err != nil {
|
||||
return &middleware.Template{
|
||||
Name: tplIndexArticles,
|
||||
Active: "index",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
return &middleware.Template{
|
||||
Name: tplIndexArticles,
|
||||
Active: "index",
|
||||
Data: map[string]interface{}{
|
||||
"articles": a,
|
||||
"categories": c,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
//GetArticleHandler returns a specific article
|
||||
//Parameters in the url form 2016/03/my-headline are used for obtaining the article
|
||||
// RSSFeed returns XML list of published articles for the RSS feed
|
||||
func RSSFeed(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) (*models.XMLData, error) {
|
||||
p := &models.Pagination{
|
||||
Limit: ctx.ConfigService.RSSFeedItems,
|
||||
|
@ -307,7 +306,7 @@ func RSSFeed(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request)
|
|||
}, nil
|
||||
}
|
||||
|
||||
//AdminListArticlesHandler returns all articles, also not yet published articles will be shown
|
||||
// AdminListArticlesHandler returns all articles, also not yet published articles
|
||||
func AdminListArticlesHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
u, _ := middleware.User(r)
|
||||
|
||||
|
@ -346,8 +345,7 @@ func AdminListArticlesHandler(ctx *middleware.AppContext, w http.ResponseWriter,
|
|||
}}
|
||||
}
|
||||
|
||||
//AdminShowArticleByIDHandler returns a specific article, renders it on the front page
|
||||
//used for the preview
|
||||
// AdminPreviewArticleByIDHandler returns a specific article, renders it on the front page used for the preview
|
||||
func AdminPreviewArticleByIDHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
u, _ := middleware.User(r)
|
||||
|
||||
|
@ -391,7 +389,7 @@ func AdminPreviewArticleByIDHandler(ctx *middleware.AppContext, w http.ResponseW
|
|||
}}
|
||||
}
|
||||
|
||||
// AdminArticleNewHandler returns the template which shows the form to create a new article
|
||||
// AdminArticleNewHandler returns the form to create a new article
|
||||
func AdminArticleNewHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
c, err := ctx.CategoryService.List(models.AllCategories)
|
||||
|
||||
|
@ -457,7 +455,7 @@ func AdminArticleNewPostHandler(ctx *middleware.AppContext, w http.ResponseWrite
|
|||
}
|
||||
}
|
||||
|
||||
//AdminArticleEditHandler shows the form for changing an article
|
||||
// AdminArticleEditHandler shows the form for changing an article
|
||||
func AdminArticleEditHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
u, _ := middleware.User(r)
|
||||
|
||||
|
@ -498,7 +496,7 @@ func AdminArticleEditHandler(ctx *middleware.AppContext, w http.ResponseWriter,
|
|||
}
|
||||
}
|
||||
|
||||
//AdminArticleEditPostHandler handles the update of an article
|
||||
// AdminArticleEditPostHandler handles the update of an article
|
||||
func AdminArticleEditPostHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
u, _ := middleware.User(r)
|
||||
|
||||
|
@ -555,7 +553,7 @@ func AdminArticleEditPostHandler(ctx *middleware.AppContext, w http.ResponseWrit
|
|||
}
|
||||
}
|
||||
|
||||
//AdminArticlePublishHandler returns the action template which asks the user if the article should be published / unpublished
|
||||
// AdminArticlePublishHandler returns the action template which asks the user if the article should be published / unpublished
|
||||
func AdminArticlePublishHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
u, _ := middleware.User(r)
|
||||
|
||||
|
@ -573,6 +571,14 @@ func AdminArticlePublishHandler(ctx *middleware.AppContext, w http.ResponseWrite
|
|||
|
||||
a, err := ctx.ArticleService.GetByID(id, u, models.All)
|
||||
|
||||
if err != nil {
|
||||
return &middleware.Template{
|
||||
Name: tplAdminArticles,
|
||||
Err: err,
|
||||
Active: "articles",
|
||||
}
|
||||
}
|
||||
|
||||
var action models.Action
|
||||
|
||||
if a.Published {
|
||||
|
@ -602,7 +608,7 @@ func AdminArticlePublishHandler(ctx *middleware.AppContext, w http.ResponseWrite
|
|||
}
|
||||
}
|
||||
|
||||
// AdminArticlePublishPostHandler publishes or "depublishes" an article
|
||||
// AdminArticlePublishPostHandler publish or unpublish an article
|
||||
func AdminArticlePublishPostHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
u, _ := middleware.User(r)
|
||||
|
||||
|
@ -631,7 +637,7 @@ func AdminArticlePublishPostHandler(ctx *middleware.AppContext, w http.ResponseW
|
|||
}
|
||||
}
|
||||
|
||||
//AdminArticleDeleteHandler returns the action template which asks the user if the article should be removed
|
||||
// AdminArticleDeleteHandler returns the action template which asks the user if the article should be removed
|
||||
func AdminArticleDeleteHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
u, _ := middleware.User(r)
|
||||
|
||||
|
@ -674,7 +680,7 @@ func AdminArticleDeleteHandler(ctx *middleware.AppContext, w http.ResponseWriter
|
|||
}
|
||||
}
|
||||
|
||||
//AdminArticleDeletePostHandler handles the removing of an article
|
||||
// AdminArticleDeletePostHandler handles the removing of an article
|
||||
func AdminArticleDeletePostHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
u, _ := middleware.User(r)
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package controllers_test
|
||||
package handler_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -13,7 +13,7 @@ import (
|
|||
"strconv"
|
||||
"testing"
|
||||
|
||||
"git.hoogi.eu/snafu/go-blog/controllers"
|
||||
"git.hoogi.eu/snafu/go-blog/handler"
|
||||
"git.hoogi.eu/snafu/go-blog/models"
|
||||
)
|
||||
|
||||
|
@ -143,7 +143,7 @@ func doGetArticleBySlugRequest(user reqUser, article *models.Article) (*models.A
|
|||
}
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
tpl := controllers.GetArticleHandler(ctx, rw, r.buildRequest())
|
||||
tpl := handler.GetArticleHandler(ctx, rw, r.buildRequest())
|
||||
|
||||
if tpl.Err != nil {
|
||||
return nil, tpl.Err
|
||||
|
@ -166,7 +166,7 @@ func doGetArticleByIDRequest(user reqUser, articleID int) (*models.Article, erro
|
|||
}
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
tpl := controllers.GetArticleByIDHandler(ctx, rw, r.buildRequest())
|
||||
tpl := handler.GetArticleByIDHandler(ctx, rw, r.buildRequest())
|
||||
|
||||
if tpl.Err != nil {
|
||||
return nil, tpl.Err
|
||||
|
@ -196,7 +196,7 @@ func doAdminEditArticleRequest(user reqUser, articleID int, article *models.Arti
|
|||
|
||||
rw := httptest.NewRecorder()
|
||||
|
||||
tpl := controllers.AdminArticleEditPostHandler(ctx, rw, r.buildRequest())
|
||||
tpl := handler.AdminArticleEditPostHandler(ctx, rw, r.buildRequest())
|
||||
|
||||
if tpl.Err != nil {
|
||||
return tpl.Err
|
||||
|
@ -222,7 +222,7 @@ func doAdminCreateArticleRequest(user reqUser, article *models.Article) (int, er
|
|||
}
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
tpl := controllers.AdminArticleNewPostHandler(ctx, rw, r.buildRequest())
|
||||
tpl := handler.AdminArticleNewPostHandler(ctx, rw, r.buildRequest())
|
||||
|
||||
if tpl.Err != nil {
|
||||
return 0, tpl.Err
|
||||
|
@ -249,7 +249,7 @@ func doAdminGetArticleByIDRequest(user reqUser, articleID int) (*models.Article,
|
|||
}
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
tpl := controllers.AdminPreviewArticleByIDHandler(ctx, rw, r.buildRequest())
|
||||
tpl := handler.AdminPreviewArticleByIDHandler(ctx, rw, r.buildRequest())
|
||||
|
||||
if tpl.Err != nil {
|
||||
return nil, tpl.Err
|
||||
|
@ -266,7 +266,7 @@ func doAdminListArticleRequest(user reqUser) ([]models.Article, error) {
|
|||
}
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
tpl := controllers.AdminListArticlesHandler(ctx, rw, r.buildRequest())
|
||||
tpl := handler.AdminListArticlesHandler(ctx, rw, r.buildRequest())
|
||||
|
||||
if tpl.Err != nil {
|
||||
return nil, tpl.Err
|
||||
|
@ -289,7 +289,7 @@ func doAdminPublishArticleRequest(user reqUser, articleID int) error {
|
|||
}
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
tpl := controllers.AdminArticlePublishPostHandler(ctx, rw, r.buildRequest())
|
||||
tpl := handler.AdminArticlePublishPostHandler(ctx, rw, r.buildRequest())
|
||||
|
||||
if tpl.Err != nil {
|
||||
return tpl.Err
|
||||
|
@ -312,7 +312,7 @@ func doAdminRemoveArticleRequest(user reqUser, articleID int) error {
|
|||
}
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
tpl := controllers.AdminArticleDeletePostHandler(ctx, rw, r.buildRequest())
|
||||
tpl := handler.AdminArticleDeletePostHandler(ctx, rw, r.buildRequest())
|
||||
|
||||
if tpl.Err != nil {
|
||||
return tpl.Err
|
|
@ -2,17 +2,18 @@
|
|||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package controllers
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"git.hoogi.eu/snafu/go-blog/components/httperror"
|
||||
"git.hoogi.eu/snafu/go-blog/httperror"
|
||||
"git.hoogi.eu/snafu/go-blog/middleware"
|
||||
"git.hoogi.eu/snafu/go-blog/models"
|
||||
)
|
||||
|
||||
// AdminListCategoriesHandler returns a list of all categories
|
||||
func AdminListCategoriesHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
c, err := ctx.CategoryService.List(models.AllCategories)
|
||||
|
||||
|
@ -31,6 +32,7 @@ func AdminListCategoriesHandler(ctx *middleware.AppContext, w http.ResponseWrite
|
|||
}}
|
||||
}
|
||||
|
||||
// AdminGetCategoryHandler get category by the ID
|
||||
func AdminGetCategoryHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
reqVar := getVar(r, "categoryID")
|
||||
id, err := parseInt(reqVar)
|
||||
|
@ -59,7 +61,7 @@ func AdminGetCategoryHandler(ctx *middleware.AppContext, w http.ResponseWriter,
|
|||
}}
|
||||
}
|
||||
|
||||
// AdminCategoryNewHandler returns the template which shows the form to create a new article
|
||||
// AdminCategoryNewHandler returns the form to create a new category
|
||||
func AdminCategoryNewHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
return &middleware.Template{
|
||||
Active: "categories",
|
||||
|
@ -67,7 +69,7 @@ func AdminCategoryNewHandler(ctx *middleware.AppContext, w http.ResponseWriter,
|
|||
}
|
||||
}
|
||||
|
||||
// AdminCategoryNewPostHandler handles the creation of a new article
|
||||
// AdminCategoryNewPostHandler handles the creation of a new category
|
||||
func AdminCategoryNewPostHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
u, _ := middleware.User(r)
|
||||
|
||||
|
@ -99,7 +101,7 @@ func AdminCategoryNewPostHandler(ctx *middleware.AppContext, w http.ResponseWrit
|
|||
}
|
||||
}
|
||||
|
||||
//AdminCategoryEditHandler shows the form for changing an article
|
||||
// AdminCategoryEditHandler shows the form to change a category
|
||||
func AdminCategoryEditHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
id, err := parseInt(getVar(r, "categoryID"))
|
||||
|
||||
|
@ -128,7 +130,7 @@ func AdminCategoryEditHandler(ctx *middleware.AppContext, w http.ResponseWriter,
|
|||
}
|
||||
}
|
||||
|
||||
//AdminArticleEditPostHandler handles the update of an article
|
||||
// AdminCategoryEditPostHandler handles the update of a category
|
||||
func AdminCategoryEditPostHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
u, _ := middleware.User(r)
|
||||
|
||||
|
@ -168,7 +170,7 @@ func AdminCategoryEditPostHandler(ctx *middleware.AppContext, w http.ResponseWri
|
|||
}
|
||||
}
|
||||
|
||||
//AdminArticleDeleteHandler returns the action template which asks the user if the article should be removed
|
||||
// AdminCategoryDeleteHandler returns the action which asks the user if the category should be removed
|
||||
func AdminCategoryDeleteHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
reqVar := getVar(r, "categoryID")
|
||||
|
||||
|
@ -209,7 +211,7 @@ func AdminCategoryDeleteHandler(ctx *middleware.AppContext, w http.ResponseWrite
|
|||
}
|
||||
}
|
||||
|
||||
//AdminArticleDeletePostHandler handles the removing of an article
|
||||
// AdminCategoryDeletePostHandler handles the removing of a category
|
||||
func AdminCategoryDeletePostHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
reqVar := getVar(r, "categoryID")
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package controllers_test
|
||||
package handler_test
|
||||
|
||||
import (
|
||||
"net/http/httptest"
|
||||
|
@ -6,7 +6,7 @@ import (
|
|||
"strconv"
|
||||
"testing"
|
||||
|
||||
"git.hoogi.eu/snafu/go-blog/controllers"
|
||||
"git.hoogi.eu/snafu/go-blog/handler"
|
||||
"git.hoogi.eu/snafu/go-blog/models"
|
||||
)
|
||||
|
||||
|
@ -82,7 +82,7 @@ func doAdminGetCategoryRequest(user reqUser, categoryID int) (*models.Category,
|
|||
}
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
tpl := controllers.AdminGetCategoryHandler(ctx, rw, r.buildRequest())
|
||||
tpl := handler.AdminGetCategoryHandler(ctx, rw, r.buildRequest())
|
||||
|
||||
if tpl.Err != nil {
|
||||
return nil, tpl.Err
|
||||
|
@ -99,7 +99,7 @@ func doAdminListCategoriesRequest(user reqUser) ([]models.Category, error) {
|
|||
}
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
tpl := controllers.AdminListCategoriesHandler(ctx, rw, r.buildRequest())
|
||||
tpl := handler.AdminListCategoriesHandler(ctx, rw, r.buildRequest())
|
||||
|
||||
if tpl.Err != nil {
|
||||
return nil, tpl.Err
|
||||
|
@ -119,7 +119,7 @@ func doAdminCategoryNewRequest(user reqUser, c *models.Category) (int, error) {
|
|||
}
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
tpl := controllers.AdminCategoryNewPostHandler(ctx, rw, r.buildRequest())
|
||||
tpl := handler.AdminCategoryNewPostHandler(ctx, rw, r.buildRequest())
|
||||
|
||||
if tpl.Err != nil {
|
||||
return -1, tpl.Err
|
||||
|
@ -144,7 +144,7 @@ func doAdminCategoryEditRequest(user reqUser, c *models.Category) error {
|
|||
}
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
tpl := controllers.AdminCategoryEditPostHandler(ctx, rw, r.buildRequest())
|
||||
tpl := handler.AdminCategoryEditPostHandler(ctx, rw, r.buildRequest())
|
||||
|
||||
if tpl.Err != nil {
|
||||
return tpl.Err
|
||||
|
@ -166,7 +166,7 @@ func doAdminDeleteCategoryRequest(user reqUser, categoryID int) error {
|
|||
}
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
tpl := controllers.AdminCategoryDeletePostHandler(ctx, rw, r.buildRequest())
|
||||
tpl := handler.AdminCategoryDeletePostHandler(ctx, rw, r.buildRequest())
|
||||
|
||||
if tpl.Err != nil {
|
||||
return tpl.Err
|
|
@ -1,4 +1,4 @@
|
|||
package controllers
|
||||
package handler
|
||||
|
||||
const (
|
||||
tplArticle = "front/article"
|
|
@ -1,4 +1,4 @@
|
|||
package controllers
|
||||
package handler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
@ -9,8 +9,8 @@ import (
|
|||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"git.hoogi.eu/snafu/go-blog/components/httperror"
|
||||
"git.hoogi.eu/snafu/go-blog/components/logger"
|
||||
"git.hoogi.eu/snafu/go-blog/httperror"
|
||||
"git.hoogi.eu/snafu/go-blog/logger"
|
||||
"git.hoogi.eu/snafu/go-blog/middleware"
|
||||
"git.hoogi.eu/snafu/go-blog/models"
|
||||
)
|
||||
|
@ -19,7 +19,7 @@ type FileHandler struct {
|
|||
Context *middleware.AppContext
|
||||
}
|
||||
|
||||
//FileGetHandler serves the file based on the url filename
|
||||
// FileGetHandler serves the file based on the unique filename
|
||||
func (fh FileHandler) FileGetHandler(w http.ResponseWriter, r *http.Request) {
|
||||
rv := getVar(r, "uniquename")
|
||||
|
||||
|
@ -55,12 +55,19 @@ func (fh FileHandler) FileGetHandler(w http.ResponseWriter, r *http.Request) {
|
|||
http.Error(w, "500 Internal Server Error", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
defer rf.Close()
|
||||
defer func(rf *os.File) {
|
||||
err := rf.Close()
|
||||
|
||||
if err != nil {
|
||||
logger.Log.Errorf("error while closing the file %v", err)
|
||||
http.Error(w, "500 Internal Server Error", http.StatusInternalServerError)
|
||||
}
|
||||
}(rf)
|
||||
|
||||
http.ServeContent(w, r, loc, f.LastModified, rf)
|
||||
}
|
||||
|
||||
//AdminListFilesHandler returns the template which lists all uploaded files belonging to a user, admins will see all files
|
||||
// AdminListFilesHandler returns the template which lists all uploaded files belonging to a user, admins will see all files
|
||||
func AdminListFilesHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
u, _ := middleware.User(r)
|
||||
|
||||
|
@ -102,7 +109,7 @@ func AdminListFilesHandler(ctx *middleware.AppContext, w http.ResponseWriter, r
|
|||
}}
|
||||
}
|
||||
|
||||
//AdminUploadFileHandler returns the form for uploading a file
|
||||
// AdminUploadFileHandler returns the form for uploading a file
|
||||
func AdminUploadFileHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
return &middleware.Template{
|
||||
Name: tplAdminFileUpload,
|
||||
|
@ -110,6 +117,7 @@ func AdminUploadFileHandler(ctx *middleware.AppContext, w http.ResponseWriter, r
|
|||
}
|
||||
}
|
||||
|
||||
// AdminToggleInlineFilePostHandler toggles the flag if the file should be inline or downloaded (toggles Content-Disposition header)
|
||||
func AdminToggleInlineFilePostHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
u, _ := middleware.User(r)
|
||||
|
||||
|
@ -173,6 +181,7 @@ func AdminUploadFilePostHandler(ctx *middleware.AppContext, w http.ResponseWrite
|
|||
}
|
||||
}
|
||||
|
||||
// AdminUploadJSONFilePostHandler
|
||||
func AdminUploadJSONFilePostHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) (*models.JSONData, error) {
|
||||
file, err := parseFileField(ctx, w, r)
|
||||
|
||||
|
@ -197,7 +206,7 @@ func AdminUploadJSONFilePostHandler(ctx *middleware.AppContext, w http.ResponseW
|
|||
return json, nil
|
||||
}
|
||||
|
||||
//AdminUploadDeleteHandler returns the action template which asks the user if the file should be removed
|
||||
// AdminUploadDeleteHandler returns the action template which asks the user if the file should be removed
|
||||
func AdminUploadDeleteHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
u, _ := middleware.User(r)
|
||||
|
||||
|
@ -239,7 +248,7 @@ func AdminUploadDeleteHandler(ctx *middleware.AppContext, w http.ResponseWriter,
|
|||
}
|
||||
}
|
||||
|
||||
//AdminUploadDeletePostHandler removes a file
|
||||
// AdminUploadDeletePostHandler removes a file
|
||||
func AdminUploadDeletePostHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
u, _ := middleware.User(r)
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package controllers_test
|
||||
package handler_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -7,7 +7,7 @@ import (
|
|||
"strconv"
|
||||
"testing"
|
||||
|
||||
"git.hoogi.eu/snafu/go-blog/controllers"
|
||||
"git.hoogi.eu/snafu/go-blog/handler"
|
||||
"git.hoogi.eu/snafu/go-blog/models"
|
||||
)
|
||||
|
||||
|
@ -63,7 +63,7 @@ func doAdminListFilesRequest(user reqUser) ([]models.File, error) {
|
|||
}
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
tpl := controllers.AdminListFilesHandler(ctx, rw, r.buildRequest())
|
||||
tpl := handler.AdminListFilesHandler(ctx, rw, r.buildRequest())
|
||||
|
||||
if tpl.Err != nil {
|
||||
return nil, tpl.Err
|
||||
|
@ -87,7 +87,7 @@ func doAdminGetFileRequest(user reqUser, uniquename string) (*httptest.ResponseR
|
|||
|
||||
rw := httptest.NewRecorder()
|
||||
|
||||
fh := controllers.FileHandler{
|
||||
fh := handler.FileHandler{
|
||||
Context: ctx,
|
||||
}
|
||||
|
||||
|
@ -116,7 +116,7 @@ func doAdminUploadFileRequest(user reqUser, file string) error {
|
|||
}
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
tpl := controllers.AdminUploadFilePostHandler(ctx, rw, r.buildRequest())
|
||||
tpl := handler.AdminUploadFilePostHandler(ctx, rw, r.buildRequest())
|
||||
|
||||
if tpl.Err != nil {
|
||||
return tpl.Err
|
||||
|
@ -139,7 +139,7 @@ func doAdminFileDeleteRequest(user reqUser, fileID int) error {
|
|||
}
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
tpl := controllers.AdminUploadDeletePostHandler(ctx, rw, r.buildRequest())
|
||||
tpl := handler.AdminUploadDeletePostHandler(ctx, rw, r.buildRequest())
|
||||
|
||||
if tpl.Err != nil {
|
||||
return tpl.Err
|
|
@ -2,13 +2,13 @@
|
|||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package controllers
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"git.hoogi.eu/snafu/go-blog/components/logger"
|
||||
"git.hoogi.eu/snafu/go-blog/logger"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
|
@ -2,17 +2,19 @@
|
|||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package controllers
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"git.hoogi.eu/snafu/go-blog/logger"
|
||||
|
||||
"git.hoogi.eu/snafu/go-blog/middleware"
|
||||
"git.hoogi.eu/snafu/go-blog/models"
|
||||
)
|
||||
|
||||
// LoginHandler shows the login form;
|
||||
// if the user is already logged in the user will be redirected to the administration page of aricles
|
||||
// if the user is already logged in the user will be redirected to the administration articles page
|
||||
func LoginHandler(ctx *middleware.AppContext, rw http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
_, err := ctx.SessionService.Get(rw, r)
|
||||
|
||||
|
@ -29,7 +31,7 @@ func LoginHandler(ctx *middleware.AppContext, rw http.ResponseWriter, r *http.Re
|
|||
}
|
||||
|
||||
// LoginPostHandler receives the login information from the form; checks the login and
|
||||
// starts a session for the user. The sesion will be stored in a cookie
|
||||
// starts a session for the user. The session will be stored in a cookie
|
||||
func LoginPostHandler(ctx *middleware.AppContext, rw http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
return &middleware.Template{
|
||||
|
@ -75,7 +77,9 @@ func LoginPostHandler(ctx *middleware.AppContext, rw http.ResponseWriter, r *htt
|
|||
|
||||
// LogoutHandler logs the user out by removing the cookie and removing the session from the session store
|
||||
func LogoutHandler(ctx *middleware.AppContext, rw http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
ctx.SessionService.Remove(rw, r)
|
||||
if err := ctx.SessionService.Remove(rw, r); err != nil {
|
||||
logger.Log.Infof("LogoutHandler: unable to remove session, err: %v", err)
|
||||
}
|
||||
|
||||
return &middleware.Template{
|
||||
RedirectPath: "admin",
|
||||
|
@ -85,9 +89,7 @@ func LogoutHandler(ctx *middleware.AppContext, rw http.ResponseWriter, r *http.R
|
|||
|
||||
// KeepAliveSessionHandler keeps a session alive.
|
||||
func KeepAliveSessionHandler(ctx *middleware.AppContext, rw http.ResponseWriter, r *http.Request) (*models.JSONData, error) {
|
||||
_, err := ctx.SessionService.Get(rw, r)
|
||||
|
||||
if err != nil {
|
||||
if _, err := ctx.SessionService.Get(rw, r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package controllers_test
|
||||
package handler_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -6,7 +6,7 @@ import (
|
|||
"net/url"
|
||||
"testing"
|
||||
|
||||
"git.hoogi.eu/snafu/go-blog/controllers"
|
||||
"git.hoogi.eu/snafu/go-blog/handler"
|
||||
)
|
||||
|
||||
func TestLogin(t *testing.T) {
|
||||
|
@ -71,7 +71,7 @@ func doLoginRequest(user reqUser, login, password string) (responseWrapper, erro
|
|||
}
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
tpl := controllers.LoginPostHandler(ctx, rr, r.buildRequest())
|
||||
tpl := handler.LoginPostHandler(ctx, rr, r.buildRequest())
|
||||
|
||||
if tpl.Err != nil {
|
||||
return responseWrapper{response: rr, template: tpl}, tpl.Err
|
|
@ -2,7 +2,7 @@
|
|||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package controllers
|
||||
package handler
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
@ -10,12 +10,12 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"git.hoogi.eu/snafu/go-blog/components/httperror"
|
||||
"git.hoogi.eu/snafu/go-blog/httperror"
|
||||
"git.hoogi.eu/snafu/go-blog/middleware"
|
||||
"git.hoogi.eu/snafu/go-blog/models"
|
||||
)
|
||||
|
||||
//GetSiteHandler returns the site template - only published sites are considered
|
||||
// GetSiteHandler returns the published sites
|
||||
func GetSiteHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
site, err := ctx.SiteService.GetByLink(getVar(r, "site"), models.OnlyPublished)
|
||||
|
||||
|
@ -40,7 +40,7 @@ func GetSiteHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.R
|
|||
}
|
||||
}
|
||||
|
||||
//AdminGetSiteHandler returns the template containing the sites
|
||||
// AdminGetSiteHandler returns a specific site by the ID
|
||||
func AdminGetSiteHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
reqVar := getVar(r, "siteID")
|
||||
|
||||
|
@ -76,7 +76,7 @@ func AdminGetSiteHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *h
|
|||
}
|
||||
}
|
||||
|
||||
//AdminSitesHandler returns the template containing the sites overview in the administration
|
||||
// AdminSitesHandler returns all sites
|
||||
func AdminSitesHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
page := getPageParam(r)
|
||||
|
||||
|
@ -121,7 +121,7 @@ func AdminSitesHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *htt
|
|||
}
|
||||
}
|
||||
|
||||
//AdminSiteNewHandler returns the template for adding a new site
|
||||
// AdminSiteNewHandler returns the form for adding a new site
|
||||
func AdminSiteNewHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
return &middleware.Template{
|
||||
Name: tplAdminSiteNew,
|
||||
|
@ -129,8 +129,8 @@ func AdminSiteNewHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *h
|
|||
}
|
||||
}
|
||||
|
||||
//AdminSiteNewPostHandler receives the form values and creating the site; on success the user is redirected with a success message
|
||||
//to the site overview
|
||||
// AdminSiteNewPostHandler receives the form values and creating the site; on success the user is redirected with a success message
|
||||
// to the site overview
|
||||
func AdminSiteNewPostHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
user, _ := middleware.User(r)
|
||||
|
||||
|
@ -169,7 +169,7 @@ func AdminSiteNewPostHandler(ctx *middleware.AppContext, w http.ResponseWriter,
|
|||
}
|
||||
}
|
||||
|
||||
//AdminSiteEditHandler returns the template for editing an existing site
|
||||
// AdminSiteEditHandler returns the form for changing an existing site
|
||||
func AdminSiteEditHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
siteID, err := parseInt(getVar(r, "siteID"))
|
||||
|
||||
|
@ -198,8 +198,8 @@ func AdminSiteEditHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *
|
|||
}
|
||||
}
|
||||
|
||||
//AdminSiteEditPostHandler receives the form values and updates the site; on success the user is redirected with a success message
|
||||
//to the site overview
|
||||
// AdminSiteEditPostHandler receives the form values and updates the site; on success the user is redirected with a success message
|
||||
// to the site overview
|
||||
func AdminSiteEditPostHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
u, _ := middleware.User(r)
|
||||
|
||||
|
@ -244,7 +244,7 @@ func AdminSiteEditPostHandler(ctx *middleware.AppContext, w http.ResponseWriter,
|
|||
}
|
||||
}
|
||||
|
||||
//AdminSiteOrderHandler moves the site with site id down or up
|
||||
// AdminSiteOrderHandler moves the site with site ID down or up
|
||||
func AdminSiteOrderHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
siteID, err := parseInt(getVar(r, "siteID"))
|
||||
|
||||
|
@ -286,7 +286,7 @@ func AdminSiteOrderHandler(ctx *middleware.AppContext, w http.ResponseWriter, r
|
|||
}
|
||||
}
|
||||
|
||||
//AdminSitePublishHandler returns the action template which asks the user if the site should be published / unpublished
|
||||
// AdminSitePublishHandler returns the action template which asks the user if the site should be published / unpublished
|
||||
func AdminSitePublishHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
reqVar := getVar(r, "siteID")
|
||||
|
||||
|
@ -338,7 +338,7 @@ func AdminSitePublishHandler(ctx *middleware.AppContext, w http.ResponseWriter,
|
|||
}
|
||||
}
|
||||
|
||||
//AdminSitePublishPostHandler handles the un-/publishing of a site
|
||||
// AdminSitePublishPostHandler handles the un-/publishing of a site
|
||||
func AdminSitePublishPostHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
reqVar := getVar(r, "siteID")
|
||||
|
||||
|
@ -367,7 +367,7 @@ func AdminSitePublishPostHandler(ctx *middleware.AppContext, w http.ResponseWrit
|
|||
}
|
||||
}
|
||||
|
||||
//AdminSiteDeleteHandler returns the action template which asks the user if the site should be removed
|
||||
// AdminSiteDeleteHandler returns the action which asks the user if the site should be removed
|
||||
func AdminSiteDeleteHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
reqVar := getVar(r, "siteID")
|
||||
|
||||
|
@ -408,7 +408,7 @@ func AdminSiteDeleteHandler(ctx *middleware.AppContext, w http.ResponseWriter, r
|
|||
}
|
||||
}
|
||||
|
||||
//AdminSiteDeletePostHandler handles the removing of a site
|
||||
// AdminSiteDeletePostHandler handles the removing of a site
|
||||
func AdminSiteDeletePostHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
reqVar := getVar(r, "siteID")
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package controllers_test
|
||||
package handler_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -7,7 +7,7 @@ import (
|
|||
"strconv"
|
||||
"testing"
|
||||
|
||||
"git.hoogi.eu/snafu/go-blog/controllers"
|
||||
"git.hoogi.eu/snafu/go-blog/handler"
|
||||
"git.hoogi.eu/snafu/go-blog/models"
|
||||
)
|
||||
|
||||
|
@ -92,7 +92,7 @@ func doGetSiteRequest(user reqUser, link string) (*models.Site, error) {
|
|||
}
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
tpl := controllers.GetSiteHandler(ctx, rw, r.buildRequest())
|
||||
tpl := handler.GetSiteHandler(ctx, rw, r.buildRequest())
|
||||
|
||||
if tpl.Err != nil {
|
||||
return nil, tpl.Err
|
||||
|
@ -115,7 +115,7 @@ func doAdminGetSiteRequest(user reqUser, siteID int) (*models.Site, error) {
|
|||
}
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
tpl := controllers.AdminGetSiteHandler(ctx, rw, r.buildRequest())
|
||||
tpl := handler.AdminGetSiteHandler(ctx, rw, r.buildRequest())
|
||||
|
||||
if tpl.Err != nil {
|
||||
return nil, tpl.Err
|
||||
|
@ -132,7 +132,7 @@ func doAdminListSitesRequest(user reqUser) ([]models.Site, error) {
|
|||
}
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
tpl := controllers.AdminSitesHandler(ctx, rw, r.buildRequest())
|
||||
tpl := handler.AdminSitesHandler(ctx, rw, r.buildRequest())
|
||||
|
||||
if tpl.Err != nil {
|
||||
return nil, tpl.Err
|
||||
|
@ -156,7 +156,7 @@ func doAdminSiteCreateRequest(user reqUser, s *models.Site) (int, error) {
|
|||
}
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
tpl := controllers.AdminSiteNewPostHandler(ctx, rw, r.buildRequest())
|
||||
tpl := handler.AdminSiteNewPostHandler(ctx, rw, r.buildRequest())
|
||||
|
||||
if tpl.Err != nil {
|
||||
return 0, tpl.Err
|
||||
|
@ -185,7 +185,7 @@ func doAdminSitePublishRequest(user reqUser, siteID int) error {
|
|||
}
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
tpl := controllers.AdminSitePublishPostHandler(ctx, rw, r.buildRequest())
|
||||
tpl := handler.AdminSitePublishPostHandler(ctx, rw, r.buildRequest())
|
||||
|
||||
if tpl.Err != nil {
|
||||
return tpl.Err
|
||||
|
@ -215,7 +215,7 @@ func doAdminSiteEditRequest(user reqUser, s *models.Site) error {
|
|||
}
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
tpl := controllers.AdminSiteEditPostHandler(ctx, rw, r.buildRequest())
|
||||
tpl := handler.AdminSiteEditPostHandler(ctx, rw, r.buildRequest())
|
||||
|
||||
if tpl.Err != nil {
|
||||
return tpl.Err
|
||||
|
@ -238,7 +238,7 @@ func doAdminSiteDeleteRequest(user reqUser, siteID int) error {
|
|||
}
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
tpl := controllers.AdminSiteDeletePostHandler(ctx, rw, r.buildRequest())
|
||||
tpl := handler.AdminSiteDeletePostHandler(ctx, rw, r.buildRequest())
|
||||
|
||||
if tpl.Err != nil {
|
||||
return tpl.Err
|
||||
|
@ -261,7 +261,7 @@ func doAdminSiteOrderRequest(user reqUser, siteID int) error {
|
|||
}
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
tpl := controllers.AdminSiteOrderHandler(ctx, rw, r.buildRequest())
|
||||
tpl := handler.AdminSiteOrderHandler(ctx, rw, r.buildRequest())
|
||||
|
||||
if tpl.Err != nil {
|
||||
return tpl.Err
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
@ -2,18 +2,18 @@
|
|||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package controllers
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"git.hoogi.eu/snafu/go-blog/components/httperror"
|
||||
"git.hoogi.eu/snafu/go-blog/httperror"
|
||||
"git.hoogi.eu/snafu/go-blog/middleware"
|
||||
"git.hoogi.eu/snafu/go-blog/models"
|
||||
)
|
||||
|
||||
//AdminUsersHandler returns an overview of the created users (admin only action)
|
||||
// AdminUsersHandler returns an overview of the created users
|
||||
func AdminUsersHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
page := getPageParam(r)
|
||||
|
||||
|
@ -73,7 +73,7 @@ func AdminUsersHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *htt
|
|||
}
|
||||
}
|
||||
|
||||
//AdminUserNewHandler returns the form for adding new user (admin only action)
|
||||
// AdminUserNewHandler returns the form for adding new user
|
||||
func AdminUserNewHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
return &middleware.Template{
|
||||
Name: tplAdminUserNew,
|
||||
|
@ -81,7 +81,7 @@ func AdminUserNewHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *h
|
|||
}
|
||||
}
|
||||
|
||||
//AdminUserNewPostHandler handles the creation of new users (admin only action)
|
||||
// AdminUserNewPostHandler handles the creation of new users
|
||||
func AdminUserNewPostHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
u := &models.User{
|
||||
DisplayName: r.FormValue("displayname"),
|
||||
|
@ -114,7 +114,7 @@ func AdminUserNewPostHandler(ctx *middleware.AppContext, w http.ResponseWriter,
|
|||
}
|
||||
}
|
||||
|
||||
//AdminUserEditHandler returns the form for editing an user (admin only action)
|
||||
// AdminUserEditHandler returns the form for editing an user
|
||||
func AdminUserEditHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
userID, err := parseInt(getVar(r, "userID"))
|
||||
|
||||
|
@ -143,7 +143,7 @@ func AdminUserEditHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *
|
|||
}
|
||||
}
|
||||
|
||||
//AdminUserEditPostHandler handles the updating of an user (admin only action)
|
||||
// AdminUserEditPostHandler handles the updating of an user
|
||||
func AdminUserEditPostHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
userID, err := parseInt(getVar(r, "userID"))
|
||||
|
||||
|
@ -196,11 +196,11 @@ func AdminUserEditPostHandler(ctx *middleware.AppContext, w http.ResponseWriter,
|
|||
}
|
||||
}
|
||||
|
||||
sids := ctx.SessionService.SessionProvider.SessionIDsFromValues("userid", u.ID)
|
||||
sessions := ctx.SessionService.SessionProvider.FindByValue("userid", u.ID)
|
||||
|
||||
for _, id := range sids {
|
||||
if session.SessionID() != id {
|
||||
ctx.SessionService.SessionProvider.Remove(id)
|
||||
for _, s := range sessions {
|
||||
if session.SessionID() != s.SessionID() {
|
||||
ctx.SessionService.SessionProvider.Remove(s.SessionID())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -212,7 +212,7 @@ func AdminUserEditPostHandler(ctx *middleware.AppContext, w http.ResponseWriter,
|
|||
}
|
||||
}
|
||||
|
||||
//AdminUserDeleteHandler returns the form for removing user (admin only action)
|
||||
// AdminUserDeleteHandler returns the form for removing a user
|
||||
func AdminUserDeleteHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
userID, err := parseInt(getVar(r, "userID"))
|
||||
|
||||
|
@ -256,7 +256,7 @@ func AdminUserDeleteHandler(ctx *middleware.AppContext, w http.ResponseWriter, r
|
|||
}
|
||||
}
|
||||
|
||||
//AdminUserDeletePostHandler handles removing of a user (admin only action)
|
||||
// AdminUserDeletePostHandler handles removing of a user
|
||||
func AdminUserDeletePostHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
userID, err := parseInt(getVar(r, "userID"))
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package controllers
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -8,6 +8,7 @@ import (
|
|||
"git.hoogi.eu/snafu/go-blog/models"
|
||||
)
|
||||
|
||||
// AdminUserInviteNewHandler shows the form to invite an user
|
||||
func AdminUserInviteNewHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
return &middleware.Template{
|
||||
Name: tplAdminUserInviteNew,
|
||||
|
@ -15,6 +16,7 @@ func AdminUserInviteNewHandler(ctx *middleware.AppContext, w http.ResponseWriter
|
|||
}
|
||||
}
|
||||
|
||||
// AdminUserInviteNewPostHandler handles the invitation, sends an activation mail to the invited user
|
||||
func AdminUserInviteNewPostHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
user, _ := middleware.User(r)
|
||||
|
||||
|
@ -52,6 +54,7 @@ func AdminUserInviteNewPostHandler(ctx *middleware.AppContext, w http.ResponseWr
|
|||
}
|
||||
}
|
||||
|
||||
// AdminUserInviteResendPostHandler resends the activation link to the user
|
||||
func AdminUserInviteResendPostHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
inviteID, err := parseInt(getVar(r, "inviteID"))
|
||||
|
||||
|
@ -86,6 +89,7 @@ func AdminUserInviteResendPostHandler(ctx *middleware.AppContext, w http.Respons
|
|||
}
|
||||
}
|
||||
|
||||
// AdminUserInviteDeleteHandler shows the form to remove an user invitation
|
||||
func AdminUserInviteDeleteHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
inviteID, err := parseInt(getVar(r, "inviteID"))
|
||||
|
||||
|
@ -124,6 +128,7 @@ func AdminUserInviteDeleteHandler(ctx *middleware.AppContext, w http.ResponseWri
|
|||
}
|
||||
}
|
||||
|
||||
// AdminUserInviteDeletePostHandler handles the removing of an user invitation
|
||||
func AdminUserInviteDeletePostHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
inviteID, err := parseInt(getVar(r, "inviteID"))
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package controllers_test
|
||||
package handler_test
|
||||
|
||||
import (
|
||||
"net/http/httptest"
|
||||
|
@ -6,7 +6,7 @@ import (
|
|||
"strconv"
|
||||
"testing"
|
||||
|
||||
"git.hoogi.eu/snafu/go-blog/controllers"
|
||||
"git.hoogi.eu/snafu/go-blog/handler"
|
||||
"git.hoogi.eu/snafu/go-blog/models"
|
||||
)
|
||||
|
||||
|
@ -70,7 +70,7 @@ func doAdminCreateUserInviteRequest(user reqUser, ui *models.UserInvite) (int, s
|
|||
}
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
tpl := controllers.AdminUserInviteNewPostHandler(ctx, rw, r.buildRequest())
|
||||
tpl := handler.AdminUserInviteNewPostHandler(ctx, rw, r.buildRequest())
|
||||
|
||||
if tpl.Err != nil {
|
||||
return -1, "", tpl.Err
|
||||
|
@ -93,7 +93,7 @@ func doAdminResendUserInviteRequest(user reqUser, inviteID int) (int, string, er
|
|||
}
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
tpl := controllers.AdminUserInviteResendPostHandler(ctx, rw, r.buildRequest())
|
||||
tpl := handler.AdminUserInviteResendPostHandler(ctx, rw, r.buildRequest())
|
||||
|
||||
if tpl.Err != nil {
|
||||
return -1, "", tpl.Err
|
||||
|
@ -116,7 +116,7 @@ func doAdminRemoveUserInviteRequest(user reqUser, inviteID int) error {
|
|||
}
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
tpl := controllers.AdminUserInviteDeletePostHandler(ctx, rw, r.buildRequest())
|
||||
tpl := handler.AdminUserInviteDeletePostHandler(ctx, rw, r.buildRequest())
|
||||
|
||||
if tpl.Err != nil {
|
||||
return tpl.Err
|
|
@ -2,7 +2,7 @@
|
|||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package controllers_test
|
||||
package handler_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -11,7 +11,7 @@ import (
|
|||
"strconv"
|
||||
"testing"
|
||||
|
||||
"git.hoogi.eu/snafu/go-blog/controllers"
|
||||
"git.hoogi.eu/snafu/go-blog/handler"
|
||||
"git.hoogi.eu/snafu/go-blog/models"
|
||||
)
|
||||
|
||||
|
@ -110,7 +110,7 @@ func doAdminGetUserRequest(user reqUser, userID int) (*models.User, error) {
|
|||
}
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
tpl := controllers.AdminUserEditHandler(ctx, rw, r.buildRequest())
|
||||
tpl := handler.AdminUserEditHandler(ctx, rw, r.buildRequest())
|
||||
|
||||
if tpl.Err != nil {
|
||||
return nil, tpl.Err
|
||||
|
@ -156,7 +156,7 @@ func doAdminEditUsersRequest(user reqUser, u *models.User) error {
|
|||
}
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
tpl := controllers.AdminUserEditPostHandler(ctx, rw, r.buildRequest())
|
||||
tpl := handler.AdminUserEditPostHandler(ctx, rw, r.buildRequest())
|
||||
|
||||
if tpl.Err != nil {
|
||||
return tpl.Err
|
||||
|
@ -176,7 +176,7 @@ func doAdminListUsersRequest(user reqUser) ([]models.User, error) {
|
|||
}
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
tpl := controllers.AdminUsersHandler(ctx, rw, r.buildRequest())
|
||||
tpl := handler.AdminUsersHandler(ctx, rw, r.buildRequest())
|
||||
|
||||
if tpl.Err != nil {
|
||||
return nil, tpl.Err
|
||||
|
@ -202,7 +202,7 @@ func doAdminCreateUserRequest(user reqUser, u *models.User) (int, error) {
|
|||
}
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
tpl := controllers.AdminUserNewPostHandler(ctx, rw, r.buildRequest())
|
||||
tpl := handler.AdminUserNewPostHandler(ctx, rw, r.buildRequest())
|
||||
|
||||
if tpl.Err != nil {
|
||||
return 0, tpl.Err
|
|
@ -2,7 +2,7 @@
|
|||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package controllers_test
|
||||
package handler_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
@ -20,13 +20,13 @@ import (
|
|||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"git.hoogi.eu/snafu/go-blog/components/database"
|
||||
"git.hoogi.eu/snafu/go-blog/components/logger"
|
||||
"git.hoogi.eu/snafu/go-blog/components/mail"
|
||||
"git.hoogi.eu/snafu/go-blog/crypt"
|
||||
"git.hoogi.eu/snafu/go-blog/database"
|
||||
"git.hoogi.eu/snafu/go-blog/logger"
|
||||
"git.hoogi.eu/snafu/go-blog/mail"
|
||||
"git.hoogi.eu/snafu/go-blog/middleware"
|
||||
"git.hoogi.eu/snafu/go-blog/models"
|
||||
"git.hoogi.eu/snafu/go-blog/settings"
|
||||
"git.hoogi.eu/snafu/go-blog/utils"
|
||||
"git.hoogi.eu/snafu/session"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
|
@ -64,58 +64,58 @@ func setup(t *testing.T) {
|
|||
|
||||
cfg.File.Location = os.TempDir()
|
||||
|
||||
userService := models.UserService{
|
||||
Datasource: models.SQLiteUserDatasource{
|
||||
userService := &models.UserService{
|
||||
Datasource: &models.SQLiteUserDatasource{
|
||||
SQLConn: db,
|
||||
},
|
||||
Config: cfg.User,
|
||||
}
|
||||
|
||||
userInviteService := models.UserInviteService{
|
||||
Datasource: models.SQLiteUserInviteDatasource{
|
||||
userInviteService := &models.UserInviteService{
|
||||
Datasource: &models.SQLiteUserInviteDatasource{
|
||||
SQLConn: db,
|
||||
},
|
||||
UserService: userService,
|
||||
}
|
||||
|
||||
articleService := models.ArticleService{
|
||||
articleService := &models.ArticleService{
|
||||
AppConfig: cfg.Application,
|
||||
Datasource: models.SQLiteArticleDatasource{
|
||||
Datasource: &models.SQLiteArticleDatasource{
|
||||
SQLConn: db,
|
||||
},
|
||||
}
|
||||
|
||||
siteService := models.SiteService{
|
||||
Datasource: models.SQLiteSiteDatasource{
|
||||
siteService := &models.SiteService{
|
||||
Datasource: &models.SQLiteSiteDatasource{
|
||||
SQLConn: db,
|
||||
},
|
||||
}
|
||||
|
||||
fileService := models.FileService{
|
||||
fileService := &models.FileService{
|
||||
Config: cfg.File,
|
||||
Datasource: models.SQLiteFileDatasource{
|
||||
Datasource: &models.SQLiteFileDatasource{
|
||||
SQLConn: db,
|
||||
},
|
||||
}
|
||||
|
||||
categoryService := models.CategoryService{
|
||||
Datasource: models.SQLiteCategoryDatasource{
|
||||
categoryService := &models.CategoryService{
|
||||
Datasource: &models.SQLiteCategoryDatasource{
|
||||
SQLConn: db,
|
||||
},
|
||||
}
|
||||
|
||||
tokenService := models.TokenService{
|
||||
Datasource: models.SQLiteTokenDatasource{
|
||||
tokenService := &models.TokenService{
|
||||
Datasource: &models.SQLiteTokenDatasource{
|
||||
SQLConn: db,
|
||||
},
|
||||
}
|
||||
|
||||
mailer := models.Mailer{
|
||||
mailer := &models.Mailer{
|
||||
Sender: MockSMTP{},
|
||||
AppConfig: &cfg.Application,
|
||||
}
|
||||
|
||||
sessionService := session.SessionService{
|
||||
sessionService := session.Service{
|
||||
Path: "/admin",
|
||||
Name: "test-session",
|
||||
HTTPOnly: true,
|
||||
|
@ -145,9 +145,9 @@ func teardown() {
|
|||
}
|
||||
|
||||
func fillSeeds(db *sql.DB) error {
|
||||
salt := utils.GenerateSalt()
|
||||
saltedPassword := utils.AppendBytes([]byte("123456789012"), salt)
|
||||
password, err := utils.CryptPassword([]byte(saltedPassword), 12)
|
||||
salt := crypt.GenerateSalt()
|
||||
saltedPassword := append([]byte("123456789012"), salt[:]...)
|
||||
password, err := crypt.CryptPassword([]byte(saltedPassword))
|
||||
|
||||
if err != nil {
|
||||
return err
|
|
@ -9,23 +9,23 @@ import (
|
|||
"net/http"
|
||||
)
|
||||
|
||||
//Error enriches the original go error type with
|
||||
//DisplayMsg the description for the displaying message
|
||||
//HTTPStatus the HTTP status code
|
||||
//Err the internal error it should not be shown to the user
|
||||
// Error enriches the original go error type with
|
||||
// DisplayMsg the human readable display message
|
||||
// HTTPStatus the HTTP status code
|
||||
// Err the internal error
|
||||
type Error struct {
|
||||
DisplayMsg string `json:"display_message"`
|
||||
HTTPStatus int `json:"status"`
|
||||
Err error `json:"-"`
|
||||
}
|
||||
|
||||
//New returns a new error
|
||||
// New returns a new error
|
||||
func New(httpStatus int, displayMsg string, err error) *Error {
|
||||
return &Error{DisplayMsg: displayMsg, Err: err, HTTPStatus: httpStatus}
|
||||
}
|
||||
|
||||
//PermissionDenied returns a permission denied message with code 403.
|
||||
//The following display message is returned: "You are not allowed to [action] the [subject]."
|
||||
// PermissionDenied returns a permission denied message with code 403.
|
||||
// The following display message is returned: "You are not allowed to [action] the [subject]."
|
||||
func PermissionDenied(action, subject string, err error) *Error {
|
||||
return &Error{
|
||||
HTTPStatus: http.StatusForbidden,
|
||||
|
@ -34,8 +34,8 @@ func PermissionDenied(action, subject string, err error) *Error {
|
|||
}
|
||||
}
|
||||
|
||||
//NotFound returns a not found message with code 404.
|
||||
//The following display message is returned: "The [res] was not found."
|
||||
// NotFound returns a not found message with code 404.
|
||||
// The following display message is returned: "The [res] was not found."
|
||||
func NotFound(res string, err error) *Error {
|
||||
return &Error{
|
||||
HTTPStatus: http.StatusNotFound,
|
||||
|
@ -44,8 +44,8 @@ func NotFound(res string, err error) *Error {
|
|||
}
|
||||
}
|
||||
|
||||
//ValueTooLong returns the following display message with code 422.
|
||||
//Display message: "The value of [param] is too long. Maximum [nchars] characters are allowed."
|
||||
// ValueTooLong returns the following display message with code 422.
|
||||
// Display message: "The value of [param] is too long. Maximum [nchars] characters are allowed."
|
||||
func ValueTooLong(param string, nchars int) *Error {
|
||||
return &Error{
|
||||
HTTPStatus: http.StatusUnprocessableEntity,
|
||||
|
@ -54,18 +54,18 @@ func ValueTooLong(param string, nchars int) *Error {
|
|||
}
|
||||
}
|
||||
|
||||
//InternalServerError returns a internal server error message with code 500.
|
||||
//Display message: "An internal server error occured."
|
||||
// InternalServerError returns a internal server error message with code 500.
|
||||
// Display message: "An internal server error occurred."
|
||||
func InternalServerError(err error) *Error {
|
||||
return &Error{
|
||||
HTTPStatus: http.StatusInternalServerError,
|
||||
DisplayMsg: "An internal server error occured.",
|
||||
DisplayMsg: "An internal server error occurred.",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
//ParameterMissing returns a parameter missing message with code 422.
|
||||
//Display message: "The parameter [param] is invalid."
|
||||
// ParameterMissing returns a parameter missing message with code 422.
|
||||
// Display message: "The parameter [param] is invalid."
|
||||
func ParameterMissing(param string, err error) *Error {
|
||||
return &Error{
|
||||
HTTPStatus: http.StatusUnprocessableEntity,
|
||||
|
@ -74,8 +74,8 @@ func ParameterMissing(param string, err error) *Error {
|
|||
}
|
||||
}
|
||||
|
||||
//ValueRequired returns a value required message with code 422.
|
||||
//Display message: "Please fill out the field [param]."
|
||||
// ValueRequired returns a value required message with code 422.
|
||||
// Display message: "Please fill out the field [param]."
|
||||
func ValueRequired(param string) *Error {
|
||||
return &Error{
|
||||
HTTPStatus: http.StatusUnprocessableEntity,
|
||||
|
@ -84,23 +84,10 @@ func ValueRequired(param string) *Error {
|
|||
}
|
||||
}
|
||||
|
||||
func Equals(a error, b error) bool {
|
||||
v, ok := a.(*Error)
|
||||
v2, ok2 := b.(*Error)
|
||||
|
||||
if ok && ok2 {
|
||||
return v.Err == v2.Err
|
||||
} else if !ok && !ok2 {
|
||||
return v == v2
|
||||
} else if ok && !ok2 {
|
||||
return v.Err == b
|
||||
} else if !ok && ok2 {
|
||||
return a == v2.Err
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (e Error) Error() string {
|
||||
return fmt.Sprintf("code=[%d], error=[%s], displayMsg=[%s]", e.HTTPStatus, e.Err.Error(), e.DisplayMsg)
|
||||
}
|
||||
|
||||
func (e *Error) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
|
@ -11,17 +11,12 @@ import (
|
|||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
//Log returns a new logrus instance
|
||||
// Log returns a new logrus instance
|
||||
var Log = logrus.New()
|
||||
|
||||
//InitLogger initializes the logger. Writes to file with the specified log level
|
||||
//Valid log levels are:
|
||||
// debug
|
||||
// info (fallback)
|
||||
// warn
|
||||
// error
|
||||
// fatal
|
||||
// panic
|
||||
// InitLogger initializes the logger
|
||||
// Valid log levels are: debug|info|warn|error|fatal|panic
|
||||
// Fallback: info
|
||||
func InitLogger(w io.Writer, level string) {
|
||||
level = strings.ToLower(level)
|
||||
|
|
@ -6,11 +6,11 @@ import (
|
|||
"fmt"
|
||||
"net/smtp"
|
||||
|
||||
"git.hoogi.eu/snafu/go-blog/components/logger"
|
||||
"git.hoogi.eu/snafu/go-blog/logger"
|
||||
)
|
||||
|
||||
//Service holds configuration for the SMTP server
|
||||
//The sender address and an optional subject prefix
|
||||
// Service holds configuration for the SMTP server
|
||||
// The sender address and an optional subject prefix
|
||||
type Service struct {
|
||||
SubjectPrefix string
|
||||
SMTPConfig SMTPConfig
|
||||
|
@ -25,8 +25,8 @@ func (m Mail) validate() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
//NewMailService returns a new Service with specified config
|
||||
func NewMailService(subjectPrefix, from string, smtpConfig SMTPConfig) Service {
|
||||
// NewMailService returns a new Service with specified config
|
||||
func NewMailService(subjectPrefix, from string, smtpConfig SMTPConfig) *Service {
|
||||
s := Service{
|
||||
SubjectPrefix: subjectPrefix,
|
||||
From: from,
|
||||
|
@ -35,8 +35,7 @@ func NewMailService(subjectPrefix, from string, smtpConfig SMTPConfig) Service {
|
|||
|
||||
go s.readBuffer()
|
||||
|
||||
return s
|
||||
|
||||
return &s
|
||||
}
|
||||
|
||||
type Sender interface {
|
||||
|
@ -44,16 +43,16 @@ type Sender interface {
|
|||
SendAsync(m Mail)
|
||||
}
|
||||
|
||||
//SMTPConfig holds the configuration for the SMTP server
|
||||
// SMTPConfig holds the configuration for the SMTP server
|
||||
type SMTPConfig struct {
|
||||
Address string
|
||||
Port int
|
||||
User string
|
||||
Helo string
|
||||
HELO string
|
||||
Password []byte
|
||||
}
|
||||
|
||||
//Mail represents a mail
|
||||
// Mail represents a mail
|
||||
type Mail struct {
|
||||
To string
|
||||
Subject string
|
||||
|
@ -89,7 +88,7 @@ func (s Service) SendAsync(m Mail) {
|
|||
}()
|
||||
}
|
||||
|
||||
//Send sends a mail over the configured SMTP server
|
||||
// Send sends a mail over the configured SMTP server
|
||||
func (s Service) Send(m Mail) error {
|
||||
if len(s.SMTPConfig.User) > 0 && len(s.SMTPConfig.Password) > 0 {
|
||||
auth := smtp.PlainAuth("", s.SMTPConfig.User, string(s.SMTPConfig.Password), s.SMTPConfig.Address)
|
||||
|
@ -109,8 +108,8 @@ func (s Service) Send(m Mail) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if len(s.SMTPConfig.Helo) > 0 {
|
||||
if err := c.Hello(s.SMTPConfig.Helo); err != nil {
|
||||
if len(s.SMTPConfig.HELO) > 0 {
|
||||
if err := c.Hello(s.SMTPConfig.HELO); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -144,7 +143,7 @@ func (s Service) Send(m Mail) error {
|
|||
}
|
||||
}
|
||||
|
||||
var buffer = make(chan Mail, 10)
|
||||
var buffer = make(chan Mail, 5)
|
||||
|
||||
func (s Service) readBuffer() {
|
||||
for {
|
81
main.go
81
main.go
|
@ -13,9 +13,9 @@ import (
|
|||
"time"
|
||||
|
||||
"git.hoogi.eu/snafu/cfg"
|
||||
"git.hoogi.eu/snafu/go-blog/components/database"
|
||||
"git.hoogi.eu/snafu/go-blog/components/logger"
|
||||
"git.hoogi.eu/snafu/go-blog/components/mail"
|
||||
"git.hoogi.eu/snafu/go-blog/database"
|
||||
"git.hoogi.eu/snafu/go-blog/logger"
|
||||
"git.hoogi.eu/snafu/go-blog/mail"
|
||||
m "git.hoogi.eu/snafu/go-blog/middleware"
|
||||
"git.hoogi.eu/snafu/go-blog/models"
|
||||
"git.hoogi.eu/snafu/go-blog/routers"
|
||||
|
@ -36,19 +36,19 @@ func main() {
|
|||
}()
|
||||
|
||||
configFiles := []cfg.File{
|
||||
cfg.File{
|
||||
{
|
||||
Name: "go-blog.conf",
|
||||
Path: ".",
|
||||
Required: true,
|
||||
},
|
||||
cfg.File{
|
||||
{
|
||||
Name: "go-blog.conf",
|
||||
Path: "./custom",
|
||||
Required: false,
|
||||
},
|
||||
}
|
||||
|
||||
cfg, err := settings.MergeConfigs(configFiles)
|
||||
config, err := settings.MergeConfigs(configFiles)
|
||||
|
||||
if err != nil {
|
||||
exitCode = 1
|
||||
|
@ -56,29 +56,29 @@ func main() {
|
|||
return
|
||||
}
|
||||
|
||||
cfg.BuildVersion = BuildVersion
|
||||
cfg.BuildGitHash = GitHash
|
||||
config.BuildVersion = BuildVersion
|
||||
config.BuildGitHash = GitHash
|
||||
|
||||
if err = cfg.CheckConfig(); err != nil {
|
||||
if err = config.CheckConfig(); err != nil {
|
||||
exitCode = 1
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
if cfg.Environment == "prod" {
|
||||
logFile, err := os.OpenFile(cfg.Log.File, os.O_APPEND|os.O_WRONLY, os.ModeAppend)
|
||||
if config.Environment == "prod" {
|
||||
logFile, err := os.OpenFile(config.Log.File, os.O_APPEND|os.O_WRONLY, os.ModeAppend)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
exitCode = 1
|
||||
}
|
||||
|
||||
logger.InitLogger(logFile, cfg.Log.Level)
|
||||
logger.InitLogger(logFile, config.Log.Level)
|
||||
} else {
|
||||
logger.InitLogger(os.Stdout, cfg.Log.Level)
|
||||
logger.InitLogger(os.Stdout, config.Log.Level)
|
||||
}
|
||||
|
||||
csrf, err := cfg.GenerateCSRF()
|
||||
csrf, err := config.GenerateCSRF()
|
||||
|
||||
if err != nil {
|
||||
exitCode = 1
|
||||
|
@ -91,10 +91,10 @@ func main() {
|
|||
}
|
||||
|
||||
logger.Log.Infof("Go-Blog version: %s, commit: %s", BuildVersion, GitHash)
|
||||
logger.Log.Infof("running in %s mode", cfg.Environment)
|
||||
logger.Log.Infof("running in %s mode", config.Environment)
|
||||
|
||||
dbConf := database.SQLiteConfig{
|
||||
File: cfg.Database.File,
|
||||
File: config.Database.File,
|
||||
}
|
||||
|
||||
db, err := dbConf.Open()
|
||||
|
@ -112,7 +112,7 @@ func main() {
|
|||
}
|
||||
}()
|
||||
|
||||
ctx, err := context(db, cfg)
|
||||
ctx, err := context(db, config)
|
||||
|
||||
if err != nil {
|
||||
logger.Log.Error(err)
|
||||
|
@ -120,22 +120,21 @@ func main() {
|
|||
return
|
||||
}
|
||||
|
||||
r := routers.InitRoutes(ctx, cfg)
|
||||
r := routers.InitRoutes(ctx, config)
|
||||
|
||||
s := &http.Server{
|
||||
Addr: fmt.Sprintf("%s:%d", cfg.Server.Address, cfg.Server.Port),
|
||||
Addr: fmt.Sprintf("%s:%d", config.Server.Address, config.Server.Port),
|
||||
Handler: r,
|
||||
ReadTimeout: 15 * time.Second,
|
||||
WriteTimeout: 20 * time.Second,
|
||||
MaxHeaderBytes: 1 << 20,
|
||||
}
|
||||
|
||||
logger.Log.Infof("server will start at %s on port %d", cfg.Server.Address, cfg.Server.Port)
|
||||
|
||||
if cfg.Server.UseTLS {
|
||||
err = s.ListenAndServeTLS(cfg.Server.Cert, cfg.Server.Key)
|
||||
|
||||
if config.Server.UseTLS {
|
||||
logger.Log.Infof("server will start at https://%s:%d", config.Server.Address, config.Server.Port)
|
||||
err = s.ListenAndServeTLS(config.Server.Cert, config.Server.Key)
|
||||
} else {
|
||||
logger.Log.Infof("server will start at http://%s:%d", config.Server.Address, config.Server.Port)
|
||||
err = s.ListenAndServe()
|
||||
}
|
||||
|
||||
|
@ -149,49 +148,49 @@ func main() {
|
|||
func context(db *sql.DB, cfg *settings.Settings) (*m.AppContext, error) {
|
||||
ic := loadUserInterceptor(cfg.User.InterceptorPlugin)
|
||||
|
||||
userService := models.UserService{
|
||||
Datasource: models.SQLiteUserDatasource{
|
||||
userService := &models.UserService{
|
||||
Datasource: &models.SQLiteUserDatasource{
|
||||
SQLConn: db,
|
||||
},
|
||||
Config: cfg.User,
|
||||
UserInterceptor: ic,
|
||||
}
|
||||
|
||||
userInviteService := models.UserInviteService{
|
||||
Datasource: models.SQLiteUserInviteDatasource{
|
||||
userInviteService := &models.UserInviteService{
|
||||
Datasource: &models.SQLiteUserInviteDatasource{
|
||||
SQLConn: db,
|
||||
},
|
||||
UserService: userService,
|
||||
}
|
||||
|
||||
articleService := models.ArticleService{
|
||||
articleService := &models.ArticleService{
|
||||
AppConfig: cfg.Application,
|
||||
Datasource: models.SQLiteArticleDatasource{
|
||||
Datasource: &models.SQLiteArticleDatasource{
|
||||
SQLConn: db,
|
||||
},
|
||||
}
|
||||
|
||||
siteService := models.SiteService{
|
||||
Datasource: models.SQLiteSiteDatasource{
|
||||
siteService := &models.SiteService{
|
||||
Datasource: &models.SQLiteSiteDatasource{
|
||||
SQLConn: db,
|
||||
},
|
||||
}
|
||||
|
||||
fileService := models.FileService{
|
||||
fileService := &models.FileService{
|
||||
Config: cfg.File,
|
||||
Datasource: models.SQLiteFileDatasource{
|
||||
Datasource: &models.SQLiteFileDatasource{
|
||||
SQLConn: db,
|
||||
},
|
||||
}
|
||||
|
||||
categoryService := models.CategoryService{
|
||||
Datasource: models.SQLiteCategoryDatasource{
|
||||
categoryService := &models.CategoryService{
|
||||
Datasource: &models.SQLiteCategoryDatasource{
|
||||
SQLConn: db,
|
||||
},
|
||||
}
|
||||
|
||||
tokenService := models.TokenService{
|
||||
Datasource: models.SQLiteTokenDatasource{
|
||||
tokenService := &models.TokenService{
|
||||
Datasource: &models.SQLiteTokenDatasource{
|
||||
SQLConn: db,
|
||||
},
|
||||
}
|
||||
|
@ -205,8 +204,8 @@ func context(db *sql.DB, cfg *settings.Settings) (*m.AppContext, error) {
|
|||
|
||||
sender := mail.NewMailService(cfg.Mail.SubjectPrefix, cfg.Mail.SenderAddress, smtpConfig)
|
||||
|
||||
mailer := models.Mailer{
|
||||
Sender: &sender,
|
||||
mailer := &models.Mailer{
|
||||
Sender: sender,
|
||||
AppConfig: &cfg.Application,
|
||||
}
|
||||
|
||||
|
@ -221,7 +220,7 @@ func context(db *sql.DB, cfg *settings.Settings) (*m.AppContext, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
sessionService := session.SessionService{
|
||||
sessionService := session.Service{
|
||||
Path: cfg.Session.CookiePath,
|
||||
Name: cfg.Session.CookieName,
|
||||
Secure: cfg.Session.CookieSecure,
|
||||
|
|
|
@ -12,17 +12,17 @@ import (
|
|||
"git.hoogi.eu/snafu/session"
|
||||
)
|
||||
|
||||
//AppContext contains the services, session store, templates, ...
|
||||
// AppContext contains the services, session store, templates, ...
|
||||
type AppContext struct {
|
||||
SessionService *session.SessionService
|
||||
ArticleService models.ArticleService
|
||||
CategoryService models.CategoryService
|
||||
UserService models.UserService
|
||||
UserInviteService models.UserInviteService
|
||||
SiteService models.SiteService
|
||||
FileService models.FileService
|
||||
TokenService models.TokenService
|
||||
Mailer models.Mailer
|
||||
SessionService *session.Service
|
||||
ArticleService *models.ArticleService
|
||||
CategoryService *models.CategoryService
|
||||
UserService *models.UserService
|
||||
UserInviteService *models.UserInviteService
|
||||
SiteService *models.SiteService
|
||||
FileService *models.FileService
|
||||
TokenService *models.TokenService
|
||||
Mailer *models.Mailer
|
||||
ConfigService *settings.Settings
|
||||
Templates *template.Template
|
||||
}
|
||||
|
|
|
@ -8,8 +8,8 @@ import (
|
|||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"git.hoogi.eu/snafu/go-blog/components/httperror"
|
||||
"git.hoogi.eu/snafu/go-blog/components/logger"
|
||||
"git.hoogi.eu/snafu/go-blog/httperror"
|
||||
"git.hoogi.eu/snafu/go-blog/logger"
|
||||
"git.hoogi.eu/snafu/go-blog/models"
|
||||
)
|
||||
|
||||
|
@ -19,11 +19,12 @@ type JSONHandler struct {
|
|||
Handler JHandler
|
||||
}
|
||||
|
||||
//JHandler enriches handler with the AppContext
|
||||
// JHandler enriches handler with the AppContext
|
||||
type JHandler func(*AppContext, http.ResponseWriter, *http.Request) (*models.JSONData, error)
|
||||
|
||||
func (fn JSONHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
statusCode := 200
|
||||
logWithIP := logger.Log.WithField("ip", getIP(r))
|
||||
code := http.StatusOK
|
||||
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
|
||||
|
@ -32,34 +33,50 @@ func (fn JSONHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
|||
if err != nil {
|
||||
switch e := err.(type) {
|
||||
case *httperror.Error:
|
||||
statusCode = e.HTTPStatus
|
||||
code = e.HTTPStatus
|
||||
default:
|
||||
statusCode = 500
|
||||
logger.Log.Error(e)
|
||||
code = http.StatusInternalServerError
|
||||
}
|
||||
|
||||
logger.Log.Error(err)
|
||||
logWithIP.Error(err)
|
||||
|
||||
mjson, err2 := json.Marshal(err)
|
||||
if err2 != nil {
|
||||
logger.Log.Error(err2)
|
||||
http.Error(rw, err2.Error(), http.StatusInternalServerError)
|
||||
j, err := json.Marshal(err)
|
||||
|
||||
if err != nil {
|
||||
logWithIP.Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
rw.WriteHeader(statusCode)
|
||||
rw.Write(mjson)
|
||||
rw.WriteHeader(code)
|
||||
|
||||
_, err = rw.Write(j)
|
||||
|
||||
if err != nil {
|
||||
logWithIP.Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
mjson, err2 := json.Marshal(data)
|
||||
j, err := json.Marshal(data)
|
||||
|
||||
if err2 != nil {
|
||||
http.Error(rw, err2.Error(), http.StatusInternalServerError)
|
||||
rw.WriteHeader(500)
|
||||
if err != nil {
|
||||
logWithIP.Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
rw.WriteHeader(statusCode)
|
||||
rw.Write(mjson)
|
||||
rw.WriteHeader(code)
|
||||
|
||||
_, err = rw.Write(j)
|
||||
|
||||
if err != nil {
|
||||
logWithIP.Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,25 +5,25 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.hoogi.eu/snafu/go-blog/components/logger"
|
||||
"git.hoogi.eu/snafu/go-blog/utils"
|
||||
"git.hoogi.eu/snafu/go-blog/logger"
|
||||
)
|
||||
|
||||
var locals = [...]net.IPNet{
|
||||
net.IPNet{
|
||||
{
|
||||
IP: net.IPv4(10, 0, 0, 0),
|
||||
Mask: net.CIDRMask(8, 32),
|
||||
},
|
||||
net.IPNet{
|
||||
{
|
||||
IP: net.IPv4(172, 16, 0, 0),
|
||||
Mask: net.CIDRMask(12, 32),
|
||||
},
|
||||
net.IPNet{
|
||||
{
|
||||
IP: net.IPv4(192, 168, 0, 0),
|
||||
Mask: net.CIDRMask(16, 32),
|
||||
},
|
||||
|
@ -72,7 +72,7 @@ func setCookie(rw http.ResponseWriter, name, path, data string) {
|
|||
c := &http.Cookie{
|
||||
Name: name,
|
||||
Path: path,
|
||||
Value: utils.EncodeBase64(data),
|
||||
Value: base64.StdEncoding.EncodeToString([]byte(data)),
|
||||
}
|
||||
|
||||
http.SetCookie(rw, c)
|
||||
|
@ -80,6 +80,7 @@ func setCookie(rw http.ResponseWriter, name, path, data string) {
|
|||
|
||||
func getFlash(w http.ResponseWriter, r *http.Request, name string) (string, error) {
|
||||
c, err := r.Cookie(name)
|
||||
|
||||
if err != nil {
|
||||
switch err {
|
||||
case http.ErrNoCookie:
|
||||
|
@ -88,7 +89,9 @@ func getFlash(w http.ResponseWriter, r *http.Request, name string) (string, erro
|
|||
return "", err
|
||||
}
|
||||
}
|
||||
value, err := utils.DecodeBase64(c.Value)
|
||||
|
||||
value, err := base64.StdEncoding.DecodeString(c.Value)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -98,9 +101,10 @@ func getFlash(w http.ResponseWriter, r *http.Request, name string) (string, erro
|
|||
Name: name,
|
||||
MaxAge: -1,
|
||||
Expires: time.Unix(1, 0),
|
||||
Path: "/"}
|
||||
Path: "/",
|
||||
}
|
||||
|
||||
http.SetCookie(w, dc)
|
||||
|
||||
return value, nil
|
||||
return string(value), nil
|
||||
}
|
||||
|
|
|
@ -16,17 +16,20 @@ import (
|
|||
"time"
|
||||
|
||||
"git.hoogi.eu/snafu/cfg"
|
||||
"git.hoogi.eu/snafu/go-blog/components/httperror"
|
||||
"git.hoogi.eu/snafu/go-blog/components/logger"
|
||||
"git.hoogi.eu/snafu/go-blog/httperror"
|
||||
"git.hoogi.eu/snafu/go-blog/logger"
|
||||
"git.hoogi.eu/snafu/go-blog/models"
|
||||
"git.hoogi.eu/snafu/go-blog/settings"
|
||||
"git.hoogi.eu/snafu/go-blog/utils"
|
||||
)
|
||||
|
||||
// Template contains the information about the template to render.
|
||||
// The DisplayMsg in models.Error will be the ErrorMsg in the flash bubble,
|
||||
// the SuccessMsg is an optional variable which is also displayed as a green flash bubble,
|
||||
// both are appended to the data map with keys 'ErrorMsg' or 'SuccessMsg' in the AppHandler
|
||||
// Active contains the current active navigation.
|
||||
// Data the data which is injected into the templates.
|
||||
// SuccessMsg is an optional variable which is displayed as a green message.
|
||||
// WarnMsg is an optional variable which is displayed as an orange message.
|
||||
// RedirectPath contains the path where the request should be redirected.
|
||||
// Err will be shown as red message in templates. If it's a httperror, the display message will be shown,
|
||||
// otherwise generich 'An internal error occurred' is shown.
|
||||
type Template struct {
|
||||
Name string
|
||||
Active string
|
||||
|
@ -37,19 +40,21 @@ type Template struct {
|
|||
Err error
|
||||
}
|
||||
|
||||
//Templates defines in which directory should be looked for template
|
||||
// Templates defines the directory where the templates are located, the FuncMap are additional functions, which can
|
||||
// be used in the templates.
|
||||
type Templates struct {
|
||||
Directory string
|
||||
FuncMap template.FuncMap
|
||||
}
|
||||
|
||||
//NotFound returned if no route matches
|
||||
// NotFound returned if no route matches
|
||||
func NotFound(ctx *AppContext, rw http.ResponseWriter, r *http.Request) *Template {
|
||||
//For deleting flash cookies
|
||||
getFlash(rw, r, "ErrorMsg")
|
||||
getFlash(rw, r, "SuccessMsg")
|
||||
|
||||
session, _ := ctx.SessionService.Get(rw, r)
|
||||
|
||||
if session != nil && strings.HasPrefix(r.URL.EscapedPath(), "/admin") {
|
||||
return &Template{
|
||||
Name: "admin/error",
|
||||
|
@ -63,8 +68,8 @@ func NotFound(ctx *AppContext, rw http.ResponseWriter, r *http.Request) *Templat
|
|||
}
|
||||
}
|
||||
|
||||
//FuncMap some function that can be used in templates
|
||||
func FuncMap(ss models.SiteService, settings *settings.Settings) template.FuncMap {
|
||||
// FuncMap some function that can be used in templates
|
||||
func FuncMap(ss *models.SiteService, settings *settings.Settings) template.FuncMap {
|
||||
return template.FuncMap{
|
||||
"GetMetadata": func(data map[string]interface{}) template.HTML {
|
||||
var meta, desc string
|
||||
|
@ -157,8 +162,7 @@ func FuncMap(ss models.SiteService, settings *settings.Settings) template.FuncMa
|
|||
return t.Time.In(time.Local).Format("January 2, 2006 at 3:04 PM")
|
||||
},
|
||||
"HumanizeFilesize": func(size int64) string {
|
||||
fs := cfg.FileSize(size)
|
||||
return fs.HumanReadable()
|
||||
return cfg.FileSize(size).HumanReadable()
|
||||
},
|
||||
"FormatDateTime": func(t time.Time) string {
|
||||
return t.In(time.Local).Format("January 2, 2006 at 3:04 PM")
|
||||
|
@ -185,8 +189,7 @@ func FuncMap(ss models.SiteService, settings *settings.Settings) template.FuncMa
|
|||
return template.HTML(models.MarkdownToHTML([]byte(s)))
|
||||
},
|
||||
"NToBr": func(in string) template.HTML {
|
||||
out := models.NewlineToBr(models.EscapeHTML(in))
|
||||
return template.HTML(out)
|
||||
return template.HTML(models.NewlineToBr(models.EscapeHTML(in)))
|
||||
},
|
||||
"EscapeHTML": func(in string) string {
|
||||
return html.EscapeString(in)
|
||||
|
@ -204,7 +207,7 @@ func FuncMap(ss models.SiteService, settings *settings.Settings) template.FuncMa
|
|||
}
|
||||
}
|
||||
|
||||
//Load walks threw directory and parses templates ending with html
|
||||
// Load walks threw directory and parses templates ending with html
|
||||
func (ts Templates) Load() (*template.Template, error) {
|
||||
tpl := template.New("").Funcs(ts.FuncMap)
|
||||
|
||||
|
@ -229,10 +232,10 @@ func (ts Templates) Load() (*template.Template, error) {
|
|||
return tpl, err
|
||||
}
|
||||
|
||||
//RedirectURL builds a URL for redirecting
|
||||
// RedirectURL builds a URL for redirecting
|
||||
func (t Template) RedirectURL() string {
|
||||
if t.RedirectPath[0] == byte('/') {
|
||||
return t.RedirectPath
|
||||
}
|
||||
return utils.AppendString("/", t.RedirectPath)
|
||||
return "/" + t.RedirectPath
|
||||
}
|
||||
|
|
|
@ -12,8 +12,8 @@ import (
|
|||
|
||||
"github.com/gorilla/csrf"
|
||||
|
||||
"git.hoogi.eu/snafu/go-blog/components/httperror"
|
||||
"git.hoogi.eu/snafu/go-blog/components/logger"
|
||||
"git.hoogi.eu/snafu/go-blog/httperror"
|
||||
"git.hoogi.eu/snafu/go-blog/logger"
|
||||
"git.hoogi.eu/snafu/go-blog/models"
|
||||
)
|
||||
|
||||
|
@ -23,59 +23,57 @@ var (
|
|||
UserContextKey = contextKey("user")
|
||||
)
|
||||
|
||||
//TemplateHandler enriches handlers with a application context containing 'services'
|
||||
// TemplateHandler enriches handlers with a application context containing 'services'
|
||||
type TemplateHandler struct {
|
||||
AppCtx *AppContext
|
||||
Handler Handler
|
||||
}
|
||||
|
||||
//Handler enriches handler with the AppContext
|
||||
// Handler enriches handler with the AppContext
|
||||
type Handler func(*AppContext, http.ResponseWriter, *http.Request) *Template
|
||||
|
||||
func (fn TemplateHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
var errorMsg, warnMsg, successMsg string
|
||||
statusCode := 200
|
||||
|
||||
ip := getIP(r)
|
||||
en := logger.Log.WithField("ip", ip)
|
||||
|
||||
t := fn.Handler(fn.AppCtx, rw, r)
|
||||
|
||||
if t.Data == nil {
|
||||
t.Data = make(map[string]interface{})
|
||||
}
|
||||
|
||||
user, err := User(r)
|
||||
|
||||
if err == nil {
|
||||
t.Data["currentUser"] = user
|
||||
}
|
||||
|
||||
var errorMsg, warnMsg, successMsg string
|
||||
successMsg = t.SuccessMsg
|
||||
warnMsg = t.WarnMsg
|
||||
code := http.StatusOK
|
||||
|
||||
logWithIP := logger.Log.WithField("ip", getIP(r))
|
||||
|
||||
t.Data["CSRFToken"] = csrf.Token(r)
|
||||
|
||||
if t.Err != nil {
|
||||
|
||||
switch e := t.Err.(type) {
|
||||
case *httperror.Error:
|
||||
statusCode = e.HTTPStatus
|
||||
en.Error(e)
|
||||
code = e.HTTPStatus
|
||||
logWithIP.Error(e)
|
||||
errorMsg = e.DisplayMsg
|
||||
default:
|
||||
en.Error(e)
|
||||
errorMsg = "Sorry, an internal server error occured"
|
||||
logWithIP.Error(e)
|
||||
errorMsg = "Sorry, an internal server error occurred"
|
||||
}
|
||||
|
||||
t.Data["ErrorMsg"] = errorMsg
|
||||
}
|
||||
|
||||
if user, err := User(r); err == nil {
|
||||
t.Data["currentUser"] = user
|
||||
}
|
||||
|
||||
if len(t.RedirectPath) == 0 {
|
||||
t.Data["SuccessMsg"] = successMsg
|
||||
t.Data["WarnsMsg"] = warnMsg
|
||||
|
||||
fl, err := getFlash(rw, r, "SuccessMsg")
|
||||
|
||||
if err != nil {
|
||||
logger.Log.Error(err)
|
||||
logWithIP.Error(err)
|
||||
} else if len(fl) > 0 {
|
||||
t.Data["SuccessMsg"] = fl
|
||||
}
|
||||
|
@ -83,7 +81,7 @@ func (fn TemplateHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
|||
fl, err = getFlash(rw, r, "ErrorMsg")
|
||||
|
||||
if err != nil {
|
||||
en.Error(err)
|
||||
logWithIP.Error(err)
|
||||
} else if len(fl) > 0 {
|
||||
t.Data["ErrorMsg"] = fl
|
||||
}
|
||||
|
@ -91,7 +89,7 @@ func (fn TemplateHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
|||
fl, err = getFlash(rw, r, "WarnMsg")
|
||||
|
||||
if err != nil {
|
||||
en.Error(err)
|
||||
logWithIP.Error(err)
|
||||
} else if len(fl) > 0 {
|
||||
t.Data["WarnMsg"] = fl
|
||||
}
|
||||
|
@ -99,14 +97,14 @@ func (fn TemplateHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
|||
t.Data[csrf.TemplateTag] = csrf.TemplateField(r)
|
||||
t.Data["active"] = t.Active
|
||||
|
||||
rw.WriteHeader(statusCode)
|
||||
rw.WriteHeader(code)
|
||||
|
||||
if err := fn.AppCtx.Templates.ExecuteTemplate(rw, t.Name, t.Data); err != nil {
|
||||
en.Error(err)
|
||||
logWithIP.Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
} else {
|
||||
statusCode = http.StatusFound
|
||||
code = http.StatusFound
|
||||
if len(errorMsg) > 0 {
|
||||
setCookie(rw, "ErrorMsg", "/", errorMsg)
|
||||
} else if len(warnMsg) > 0 {
|
||||
|
@ -114,88 +112,110 @@ func (fn TemplateHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
|||
} else if len(successMsg) > 0 {
|
||||
setCookie(rw, "SuccessMsg", "/", successMsg)
|
||||
}
|
||||
http.Redirect(rw, r, path.Clean(t.RedirectURL()), statusCode)
|
||||
http.Redirect(rw, r, path.Clean(t.RedirectURL()), code)
|
||||
}
|
||||
}
|
||||
|
||||
//AuthHandler checks if the user is authenticated; if not next handler in chain is not called
|
||||
// AuthHandler checks if the user is authenticated; if not next handler in chain is not called
|
||||
func (ctx AppContext) AuthHandler(handler http.Handler) http.Handler {
|
||||
fn := func(rw http.ResponseWriter, r *http.Request) {
|
||||
logWithIP := logger.Log.WithField("ip", getIP(r))
|
||||
|
||||
session, err := ctx.SessionService.Get(rw, r)
|
||||
|
||||
if err != nil {
|
||||
logger.Log.Error(err)
|
||||
logWithIP.Error(err)
|
||||
rw.WriteHeader(http.StatusUnauthorized)
|
||||
ctx.Templates.ExecuteTemplate(rw, "admin/login", map[string]interface{}{
|
||||
if err := ctx.Templates.ExecuteTemplate(rw, "admin/login", map[string]interface{}{
|
||||
"ErrorMsg": "Please provide login credentials.",
|
||||
"state": r.URL.EscapedPath(),
|
||||
csrf.TemplateTag: csrf.TemplateField(r),
|
||||
})
|
||||
}); err != nil {
|
||||
logWithIP.Errorf("error while executing the template %v", err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
userid, ok := session.GetValue("userid").(int)
|
||||
|
||||
if !ok {
|
||||
logger.Log.Errorf("userid is not an integer %v", userid)
|
||||
logWithIP.Error(err)
|
||||
|
||||
rw.WriteHeader(http.StatusUnauthorized)
|
||||
ctx.Templates.ExecuteTemplate(rw, "admin/login", map[string]interface{}{
|
||||
if err := ctx.Templates.ExecuteTemplate(rw, "admin/login", map[string]interface{}{
|
||||
"ErrorMsg": "Please provide login credentials.",
|
||||
"state": r.URL.EscapedPath(),
|
||||
csrf.TemplateTag: csrf.TemplateField(r),
|
||||
})
|
||||
}); err != nil {
|
||||
logWithIP.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
u, err := ctx.UserService.GetByID(userid)
|
||||
|
||||
if err != nil {
|
||||
logger.Log.Error(err)
|
||||
logWithIP.Error(err)
|
||||
rw.WriteHeader(http.StatusUnauthorized)
|
||||
ctx.Templates.ExecuteTemplate(rw, "admin/login", map[string]interface{}{
|
||||
if err := ctx.Templates.ExecuteTemplate(rw, "admin/login", map[string]interface{}{
|
||||
"ErrorMsg": "Please provide login credentials.",
|
||||
"state": r.URL.EscapedPath(),
|
||||
csrf.TemplateTag: csrf.TemplateField(r),
|
||||
})
|
||||
}); err != nil {
|
||||
logger.Log.Errorf("error while executing the template %v", err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), UserContextKey, u)
|
||||
handler.ServeHTTP(rw, r.WithContext(ctx))
|
||||
handler.ServeHTTP(rw, r.WithContext(context.WithValue(r.Context(), UserContextKey, u)))
|
||||
}
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
|
||||
//RequireAdmin ensures that the user is an admin; if not next handler in chain is not called
|
||||
// RequireAdmin ensures that the user is an admin, if not next handler in chain is not called.
|
||||
func (ctx AppContext) RequireAdmin(handler http.Handler) http.Handler {
|
||||
fn := func(rw http.ResponseWriter, r *http.Request) {
|
||||
logWithIP := logger.Log.WithField("ip", getIP(r))
|
||||
|
||||
u, err := User(r)
|
||||
|
||||
if err != nil {
|
||||
logger.Log.Error(err)
|
||||
ctx.Templates.ExecuteTemplate(rw, "admin/error", map[string]interface{}{
|
||||
"ErrorMsg": "An internal server error occured",
|
||||
})
|
||||
logWithIP.Error(err)
|
||||
if err := ctx.Templates.ExecuteTemplate(rw, "admin/error", map[string]interface{}{
|
||||
"ErrorMsg": "An internal server error occurred",
|
||||
}); err != nil {
|
||||
logWithIP.Errorf("error while executing the template %v", err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if u.IsAdmin == false {
|
||||
ctx.Templates.ExecuteTemplate(rw, "admin/error", map[string]interface{}{
|
||||
if err := ctx.Templates.ExecuteTemplate(rw, "admin/error", map[string]interface{}{
|
||||
"ErrorMsg": "You have not the permissions to execute this action",
|
||||
"currentUser": u,
|
||||
})
|
||||
}); err != nil {
|
||||
logWithIP.Errorf("error while executing the template %v", err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
handler.ServeHTTP(rw, r)
|
||||
}
|
||||
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
|
||||
//User gets the user from the request context
|
||||
// User gets the user from the request context
|
||||
func User(r *http.Request) (*models.User, error) {
|
||||
v := r.Context().Value(UserContextKey)
|
||||
if v == nil {
|
||||
return nil, httperror.InternalServerError(errors.New("user is not available in context. is the authentication handler in chain?"))
|
||||
return nil, httperror.InternalServerError(errors.New("user is not available in context"))
|
||||
}
|
||||
|
||||
return v.(*models.User), nil
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"encoding/xml"
|
||||
"net/http"
|
||||
|
||||
"git.hoogi.eu/snafu/go-blog/components/logger"
|
||||
"git.hoogi.eu/snafu/go-blog/logger"
|
||||
"git.hoogi.eu/snafu/go-blog/models"
|
||||
)
|
||||
|
||||
|
@ -15,32 +15,40 @@ type XMLHandler struct {
|
|||
Handler XHandler
|
||||
}
|
||||
|
||||
//XNLHandler enriches handler with the AppContext
|
||||
// XHandler enriches handler with the AppContext
|
||||
type XHandler func(*AppContext, http.ResponseWriter, *http.Request) (*models.XMLData, error)
|
||||
|
||||
func (fn XMLHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
logWithIP := logger.Log.WithField("ip", getIP(r))
|
||||
|
||||
rw.Header().Set("Content-Type", "application/xml")
|
||||
|
||||
h, err := fn.Handler(fn.AppCtx, rw, r)
|
||||
|
||||
if err != nil {
|
||||
logger.Log.Error(err)
|
||||
logWithIP.Error(err)
|
||||
|
||||
x, err2 := xml.Marshal(err)
|
||||
x, err := xml.Marshal(err)
|
||||
|
||||
if err2 != nil {
|
||||
logger.Log.Error(err2)
|
||||
http.Error(rw, err2.Error(), http.StatusInternalServerError)
|
||||
if err != nil {
|
||||
logWithIP.Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = rw.Write(x); err != nil {
|
||||
logWithIP.Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
rw.Write(x)
|
||||
return
|
||||
}
|
||||
|
||||
x, err2 := xml.MarshalIndent(h.Data, "", "\t")
|
||||
|
||||
if err2 != nil {
|
||||
logWithIP.Error(err)
|
||||
http.Error(rw, err2.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
@ -55,5 +63,9 @@ func (fn XMLHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
|||
x = bytes.Replace(x, []byte(">"), []byte(">"), -1) // >
|
||||
}
|
||||
|
||||
rw.Write(x)
|
||||
if _, err := rw.Write(x); err != nil {
|
||||
logger.Log.Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,12 +4,12 @@
|
|||
|
||||
package models
|
||||
|
||||
// Action this type is used for YES/NO actions see template/admin/action.html
|
||||
// Title is shown in the headline
|
||||
// ActionURL defines where the form should be sent
|
||||
// BackLinkURL defines where to go back (if clicking on cancel)
|
||||
// WarnMsg defines an optional warning which is shown above the description
|
||||
// Description describes what action the user has to decide
|
||||
// Action this type is used for YES/NO actions see template/admin/action.html.
|
||||
// Title is shown in the headline.
|
||||
// ActionURL defines where the form should be sent.
|
||||
// BackLinkURL defines where to go back (if clicking on cancel).
|
||||
// WarnMsg defines an optional warning which is shown above the description.
|
||||
// Description describes what question the user has to decide.
|
||||
type Action struct {
|
||||
ID string
|
||||
Title string
|
||||
|
|
|
@ -14,9 +14,9 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"git.hoogi.eu/snafu/go-blog/components/httperror"
|
||||
"git.hoogi.eu/snafu/go-blog/httperror"
|
||||
"git.hoogi.eu/snafu/go-blog/settings"
|
||||
"git.hoogi.eu/snafu/go-blog/utils"
|
||||
"git.hoogi.eu/snafu/go-blog/slug"
|
||||
)
|
||||
|
||||
// Article represents an article
|
||||
|
@ -37,7 +37,7 @@ type Article struct {
|
|||
CName sql.NullString
|
||||
}
|
||||
|
||||
//ArticleDatasourceService defines an interface for CRUD operations of articles
|
||||
// ArticleDatasourceService defines an interface for CRUD operations of articles
|
||||
type ArticleDatasourceService interface {
|
||||
Create(a *Article) (int, error)
|
||||
List(u *User, c *Category, p *Pagination, pc PublishedCriteria) ([]Article, error)
|
||||
|
@ -53,24 +53,28 @@ const (
|
|||
maxHeadlineSize = 150
|
||||
)
|
||||
|
||||
//SlugEscape escapes the slug for use in URLs
|
||||
// SlugEscape escapes the slug for use in URLs
|
||||
func (a Article) SlugEscape() string {
|
||||
spl := strings.Split(a.Slug, "/")
|
||||
return fmt.Sprintf("%s/%s/%s", spl[0], spl[1], url.PathEscape(spl[2]))
|
||||
}
|
||||
|
||||
func (a *Article) buildSlug(now time.Time, suffix int) string {
|
||||
return utils.AppendString(strconv.Itoa(now.Year()), "/", strconv.Itoa(int(now.Month())), "/", utils.CreateURLSafeSlug(a.Headline, suffix))
|
||||
var sb strings.Builder
|
||||
sb.WriteString(strconv.Itoa(now.Year()))
|
||||
sb.WriteString("/")
|
||||
sb.WriteString(strconv.Itoa(int(now.Month())))
|
||||
sb.WriteString("/")
|
||||
sb.WriteString(slug.CreateURLSafeSlug(a.Headline, suffix))
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (a *Article) slug(as ArticleService, now time.Time) error {
|
||||
func (a *Article) slug(as *ArticleService, now time.Time) error {
|
||||
for i := 0; i < 10; i++ {
|
||||
a.Slug = a.buildSlug(now, i)
|
||||
|
||||
_, err := as.Datasource.GetBySlug(a.Slug, nil, All)
|
||||
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
if _, err := as.Datasource.GetBySlug(a.Slug, nil, All); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
break
|
||||
}
|
||||
return err
|
||||
|
@ -102,14 +106,14 @@ func (a *Article) validate() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
//ArticleService containing the service to access articles
|
||||
// ArticleService containing the service to access articles
|
||||
type ArticleService struct {
|
||||
Datasource ArticleDatasourceService
|
||||
AppConfig settings.Application
|
||||
}
|
||||
|
||||
// Create creates an article
|
||||
func (as ArticleService) Create(a *Article) (int, error) {
|
||||
func (as *ArticleService) Create(a *Article) (int, error) {
|
||||
now := time.Now()
|
||||
|
||||
a.PublishedOn = NullTime{Time: now, Valid: true}
|
||||
|
@ -118,22 +122,15 @@ func (as ArticleService) Create(a *Article) (int, error) {
|
|||
return 0, err
|
||||
}
|
||||
|
||||
err := a.slug(as, now)
|
||||
|
||||
if err != nil {
|
||||
if err := a.slug(as, now); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
artID, err := as.Datasource.Create(a)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return artID, nil
|
||||
return as.Datasource.Create(a)
|
||||
}
|
||||
|
||||
//Update updates an article
|
||||
func (as ArticleService) Update(a *Article, u *User, updateSlug bool) error {
|
||||
// Update updates an article
|
||||
func (as *ArticleService) Update(a *Article, u *User, updateSlug bool) error {
|
||||
if err := a.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -148,9 +145,8 @@ func (as ArticleService) Update(a *Article, u *User, updateSlug bool) error {
|
|||
a.Slug = oldArt.Slug
|
||||
} else {
|
||||
now := time.Now()
|
||||
err := a.slug(as, now)
|
||||
|
||||
if err != nil {
|
||||
if err := a.slug(as, now); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -164,8 +160,8 @@ func (as ArticleService) Update(a *Article, u *User, updateSlug bool) error {
|
|||
return as.Datasource.Update(a)
|
||||
}
|
||||
|
||||
//Publish publishes or 'unpublishes' an article
|
||||
func (as ArticleService) Publish(id int, u *User) error {
|
||||
// Publish publishes or 'unpublishes' an article
|
||||
func (as *ArticleService) Publish(id int, u *User) error {
|
||||
a, err := as.Datasource.Get(id, nil, All)
|
||||
|
||||
if err != nil {
|
||||
|
@ -181,8 +177,8 @@ func (as ArticleService) Publish(id int, u *User) error {
|
|||
return as.Datasource.Publish(a)
|
||||
}
|
||||
|
||||
//Delete deletes an article
|
||||
func (as ArticleService) Delete(id int, u *User) error {
|
||||
// Delete deletes an article
|
||||
func (as *ArticleService) Delete(id int, u *User) error {
|
||||
a, err := as.Datasource.Get(id, nil, All)
|
||||
|
||||
if err != nil {
|
||||
|
@ -198,13 +194,13 @@ func (as ArticleService) Delete(id int, u *User) error {
|
|||
return as.Datasource.Delete(a.ID)
|
||||
}
|
||||
|
||||
// GetBySlug gets a article by the slug.
|
||||
// GetBySlug gets an article by the slug.
|
||||
// The publishedCriteria defines whether the published and/or unpublished articles should be considered
|
||||
func (as ArticleService) GetBySlug(s string, u *User, pc PublishedCriteria) (*Article, error) {
|
||||
func (as *ArticleService) GetBySlug(s string, u *User, pc PublishedCriteria) (*Article, error) {
|
||||
a, err := as.Datasource.GetBySlug(s, u, pc)
|
||||
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, httperror.NotFound("article", err)
|
||||
}
|
||||
return nil, err
|
||||
|
@ -223,11 +219,11 @@ func (as ArticleService) GetBySlug(s string, u *User, pc PublishedCriteria) (*Ar
|
|||
|
||||
// GetByID get a article by the id.
|
||||
// The publishedCriteria defines whether the published and/or unpublished articles should be considered
|
||||
func (as ArticleService) GetByID(id int, u *User, pc PublishedCriteria) (*Article, error) {
|
||||
func (as *ArticleService) GetByID(id int, u *User, pc PublishedCriteria) (*Article, error) {
|
||||
a, err := as.Datasource.Get(id, u, pc)
|
||||
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, httperror.NotFound("article", fmt.Errorf("the article with id %d was not found", id))
|
||||
}
|
||||
return nil, err
|
||||
|
@ -246,18 +242,18 @@ func (as ArticleService) GetByID(id int, u *User, pc PublishedCriteria) (*Articl
|
|||
|
||||
// Count returns the number of articles.
|
||||
// The publishedCriteria defines whether the published and/or unpublished articles should be considered
|
||||
func (as ArticleService) Count(u *User, c *Category, pc PublishedCriteria) (int, error) {
|
||||
func (as *ArticleService) Count(u *User, c *Category, pc PublishedCriteria) (int, error) {
|
||||
return as.Datasource.Count(u, c, pc)
|
||||
}
|
||||
|
||||
// List returns all article by the slug.
|
||||
// The publishedCriteria defines whether the published and/or unpublished articles should be considered
|
||||
func (as ArticleService) List(u *User, c *Category, p *Pagination, pc PublishedCriteria) ([]Article, error) {
|
||||
func (as *ArticleService) List(u *User, c *Category, p *Pagination, pc PublishedCriteria) ([]Article, error) {
|
||||
return as.Datasource.List(u, c, p, pc)
|
||||
}
|
||||
|
||||
// RSSFeed receives a specified number of articles in RSS
|
||||
func (as ArticleService) RSSFeed(p *Pagination, pc PublishedCriteria) (RSS, error) {
|
||||
func (as *ArticleService) RSSFeed(p *Pagination, pc PublishedCriteria) (RSS, error) {
|
||||
c := RSSChannel{
|
||||
Title: as.AppConfig.Title,
|
||||
Link: as.AppConfig.Domain,
|
||||
|
@ -272,7 +268,8 @@ func (as ArticleService) RSSFeed(p *Pagination, pc PublishedCriteria) (RSS, erro
|
|||
return RSS{}, err
|
||||
}
|
||||
|
||||
items := []RSSItem{}
|
||||
var items []RSSItem
|
||||
|
||||
for _, a := range articles {
|
||||
link := fmt.Sprint(as.AppConfig.Domain, "/article/by-id/", a.ID)
|
||||
item := RSSItem{
|
||||
|
@ -300,7 +297,7 @@ type IndexArticle struct {
|
|||
Articles []Article
|
||||
}
|
||||
|
||||
func (as ArticleService) Index(u *User, c *Category, p *Pagination, pc PublishedCriteria) ([]IndexArticle, error) {
|
||||
func (as *ArticleService) Index(u *User, c *Category, p *Pagination, pc PublishedCriteria) ([]IndexArticle, error) {
|
||||
articles, err := as.Datasource.List(u, c, p, pc)
|
||||
|
||||
if err != nil {
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"git.hoogi.eu/snafu/go-blog/logger"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -12,7 +13,7 @@ type SQLiteArticleDatasource struct {
|
|||
}
|
||||
|
||||
// Create creates an article
|
||||
func (rdb SQLiteArticleDatasource) Create(a *Article) (int, error) {
|
||||
func (rdb *SQLiteArticleDatasource) Create(a *Article) (int, error) {
|
||||
res, err := rdb.SQLConn.Exec("INSERT INTO article (headline, teaser, content, slug, published_on, published, last_modified, category_id, user_id) "+
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
a.Headline,
|
||||
|
@ -38,16 +39,20 @@ func (rdb SQLiteArticleDatasource) Create(a *Article) (int, error) {
|
|||
return int(id), nil
|
||||
}
|
||||
|
||||
//List returns a slice of articles; if the user is not nil the number of articles for this explcit user is returned
|
||||
//the PublishedCritera specifies which articles should be considered
|
||||
func (rdb SQLiteArticleDatasource) List(u *User, c *Category, p *Pagination, pc PublishedCriteria) ([]Article, error) {
|
||||
// List returns a slice of articles; if the user is not nil the number of articles for this explcit user is returned
|
||||
// the PublishedCritera specifies which articles should be considered
|
||||
func (rdb *SQLiteArticleDatasource) List(u *User, c *Category, p *Pagination, pc PublishedCriteria) ([]Article, error) {
|
||||
rows, err := selectArticlesStmt(rdb.SQLConn, u, c, p, pc)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
defer func() {
|
||||
if err := rows.Close(); err != nil {
|
||||
logger.Log.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
articles := []Article{}
|
||||
|
||||
|
@ -68,18 +73,17 @@ func (rdb SQLiteArticleDatasource) List(u *User, c *Category, p *Pagination, pc
|
|||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return articles, nil
|
||||
|
||||
return articles, nil
|
||||
}
|
||||
|
||||
//Count returns the number of article found; if the user is not nil the number of articles for this explcit user is returned
|
||||
//the PublishedCritera specifies which articles should be considered
|
||||
func (rdb SQLiteArticleDatasource) Count(u *User, c *Category, pc PublishedCriteria) (int, error) {
|
||||
// Count returns the number of article found; if the user is not nil the number of articles for this explcit user is returned
|
||||
// the PublishedCritera specifies which articles should be considered
|
||||
func (rdb *SQLiteArticleDatasource) Count(u *User, c *Category, pc PublishedCriteria) (int, error) {
|
||||
var total int
|
||||
|
||||
var stmt strings.Builder
|
||||
var args []interface{}
|
||||
|
||||
var stmt bytes.Buffer
|
||||
stmt.WriteString("SELECT count(a.id) FROM article a ")
|
||||
|
||||
if c != nil {
|
||||
|
@ -117,9 +121,9 @@ func (rdb SQLiteArticleDatasource) Count(u *User, c *Category, pc PublishedCrite
|
|||
return total, nil
|
||||
}
|
||||
|
||||
//Get returns a article by its id; if the user is not nil the article for this explcit user is returned
|
||||
//the PublishedCritera specifies which articles should be considered
|
||||
func (rdb SQLiteArticleDatasource) Get(articleID int, u *User, pc PublishedCriteria) (*Article, error) {
|
||||
// Get returns a article by its id; if the user is not nil the article for this explcit user is returned
|
||||
// the PublishedCritera specifies which articles should be considered
|
||||
func (rdb *SQLiteArticleDatasource) Get(articleID int, u *User, pc PublishedCriteria) (*Article, error) {
|
||||
var a Article
|
||||
var ru User
|
||||
|
||||
|
@ -133,9 +137,9 @@ func (rdb SQLiteArticleDatasource) Get(articleID int, u *User, pc PublishedCrite
|
|||
return &a, nil
|
||||
}
|
||||
|
||||
//GetBySlug returns a article by its slug; if the user is not nil the article for this explcit user is returned
|
||||
//the PublishedCritera specifies which articles should be considered
|
||||
func (rdb SQLiteArticleDatasource) GetBySlug(slug string, u *User, pc PublishedCriteria) (*Article, error) {
|
||||
// GetBySlug returns a article by its slug; if the user is not nil the article for this explcit user is returned
|
||||
// the PublishedCritera specifies which articles should be considered
|
||||
func (rdb *SQLiteArticleDatasource) GetBySlug(slug string, u *User, pc PublishedCriteria) (*Article, error) {
|
||||
var a Article
|
||||
var ru User
|
||||
|
||||
|
@ -150,7 +154,7 @@ func (rdb SQLiteArticleDatasource) GetBySlug(slug string, u *User, pc PublishedC
|
|||
}
|
||||
|
||||
// Update updates an aricle
|
||||
func (rdb SQLiteArticleDatasource) Update(a *Article) error {
|
||||
func (rdb *SQLiteArticleDatasource) Update(a *Article) error {
|
||||
if _, err := rdb.SQLConn.Exec("UPDATE article SET headline=?, teaser=?, slug=?, content=?, last_modified=?, category_id=? WHERE id=? ", a.Headline, &a.Teaser, a.Slug,
|
||||
a.Content, time.Now(), a.CID, a.ID); err != nil {
|
||||
return err
|
||||
|
@ -160,7 +164,7 @@ func (rdb SQLiteArticleDatasource) Update(a *Article) error {
|
|||
}
|
||||
|
||||
// Publish checks if the article is published or not - switches the appropriate status
|
||||
func (rdb SQLiteArticleDatasource) Publish(a *Article) error {
|
||||
func (rdb *SQLiteArticleDatasource) Publish(a *Article) error {
|
||||
publishOn := NullTime{Valid: false}
|
||||
|
||||
if !a.Published {
|
||||
|
@ -176,7 +180,7 @@ func (rdb SQLiteArticleDatasource) Publish(a *Article) error {
|
|||
}
|
||||
|
||||
// Delete deletes the article specified by the articleID
|
||||
func (rdb SQLiteArticleDatasource) Delete(articleID int) error {
|
||||
func (rdb *SQLiteArticleDatasource) Delete(articleID int) error {
|
||||
if _, err := rdb.SQLConn.Exec("DELETE FROM article WHERE id=? ", articleID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -184,7 +188,7 @@ func (rdb SQLiteArticleDatasource) Delete(articleID int) error {
|
|||
}
|
||||
|
||||
func selectArticleStmt(db *sql.DB, articleID int, slug string, u *User, pc PublishedCriteria) *sql.Row {
|
||||
var stmt bytes.Buffer
|
||||
var stmt strings.Builder
|
||||
|
||||
var args []interface{}
|
||||
|
||||
|
@ -211,19 +215,21 @@ func selectArticleStmt(db *sql.DB, articleID int, slug string, u *User, pc Publi
|
|||
stmt.WriteString("AND a.id=? ")
|
||||
args = append(args, articleID)
|
||||
}
|
||||
|
||||
if u != nil {
|
||||
if !u.IsAdmin {
|
||||
stmt.WriteString("AND a.user_id=? ")
|
||||
args = append(args, u.ID)
|
||||
}
|
||||
}
|
||||
|
||||
stmt.WriteString("LIMIT 1")
|
||||
|
||||
return db.QueryRow(stmt.String(), args...)
|
||||
}
|
||||
|
||||
func selectArticlesStmt(db *sql.DB, u *User, c *Category, p *Pagination, pc PublishedCriteria) (*sql.Rows, error) {
|
||||
var stmt bytes.Buffer
|
||||
|
||||
var stmt strings.Builder
|
||||
var args []interface{}
|
||||
|
||||
stmt.WriteString("SELECT a.id, a.headline, a.teaser, a.content, a.published, a.published_on, a.slug, a.last_modified, ")
|
||||
|
|
|
@ -8,8 +8,8 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"git.hoogi.eu/snafu/go-blog/components/httperror"
|
||||
"git.hoogi.eu/snafu/go-blog/utils"
|
||||
"git.hoogi.eu/snafu/go-blog/httperror"
|
||||
"git.hoogi.eu/snafu/go-blog/slug"
|
||||
)
|
||||
|
||||
type Category struct {
|
||||
|
@ -39,6 +39,7 @@ func (c *Category) validate() error {
|
|||
if c.Author == nil {
|
||||
return httperror.InternalServerError(errors.New("category validation failed - the author is missing"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -52,21 +53,21 @@ type CategoryDatasourceService interface {
|
|||
Delete(categoryID int) error
|
||||
}
|
||||
|
||||
//CategoryService containing the service to access categories
|
||||
// CategoryService containing the service to access categories
|
||||
type CategoryService struct {
|
||||
Datasource CategoryDatasourceService
|
||||
}
|
||||
|
||||
//SlugEscape escapes the slug for use in URLs
|
||||
// SlugEscape escapes the slug for use in URLs
|
||||
func (c Category) SlugEscape() string {
|
||||
return url.PathEscape(c.Slug)
|
||||
}
|
||||
|
||||
func (cs CategoryService) GetBySlug(s string, fc FilterCriteria) (*Category, error) {
|
||||
func (cs *CategoryService) GetBySlug(s string, fc FilterCriteria) (*Category, error) {
|
||||
c, err := cs.Datasource.GetBySlug(s, fc)
|
||||
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, httperror.NotFound("category", fmt.Errorf("the category with slug %s was not found", s))
|
||||
}
|
||||
return nil, err
|
||||
|
@ -75,35 +76,34 @@ func (cs CategoryService) GetBySlug(s string, fc FilterCriteria) (*Category, err
|
|||
return c, nil
|
||||
}
|
||||
|
||||
func (cs CategoryService) GetByID(id int, fc FilterCriteria) (*Category, error) {
|
||||
func (cs *CategoryService) GetByID(id int, fc FilterCriteria) (*Category, error) {
|
||||
c, err := cs.Datasource.Get(id, fc)
|
||||
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, httperror.NotFound("category", fmt.Errorf("the category with id %d was not found", id))
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (cs CategoryService) Count(fc FilterCriteria) (int, error) {
|
||||
func (cs *CategoryService) Count(fc FilterCriteria) (int, error) {
|
||||
return cs.Datasource.Count(fc)
|
||||
}
|
||||
|
||||
func (cs CategoryService) List(fc FilterCriteria) ([]Category, error) {
|
||||
func (cs *CategoryService) List(fc FilterCriteria) ([]Category, error) {
|
||||
return cs.Datasource.List(fc)
|
||||
}
|
||||
|
||||
//Create creates a category
|
||||
func (cs CategoryService) Create(c *Category) (int, error) {
|
||||
// Create creates a category
|
||||
func (cs *CategoryService) Create(c *Category) (int, error) {
|
||||
for i := 0; i < 10; i++ {
|
||||
c.Slug = utils.CreateURLSafeSlug(c.Name, i)
|
||||
_, err := cs.Datasource.GetBySlug(c.Slug, AllCategories)
|
||||
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
c.Slug = slug.CreateURLSafeSlug(c.Name, i)
|
||||
if _, err := cs.Datasource.GetBySlug(c.Slug, AllCategories); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
break
|
||||
}
|
||||
return -1, err
|
||||
|
@ -114,16 +114,11 @@ func (cs CategoryService) Create(c *Category) (int, error) {
|
|||
return 0, err
|
||||
}
|
||||
|
||||
cid, err := cs.Datasource.Create(c)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return cid, nil
|
||||
return cs.Datasource.Create(c)
|
||||
}
|
||||
|
||||
//Update updates a category
|
||||
func (cs CategoryService) Update(c *Category) error {
|
||||
// Update updates a category
|
||||
func (cs *CategoryService) Update(c *Category) error {
|
||||
if err := c.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -131,8 +126,8 @@ func (cs CategoryService) Update(c *Category) error {
|
|||
return cs.Datasource.Update(c)
|
||||
}
|
||||
|
||||
//Delete removes a category
|
||||
func (cs CategoryService) Delete(id int) error {
|
||||
// Delete removes a category
|
||||
func (cs *CategoryService) Delete(id int) error {
|
||||
c, err := cs.Datasource.Get(id, AllCategories)
|
||||
|
||||
if err != nil {
|
||||
|
|
|
@ -3,15 +3,17 @@ package models
|
|||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"git.hoogi.eu/snafu/go-blog/logger"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SQLiteArticleDatasource providing an implementation of ArticleDatasourceService for SQLite
|
||||
// SQLiteCategoryDatasource providing an implementation of CategoryDatasourceService for SQLite
|
||||
type SQLiteCategoryDatasource struct {
|
||||
SQLConn *sql.DB
|
||||
}
|
||||
|
||||
func (rdb SQLiteCategoryDatasource) Create(c *Category) (int, error) {
|
||||
func (rdb *SQLiteCategoryDatasource) Create(c *Category) (int, error) {
|
||||
res, err := rdb.SQLConn.Exec("INSERT INTO category (name, slug, last_modified, user_id) "+
|
||||
"VALUES (?, ?, ?, ?)",
|
||||
c.Name,
|
||||
|
@ -32,10 +34,9 @@ func (rdb SQLiteCategoryDatasource) Create(c *Category) (int, error) {
|
|||
return int(id), nil
|
||||
}
|
||||
|
||||
func (rdb SQLiteCategoryDatasource) List(fc FilterCriteria) ([]Category, error) {
|
||||
var stmt bytes.Buffer
|
||||
|
||||
func (rdb *SQLiteCategoryDatasource) List(fc FilterCriteria) ([]Category, error) {
|
||||
var args []interface{}
|
||||
var stmt strings.Builder
|
||||
|
||||
stmt.WriteString("SELECT DISTINCT c.id, c.name, c.slug, c.last_modified, ")
|
||||
stmt.WriteString("u.id, u.display_name, u.username, u.email, u.is_admin ")
|
||||
|
@ -61,9 +62,13 @@ func (rdb SQLiteCategoryDatasource) List(fc FilterCriteria) ([]Category, error)
|
|||
return nil, err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
defer func() {
|
||||
if err := rows.Close(); err != nil {
|
||||
logger.Log.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
cs := []Category{}
|
||||
var cs []Category
|
||||
|
||||
for rows.Next() {
|
||||
var c Category
|
||||
|
@ -85,7 +90,7 @@ func (rdb SQLiteCategoryDatasource) List(fc FilterCriteria) ([]Category, error)
|
|||
return cs, nil
|
||||
}
|
||||
|
||||
func (rdb SQLiteCategoryDatasource) Count(fc FilterCriteria) (int, error) {
|
||||
func (rdb *SQLiteCategoryDatasource) Count(fc FilterCriteria) (int, error) {
|
||||
var total int
|
||||
|
||||
if err := rdb.SQLConn.QueryRow("SELECT count(id) FROM category ").Scan(&total); err != nil {
|
||||
|
@ -95,7 +100,7 @@ func (rdb SQLiteCategoryDatasource) Count(fc FilterCriteria) (int, error) {
|
|||
return total, nil
|
||||
}
|
||||
|
||||
func (rdb SQLiteCategoryDatasource) Get(categoryID int, fc FilterCriteria) (*Category, error) {
|
||||
func (rdb *SQLiteCategoryDatasource) Get(categoryID int, fc FilterCriteria) (*Category, error) {
|
||||
var stmt bytes.Buffer
|
||||
|
||||
stmt.WriteString("SELECT c.id, c.name, c.slug, c.last_modified, ")
|
||||
|
@ -131,14 +136,15 @@ func (rdb SQLiteCategoryDatasource) Get(categoryID int, fc FilterCriteria) (*Cat
|
|||
return &c, nil
|
||||
}
|
||||
|
||||
func (rdb SQLiteCategoryDatasource) GetBySlug(slug string, fc FilterCriteria) (*Category, error) {
|
||||
var stmt bytes.Buffer
|
||||
func (rdb *SQLiteCategoryDatasource) GetBySlug(slug string, fc FilterCriteria) (*Category, error) {
|
||||
var stmt strings.Builder
|
||||
|
||||
stmt.WriteString("SELECT c.id, c.name, c.slug, c.last_modified, ")
|
||||
stmt.WriteString("u.id, u.display_name, u.username, u.email, u.is_admin ")
|
||||
stmt.WriteString("FROM category as c ")
|
||||
stmt.WriteString("INNER JOIN user as u ")
|
||||
stmt.WriteString("ON u.id = c.user_id ")
|
||||
|
||||
if fc == CategoriesWithPublishedArticles {
|
||||
stmt.WriteString("INNER JOIN article as a ")
|
||||
stmt.WriteString("ON c.id = a.category_id ")
|
||||
|
@ -166,24 +172,19 @@ func (rdb SQLiteCategoryDatasource) GetBySlug(slug string, fc FilterCriteria) (*
|
|||
return &c, nil
|
||||
}
|
||||
|
||||
func (rdb SQLiteCategoryDatasource) Update(c *Category) error {
|
||||
_, err := rdb.SQLConn.Exec("UPDATE category SET name=?, slug=?, last_modified=?, user_id=? WHERE id=?",
|
||||
c.Name,
|
||||
c.Slug,
|
||||
time.Now(),
|
||||
c.Author.ID,
|
||||
c.ID)
|
||||
|
||||
if err != nil {
|
||||
func (rdb *SQLiteCategoryDatasource) Update(c *Category) error {
|
||||
if _, err := rdb.SQLConn.Exec("UPDATE category SET name=?, slug=?, last_modified=?, user_id=? WHERE id=?",
|
||||
c.Name, c.Slug, time.Now(), c.Author.ID, c.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rdb SQLiteCategoryDatasource) Delete(categoryID int) error {
|
||||
func (rdb *SQLiteCategoryDatasource) Delete(categoryID int) error {
|
||||
if _, err := rdb.SQLConn.Exec("DELETE FROM category WHERE id=?", categoryID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -4,12 +4,12 @@
|
|||
|
||||
package models
|
||||
|
||||
// JSONData represents arbritary JSON data
|
||||
// JSONData represents arbitrary JSON data
|
||||
type JSONData struct {
|
||||
Data interface{} `json:"data,-" xml:"data,-"`
|
||||
}
|
||||
|
||||
// XMLData represents arbritary XML data
|
||||
// XMLData represents arbitrary XML data
|
||||
type XMLData struct {
|
||||
Data interface{} `xml:"data,-"`
|
||||
HexEncode bool `xml:"-"`
|
||||
|
|
|
@ -11,14 +11,14 @@ import (
|
|||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"git.hoogi.eu/snafu/go-blog/components/httperror"
|
||||
"git.hoogi.eu/snafu/go-blog/components/logger"
|
||||
"git.hoogi.eu/snafu/go-blog/httperror"
|
||||
"git.hoogi.eu/snafu/go-blog/logger"
|
||||
"git.hoogi.eu/snafu/go-blog/settings"
|
||||
"git.hoogi.eu/snafu/go-blog/utils"
|
||||
)
|
||||
|
||||
//File represents a file
|
||||
// File represents a file
|
||||
type File struct {
|
||||
ID int
|
||||
UniqueName string `json:"unique_name"`
|
||||
|
@ -33,15 +33,15 @@ type File struct {
|
|||
Author *User
|
||||
}
|
||||
|
||||
//FileInfo contains Path, Name and Extension of a file.
|
||||
//Use SplitFilename to split the information from a filename
|
||||
// FileInfo contains Path, Name and Extension of a file.
|
||||
// Use SplitFilename to split the information from a filename
|
||||
type FileInfo struct {
|
||||
Path string
|
||||
Name string
|
||||
Extension string
|
||||
}
|
||||
|
||||
//FileDatasourceService defines an interface for CRUD operations of files
|
||||
// FileDatasourceService defines an interface for CRUD operations of files
|
||||
type FileDatasourceService interface {
|
||||
Create(f *File) (int, error)
|
||||
Get(fileID int, u *User) (*File, error)
|
||||
|
@ -67,12 +67,16 @@ func (f *File) validate() error {
|
|||
|
||||
func (f File) randomFilename() string {
|
||||
var buf bytes.Buffer
|
||||
sanFilename := utils.SanitizeFilename(f.FileInfo.Name)
|
||||
|
||||
sanFilename := sanitizeFilename(f.FileInfo.Name)
|
||||
|
||||
if len(sanFilename) == 0 {
|
||||
sanFilename = "unnamed"
|
||||
}
|
||||
|
||||
buf.WriteString(sanFilename)
|
||||
buf.WriteString(f.FileInfo.Extension)
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
|
@ -100,37 +104,37 @@ func SplitFilename(filename string) FileInfo {
|
|||
}
|
||||
}
|
||||
|
||||
//FileService containing the service to interact with files
|
||||
// FileService containing the service to interact with files
|
||||
type FileService struct {
|
||||
Datasource FileDatasourceService
|
||||
Config settings.File
|
||||
}
|
||||
|
||||
//GetByID returns the file based on the fileID; it the user is given and it is a non admin
|
||||
//only file specific to this user is returned
|
||||
func (fs FileService) GetByID(fileID int, u *User) (*File, error) {
|
||||
// GetByID returns the file based on the fileID; it the user is given and it is a non admin
|
||||
// only file specific to this user is returned
|
||||
func (fs *FileService) GetByID(fileID int, u *User) (*File, error) {
|
||||
return fs.Datasource.Get(fileID, u)
|
||||
}
|
||||
|
||||
//GetByUniqueName returns the file based on the unique name; it the user is given and it is a non admin
|
||||
//only file specific to this user is returned
|
||||
func (fs FileService) GetByUniqueName(uniqueName string, u *User) (*File, error) {
|
||||
// GetByUniqueName returns the file based on the unique name; it the user is given and it is a non admin
|
||||
// only file specific to this user is returned
|
||||
func (fs *FileService) GetByUniqueName(uniqueName string, u *User) (*File, error) {
|
||||
return fs.Datasource.GetByUniqueName(uniqueName, u)
|
||||
}
|
||||
|
||||
//List returns a list of files based on the filename; it the user is given and it is a non admin
|
||||
//only files specific to this user are returned
|
||||
func (fs FileService) List(u *User, p *Pagination) ([]File, error) {
|
||||
// List returns a list of files based on the filename; it the user is given and it is a non admin
|
||||
// only files specific to this user are returned
|
||||
func (fs *FileService) List(u *User, p *Pagination) ([]File, error) {
|
||||
return fs.Datasource.List(u, p)
|
||||
}
|
||||
|
||||
//Count returns a number of files based on the filename; it the user is given and it is a non admin
|
||||
//only files specific to this user are counted
|
||||
func (fs FileService) Count(u *User) (int, error) {
|
||||
// Count returns a number of files based on the filename; it the user is given and it is a non admin
|
||||
// only files specific to this user are counted
|
||||
func (fs *FileService) Count(u *User) (int, error) {
|
||||
return fs.Datasource.Count(u)
|
||||
}
|
||||
|
||||
func (fs FileService) ToggleInline(fileID int, u *User) error {
|
||||
func (fs *FileService) ToggleInline(fileID int, u *User) error {
|
||||
f, err := fs.Datasource.Get(fileID, u)
|
||||
|
||||
if err != nil {
|
||||
|
@ -152,8 +156,8 @@ func (fs FileService) ToggleInline(fileID int, u *User) error {
|
|||
return fs.Datasource.Update(f)
|
||||
}
|
||||
|
||||
//Delete deletes a file based on fileID; users which are not the owner are not allowed to remove files; except admins
|
||||
func (fs FileService) Delete(fileID int, u *User) error {
|
||||
// Delete deletes a file based on fileID; users which are not the owner are not allowed to remove files; except admins
|
||||
func (fs *FileService) Delete(fileID int, u *User) error {
|
||||
file, err := fs.Datasource.Get(fileID, u)
|
||||
|
||||
if err != nil {
|
||||
|
@ -175,8 +179,8 @@ func (fs FileService) Delete(fileID int, u *User) error {
|
|||
return os.Remove(filepath.Join(fs.Config.Location, file.UniqueName))
|
||||
}
|
||||
|
||||
//Upload uploaded files will be saved at the configured file location, filename is saved in the database
|
||||
func (fs FileService) Upload(f *File) (int, error) {
|
||||
// Upload uploaded files will be saved at the configured file location, filename is saved in the database
|
||||
func (fs *FileService) Upload(f *File) (int, error) {
|
||||
if err := f.validate(); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
@ -218,9 +222,7 @@ func (fs FileService) Upload(f *File) (int, error) {
|
|||
|
||||
fi := filepath.Join(fs.Config.Location, f.UniqueName)
|
||||
|
||||
err = ioutil.WriteFile(fi, f.Data, 0640)
|
||||
|
||||
if err != nil {
|
||||
if err = ioutil.WriteFile(fi, f.Data, 0640); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
|
@ -238,3 +240,36 @@ func (fs FileService) Upload(f *File) (int, error) {
|
|||
|
||||
return i, nil
|
||||
}
|
||||
|
||||
var filenameSubs = map[rune]string{
|
||||
'/': "",
|
||||
'\\': "",
|
||||
':': "",
|
||||
'*': "",
|
||||
'?': "",
|
||||
'"': "",
|
||||
'<': "",
|
||||
'>': "",
|
||||
'|': "",
|
||||
' ': "",
|
||||
}
|
||||
|
||||
func isDot(r rune) bool {
|
||||
return '.' == r
|
||||
}
|
||||
|
||||
// sanitizeFilename sanitizes a filename for safe use when serving file
|
||||
func sanitizeFilename(s string) string {
|
||||
s = strings.ToValidUTF8(s, "")
|
||||
s = strings.TrimFunc(s, unicode.IsSpace)
|
||||
|
||||
s = strings.Map(func(r rune) rune {
|
||||
if _, ok := filenameSubs[r]; ok {
|
||||
return -1
|
||||
}
|
||||
return r
|
||||
}, s)
|
||||
|
||||
s = strings.TrimLeftFunc(s, isDot)
|
||||
return s
|
||||
}
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"git.hoogi.eu/snafu/go-blog/logger"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
//SQLiteFileDatasource providing an implementation of FileDatasourceService using MariaDB
|
||||
// SQLiteFileDatasource providing an implementation of FileDatasourceService using MariaDB
|
||||
type SQLiteFileDatasource struct {
|
||||
SQLConn *sql.DB
|
||||
}
|
||||
|
||||
//GetByFilename returns the file based on the filename; it the user is given and it is a non admin
|
||||
//only file specific to this user is returned
|
||||
func (rdb SQLiteFileDatasource) GetByUniqueName(uniqueName string, u *User) (*File, error) {
|
||||
var stmt bytes.Buffer
|
||||
|
||||
// GetByUniqueName returns the file based on the unique filename; it the user is given and it is a non admin
|
||||
// only file specific to this user is returned
|
||||
func (rdb *SQLiteFileDatasource) GetByUniqueName(uniqueName string, u *User) (*File, error) {
|
||||
var stmt strings.Builder
|
||||
var args []interface{}
|
||||
|
||||
stmt.WriteString("SELECT f.id, f.filename, f.unique_name, f.content_type, f.inline, f.size, f.last_modified, f.user_id, ")
|
||||
|
@ -47,11 +47,10 @@ func (rdb SQLiteFileDatasource) GetByUniqueName(uniqueName string, u *User) (*Fi
|
|||
return &f, nil
|
||||
}
|
||||
|
||||
//Get returns the file based on the filename; it the user is given and it is a non admin
|
||||
//only file specific to this user is returned
|
||||
func (rdb SQLiteFileDatasource) Get(fileID int, u *User) (*File, error) {
|
||||
var stmt bytes.Buffer
|
||||
|
||||
// Get returns the file based on the filename; it the user is given and it is a non admin
|
||||
// only file specific to this user is returned
|
||||
func (rdb *SQLiteFileDatasource) Get(fileID int, u *User) (*File, error) {
|
||||
var stmt strings.Builder
|
||||
var args []interface{}
|
||||
|
||||
stmt.WriteString("SELECT f.id, f.filename, f.unique_name, f.content_type, f.inline, f.size, f.last_modified, f.user_id, ")
|
||||
|
@ -83,8 +82,8 @@ func (rdb SQLiteFileDatasource) Get(fileID int, u *User) (*File, error) {
|
|||
return &f, nil
|
||||
}
|
||||
|
||||
//Create inserts some file meta information into the database
|
||||
func (rdb SQLiteFileDatasource) Create(f *File) (int, error) {
|
||||
// Create inserts some file meta information into the database
|
||||
func (rdb *SQLiteFileDatasource) Create(f *File) (int, error) {
|
||||
res, err := rdb.SQLConn.Exec("INSERT INTO file (filename, unique_name, content_type, inline, size, last_modified, user_id) VALUES(?, ?, ?, ?, ?, ?, ?)",
|
||||
f.FullFilename, f.UniqueName, f.ContentType, f.Inline, f.Size, time.Now(), f.Author.ID)
|
||||
|
||||
|
@ -101,18 +100,19 @@ func (rdb SQLiteFileDatasource) Create(f *File) (int, error) {
|
|||
return int(i), nil
|
||||
}
|
||||
|
||||
func (rdb SQLiteFileDatasource) Update(f *File) error {
|
||||
if _, err := rdb.SQLConn.Exec("UPDATE file SET filename=?, unique_name=?, content_type=?, inline=?, size=?, last_modified=?, user_id=? WHERE id=?", f.FullFilename, f.UniqueName, f.ContentType, f.Inline, f.Size, time.Now(), f.Author.ID, f.ID); err != nil {
|
||||
func (rdb *SQLiteFileDatasource) Update(f *File) error {
|
||||
if _, err := rdb.SQLConn.Exec("UPDATE file SET filename=?, unique_name=?, content_type=?, inline=?, size=?, last_modified=?, user_id=? WHERE id=?",
|
||||
f.FullFilename, f.UniqueName, f.ContentType, f.Inline, f.Size, time.Now(), f.Author.ID, f.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//List returns a list of files based on the filename; it the user is given and it is a non admin
|
||||
//only files specific to this user are returned
|
||||
func (rdb SQLiteFileDatasource) List(u *User, p *Pagination) ([]File, error) {
|
||||
var stmt bytes.Buffer
|
||||
// List returns a list of files based on the filename; it the user is given and it is a non admin
|
||||
// only files specific to this user are returned
|
||||
func (rdb *SQLiteFileDatasource) List(u *User, p *Pagination) ([]File, error) {
|
||||
var stmt strings.Builder
|
||||
|
||||
var args []interface{}
|
||||
|
||||
|
@ -142,10 +142,13 @@ func (rdb SQLiteFileDatasource) List(u *User, p *Pagination) ([]File, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
files := []File{}
|
||||
defer func() {
|
||||
if err := rows.Close(); err != nil {
|
||||
logger.Log.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
var files []File
|
||||
var f File
|
||||
var us User
|
||||
|
||||
|
@ -168,10 +171,10 @@ func (rdb SQLiteFileDatasource) List(u *User, p *Pagination) ([]File, error) {
|
|||
|
||||
}
|
||||
|
||||
//Count returns a number of files based on the filename; it the user is given and it is a non admin
|
||||
//only files specific to this user are counted
|
||||
func (rdb SQLiteFileDatasource) Count(u *User) (int, error) {
|
||||
var stmt bytes.Buffer
|
||||
// Count returns a number of files based on the filename; it the user is given and it is a non admin
|
||||
// only files specific to this user are counted
|
||||
func (rdb *SQLiteFileDatasource) Count(u *User) (int, error) {
|
||||
var stmt strings.Builder
|
||||
|
||||
var args []interface{}
|
||||
|
||||
|
@ -193,9 +196,9 @@ func (rdb SQLiteFileDatasource) Count(u *User) (int, error) {
|
|||
return total, nil
|
||||
}
|
||||
|
||||
//Delete deletes a file based on fileID; users which are not the owner are not allowed to remove files;
|
||||
//except admins
|
||||
func (rdb SQLiteFileDatasource) Delete(fileID int) error {
|
||||
// Delete deletes a file based on fileID; users which are not the owner are not allowed to remove files;
|
||||
// except admins
|
||||
func (rdb *SQLiteFileDatasource) Delete(fileID int) error {
|
||||
if _, err := rdb.SQLConn.Exec("DELETE FROM file WHERE id=?", fileID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -13,15 +13,11 @@ import (
|
|||
bf "github.com/russross/blackfriday/v2"
|
||||
)
|
||||
|
||||
// Defines the extensions that are used
|
||||
var exts = bf.NoIntraEmphasis | bf.Tables | bf.FencedCode | bf.Autolink |
|
||||
// ext Defines the extensions that are used
|
||||
var ext = bf.NoIntraEmphasis | bf.Tables | bf.FencedCode | bf.Autolink |
|
||||
bf.Strikethrough | bf.SpaceHeadings | bf.BackslashLineBreak |
|
||||
bf.DefinitionLists | bf.Footnotes | bf.HardLineBreak
|
||||
|
||||
// Defines the HTML rendering flags that are used
|
||||
var flags = bf.UseXHTML | bf.Smartypants | bf.SmartypantsFractions |
|
||||
bf.SmartypantsDashes | bf.SmartypantsLatexDashes | bf.TOC
|
||||
|
||||
var p *bluemonday.Policy
|
||||
|
||||
func init() {
|
||||
|
@ -30,10 +26,10 @@ func init() {
|
|||
p.AllowAttrs("style").OnElements("span")
|
||||
}
|
||||
|
||||
//MarkdownToHTML sanitizes and parses markdown to HTML
|
||||
// MarkdownToHTML sanitizes and parses markdown to HTML
|
||||
func MarkdownToHTML(md []byte) []byte {
|
||||
md = bytes.Replace(md, []byte("\r\n"), []byte("\n"), -1)
|
||||
unsafe := bf.Run((md), bf.WithExtensions(exts))
|
||||
unsafe := bf.Run((md), bf.WithExtensions(ext))
|
||||
|
||||
return sanitize(unsafe)
|
||||
}
|
||||
|
|
|
@ -3,9 +3,8 @@ package models
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"git.hoogi.eu/snafu/go-blog/components/mail"
|
||||
"git.hoogi.eu/snafu/go-blog/mail"
|
||||
"git.hoogi.eu/snafu/go-blog/settings"
|
||||
"git.hoogi.eu/snafu/go-blog/utils"
|
||||
)
|
||||
|
||||
type Mailer struct {
|
||||
|
@ -13,36 +12,36 @@ type Mailer struct {
|
|||
Sender mail.Sender
|
||||
}
|
||||
|
||||
func (m Mailer) SendActivationLink(ui *UserInvite) {
|
||||
activation := utils.AppendString(m.AppConfig.Domain, "/admin/activate-account/", ui.Hash)
|
||||
func (m *Mailer) SendActivationLink(ui *UserInvite) {
|
||||
activation := m.AppConfig.Domain + "/admin/activate-account/" + ui.Hash
|
||||
|
||||
mail := mail.Mail{
|
||||
ml := mail.Mail{
|
||||
To: ui.Email,
|
||||
Subject: "You got an invitation",
|
||||
Body: fmt.Sprintf("Hi %s,\n\n you are invited join %s. To activate your account click the following link and enter a password %s", ui.DisplayName, m.AppConfig.Title, activation),
|
||||
}
|
||||
|
||||
m.Sender.SendAsync(mail)
|
||||
m.Sender.SendAsync(ml)
|
||||
}
|
||||
|
||||
func (m Mailer) SendPasswordChangeConfirmation(u *User) {
|
||||
mail := mail.Mail{
|
||||
func (m *Mailer) SendPasswordChangeConfirmation(u *User) {
|
||||
ml := mail.Mail{
|
||||
To: u.Email,
|
||||
Subject: "Password change",
|
||||
Body: fmt.Sprintf("Hi %s,\n\nyour password change was successful.", u.DisplayName),
|
||||
}
|
||||
|
||||
m.Sender.SendAsync(mail)
|
||||
m.Sender.SendAsync(ml)
|
||||
}
|
||||
|
||||
func (m Mailer) SendPasswordResetLink(u *User, t *Token) {
|
||||
resetLink := utils.AppendString(m.AppConfig.Domain, "/admin/reset-password/", t.Hash)
|
||||
func (m *Mailer) SendPasswordResetLink(u *User, t *Token) {
|
||||
resetLink := m.AppConfig.Domain + "/admin/reset-password/" + t.Hash
|
||||
|
||||
mail := mail.Mail{
|
||||
ml := mail.Mail{
|
||||
To: u.Email,
|
||||
Subject: "Changing password instructions",
|
||||
Body: fmt.Sprintf("Hi %s,\n\nuse the following link to reset your password:\n\n%s", u.DisplayName, resetLink),
|
||||
}
|
||||
|
||||
m.Sender.SendAsync(mail)
|
||||
m.Sender.SendAsync(ml)
|
||||
}
|
||||
|
|
|
@ -5,15 +5,13 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"math"
|
||||
|
||||
"git.hoogi.eu/snafu/go-blog/utils"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//Pagination type is used to provide a page selector
|
||||
// Pagination type is used to provide a page selector
|
||||
type Pagination struct {
|
||||
Total int
|
||||
Limit int
|
||||
|
@ -21,86 +19,86 @@ type Pagination struct {
|
|||
RelURL string
|
||||
}
|
||||
|
||||
//Offset returns the offset where to start
|
||||
func (p Pagination) Offset() int {
|
||||
// Offset returns the offset where to start
|
||||
func (p *Pagination) Offset() int {
|
||||
return (p.CurrentPage - 1) * p.Limit
|
||||
}
|
||||
|
||||
//url returns the absolute url
|
||||
func (p Pagination) url() string {
|
||||
// url returns the absolute url
|
||||
func (p *Pagination) url() string {
|
||||
if p.RelURL[0] == '/' {
|
||||
return utils.AppendString(p.RelURL)
|
||||
return p.RelURL
|
||||
}
|
||||
return utils.AppendString("/", p.RelURL)
|
||||
return "/" + p.RelURL
|
||||
}
|
||||
|
||||
//pages returns the amount of pages
|
||||
func (p Pagination) pages() int {
|
||||
// pages returns the amount of pages
|
||||
func (p *Pagination) pages() int {
|
||||
return int(math.Ceil(float64(p.Total) / float64(p.Limit)))
|
||||
}
|
||||
|
||||
//hasNext returns true if a next page is available
|
||||
func (p Pagination) hasNext() bool {
|
||||
// hasNext returns true if a next page is available
|
||||
func (p *Pagination) hasNext() bool {
|
||||
if p.CurrentPage*p.Limit >= p.Total {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
//hasMoreThanOnePage returns true if the bar has more than one page
|
||||
func (p Pagination) hasMoreThanOnePage() bool {
|
||||
// hasMoreThanOnePage returns true if the bar has more than one page
|
||||
func (p *Pagination) hasMoreThanOnePage() bool {
|
||||
return p.Limit < p.Total
|
||||
}
|
||||
|
||||
//hasPrevious returns true if a previous page is available
|
||||
func (p Pagination) hasPrevious() bool {
|
||||
// hasPrevious returns true if a previous page is available
|
||||
func (p *Pagination) hasPrevious() bool {
|
||||
return !(p.CurrentPage == 1)
|
||||
}
|
||||
|
||||
//nextPage returns the next page
|
||||
func (p Pagination) nextPage() int {
|
||||
// nextPage returns the next page
|
||||
func (p *Pagination) nextPage() int {
|
||||
if !p.hasNext() {
|
||||
return p.CurrentPage
|
||||
}
|
||||
return p.CurrentPage + 1
|
||||
}
|
||||
|
||||
//previousPage returns the previous page
|
||||
func (p Pagination) previousPage() int {
|
||||
// previousPage returns the previous page
|
||||
func (p *Pagination) previousPage() int {
|
||||
if !p.hasPrevious() {
|
||||
return p.CurrentPage
|
||||
}
|
||||
return p.CurrentPage - 1
|
||||
}
|
||||
|
||||
//PaginationBar returns the HTML for the pagination bar which can be embedded
|
||||
func (p Pagination) PaginationBar() template.HTML {
|
||||
var buffer bytes.Buffer
|
||||
// PaginationBar returns the HTML for the pagination bar which can be embedded
|
||||
func (p *Pagination) PaginationBar() template.HTML {
|
||||
var sb strings.Builder
|
||||
|
||||
if p.pages() > 1 {
|
||||
buffer.WriteString(`<div id="pagination">`)
|
||||
sb.WriteString(`<div id="pagination">`)
|
||||
|
||||
if !p.hasPrevious() {
|
||||
buffer.WriteString(`<a class="button button-inactive" href="#">« Backward</a>`)
|
||||
sb.WriteString(`<a class="button button-inactive" href="#">« Backward</a>`)
|
||||
} else {
|
||||
buffer.WriteString(fmt.Sprintf(`<a class="button button-active" href="%s/%d">« Backward</a>`, p.url(), p.previousPage()))
|
||||
sb.WriteString(fmt.Sprintf(`<a class="button button-active" href="%s/%d">« Backward</a>`, p.url(), p.previousPage()))
|
||||
}
|
||||
|
||||
for i := 1; i <= p.pages(); i++ {
|
||||
if p.CurrentPage == i {
|
||||
buffer.WriteString(fmt.Sprintf(`<a class="button button-inactive" href="#">%d</a></li>`, i))
|
||||
sb.WriteString(fmt.Sprintf(`<a class="button button-inactive" href="#">%d</a>`, i))
|
||||
} else {
|
||||
buffer.WriteString(fmt.Sprintf(`<a class="button button-active" href="%s/%d">%d</a></li>`, p.url(), i, i))
|
||||
sb.WriteString(fmt.Sprintf(`<a class="button button-active" href="%s/%d">%d</a>`, p.url(), i, i))
|
||||
}
|
||||
}
|
||||
|
||||
if !p.hasNext() {
|
||||
buffer.WriteString(`<a class="button button-inactive" href="#">Forward »</a>`)
|
||||
sb.WriteString(`<a class="button button-inactive" href="#">Forward »</a>`)
|
||||
} else {
|
||||
buffer.WriteString(fmt.Sprintf(`<a class="button button-active" href="%s/%d">Forward »</a>`, p.url(), p.nextPage()))
|
||||
sb.WriteString(fmt.Sprintf(`<a class="button button-active" href="%s/%d">Forward »</a>`, p.url(), p.nextPage()))
|
||||
}
|
||||
|
||||
buffer.WriteString(`</div>`)
|
||||
sb.WriteString(`</div>`)
|
||||
}
|
||||
return template.HTML(buffer.String())
|
||||
return template.HTML(sb.String())
|
||||
}
|
||||
|
|
|
@ -7,11 +7,11 @@ import (
|
|||
"net/url"
|
||||
"time"
|
||||
|
||||
"git.hoogi.eu/snafu/go-blog/components/httperror"
|
||||
"git.hoogi.eu/snafu/go-blog/utils"
|
||||
"git.hoogi.eu/snafu/go-blog/httperror"
|
||||
"git.hoogi.eu/snafu/go-blog/slug"
|
||||
)
|
||||
|
||||
//SiteDatasourceService defines an interface for CRUD operations on sites
|
||||
// SiteDatasourceService defines an interface for CRUD operations on sites
|
||||
type SiteDatasourceService interface {
|
||||
Create(s *Site) (int, error)
|
||||
List(pc PublishedCriteria, p *Pagination) ([]Site, error)
|
||||
|
@ -25,17 +25,17 @@ type SiteDatasourceService interface {
|
|||
Count(pc PublishedCriteria) (int, error)
|
||||
}
|
||||
|
||||
//Direction type to distinct if a site should be moved up or down
|
||||
// Direction type to distinct if a site should be moved up or down
|
||||
type Direction int
|
||||
|
||||
const (
|
||||
//Up for moving the site one up
|
||||
// Up for moving the site one up
|
||||
Up = iota
|
||||
//Down for moving the site one down
|
||||
// Down for moving the site one down
|
||||
Down
|
||||
)
|
||||
|
||||
//Site represents a site
|
||||
// Site represents a site
|
||||
type Site struct {
|
||||
ID int
|
||||
Title string
|
||||
|
@ -50,21 +50,21 @@ type Site struct {
|
|||
}
|
||||
|
||||
// LinkEscape escapes a link for safe use in URLs
|
||||
func (s Site) LinkEscape() string {
|
||||
func (s *Site) LinkEscape() string {
|
||||
if s.isExternal() {
|
||||
return s.Link
|
||||
}
|
||||
return utils.AppendString("/site/", url.PathEscape(s.Link))
|
||||
return "/site/" + url.PathEscape(s.Link)
|
||||
}
|
||||
|
||||
func (s Site) safeLink() string {
|
||||
func (s *Site) safeLink() string {
|
||||
if s.isExternal() {
|
||||
return s.Link
|
||||
}
|
||||
return utils.CreateURLSafeSlug(s.Link, -1)
|
||||
return slug.CreateURLSafeSlug(s.Link, -1)
|
||||
}
|
||||
|
||||
func (s Site) isExternal() bool {
|
||||
func (s *Site) isExternal() bool {
|
||||
if len(s.Link) > 6 {
|
||||
if s.Link[:7] == "http://" {
|
||||
return true
|
||||
|
@ -114,35 +114,29 @@ func (s *Site) validate(ds SiteDatasourceService, changeLink bool) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
//SiteService containing the service to access site
|
||||
// SiteService containing the service to access site
|
||||
type SiteService struct {
|
||||
Datasource SiteDatasourceService
|
||||
}
|
||||
|
||||
//List returns all sites
|
||||
func (ss SiteService) List(pc PublishedCriteria, p *Pagination) ([]Site, error) {
|
||||
// List returns all sites
|
||||
func (ss *SiteService) List(pc PublishedCriteria, p *Pagination) ([]Site, error) {
|
||||
return ss.Datasource.List(pc, p)
|
||||
}
|
||||
|
||||
//Publish switches the publish state of the site
|
||||
func (ss SiteService) Publish(siteID int) error {
|
||||
// Publish switches the publish state of the site
|
||||
func (ss *SiteService) Publish(siteID int) error {
|
||||
s, err := ss.Datasource.Get(siteID, All)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ss.Datasource.Publish(s)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return ss.Datasource.Publish(s)
|
||||
}
|
||||
|
||||
//Create creates a site
|
||||
func (ss SiteService) Create(s *Site) (int, error) {
|
||||
// Create creates a site
|
||||
func (ss *SiteService) Create(s *Site) (int, error) {
|
||||
if err := s.validate(ss.Datasource, true); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
@ -160,13 +154,13 @@ func (ss SiteService) Create(s *Site) (int, error) {
|
|||
return ss.Datasource.Create(s)
|
||||
}
|
||||
|
||||
//Order reorder the site
|
||||
func (ss SiteService) Order(siteID int, dir Direction) error {
|
||||
// Order reorder the site
|
||||
func (ss *SiteService) Order(siteID int, dir Direction) error {
|
||||
return ss.Datasource.Order(siteID, dir)
|
||||
}
|
||||
|
||||
//Update updates a site
|
||||
func (ss SiteService) Update(s *Site) error {
|
||||
// Update updates a site
|
||||
func (ss *SiteService) Update(s *Site) error {
|
||||
oldSite, err := ss.GetByID(s.ID, All)
|
||||
|
||||
if err != nil {
|
||||
|
@ -174,6 +168,7 @@ func (ss SiteService) Update(s *Site) error {
|
|||
}
|
||||
|
||||
changeLink := false
|
||||
|
||||
if oldSite.Link != s.Link {
|
||||
changeLink = true
|
||||
s.Link = s.safeLink()
|
||||
|
@ -186,8 +181,8 @@ func (ss SiteService) Update(s *Site) error {
|
|||
return ss.Datasource.Update(s)
|
||||
}
|
||||
|
||||
//Delete deletes a site
|
||||
func (ss SiteService) Delete(siteID int) error {
|
||||
// Delete deletes a site
|
||||
func (ss *SiteService) Delete(siteID int) error {
|
||||
s, err := ss.GetByID(siteID, All)
|
||||
|
||||
if err != nil {
|
||||
|
@ -198,18 +193,18 @@ func (ss SiteService) Delete(siteID int) error {
|
|||
}
|
||||
|
||||
// GetByLink Get a site by the link.
|
||||
func (ss SiteService) GetByLink(link string, pc PublishedCriteria) (*Site, error) {
|
||||
func (ss *SiteService) GetByLink(link string, pc PublishedCriteria) (*Site, error) {
|
||||
return ss.Datasource.GetByLink(link, pc)
|
||||
|
||||
}
|
||||
|
||||
// GetByID Get a site by the id.
|
||||
func (ss SiteService) GetByID(siteID int, pc PublishedCriteria) (*Site, error) {
|
||||
func (ss *SiteService) GetByID(siteID int, pc PublishedCriteria) (*Site, error) {
|
||||
return ss.Datasource.Get(siteID, pc)
|
||||
|
||||
}
|
||||
|
||||
// Count returns the number of sites
|
||||
func (ss SiteService) Count(pc PublishedCriteria) (int, error) {
|
||||
func (ss *SiteService) Count(pc PublishedCriteria) (int, error) {
|
||||
return ss.Datasource.Count(pc)
|
||||
}
|
||||
|
|
|
@ -1,22 +1,21 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.hoogi.eu/snafu/go-blog/components/logger"
|
||||
"git.hoogi.eu/snafu/go-blog/logger"
|
||||
)
|
||||
|
||||
//SQLiteSiteDatasource providing an implementation of SiteDatasourceService for sqlite
|
||||
// SQLiteSiteDatasource providing an implementation of SiteDatasourceService for sqlite
|
||||
type SQLiteSiteDatasource struct {
|
||||
SQLConn *sql.DB
|
||||
}
|
||||
|
||||
//List returns a array of sites
|
||||
func (rdb SQLiteSiteDatasource) List(pc PublishedCriteria, p *Pagination) ([]Site, error) {
|
||||
var stmt bytes.Buffer
|
||||
|
||||
// List returns a array of sites
|
||||
func (rdb *SQLiteSiteDatasource) List(pc PublishedCriteria, p *Pagination) ([]Site, error) {
|
||||
var stmt strings.Builder
|
||||
var args []interface{}
|
||||
|
||||
stmt.WriteString("SELECT s.id, s.title, s.link, s.section, s.content, s.published, s.published_on, s.last_modified, s.order_no, u.id, u.display_name, u.email, u.username ")
|
||||
|
@ -40,14 +39,18 @@ func (rdb SQLiteSiteDatasource) List(pc PublishedCriteria, p *Pagination) ([]Sit
|
|||
}
|
||||
|
||||
rows, err := rdb.SQLConn.Query(stmt.String(), args...)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
sites := []Site{}
|
||||
defer func() {
|
||||
if err := rows.Close(); err != nil {
|
||||
logger.Log.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
var sites []Site
|
||||
var s Site
|
||||
var u User
|
||||
|
||||
|
@ -68,9 +71,9 @@ func (rdb SQLiteSiteDatasource) List(pc PublishedCriteria, p *Pagination) ([]Sit
|
|||
return sites, nil
|
||||
}
|
||||
|
||||
//Get returns a site based on the site id
|
||||
func (rdb SQLiteSiteDatasource) Get(siteID int, pc PublishedCriteria) (*Site, error) {
|
||||
var stmt bytes.Buffer
|
||||
// Get returns a site based on the site id
|
||||
func (rdb *SQLiteSiteDatasource) Get(siteID int, pc PublishedCriteria) (*Site, error) {
|
||||
var stmt strings.Builder
|
||||
var args []interface{}
|
||||
|
||||
stmt.WriteString("SELECT s.id, s.title, s.link, s.section, s.content, s.published, s.published_on, s.last_modified, s.order_no, u.id, u.display_name, u.email, u.username FROM site as s ")
|
||||
|
@ -97,9 +100,9 @@ func (rdb SQLiteSiteDatasource) Get(siteID int, pc PublishedCriteria) (*Site, er
|
|||
return &s, nil
|
||||
}
|
||||
|
||||
//GetByLink returns a site based on the provided link
|
||||
func (rdb SQLiteSiteDatasource) GetByLink(link string, pc PublishedCriteria) (*Site, error) {
|
||||
var stmt bytes.Buffer
|
||||
// GetByLink returns a site based on the provided link
|
||||
func (rdb *SQLiteSiteDatasource) GetByLink(link string, pc PublishedCriteria) (*Site, error) {
|
||||
var stmt strings.Builder
|
||||
var args []interface{}
|
||||
|
||||
stmt.WriteString("SELECT s.id, s.title, s.link, s.section, s.content, s.published, s.published_on, s.order_no, s.last_modified, u.id, u.display_name, u.email, u.username FROM site as s ")
|
||||
|
@ -117,7 +120,8 @@ func (rdb SQLiteSiteDatasource) GetByLink(link string, pc PublishedCriteria) (*S
|
|||
var s Site
|
||||
var u User
|
||||
|
||||
if err := rdb.SQLConn.QueryRow(stmt.String(), link).Scan(&s.ID, &s.Title, &s.Link, &s.Section, &s.Content, &s.Published, &s.PublishedOn, &s.OrderNo, &s.LastModified, &u.ID, &u.DisplayName, &u.Email, &u.Username); err != nil {
|
||||
if err := rdb.SQLConn.QueryRow(stmt.String(), link).Scan(&s.ID, &s.Title, &s.Link, &s.Section, &s.Content, &s.Published,
|
||||
&s.PublishedOn, &s.OrderNo, &s.LastModified, &u.ID, &u.DisplayName, &u.Email, &u.Username); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -126,9 +130,10 @@ func (rdb SQLiteSiteDatasource) GetByLink(link string, pc PublishedCriteria) (*S
|
|||
return &s, nil
|
||||
}
|
||||
|
||||
//Publish publishes or unpublishes a site
|
||||
func (rdb SQLiteSiteDatasource) Publish(s *Site) error {
|
||||
// Publish publishes or unpublishes a site
|
||||
func (rdb *SQLiteSiteDatasource) Publish(s *Site) error {
|
||||
publishOn := NullTime{Valid: false}
|
||||
|
||||
if !s.Published {
|
||||
publishOn = NullTime{Time: time.Now(), Valid: true}
|
||||
}
|
||||
|
@ -139,9 +144,10 @@ func (rdb SQLiteSiteDatasource) Publish(s *Site) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
//Create creates a site
|
||||
func (rdb SQLiteSiteDatasource) Create(s *Site) (int, error) {
|
||||
res, err := rdb.SQLConn.Exec("INSERT INTO site (title, link, section, content, published, published_on, last_modified, order_no, user_id) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
// Create creates a site
|
||||
func (rdb *SQLiteSiteDatasource) Create(s *Site) (int, error) {
|
||||
res, err := rdb.SQLConn.Exec("INSERT INTO site (title, link, section, content, published, published_on, last_modified, order_no, user_id) "+
|
||||
"VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
s.Title, s.Link, s.Section, s.Content, s.Published, s.PublishedOn, time.Now(), s.OrderNo, s.Author.ID)
|
||||
|
||||
if err != nil {
|
||||
|
@ -157,8 +163,8 @@ func (rdb SQLiteSiteDatasource) Create(s *Site) (int, error) {
|
|||
return int(i), nil
|
||||
}
|
||||
|
||||
//Order moves a site up or down
|
||||
func (rdb SQLiteSiteDatasource) Order(id int, d Direction) error {
|
||||
// Order moves a site up or down
|
||||
func (rdb *SQLiteSiteDatasource) Order(id int, d Direction) error {
|
||||
tx, err := rdb.SQLConn.Begin()
|
||||
|
||||
if err != nil {
|
||||
|
@ -168,14 +174,15 @@ func (rdb SQLiteSiteDatasource) Order(id int, d Direction) error {
|
|||
defer func() {
|
||||
if err != nil {
|
||||
logger.Log.Error("error during ordering of sites ", err)
|
||||
tx.Rollback()
|
||||
|
||||
err := tx.Rollback()
|
||||
|
||||
if err != nil {
|
||||
logger.Log.Error("error during transaction rollback ", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if d == Up {
|
||||
if _, err = tx.Exec("UPDATE site "+
|
||||
"SET order_no=(SELECT order_no AS order_no FROM site WHERE id=?) "+
|
||||
|
@ -189,7 +196,9 @@ func (rdb SQLiteSiteDatasource) Order(id int, d Direction) error {
|
|||
} else if d == Down {
|
||||
var max int
|
||||
|
||||
tx.QueryRow("SELECT MAX(order_no) AS max FROM site").Scan(&max)
|
||||
if err := tx.QueryRow("SELECT MAX(order_no) AS max FROM site").Scan(&max); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = tx.Exec("UPDATE site "+
|
||||
"SET order_no=(SELECT order_no AS swap_el FROM site WHERE id=?) "+
|
||||
|
@ -205,27 +214,22 @@ func (rdb SQLiteSiteDatasource) Order(id int, d Direction) error {
|
|||
}
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// Update updates a site
|
||||
func (rdb *SQLiteSiteDatasource) Update(s *Site) error {
|
||||
if _, err := rdb.SQLConn.Exec("UPDATE site SET title=?, link=?, section=?, content=?, last_modified=? WHERE id=?",
|
||||
s.Title, s.Link, s.Section, s.Content, time.Now(), s.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//Update updates a site
|
||||
func (rdb SQLiteSiteDatasource) Update(s *Site) error {
|
||||
if _, err := rdb.SQLConn.Exec("UPDATE site SET title=?, link=?, section=?, content=?, last_modified=? WHERE id=?", s.Title, s.Link, s.Section, s.Content, time.Now(), s.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//Count returns the amount of sites
|
||||
func (rdb SQLiteSiteDatasource) Count(pc PublishedCriteria) (int, error) {
|
||||
var stmt bytes.Buffer
|
||||
// Count returns the amount of sites
|
||||
func (rdb *SQLiteSiteDatasource) Count(pc PublishedCriteria) (int, error) {
|
||||
var stmt strings.Builder
|
||||
|
||||
stmt.WriteString("SELECT count(id) FROM site ")
|
||||
|
||||
|
@ -244,8 +248,8 @@ func (rdb SQLiteSiteDatasource) Count(pc PublishedCriteria) (int, error) {
|
|||
return total, nil
|
||||
}
|
||||
|
||||
//Max returns the maximum order number
|
||||
func (rdb SQLiteSiteDatasource) Max() (int, error) {
|
||||
// Max returns the maximum order number
|
||||
func (rdb *SQLiteSiteDatasource) Max() (int, error) {
|
||||
var max sql.NullInt64
|
||||
|
||||
if err := rdb.SQLConn.QueryRow("SELECT MAX(order_no) FROM site").Scan(&max); err != nil {
|
||||
|
@ -259,8 +263,8 @@ func (rdb SQLiteSiteDatasource) Max() (int, error) {
|
|||
return int(max.Int64), nil
|
||||
}
|
||||
|
||||
//Delete deletes a site and updates the order numbers
|
||||
func (rdb SQLiteSiteDatasource) Delete(s *Site) error {
|
||||
// Delete deletes a site and updates the order numbers
|
||||
func (rdb *SQLiteSiteDatasource) Delete(s *Site) error {
|
||||
tx, err := rdb.SQLConn.Begin()
|
||||
|
||||
if err != nil {
|
||||
|
@ -269,8 +273,11 @@ func (rdb SQLiteSiteDatasource) Delete(s *Site) error {
|
|||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
logger.Log.Error("error during delete transaction", err)
|
||||
tx.Rollback()
|
||||
logger.Log.Errorf("error site removal not successful %v", err)
|
||||
if err := tx.Rollback(); err != nil {
|
||||
logger.Log.Errorf("could not rollback transaction during site removal %v", err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
@ -283,9 +290,7 @@ func (rdb SQLiteSiteDatasource) Delete(s *Site) error {
|
|||
return err
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
|
||||
if err != nil {
|
||||
if err = tx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -5,15 +5,15 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
//AdminCriteria specifies which type of users should be considered
|
||||
// AdminCriteria specifies which type of users should be considered
|
||||
type AdminCriteria int
|
||||
|
||||
const (
|
||||
//OnlyAdmins conider only published
|
||||
// OnlyAdmins consider only admins
|
||||
OnlyAdmins = iota
|
||||
//NoAdmins conider no admins
|
||||
// NoAdmins consider no admins
|
||||
NoAdmins
|
||||
//AllUser conider all users
|
||||
// AllUser consider all users
|
||||
AllUser
|
||||
)
|
||||
|
||||
|
@ -21,15 +21,15 @@ const (
|
|||
type PublishedCriteria int
|
||||
|
||||
const (
|
||||
// OnlyPublished conider only published
|
||||
// OnlyPublished consider only published
|
||||
OnlyPublished = iota
|
||||
// NotPublished conider only not published
|
||||
// NotPublished consider only not published
|
||||
NotPublished
|
||||
// All conider both published and not published
|
||||
// All consider both published and not published
|
||||
All
|
||||
)
|
||||
|
||||
//NullTime reprensents a time which may not valid if time is null
|
||||
// NullTime represents a time which may not valid if time is null
|
||||
type NullTime struct {
|
||||
Time time.Time
|
||||
Valid bool
|
||||
|
|
|
@ -7,12 +7,12 @@ import (
|
|||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.hoogi.eu/snafu/go-blog/components/httperror"
|
||||
"git.hoogi.eu/snafu/go-blog/components/logger"
|
||||
"git.hoogi.eu/snafu/go-blog/utils"
|
||||
"git.hoogi.eu/snafu/go-blog/crypt"
|
||||
"git.hoogi.eu/snafu/go-blog/httperror"
|
||||
"git.hoogi.eu/snafu/go-blog/logger"
|
||||
)
|
||||
|
||||
//TokenDatasourceService defines an interface for CRUD operations for tokens
|
||||
// TokenDatasourceService defines an interface for CRUD operations for tokens
|
||||
type TokenDatasourceService interface {
|
||||
Create(t *Token) (int, error)
|
||||
Get(hash string, tt TokenType) (*Token, error)
|
||||
|
@ -20,7 +20,7 @@ type TokenDatasourceService interface {
|
|||
Remove(hash string, tt TokenType) error
|
||||
}
|
||||
|
||||
//Token represents a token
|
||||
// Token represents a token
|
||||
type Token struct {
|
||||
ID int
|
||||
Hash string
|
||||
|
@ -31,19 +31,19 @@ type Token struct {
|
|||
}
|
||||
|
||||
const (
|
||||
//PasswordReset token generated for resetting passwords
|
||||
// PasswordReset token generated for resetting passwords
|
||||
PasswordReset = iota
|
||||
)
|
||||
|
||||
var types = [...]string{"password_reset"}
|
||||
|
||||
//TokenType specifies the type where token can be used
|
||||
// TokenType specifies the type where token can be used
|
||||
type TokenType int
|
||||
|
||||
// Scan implements the Scanner interface.
|
||||
func (tt *TokenType) Scan(value interface{}) error {
|
||||
for k, t := range types {
|
||||
if t == string(value.([]byte)) {
|
||||
if t == (value.(string)) {
|
||||
tts := TokenType(k)
|
||||
tt = &tts
|
||||
return nil
|
||||
|
@ -61,23 +61,25 @@ func (tt TokenType) String() string {
|
|||
return types[tt]
|
||||
}
|
||||
|
||||
//TokenService containing the service to access tokens
|
||||
// TokenService containing the service to access tokens
|
||||
type TokenService struct {
|
||||
Datasource TokenDatasourceService
|
||||
}
|
||||
|
||||
//Create creates a new token
|
||||
func (ts TokenService) Create(t *Token) error {
|
||||
t.Hash = utils.RandomHash(32)
|
||||
// Create creates a new token
|
||||
func (ts *TokenService) Create(t *Token) error {
|
||||
t.Hash = crypt.RandomHash(32)
|
||||
|
||||
_, err := ts.Datasource.Create(t)
|
||||
if _, err := ts.Datasource.Create(t); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
//Get token for a defined token type expires after a defined time
|
||||
//Expired token will be removed
|
||||
func (ts TokenService) Get(hash string, tt TokenType, expireAfter time.Duration) (*Token, error) {
|
||||
// Get token for a defined token type expires after a defined time
|
||||
// Expired token will be removed
|
||||
func (ts *TokenService) Get(hash string, tt TokenType, expireAfter time.Duration) (*Token, error) {
|
||||
token, err := ts.Datasource.Get(hash, tt)
|
||||
|
||||
if err != nil {
|
||||
|
@ -96,8 +98,8 @@ func (ts TokenService) Get(hash string, tt TokenType, expireAfter time.Duration)
|
|||
return token, nil
|
||||
}
|
||||
|
||||
//RateLimit returns an error if a token is requested greater three times in a time span of 15 minutes
|
||||
func (ts TokenService) RateLimit(userID int, tt TokenType) error {
|
||||
// RateLimit returns an error if a token is requested greater three times in a time span of 15 minutes
|
||||
func (ts *TokenService) RateLimit(userID int, tt TokenType) error {
|
||||
tokens, err := ts.Datasource.ListByUser(userID, tt)
|
||||
|
||||
if err != nil {
|
||||
|
@ -106,7 +108,7 @@ func (ts TokenService) RateLimit(userID int, tt TokenType) error {
|
|||
|
||||
now := time.Now()
|
||||
|
||||
rate := []Token{}
|
||||
var rate []Token
|
||||
for _, t := range tokens {
|
||||
if now.Sub(t.RequestedAt) < time.Minute*15 {
|
||||
rate = append(rate, t)
|
||||
|
@ -120,7 +122,7 @@ func (ts TokenService) RateLimit(userID int, tt TokenType) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
//Remove removes a token
|
||||
func (ts TokenService) Remove(hash string, tt TokenType) error {
|
||||
// Remove removes a token
|
||||
func (ts *TokenService) Remove(hash string, tt TokenType) error {
|
||||
return ts.Datasource.Remove(hash, tt)
|
||||
}
|
||||
|
|
|
@ -2,16 +2,17 @@ package models
|
|||
|
||||
import (
|
||||
"database/sql"
|
||||
"git.hoogi.eu/snafu/go-blog/logger"
|
||||
"time"
|
||||
)
|
||||
|
||||
//SQLiteTokenDatasource providing an implementation of TokenDatasourceService using MariaDB
|
||||
// SQLiteTokenDatasource providing an implementation of TokenDatasourceService using MariaDB
|
||||
type SQLiteTokenDatasource struct {
|
||||
SQLConn *sql.DB
|
||||
}
|
||||
|
||||
//Create creates a new token
|
||||
func (rdb SQLiteTokenDatasource) Create(t *Token) (int, error) {
|
||||
// Create creates a new token
|
||||
func (rdb *SQLiteTokenDatasource) Create(t *Token) (int, error) {
|
||||
res, err := rdb.SQLConn.Exec("INSERT INTO token (hash, requested_at, token_type, user_id) VALUES(?, ?, ?, ?)",
|
||||
t.Hash, time.Now(), t.Type, t.Author.ID)
|
||||
|
||||
|
@ -28,8 +29,8 @@ func (rdb SQLiteTokenDatasource) Create(t *Token) (int, error) {
|
|||
return int(i), nil
|
||||
}
|
||||
|
||||
//Get gets a token based on the hash and the token type
|
||||
func (rdb SQLiteTokenDatasource) Get(hash string, tt TokenType) (*Token, error) {
|
||||
// Get gets a token based on the hash and the token type
|
||||
func (rdb *SQLiteTokenDatasource) Get(hash string, tt TokenType) (*Token, error) {
|
||||
var t Token
|
||||
var u User
|
||||
|
||||
|
@ -43,15 +44,19 @@ func (rdb SQLiteTokenDatasource) Get(hash string, tt TokenType) (*Token, error)
|
|||
return &t, nil
|
||||
}
|
||||
|
||||
//ListByUser receives all tokens based on the user id and the token type ordered by requested
|
||||
func (rdb SQLiteTokenDatasource) ListByUser(userID int, tt TokenType) ([]Token, error) {
|
||||
// ListByUser receives all tokens based on the user id and the token type ordered by requested
|
||||
func (rdb *SQLiteTokenDatasource) ListByUser(userID int, tt TokenType) ([]Token, error) {
|
||||
rows, err := rdb.SQLConn.Query("SELECT t.id, t.hash, t.requested_at, t.token_type, t.user_id FROM token as t WHERE t.user_id=? AND t.token_type=? ", userID, tt.String())
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
defer func() {
|
||||
if err := rows.Close(); err != nil {
|
||||
logger.Log.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
tokens := []Token{}
|
||||
|
||||
|
@ -70,8 +75,8 @@ func (rdb SQLiteTokenDatasource) ListByUser(userID int, tt TokenType) ([]Token,
|
|||
return tokens, nil
|
||||
}
|
||||
|
||||
//Remove removes a token based on the hash
|
||||
func (rdb SQLiteTokenDatasource) Remove(hash string, tt TokenType) error {
|
||||
// Remove removes a token based on the hash
|
||||
func (rdb *SQLiteTokenDatasource) Remove(hash string, tt TokenType) error {
|
||||
if _, err := rdb.SQLConn.Exec("DELETE FROM token WHERE hash=? AND token_type=? ", hash, tt.String()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
113
models/user.go
113
models/user.go
|
@ -12,14 +12,14 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"git.hoogi.eu/snafu/go-blog/components/httperror"
|
||||
"git.hoogi.eu/snafu/go-blog/components/logger"
|
||||
"git.hoogi.eu/snafu/go-blog/crypt"
|
||||
"git.hoogi.eu/snafu/go-blog/httperror"
|
||||
"git.hoogi.eu/snafu/go-blog/logger"
|
||||
"git.hoogi.eu/snafu/go-blog/settings"
|
||||
"git.hoogi.eu/snafu/go-blog/utils"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
//UserDatasourceService defines an interface for CRUD operations for users
|
||||
// UserDatasourceService defines an interface for CRUD operations for users
|
||||
type UserDatasourceService interface {
|
||||
Create(u *User) (int, error)
|
||||
List(p *Pagination) ([]User, error)
|
||||
|
@ -31,7 +31,7 @@ type UserDatasourceService interface {
|
|||
Remove(userID int) error
|
||||
}
|
||||
|
||||
//User represents a user
|
||||
// User represents a user
|
||||
type User struct {
|
||||
ID int
|
||||
Username string
|
||||
|
@ -45,18 +45,14 @@ type User struct {
|
|||
IsAdmin bool
|
||||
}
|
||||
|
||||
const (
|
||||
bcryptRounds = 12
|
||||
)
|
||||
|
||||
//UserService containing the service to access users
|
||||
// UserService containing the service to access users
|
||||
type UserService struct {
|
||||
Datasource UserDatasourceService
|
||||
Config settings.User
|
||||
UserInterceptor UserInterceptor
|
||||
}
|
||||
|
||||
//UserInterceptor will be executed before and after updating/creating users
|
||||
// UserInterceptor will be executed before and after updating/creating users
|
||||
type UserInterceptor interface {
|
||||
PreCreate(user *User) error
|
||||
PostCreate(user *User) error
|
||||
|
@ -74,7 +70,7 @@ const (
|
|||
VPassword
|
||||
)
|
||||
|
||||
func (u *User) validate(us UserService, minPasswordLength int, v Validations) error {
|
||||
func (u *User) validate(us *UserService, minPasswordLength int, v Validations) error {
|
||||
u.DisplayName = strings.TrimSpace(u.DisplayName)
|
||||
u.Email = strings.TrimSpace(u.Email)
|
||||
u.Username = strings.TrimSpace(u.Username)
|
||||
|
@ -113,17 +109,13 @@ func (u *User) validate(us UserService, minPasswordLength int, v Validations) er
|
|||
}
|
||||
|
||||
if (v & VDupEmail) != 0 {
|
||||
err := us.duplicateMail(u.Email)
|
||||
|
||||
if err != nil {
|
||||
if err := us.duplicateMail(u.Email); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if (v & VDupUsername) != 0 {
|
||||
err := us.duplicateUsername(u.Username)
|
||||
|
||||
if err != nil {
|
||||
if err := us.duplicateUsername(u.Username); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -131,10 +123,11 @@ func (u *User) validate(us UserService, minPasswordLength int, v Validations) er
|
|||
return nil
|
||||
}
|
||||
|
||||
func (us UserService) duplicateMail(mail string) error {
|
||||
func (us *UserService) duplicateMail(mail string) error {
|
||||
user, err := us.Datasource.GetByMail(mail)
|
||||
|
||||
if err != nil {
|
||||
if err != sql.ErrNoRows {
|
||||
if !errors.Is(err, sql.ErrNoRows) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -146,13 +139,15 @@ func (us UserService) duplicateMail(mail string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (us UserService) duplicateUsername(username string) error {
|
||||
func (us *UserService) duplicateUsername(username string) error {
|
||||
user, err := us.Datasource.GetByUsername(username)
|
||||
|
||||
if err != nil {
|
||||
if err != sql.ErrNoRows {
|
||||
if !errors.Is(err, sql.ErrNoRows) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if user != nil {
|
||||
return httperror.New(http.StatusUnprocessableEntity,
|
||||
fmt.Sprintf("The username %s already exists.", username),
|
||||
|
@ -162,21 +157,22 @@ func (us UserService) duplicateUsername(username string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
//Count returns the amount of users
|
||||
func (us UserService) Count(a AdminCriteria) (int, error) {
|
||||
// Count returns the amount of users
|
||||
func (us *UserService) Count(a AdminCriteria) (int, error) {
|
||||
return us.Datasource.Count(a)
|
||||
}
|
||||
|
||||
//List returns a list of users. Limits the amount based on the defined pagination
|
||||
func (us UserService) List(p *Pagination) ([]User, error) {
|
||||
// List returns a list of users. Limits the amount based on the defined pagination
|
||||
func (us *UserService) List(p *Pagination) ([]User, error) {
|
||||
return us.Datasource.List(p)
|
||||
}
|
||||
|
||||
//GetByID gets the user based on the given id; will not contain the user password
|
||||
func (us UserService) GetByID(userID int) (*User, error) {
|
||||
// GetByID gets the user based on the given id; will not contain the user password
|
||||
func (us *UserService) GetByID(userID int) (*User, error) {
|
||||
u, err := us.Datasource.Get(userID)
|
||||
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, httperror.NotFound("user", fmt.Errorf("the user with id %d was not found", userID))
|
||||
}
|
||||
return nil, err
|
||||
|
@ -185,11 +181,12 @@ func (us UserService) GetByID(userID int) (*User, error) {
|
|||
return u, nil
|
||||
}
|
||||
|
||||
//GetByUsername gets the user based on the given username; will contain the user password
|
||||
func (us UserService) GetByUsername(username string) (*User, error) {
|
||||
// GetByUsername gets the user based on the given username; will contain the user password
|
||||
func (us *UserService) GetByUsername(username string) (*User, error) {
|
||||
u, err := us.Datasource.GetByUsername(username)
|
||||
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, httperror.NotFound("user", err)
|
||||
}
|
||||
return nil, err
|
||||
|
@ -198,12 +195,12 @@ func (us UserService) GetByUsername(username string) (*User, error) {
|
|||
return u, nil
|
||||
}
|
||||
|
||||
//GetByMail gets the user based on the given mail; will contain the user password
|
||||
func (us UserService) GetByMail(mail string) (*User, error) {
|
||||
// GetByMail gets the user based on the given mail; will contain the user password
|
||||
func (us *UserService) GetByMail(mail string) (*User, error) {
|
||||
u, err := us.Datasource.GetByMail(mail)
|
||||
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, httperror.NotFound("user", err)
|
||||
}
|
||||
|
||||
|
@ -212,9 +209,9 @@ func (us UserService) GetByMail(mail string) (*User, error) {
|
|||
return u, nil
|
||||
}
|
||||
|
||||
//Create creates the user
|
||||
//If an UserInterceptor is available the action PreCreate is executed before creating and PostCreate after creating the user
|
||||
func (us UserService) Create(u *User) (int, error) {
|
||||
// Create creates the user
|
||||
// If an UserInterceptor is available the action PreCreate is executed before creating and PostCreate after creating the user
|
||||
func (us *UserService) Create(u *User) (int, error) {
|
||||
if us.UserInterceptor != nil {
|
||||
if err := us.UserInterceptor.PreCreate(u); err != nil {
|
||||
return -1, httperror.InternalServerError(fmt.Errorf("error while executing user interceptor 'PreCreate' error %v", err))
|
||||
|
@ -225,9 +222,9 @@ func (us UserService) Create(u *User) (int, error) {
|
|||
return -1, err
|
||||
}
|
||||
|
||||
salt := utils.GenerateSalt()
|
||||
saltedPassword := utils.AppendBytes(u.PlainPassword, salt)
|
||||
password, err := utils.CryptPassword([]byte(saltedPassword), bcryptRounds)
|
||||
salt := crypt.GenerateSalt()
|
||||
saltedPassword := append(u.PlainPassword[:], salt[:]...)
|
||||
password, err := crypt.CryptPassword([]byte(saltedPassword))
|
||||
|
||||
if err != nil {
|
||||
return -1, err
|
||||
|
@ -256,7 +253,7 @@ func (us UserService) Create(u *User) (int, error) {
|
|||
|
||||
//Update updates the user
|
||||
//If an UserInterceptor is available the action PreUpdate is executed before updating and PostUpdate after updating the user
|
||||
func (us UserService) Update(u *User, changePassword bool) error {
|
||||
func (us *UserService) Update(u *User, changePassword bool) error {
|
||||
oldUser, err := us.Datasource.Get(u.ID)
|
||||
|
||||
if err != nil {
|
||||
|
@ -270,7 +267,6 @@ func (us UserService) Update(u *User, changePassword bool) error {
|
|||
}
|
||||
|
||||
if us.UserInterceptor != nil {
|
||||
|
||||
if err := us.UserInterceptor.PreUpdate(oldUser, u); err != nil {
|
||||
return httperror.InternalServerError(fmt.Errorf("error while executing user interceptor 'PreUpdate' error %v", err))
|
||||
}
|
||||
|
@ -309,9 +305,9 @@ func (us UserService) Update(u *User, changePassword bool) error {
|
|||
}
|
||||
|
||||
if changePassword {
|
||||
salt := utils.GenerateSalt()
|
||||
saltedPassword := utils.AppendBytes(u.PlainPassword, salt)
|
||||
password, err := utils.CryptPassword([]byte(saltedPassword), bcryptRounds)
|
||||
salt := crypt.GenerateSalt()
|
||||
saltedPassword := append(u.PlainPassword[:], salt[:]...)
|
||||
password, err := crypt.CryptPassword([]byte(saltedPassword))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -321,7 +317,9 @@ func (us UserService) Update(u *User, changePassword bool) error {
|
|||
u.Salt = salt
|
||||
}
|
||||
|
||||
err = us.Datasource.Update(u, changePassword)
|
||||
if err = us.Datasource.Update(u, changePassword); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
u.Password = nil
|
||||
|
||||
|
@ -330,14 +328,15 @@ func (us UserService) Update(u *User, changePassword bool) error {
|
|||
logger.Log.Errorf("error while executing PostUpdate user interceptor method %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
u.PlainPassword = nil
|
||||
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
// Authenticate authenticates the user by the given login method (email or username)
|
||||
// if the user was found but the password is wrong the found user and an error will be returned
|
||||
func (us UserService) Authenticate(u *User, loginMethod settings.LoginMethod) (*User, error) {
|
||||
func (us *UserService) Authenticate(u *User, loginMethod settings.LoginMethod) (*User, error) {
|
||||
var err error
|
||||
|
||||
if len(u.Username) == 0 || len(u.PlainPassword) == 0 {
|
||||
|
@ -353,9 +352,9 @@ func (us UserService) Authenticate(u *User, loginMethod settings.LoginMethod) (*
|
|||
}
|
||||
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
//Do some extra work
|
||||
bcrypt.CompareHashAndPassword([]byte("$2a$12$bQlRnXTNZMp6kCyoAlnf3uZW5vtmSj9CHP7pYplRUVK2n0C5xBHBa"), password)
|
||||
//Do some extra work
|
||||
bcrypt.CompareHashAndPassword([]byte("$2a$12$bQlRnXTNZMp6kCyoAlnf3uZW5vtmSj9CHP7pYplRUVK2n0C5xBHBa"), password)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, httperror.New(http.StatusUnauthorized, "Your username or password is invalid.", err)
|
||||
}
|
||||
return nil, err
|
||||
|
@ -381,7 +380,7 @@ func (us UserService) Authenticate(u *User, loginMethod settings.LoginMethod) (*
|
|||
}
|
||||
|
||||
// Remove removes the user returns an error if no administrator would remain
|
||||
func (us UserService) Remove(u *User) error {
|
||||
func (us *UserService) Remove(u *User) error {
|
||||
if us.UserInterceptor != nil {
|
||||
if err := us.UserInterceptor.PreRemove(u); err != nil {
|
||||
return httperror.InternalServerError(fmt.Errorf("error while executing user interceptor 'PreRemove' error %v", err))
|
||||
|
@ -413,8 +412,8 @@ func (us UserService) Remove(u *User) error {
|
|||
return err
|
||||
}
|
||||
|
||||
//OneAdmin returns true if there is only one admin
|
||||
func (us UserService) OneAdmin() (bool, error) {
|
||||
// OneAdmin returns true if there is only one admin
|
||||
func (us *UserService) OneAdmin() (bool, error) {
|
||||
c, err := us.Datasource.Count(OnlyAdmins)
|
||||
|
||||
if err != nil {
|
||||
|
@ -428,6 +427,6 @@ func (us UserService) OneAdmin() (bool, error) {
|
|||
return false, nil
|
||||
}
|
||||
|
||||
func (u User) comparePassword() error {
|
||||
return bcrypt.CompareHashAndPassword(u.Password, utils.AppendBytes(u.PlainPassword, u.Salt))
|
||||
func (u *User) comparePassword() error {
|
||||
return bcrypt.CompareHashAndPassword(u.Password, append(u.PlainPassword[:], u.Salt[:]...))
|
||||
}
|
||||
|
|
|
@ -3,11 +3,12 @@ package models
|
|||
import (
|
||||
"time"
|
||||
|
||||
"git.hoogi.eu/snafu/go-blog/components/mail"
|
||||
"git.hoogi.eu/snafu/go-blog/utils"
|
||||
"git.hoogi.eu/snafu/go-blog/crypt"
|
||||
"git.hoogi.eu/snafu/go-blog/mail"
|
||||
)
|
||||
|
||||
//User represents a user
|
||||
// TODO: refactor
|
||||
// UserInvite represents a new invited user
|
||||
type UserInvite struct {
|
||||
ID int
|
||||
Hash string
|
||||
|
@ -29,7 +30,7 @@ func (ui UserInvite) Copy() *User {
|
|||
}
|
||||
}
|
||||
|
||||
//UserInviteDatasourceService defines an interface for CRUD operations for users
|
||||
// UserInviteDatasourceService defines an interface for CRUD operations for users
|
||||
type UserInviteDatasourceService interface {
|
||||
List() ([]UserInvite, error)
|
||||
Get(inviteID int) (*UserInvite, error)
|
||||
|
@ -39,32 +40,26 @@ type UserInviteDatasourceService interface {
|
|||
Remove(inviteID int) error
|
||||
}
|
||||
|
||||
//UserInviteService
|
||||
// UserInviteService
|
||||
type UserInviteService struct {
|
||||
Datasource UserInviteDatasourceService
|
||||
UserService UserService
|
||||
MailService mail.Service
|
||||
UserService *UserService
|
||||
MailService *mail.Service
|
||||
}
|
||||
|
||||
// validate A user invitation must conform the user validations except the password checks
|
||||
func (ui UserInvite) validate(uis UserInviteService) error {
|
||||
func (ui UserInvite) validate(uis *UserInviteService) error {
|
||||
user := ui.Copy()
|
||||
|
||||
err := user.validate(uis.UserService, -1, VDupEmail|VDupUsername)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return user.validate(uis.UserService, -1, VDupEmail|VDupUsername)
|
||||
}
|
||||
|
||||
func (uis UserInviteService) List() ([]UserInvite, error) {
|
||||
func (uis *UserInviteService) List() ([]UserInvite, error) {
|
||||
return uis.Datasource.List()
|
||||
}
|
||||
|
||||
func (uis UserInviteService) Update(ui *UserInvite) error {
|
||||
ui.Hash = utils.RandomHash(32)
|
||||
func (uis *UserInviteService) Update(ui *UserInvite) error {
|
||||
ui.Hash = crypt.RandomHash(32)
|
||||
|
||||
if err := ui.validate(uis); err != nil {
|
||||
return err
|
||||
|
@ -73,8 +68,8 @@ func (uis UserInviteService) Update(ui *UserInvite) error {
|
|||
return uis.Datasource.Update(ui)
|
||||
}
|
||||
|
||||
func (uis UserInviteService) Create(ui *UserInvite) (int, error) {
|
||||
ui.Hash = utils.RandomHash(32)
|
||||
func (uis *UserInviteService) Create(ui *UserInvite) (int, error) {
|
||||
ui.Hash = crypt.RandomHash(32)
|
||||
|
||||
if err := ui.validate(uis); err != nil {
|
||||
return -1, err
|
||||
|
@ -83,14 +78,14 @@ func (uis UserInviteService) Create(ui *UserInvite) (int, error) {
|
|||
return uis.Datasource.Create(ui)
|
||||
}
|
||||
|
||||
func (uis UserInviteService) Get(inviteID int) (*UserInvite, error) {
|
||||
func (uis *UserInviteService) Get(inviteID int) (*UserInvite, error) {
|
||||
return uis.Datasource.Get(inviteID)
|
||||
}
|
||||
|
||||
func (uis UserInviteService) GetByHash(hash string) (*UserInvite, error) {
|
||||
func (uis *UserInviteService) GetByHash(hash string) (*UserInvite, error) {
|
||||
return uis.Datasource.GetByHash(hash)
|
||||
}
|
||||
|
||||
func (uis UserInviteService) Remove(inviteID int) error {
|
||||
func (uis *UserInviteService) Remove(inviteID int) error {
|
||||
return uis.Datasource.Remove(inviteID)
|
||||
}
|
||||
|
|
|
@ -1,39 +1,33 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"git.hoogi.eu/snafu/go-blog/logger"
|
||||
"time"
|
||||
)
|
||||
|
||||
//SQLiteUserInviteDatasource
|
||||
// SQLiteUserInviteDatasource
|
||||
type SQLiteUserInviteDatasource struct {
|
||||
SQLConn *sql.DB
|
||||
}
|
||||
|
||||
func (rdb SQLiteUserInviteDatasource) List() ([]UserInvite, error) {
|
||||
var stmt bytes.Buffer
|
||||
var args []interface{}
|
||||
func (rdb *SQLiteUserInviteDatasource) List() ([]UserInvite, error) {
|
||||
var invites []UserInvite
|
||||
var ui UserInvite
|
||||
var u User
|
||||
|
||||
stmt.WriteString("SELECT ui.id, ui.username, ui.email, ui.display_name, ui.created_at, ui.is_admin, ")
|
||||
stmt.WriteString("u.id, u.username, u.email, u.display_name ")
|
||||
stmt.WriteString("FROM user_invite as ui ")
|
||||
stmt.WriteString("INNER JOIN user as u ")
|
||||
stmt.WriteString("ON u.id = ui.created_by ")
|
||||
stmt.WriteString("ORDER BY ui.username ASC ")
|
||||
|
||||
rows, err := rdb.SQLConn.Query(stmt.String(), args...)
|
||||
rows, err := rdb.SQLConn.Query("SELECT ui.id, ui.username, ui.email, ui.display_name, ui.created_at, ui.is_admin," +
|
||||
" u.id, u.username, u.email, u.display_name FROM user_invite as ui INNER JOIN user as u ON u.id = ui.created_by ORDER BY ui.username ASC")
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
invites := []UserInvite{}
|
||||
|
||||
var ui UserInvite
|
||||
var u User
|
||||
defer func() {
|
||||
if err := rows.Close(); err != nil {
|
||||
logger.Log.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
for rows.Next() {
|
||||
if err = rows.Scan(&ui.ID, &ui.Username, &ui.Email, &ui.DisplayName, &ui.CreatedAt, &ui.IsAdmin, &u.ID, &u.Username, &u.Email, &u.DisplayName); err != nil {
|
||||
|
@ -50,7 +44,7 @@ func (rdb SQLiteUserInviteDatasource) List() ([]UserInvite, error) {
|
|||
return invites, nil
|
||||
}
|
||||
|
||||
func (rdb SQLiteUserInviteDatasource) Get(inviteID int) (*UserInvite, error) {
|
||||
func (rdb *SQLiteUserInviteDatasource) Get(inviteID int) (*UserInvite, error) {
|
||||
var u User
|
||||
var ui UserInvite
|
||||
|
||||
|
@ -69,7 +63,7 @@ func (rdb SQLiteUserInviteDatasource) Get(inviteID int) (*UserInvite, error) {
|
|||
return &ui, nil
|
||||
}
|
||||
|
||||
func (rdb SQLiteUserInviteDatasource) GetByHash(hash string) (*UserInvite, error) {
|
||||
func (rdb *SQLiteUserInviteDatasource) GetByHash(hash string) (*UserInvite, error) {
|
||||
var ui UserInvite
|
||||
var u User
|
||||
|
||||
|
@ -88,7 +82,7 @@ func (rdb SQLiteUserInviteDatasource) GetByHash(hash string) (*UserInvite, error
|
|||
return &ui, nil
|
||||
}
|
||||
|
||||
func (rdb SQLiteUserInviteDatasource) Update(ui *UserInvite) error {
|
||||
func (rdb *SQLiteUserInviteDatasource) Update(ui *UserInvite) error {
|
||||
if _, err := rdb.SQLConn.Exec("UPDATE user_invite SET hash=?, username=?, email=?, display_name=?, is_admin=?, created_at=?, created_by=? "+
|
||||
"WHERE id=? ", ui.Hash, ui.Username, ui.Email, ui.DisplayName, ui.IsAdmin, ui.CreatedBy.ID, ui.ID); err != nil {
|
||||
return err
|
||||
|
@ -97,8 +91,8 @@ func (rdb SQLiteUserInviteDatasource) Update(ui *UserInvite) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
//Create creates an new user invitation
|
||||
func (rdb SQLiteUserInviteDatasource) Create(ui *UserInvite) (int, error) {
|
||||
// Create creates an new user invitation
|
||||
func (rdb *SQLiteUserInviteDatasource) Create(ui *UserInvite) (int, error) {
|
||||
res, err := rdb.SQLConn.Exec("INSERT INTO user_invite (hash, username, email, display_name, is_admin, created_at, created_by) VALUES(?, ?, ?, ?, ?, ?, ?);",
|
||||
ui.Hash, ui.Username, ui.Email, ui.DisplayName, ui.IsAdmin, time.Now(), ui.CreatedBy.ID)
|
||||
|
||||
|
@ -111,11 +105,12 @@ func (rdb SQLiteUserInviteDatasource) Create(ui *UserInvite) (int, error) {
|
|||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
return int(i), nil
|
||||
}
|
||||
|
||||
//Count retuns the amount of users invitations
|
||||
func (rdb SQLiteUserInviteDatasource) Count() (int, error) {
|
||||
// Count retuns the amount of users invitations
|
||||
func (rdb *SQLiteUserInviteDatasource) Count() (int, error) {
|
||||
var total int
|
||||
|
||||
if err := rdb.SQLConn.QueryRow("SELECT count(id) FROM user_invite").Scan(&total); err != nil {
|
||||
|
@ -125,13 +120,9 @@ func (rdb SQLiteUserInviteDatasource) Count() (int, error) {
|
|||
return total, nil
|
||||
}
|
||||
|
||||
//Removes an user invitation
|
||||
func (rdb SQLiteUserInviteDatasource) Remove(inviteID int) error {
|
||||
var stmt bytes.Buffer
|
||||
|
||||
stmt.WriteString("DELETE FROM user_invite WHERE id=?")
|
||||
|
||||
if _, err := rdb.SQLConn.Exec(stmt.String(), inviteID); err != nil {
|
||||
// Remove removes an user invitation
|
||||
func (rdb *SQLiteUserInviteDatasource) Remove(inviteID int) error {
|
||||
if _, err := rdb.SQLConn.Exec("DELETE FROM user_invite WHERE id=?", inviteID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -1,20 +1,23 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"git.hoogi.eu/snafu/go-blog/logger"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
//SQLiteUserDatasource providing an implementation of UserDatasourceService using SQLite
|
||||
// SQLiteUserDatasource providing an implementation of UserDatasourceService using SQLite
|
||||
type SQLiteUserDatasource struct {
|
||||
SQLConn *sql.DB
|
||||
}
|
||||
|
||||
//List returns a list of user
|
||||
func (rdb SQLiteUserDatasource) List(p *Pagination) ([]User, error) {
|
||||
var stmt bytes.Buffer
|
||||
// List returns a list of users
|
||||
func (rdb *SQLiteUserDatasource) List(p *Pagination) ([]User, error) {
|
||||
var stmt strings.Builder
|
||||
var args []interface{}
|
||||
var users []User
|
||||
var u User
|
||||
|
||||
stmt.WriteString("SELECT id, username, email, display_name, last_modified, active, is_admin FROM user ORDER BY username ASC ")
|
||||
|
||||
|
@ -29,11 +32,11 @@ func (rdb SQLiteUserDatasource) List(p *Pagination) ([]User, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
users := []User{}
|
||||
|
||||
var u User
|
||||
defer func() {
|
||||
if err := rows.Close(); err != nil {
|
||||
logger.Log.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
for rows.Next() {
|
||||
if err = rows.Scan(&u.ID, &u.Username, &u.Email, &u.DisplayName, &u.LastModified, &u.Active, &u.IsAdmin); err != nil {
|
||||
|
@ -50,8 +53,8 @@ func (rdb SQLiteUserDatasource) List(p *Pagination) ([]User, error) {
|
|||
return users, nil
|
||||
}
|
||||
|
||||
//Get gets an user by his userID
|
||||
func (rdb SQLiteUserDatasource) Get(userID int) (*User, error) {
|
||||
// Get gets a user by his userID
|
||||
func (rdb *SQLiteUserDatasource) Get(userID int) (*User, error) {
|
||||
var u User
|
||||
|
||||
if err := rdb.SQLConn.QueryRow("SELECT u.id, u.username, u.email, u.display_name, u.last_modified, u.active, u.is_admin, u.salt "+
|
||||
|
@ -64,8 +67,8 @@ func (rdb SQLiteUserDatasource) Get(userID int) (*User, error) {
|
|||
return &u, nil
|
||||
}
|
||||
|
||||
//GetByMail gets an user by his mail, includes the password and salt
|
||||
func (rdb SQLiteUserDatasource) GetByMail(mail string) (*User, error) {
|
||||
// GetByMail gets a user by his mail, includes the password and salt
|
||||
func (rdb *SQLiteUserDatasource) GetByMail(mail string) (*User, error) {
|
||||
var u User
|
||||
|
||||
if err := rdb.SQLConn.QueryRow("SELECT id, is_admin, active, display_name, username, email, salt, password FROM user WHERE email=? ", mail).
|
||||
|
@ -75,8 +78,8 @@ func (rdb SQLiteUserDatasource) GetByMail(mail string) (*User, error) {
|
|||
return &u, nil
|
||||
}
|
||||
|
||||
//GetByUsername gets an user by his username, includes the password and salt
|
||||
func (rdb SQLiteUserDatasource) GetByUsername(username string) (*User, error) {
|
||||
// GetByUsername gets a user by his username, includes the password and salt
|
||||
func (rdb *SQLiteUserDatasource) GetByUsername(username string) (*User, error) {
|
||||
var u User
|
||||
|
||||
if err := rdb.SQLConn.QueryRow("SELECT id, is_admin, active, display_name, username, email, salt, password FROM user WHERE username=? ", username).
|
||||
|
@ -86,8 +89,8 @@ func (rdb SQLiteUserDatasource) GetByUsername(username string) (*User, error) {
|
|||
return &u, nil
|
||||
}
|
||||
|
||||
//Create creates an new user
|
||||
func (rdb SQLiteUserDatasource) Create(u *User) (int, error) {
|
||||
// Create creates a new user
|
||||
func (rdb *SQLiteUserDatasource) Create(u *User) (int, error) {
|
||||
res, err := rdb.SQLConn.Exec("INSERT INTO user (salt, password, username, email, display_name, last_modified, active, is_admin) VALUES(?, ?, ?, ?, ?, ?, ?, ?);",
|
||||
u.Salt, u.Password, u.Username, u.Email, u.DisplayName, time.Now(), u.Active, u.IsAdmin)
|
||||
|
||||
|
@ -100,13 +103,13 @@ func (rdb SQLiteUserDatasource) Create(u *User) (int, error) {
|
|||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
return int(i), nil
|
||||
}
|
||||
|
||||
//Update updates an user
|
||||
func (rdb SQLiteUserDatasource) Update(u *User, changePassword bool) error {
|
||||
var stmt bytes.Buffer
|
||||
|
||||
// Update updates an user
|
||||
func (rdb *SQLiteUserDatasource) Update(u *User, changePassword bool) error {
|
||||
var stmt strings.Builder
|
||||
var args []interface{}
|
||||
|
||||
stmt.WriteString("UPDATE user SET display_name=?, username=?, email=?, last_modified=?, active=?, is_admin=? ")
|
||||
|
@ -114,9 +117,9 @@ func (rdb SQLiteUserDatasource) Update(u *User, changePassword bool) error {
|
|||
|
||||
if changePassword {
|
||||
stmt.WriteString(", salt=?, password=? ")
|
||||
|
||||
args = append(args, u.Salt, u.Password)
|
||||
}
|
||||
|
||||
stmt.WriteString("WHERE id=?;")
|
||||
|
||||
args = append(args, u.ID)
|
||||
|
@ -128,10 +131,9 @@ func (rdb SQLiteUserDatasource) Update(u *User, changePassword bool) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
//Count retuns the amount of users matches the AdminCriteria
|
||||
func (rdb SQLiteUserDatasource) Count(ac AdminCriteria) (int, error) {
|
||||
var stmt bytes.Buffer
|
||||
|
||||
// Count returns the amount of users matches the AdminCriteria
|
||||
func (rdb *SQLiteUserDatasource) Count(ac AdminCriteria) (int, error) {
|
||||
var stmt strings.Builder
|
||||
stmt.WriteString("SELECT count(id) FROM user ")
|
||||
|
||||
if ac == OnlyAdmins {
|
||||
|
@ -149,13 +151,9 @@ func (rdb SQLiteUserDatasource) Count(ac AdminCriteria) (int, error) {
|
|||
return total, nil
|
||||
}
|
||||
|
||||
//Removes an user
|
||||
func (rdb SQLiteUserDatasource) Remove(userID int) error {
|
||||
var stmt bytes.Buffer
|
||||
|
||||
stmt.WriteString("DELETE FROM user WHERE id=?")
|
||||
|
||||
if _, err := rdb.SQLConn.Exec(stmt.String(), userID); err != nil {
|
||||
// Remove removes an user
|
||||
func (rdb *SQLiteUserDatasource) Remove(userID int) error {
|
||||
if _, err := rdb.SQLConn.Exec("DELETE FROM user WHERE id=?", userID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
"net/http"
|
||||
"os"
|
||||
|
||||
c "git.hoogi.eu/snafu/go-blog/controllers"
|
||||
"git.hoogi.eu/snafu/go-blog/handler"
|
||||
m "git.hoogi.eu/snafu/go-blog/middleware"
|
||||
"git.hoogi.eu/snafu/go-blog/settings"
|
||||
|
||||
|
@ -18,13 +18,13 @@ import (
|
|||
"github.com/justinas/alice"
|
||||
)
|
||||
|
||||
//InitRoutes initializes restricted and public routes
|
||||
// InitRoutes initializes restricted and public routes
|
||||
func InitRoutes(ctx *m.AppContext, cfg *settings.Settings) *mux.Router {
|
||||
router := mux.NewRouter()
|
||||
router = router.StrictSlash(false)
|
||||
sr := router.PathPrefix("/").Subrouter()
|
||||
|
||||
csrf :=
|
||||
rf :=
|
||||
csrf.Protect([]byte(cfg.CSRF.RandomKey),
|
||||
csrf.Secure(cfg.CSRF.CookieSecure),
|
||||
csrf.FieldName(cfg.CSRF.CookieName),
|
||||
|
@ -45,7 +45,7 @@ func InitRoutes(ctx *m.AppContext, cfg *settings.Settings) *mux.Router {
|
|||
|
||||
ar := router.PathPrefix("/admin").Subrouter()
|
||||
|
||||
restrictedChain := chain.Append(csrf).Append(ctx.AuthHandler)
|
||||
restrictedChain := chain.Append(rf).Append(ctx.AuthHandler)
|
||||
|
||||
restrictedRoutes(ctx, ar, restrictedChain)
|
||||
|
||||
|
@ -89,109 +89,109 @@ func fileLoggingHandler(accessLogPath string) (flh func(http.Handler) http.Handl
|
|||
}
|
||||
|
||||
func restrictedRoutes(ctx *m.AppContext, router *mux.Router, chain alice.Chain) {
|
||||
//article
|
||||
router.Handle("/articles", chain.Then(useTemplateHandler(ctx, c.AdminListArticlesHandler))).Methods("GET")
|
||||
router.Handle("/articles/page/{page}", chain.Then(useTemplateHandler(ctx, c.AdminListArticlesHandler))).Methods("GET")
|
||||
router.Handle("/article/new", chain.Then(useTemplateHandler(ctx, c.AdminArticleNewHandler))).Methods("GET")
|
||||
router.Handle("/article/new", chain.Then(useTemplateHandler(ctx, c.AdminArticleNewPostHandler))).Methods("POST")
|
||||
router.Handle("/article/edit/{articleID}", chain.Then(useTemplateHandler(ctx, c.AdminArticleEditHandler))).Methods("GET")
|
||||
router.Handle("/article/edit/{articleID}", chain.Then(useTemplateHandler(ctx, c.AdminArticleEditPostHandler))).Methods("POST")
|
||||
router.Handle("/article/publish/{articleID}", chain.Then(useTemplateHandler(ctx, c.AdminArticlePublishHandler))).Methods("GET")
|
||||
router.Handle("/article/publish/{articleID}", chain.Then(useTemplateHandler(ctx, c.AdminArticlePublishPostHandler))).Methods("POST")
|
||||
router.Handle("/article/delete/{articleID}", chain.Then(useTemplateHandler(ctx, c.AdminArticleDeleteHandler))).Methods("GET")
|
||||
router.Handle("/article/delete/{articleID}", chain.Then(useTemplateHandler(ctx, c.AdminArticleDeletePostHandler))).Methods("POST")
|
||||
router.Handle("/article/{articleID}", chain.Then(useTemplateHandler(ctx, c.AdminPreviewArticleByIDHandler))).Methods("GET")
|
||||
// article
|
||||
router.Handle("/articles", chain.Then(useTemplateHandler(ctx, handler.AdminListArticlesHandler))).Methods("GET")
|
||||
router.Handle("/articles/page/{page}", chain.Then(useTemplateHandler(ctx, handler.AdminListArticlesHandler))).Methods("GET")
|
||||
router.Handle("/article/new", chain.Then(useTemplateHandler(ctx, handler.AdminArticleNewHandler))).Methods("GET")
|
||||
router.Handle("/article/new", chain.Then(useTemplateHandler(ctx, handler.AdminArticleNewPostHandler))).Methods("POST")
|
||||
router.Handle("/article/edit/{articleID}", chain.Then(useTemplateHandler(ctx, handler.AdminArticleEditHandler))).Methods("GET")
|
||||
router.Handle("/article/edit/{articleID}", chain.Then(useTemplateHandler(ctx, handler.AdminArticleEditPostHandler))).Methods("POST")
|
||||
router.Handle("/article/publish/{articleID}", chain.Then(useTemplateHandler(ctx, handler.AdminArticlePublishHandler))).Methods("GET")
|
||||
router.Handle("/article/publish/{articleID}", chain.Then(useTemplateHandler(ctx, handler.AdminArticlePublishPostHandler))).Methods("POST")
|
||||
router.Handle("/article/delete/{articleID}", chain.Then(useTemplateHandler(ctx, handler.AdminArticleDeleteHandler))).Methods("GET")
|
||||
router.Handle("/article/delete/{articleID}", chain.Then(useTemplateHandler(ctx, handler.AdminArticleDeletePostHandler))).Methods("POST")
|
||||
router.Handle("/article/{articleID}", chain.Then(useTemplateHandler(ctx, handler.AdminPreviewArticleByIDHandler))).Methods("GET")
|
||||
|
||||
//user
|
||||
router.Handle("/user/profile", chain.Then(useTemplateHandler(ctx, c.AdminProfileHandler))).Methods("GET")
|
||||
router.Handle("/user/profile", chain.Then(useTemplateHandler(ctx, c.AdminProfilePostHandler))).Methods("POST")
|
||||
router.Handle("/users", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, c.AdminUsersHandler))).Methods("GET")
|
||||
router.Handle("/users/page/{page}", chain.Then(useTemplateHandler(ctx, c.AdminUsersHandler))).Methods("GET")
|
||||
router.Handle("/user/new", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, c.AdminUserNewHandler))).Methods("GET")
|
||||
router.Handle("/user/new", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, c.AdminUserNewPostHandler))).Methods("POST")
|
||||
router.Handle("/user/edit/{userID}", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, c.AdminUserEditHandler))).Methods("GET")
|
||||
router.Handle("/user/edit/{userID}", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, c.AdminUserEditPostHandler))).Methods("POST")
|
||||
router.Handle("/user/delete/{userID}", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, c.AdminUserDeleteHandler))).Methods("GET")
|
||||
router.Handle("/user/delete/{userID}", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, c.AdminUserDeletePostHandler))).Methods("POST")
|
||||
// user
|
||||
router.Handle("/user/profile", chain.Then(useTemplateHandler(ctx, handler.AdminProfileHandler))).Methods("GET")
|
||||
router.Handle("/user/profile", chain.Then(useTemplateHandler(ctx, handler.AdminProfilePostHandler))).Methods("POST")
|
||||
router.Handle("/users", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, handler.AdminUsersHandler))).Methods("GET")
|
||||
router.Handle("/users/page/{page}", chain.Then(useTemplateHandler(ctx, handler.AdminUsersHandler))).Methods("GET")
|
||||
router.Handle("/user/new", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, handler.AdminUserNewHandler))).Methods("GET")
|
||||
router.Handle("/user/new", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, handler.AdminUserNewPostHandler))).Methods("POST")
|
||||
router.Handle("/user/edit/{userID}", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, handler.AdminUserEditHandler))).Methods("GET")
|
||||
router.Handle("/user/edit/{userID}", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, handler.AdminUserEditPostHandler))).Methods("POST")
|
||||
router.Handle("/user/delete/{userID}", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, handler.AdminUserDeleteHandler))).Methods("GET")
|
||||
router.Handle("/user/delete/{userID}", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, handler.AdminUserDeletePostHandler))).Methods("POST")
|
||||
|
||||
//user invites
|
||||
router.Handle("/user-invite/new", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, c.AdminUserInviteNewHandler))).Methods("GET")
|
||||
router.Handle("/user-invite/new", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, c.AdminUserInviteNewPostHandler))).Methods("POST")
|
||||
router.Handle("/user-invite/resend/{inviteID}", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, c.AdminUserInviteResendPostHandler))).Methods("POST")
|
||||
router.Handle("/user-invite/delete/{inviteID}", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, c.AdminUserInviteDeleteHandler))).Methods("GET")
|
||||
router.Handle("/user-invite/delete/{inviteID}", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, c.AdminUserInviteDeletePostHandler))).Methods("POST")
|
||||
// user invites
|
||||
router.Handle("/user-invite/new", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, handler.AdminUserInviteNewHandler))).Methods("GET")
|
||||
router.Handle("/user-invite/new", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, handler.AdminUserInviteNewPostHandler))).Methods("POST")
|
||||
router.Handle("/user-invite/resend/{inviteID}", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, handler.AdminUserInviteResendPostHandler))).Methods("POST")
|
||||
router.Handle("/user-invite/delete/{inviteID}", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, handler.AdminUserInviteDeleteHandler))).Methods("GET")
|
||||
router.Handle("/user-invite/delete/{inviteID}", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, handler.AdminUserInviteDeletePostHandler))).Methods("POST")
|
||||
|
||||
//site
|
||||
router.Handle("/sites", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, c.AdminSitesHandler))).Methods("GET")
|
||||
router.Handle("/site/page/{page}", chain.Then(useTemplateHandler(ctx, c.AdminSitesHandler))).Methods("GET")
|
||||
router.Handle("/site/new", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, c.AdminSiteNewHandler))).Methods("GET")
|
||||
router.Handle("/site/new", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, c.AdminSiteNewPostHandler))).Methods("POST")
|
||||
router.Handle("/site/publish/{siteID}", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, c.AdminSitePublishHandler))).Methods("GET")
|
||||
router.Handle("/site/publish/{siteID}", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, c.AdminSitePublishPostHandler))).Methods("POST")
|
||||
router.Handle("/site/edit/{siteID}", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, c.AdminSiteEditHandler))).Methods("GET")
|
||||
router.Handle("/site/edit/{siteID}", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, c.AdminSiteEditPostHandler))).Methods("POST")
|
||||
router.Handle("/site/delete/{siteID}", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, c.AdminSiteDeleteHandler))).Methods("GET")
|
||||
router.Handle("/site/delete/{siteID}", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, c.AdminSiteDeletePostHandler))).Methods("POST")
|
||||
router.Handle("/site/order/{siteID}", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, c.AdminSiteOrderHandler))).Methods("POST")
|
||||
router.Handle("/site/{siteID:[0-9]+}}", chain.Then(useTemplateHandler(ctx, c.AdminGetSiteHandler))).Methods("GET")
|
||||
// site
|
||||
router.Handle("/sites", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, handler.AdminSitesHandler))).Methods("GET")
|
||||
router.Handle("/site/page/{page}", chain.Then(useTemplateHandler(ctx, handler.AdminSitesHandler))).Methods("GET")
|
||||
router.Handle("/site/new", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, handler.AdminSiteNewHandler))).Methods("GET")
|
||||
router.Handle("/site/new", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, handler.AdminSiteNewPostHandler))).Methods("POST")
|
||||
router.Handle("/site/publish/{siteID}", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, handler.AdminSitePublishHandler))).Methods("GET")
|
||||
router.Handle("/site/publish/{siteID}", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, handler.AdminSitePublishPostHandler))).Methods("POST")
|
||||
router.Handle("/site/edit/{siteID}", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, handler.AdminSiteEditHandler))).Methods("GET")
|
||||
router.Handle("/site/edit/{siteID}", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, handler.AdminSiteEditPostHandler))).Methods("POST")
|
||||
router.Handle("/site/delete/{siteID}", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, handler.AdminSiteDeleteHandler))).Methods("GET")
|
||||
router.Handle("/site/delete/{siteID}", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, handler.AdminSiteDeletePostHandler))).Methods("POST")
|
||||
router.Handle("/site/order/{siteID}", chain.Append(ctx.RequireAdmin).Then(useTemplateHandler(ctx, handler.AdminSiteOrderHandler))).Methods("POST")
|
||||
router.Handle("/site/{siteID:[0-9]+}}", chain.Then(useTemplateHandler(ctx, handler.AdminGetSiteHandler))).Methods("GET")
|
||||
|
||||
//article
|
||||
router.Handle("/categories", chain.Then(useTemplateHandler(ctx, c.AdminListCategoriesHandler))).Methods("GET")
|
||||
router.Handle("/category/{categoryID:[0-9]+}}", chain.Then(useTemplateHandler(ctx, c.AdminGetCategoryHandler))).Methods("POST")
|
||||
router.Handle("/category/new", chain.Then(useTemplateHandler(ctx, c.AdminCategoryNewHandler))).Methods("GET")
|
||||
router.Handle("/category/new", chain.Then(useTemplateHandler(ctx, c.AdminCategoryNewPostHandler))).Methods("POST")
|
||||
router.Handle("/category/edit/{categoryID}", chain.Then(useTemplateHandler(ctx, c.AdminCategoryEditHandler))).Methods("GET")
|
||||
router.Handle("/category/edit/{categoryID}", chain.Then(useTemplateHandler(ctx, c.AdminCategoryEditPostHandler))).Methods("POST")
|
||||
router.Handle("/category/delete/{categoryID}", chain.Then(useTemplateHandler(ctx, c.AdminCategoryDeleteHandler))).Methods("GET")
|
||||
router.Handle("/category/delete/{categoryID}", chain.Then(useTemplateHandler(ctx, c.AdminCategoryDeletePostHandler))).Methods("POST")
|
||||
// article
|
||||
router.Handle("/categories", chain.Then(useTemplateHandler(ctx, handler.AdminListCategoriesHandler))).Methods("GET")
|
||||
router.Handle("/category/{categoryID:[0-9]+}}", chain.Then(useTemplateHandler(ctx, handler.AdminGetCategoryHandler))).Methods("POST")
|
||||
router.Handle("/category/new", chain.Then(useTemplateHandler(ctx, handler.AdminCategoryNewHandler))).Methods("GET")
|
||||
router.Handle("/category/new", chain.Then(useTemplateHandler(ctx, handler.AdminCategoryNewPostHandler))).Methods("POST")
|
||||
router.Handle("/category/edit/{categoryID}", chain.Then(useTemplateHandler(ctx, handler.AdminCategoryEditHandler))).Methods("GET")
|
||||
router.Handle("/category/edit/{categoryID}", chain.Then(useTemplateHandler(ctx, handler.AdminCategoryEditPostHandler))).Methods("POST")
|
||||
router.Handle("/category/delete/{categoryID}", chain.Then(useTemplateHandler(ctx, handler.AdminCategoryDeleteHandler))).Methods("GET")
|
||||
router.Handle("/category/delete/{categoryID}", chain.Then(useTemplateHandler(ctx, handler.AdminCategoryDeletePostHandler))).Methods("POST")
|
||||
|
||||
//file
|
||||
router.Handle("/files", chain.Then(useTemplateHandler(ctx, c.AdminListFilesHandler))).Methods("GET")
|
||||
router.Handle("/files/page/{page}", chain.Then(useTemplateHandler(ctx, c.AdminListFilesHandler))).Methods("GET")
|
||||
router.Handle("/file/upload", chain.Then(useTemplateHandler(ctx, c.AdminUploadFileHandler))).Methods("GET")
|
||||
router.Handle("/file/upload", chain.Then(useTemplateHandler(ctx, c.AdminUploadFilePostHandler))).Methods("POST")
|
||||
router.Handle("/file/toggleInline/{fileID}", chain.Then(useTemplateHandler(ctx, c.AdminToggleInlineFilePostHandler))).Methods("POST")
|
||||
router.Handle("/file/delete/{fileID}", chain.Then(useTemplateHandler(ctx, c.AdminUploadDeleteHandler))).Methods("GET")
|
||||
router.Handle("/file/delete/{fileID}", chain.Then(useTemplateHandler(ctx, c.AdminUploadDeletePostHandler))).Methods("POST")
|
||||
// file
|
||||
router.Handle("/files", chain.Then(useTemplateHandler(ctx, handler.AdminListFilesHandler))).Methods("GET")
|
||||
router.Handle("/files/page/{page}", chain.Then(useTemplateHandler(ctx, handler.AdminListFilesHandler))).Methods("GET")
|
||||
router.Handle("/file/upload", chain.Then(useTemplateHandler(ctx, handler.AdminUploadFileHandler))).Methods("GET")
|
||||
router.Handle("/file/upload", chain.Then(useTemplateHandler(ctx, handler.AdminUploadFilePostHandler))).Methods("POST")
|
||||
router.Handle("/file/toggleInline/{fileID}", chain.Then(useTemplateHandler(ctx, handler.AdminToggleInlineFilePostHandler))).Methods("POST")
|
||||
router.Handle("/file/delete/{fileID}", chain.Then(useTemplateHandler(ctx, handler.AdminUploadDeleteHandler))).Methods("GET")
|
||||
router.Handle("/file/delete/{fileID}", chain.Then(useTemplateHandler(ctx, handler.AdminUploadDeletePostHandler))).Methods("POST")
|
||||
|
||||
router.Handle("/logout", chain.Then(useTemplateHandler(ctx, c.LogoutHandler))).Methods("GET")
|
||||
router.Handle("/logout", chain.Then(useTemplateHandler(ctx, handler.LogoutHandler))).Methods("GET")
|
||||
|
||||
router.Handle("/json/session/keep-alive", chain.Then(useJSONHandler(ctx, c.KeepAliveSessionHandler))).Methods("POST")
|
||||
router.Handle("/json/file/upload", chain.Then(useJSONHandler(ctx, c.AdminUploadJSONFilePostHandler))).Methods("POST")
|
||||
router.Handle("/json/session/keep-alive", chain.Then(useJSONHandler(ctx, handler.KeepAliveSessionHandler))).Methods("POST")
|
||||
router.Handle("/json/file/upload", chain.Then(useJSONHandler(ctx, handler.AdminUploadJSONFilePostHandler))).Methods("POST")
|
||||
}
|
||||
|
||||
func publicRoutes(ctx *m.AppContext, router *mux.Router, chain alice.Chain) {
|
||||
fh := c.FileHandler{
|
||||
fh := handler.FileHandler{
|
||||
Context: ctx,
|
||||
}
|
||||
|
||||
router.Handle("/", chain.Then(useTemplateHandler(ctx, c.ListArticlesHandler))).Methods("GET")
|
||||
router.Handle("/articles/category/{categorySlug}", chain.Then(useTemplateHandler(ctx, c.ListArticlesCategoryHandler))).Methods("GET")
|
||||
router.Handle("/articles/category/{categorySlug}/{page}", chain.Then(useTemplateHandler(ctx, c.ListArticlesCategoryHandler))).Methods("GET")
|
||||
router.Handle("/index", chain.Then(useTemplateHandler(ctx, c.IndexArticlesHandler))).Methods("GET")
|
||||
router.Handle("/index/category/{categorySlug}", chain.Then(useTemplateHandler(ctx, c.IndexArticlesCategoryHandler))).Methods("GET")
|
||||
router.Handle("/", chain.Then(useTemplateHandler(ctx, handler.ListArticlesHandler))).Methods("GET")
|
||||
router.Handle("/articles/category/{categorySlug}", chain.Then(useTemplateHandler(ctx, handler.ListArticlesCategoryHandler))).Methods("GET")
|
||||
router.Handle("/articles/category/{categorySlug}/{page}", chain.Then(useTemplateHandler(ctx, handler.ListArticlesCategoryHandler))).Methods("GET")
|
||||
router.Handle("/index", chain.Then(useTemplateHandler(ctx, handler.IndexArticlesHandler))).Methods("GET")
|
||||
router.Handle("/index/category/{categorySlug}", chain.Then(useTemplateHandler(ctx, handler.IndexArticlesCategoryHandler))).Methods("GET")
|
||||
|
||||
router.Handle("/articles/page/{page}", chain.Then(useTemplateHandler(ctx, c.ListArticlesHandler))).Methods("GET")
|
||||
router.Handle("/article/{year}/{month}/{slug}", chain.Then(useTemplateHandler(ctx, c.GetArticleHandler))).Methods("GET")
|
||||
router.Handle("/article/by-id/{articleID}", chain.Then(useTemplateHandler(ctx, c.GetArticleByIDHandler))).Methods("GET")
|
||||
router.Handle("/articles/page/{page}", chain.Then(useTemplateHandler(ctx, handler.ListArticlesHandler))).Methods("GET")
|
||||
router.Handle("/article/{year}/{month}/{slug}", chain.Then(useTemplateHandler(ctx, handler.GetArticleHandler))).Methods("GET")
|
||||
router.Handle("/article/by-id/{articleID}", chain.Then(useTemplateHandler(ctx, handler.GetArticleByIDHandler))).Methods("GET")
|
||||
|
||||
router.Handle("/rss.xml", chain.Then(useXMLHandler(ctx, c.RSSFeed))).Methods("GET")
|
||||
router.Handle("/rss.xml", chain.Then(useXMLHandler(ctx, handler.RSSFeed))).Methods("GET")
|
||||
|
||||
router.Handle("/site/{site}", chain.Then(useTemplateHandler(ctx, c.GetSiteHandler))).Methods("GET")
|
||||
router.Handle("/site/{site}", chain.Then(useTemplateHandler(ctx, handler.GetSiteHandler))).Methods("GET")
|
||||
|
||||
router.Handle("/file/{uniquename}", chain.ThenFunc(fh.FileGetHandler)).Methods("GET")
|
||||
|
||||
router.Handle("/admin", chain.Then(useTemplateHandler(ctx, c.LoginHandler))).Methods("GET")
|
||||
router.Handle("/admin", chain.Then(useTemplateHandler(ctx, c.LoginPostHandler))).Methods("POST")
|
||||
router.Handle("/admin", chain.Then(useTemplateHandler(ctx, handler.LoginHandler))).Methods("GET")
|
||||
router.Handle("/admin", chain.Then(useTemplateHandler(ctx, handler.LoginPostHandler))).Methods("POST")
|
||||
|
||||
router.Handle("/admin/forgot-password", chain.Then(useTemplateHandler(ctx, c.ForgotPasswordHandler))).Methods("GET")
|
||||
router.Handle("/admin/forgot-password", chain.Then(useTemplateHandler(ctx, c.ForgotPasswordPostHandler))).Methods("POST")
|
||||
router.Handle("/admin/forgot-password", chain.Then(useTemplateHandler(ctx, handler.ForgotPasswordHandler))).Methods("GET")
|
||||
router.Handle("/admin/forgot-password", chain.Then(useTemplateHandler(ctx, handler.ForgotPasswordPostHandler))).Methods("POST")
|
||||
|
||||
router.Handle("/admin/reset-password/{hash}", chain.Then(useTemplateHandler(ctx, c.ResetPasswordHandler))).Methods("GET")
|
||||
router.Handle("/admin/reset-password/{hash}", chain.Then(useTemplateHandler(ctx, c.ResetPasswordPostHandler))).Methods("POST")
|
||||
router.Handle("/admin/reset-password/{hash}", chain.Then(useTemplateHandler(ctx, handler.ResetPasswordHandler))).Methods("GET")
|
||||
router.Handle("/admin/reset-password/{hash}", chain.Then(useTemplateHandler(ctx, handler.ResetPasswordPostHandler))).Methods("POST")
|
||||
|
||||
router.Handle("/admin/activate-account/{hash}", chain.Then(useTemplateHandler(ctx, c.ActivateAccountHandler))).Methods("GET")
|
||||
router.Handle("/admin/activate-account/{hash}", chain.Then(useTemplateHandler(ctx, c.ActivateAccountPostHandler))).Methods("POST")
|
||||
router.Handle("/admin/activate-account/{hash}", chain.Then(useTemplateHandler(ctx, handler.ActivateAccountHandler))).Methods("GET")
|
||||
router.Handle("/admin/activate-account/{hash}", chain.Then(useTemplateHandler(ctx, handler.ActivateAccountPostHandler))).Methods("POST")
|
||||
}
|
||||
|
||||
func useTemplateHandler(ctx *m.AppContext, handler m.Handler) m.TemplateHandler {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//Package cfg parses and validates the configuration
|
||||
// Package settings parses and validates the configuration
|
||||
package settings
|
||||
|
||||
import (
|
||||
|
@ -15,8 +15,8 @@ import (
|
|||
"time"
|
||||
|
||||
"git.hoogi.eu/snafu/cfg"
|
||||
"git.hoogi.eu/snafu/go-blog/components/logger"
|
||||
"git.hoogi.eu/snafu/go-blog/utils"
|
||||
"git.hoogi.eu/snafu/go-blog/crypt"
|
||||
"git.hoogi.eu/snafu/go-blog/logger"
|
||||
)
|
||||
|
||||
type LoginMethod int
|
||||
|
@ -55,7 +55,7 @@ func (afe *AllowedFileExts) Unmarshal(value string) error {
|
|||
|
||||
kv[trimmed] = trimmed
|
||||
|
||||
*afe = AllowedFileExts(kv)
|
||||
*afe = kv
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -183,7 +183,7 @@ func LoadConfig(filename string) (*Settings, error) {
|
|||
}
|
||||
|
||||
func (cfg *Settings) CheckConfig() error {
|
||||
//check log file is rw in production mode
|
||||
// check log file is rw in production mode
|
||||
if cfg.Environment != "dev" {
|
||||
if _, err := os.OpenFile(cfg.Log.File, os.O_RDONLY|os.O_CREATE, 0640); err != nil {
|
||||
return fmt.Errorf("config 'log_file': could not open log file %s error %v", cfg.Log.File, err)
|
||||
|
@ -258,7 +258,7 @@ func (cfg *Settings) CheckConfig() error {
|
|||
cfg.Application.Favicon = "assets/favicon.ico"
|
||||
}
|
||||
|
||||
//server settings
|
||||
// server settings
|
||||
if cfg.Server.UseTLS {
|
||||
if _, err := os.Open(cfg.Server.Cert); err != nil {
|
||||
return fmt.Errorf("config: could not open certificate %s error %v", cfg.Server.Cert, err)
|
||||
|
@ -285,16 +285,10 @@ func (cfg *Settings) GenerateCSRF() (bool, error) {
|
|||
var b []byte
|
||||
|
||||
if _, err := os.Stat(csrfTokenFilename); os.IsNotExist(err) {
|
||||
//create a random csrf token
|
||||
r := utils.RandomSource{
|
||||
CharsToGen: utils.AlphaUpperLowerNumericSpecial,
|
||||
}
|
||||
// create a random csrf token
|
||||
b = crypt.AlphaUpperLowerNumericSpecial.RandomSequence(32)
|
||||
|
||||
b = r.RandomSequence(32)
|
||||
|
||||
err := ioutil.WriteFile(csrfTokenFilename, b, 0640)
|
||||
|
||||
if err != nil {
|
||||
if err := ioutil.WriteFile(csrfTokenFilename, b, 0640); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
|
@ -302,7 +296,7 @@ func (cfg *Settings) GenerateCSRF() (bool, error) {
|
|||
|
||||
return true, nil
|
||||
} else {
|
||||
//read existing csrf token
|
||||
// read existing csrf token
|
||||
b, err = ioutil.ReadFile(csrfTokenFilename)
|
||||
|
||||
if err != nil {
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
package slug
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
var multipleDashes = regexp.MustCompile(`[-]{2,}`)
|
||||
|
||||
// CreateURLSafeSlug creates a URL safe slug to use in URL
|
||||
func CreateURLSafeSlug(input string, suffix int) string {
|
||||
input = strings.Replace(input, "-", "", -1)
|
||||
|
||||
input = strings.Map(func(r rune) rune {
|
||||
switch {
|
||||
case r == ' ':
|
||||
return '-'
|
||||
case unicode.IsLetter(r), unicode.IsDigit(r):
|
||||
return r
|
||||
default:
|
||||
return -1
|
||||
}
|
||||
}, strings.ToLower(strings.TrimSpace(input)))
|
||||
|
||||
input = strings.Trim(input, "-")
|
||||
|
||||
input = multipleDashes.ReplaceAllString(input, "-")
|
||||
|
||||
if suffix > 0 {
|
||||
input += strconv.Itoa(suffix)
|
||||
}
|
||||
|
||||
return input
|
||||
}
|
|
@ -2,12 +2,12 @@
|
|||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package utils_test
|
||||
package slug_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.hoogi.eu/snafu/go-blog/utils"
|
||||
"git.hoogi.eu/snafu/go-blog/slug"
|
||||
)
|
||||
|
||||
func TestCreateURLSafeSlug(t *testing.T) {
|
||||
|
@ -26,7 +26,7 @@ func TestCreateURLSafeSlug(t *testing.T) {
|
|||
}
|
||||
|
||||
for i := 0; i < len(testcases)-1; i = i + 2 {
|
||||
actual := utils.CreateURLSafeSlug(testcases[i], 0)
|
||||
actual := slug.CreateURLSafeSlug(testcases[i], 0)
|
||||
|
||||
if actual != testcases[i+1] {
|
||||
t.Errorf("Got: '%s'; want '%s'", actual, testcases[i+1])
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
<form id="autosave-form" action="/admin/article/new" method="post">
|
||||
<label for="category">Category</label>
|
||||
<select name="categoryID">
|
||||
<select id="category" name="categoryID">
|
||||
<option></option>
|
||||
{{range .categories}}
|
||||
<option value="{{.ID}}">{{.Name}}</option>
|
||||
|
|
|
@ -10,7 +10,8 @@
|
|||
|
||||
{{with .article}}
|
||||
<form id="autosave-form" action="/admin/article/edit/{{.ID}}" method="post">
|
||||
<select name="categoryID">
|
||||
<label for="category">Category</label>
|
||||
<select id="category" name="categoryID">
|
||||
<option></option>
|
||||
{{range $.categories}}
|
||||
<option{{if $.article.CID.Valid}}{{if eq $.article.CID.Int64 .ID}} selected="selected"{{end}}{{end}} value="{{.ID}}">{{.Name}}</option>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
</footer>
|
||||
|
||||
<script type="text/javascript">
|
||||
var directUpload = document.getElementById('direct-upload');
|
||||
let directUpload = document.getElementById('direct-upload');
|
||||
|
||||
directUpload && directUpload.addEventListener("submit", function(e) {
|
||||
e.preventDefault();
|
||||
|
@ -41,7 +41,7 @@
|
|||
|
||||
table.style.display = "table";
|
||||
|
||||
let tableBody = table.tBodies[0]
|
||||
let tableBody = table.tBodies[0];
|
||||
|
||||
let row = tableBody.insertRow(-1);
|
||||
|
||||
|
@ -70,11 +70,11 @@
|
|||
});
|
||||
});
|
||||
|
||||
var persistForm = function() {
|
||||
let persistForm = function() {
|
||||
let form = document.getElementById("autosave-form");
|
||||
|
||||
if(form === undefined) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
let data = {};
|
||||
|
@ -99,7 +99,7 @@
|
|||
localStorage.setItem('AUTOSAVE'+action.split('/').join('_'), JSON.stringify(data));
|
||||
}
|
||||
|
||||
var autoSaveForm = document.getElementById('autosave-form');
|
||||
let autoSaveForm = document.getElementById('autosave-form');
|
||||
|
||||
autoSaveForm && autoSaveForm.addEventListener("submit", function(e) {
|
||||
let form = document.getElementById("autosave-form");
|
||||
|
@ -107,11 +107,11 @@
|
|||
localStorage.removeItem('AUTOSAVE'+action.split('/').join('_'));
|
||||
});
|
||||
|
||||
var loadForm = function() {
|
||||
let loadForm = function() {
|
||||
let form = document.getElementById("autosave-form");
|
||||
|
||||
if(form === undefined) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
let action = form.getAttribute("action");
|
||||
|
@ -119,23 +119,25 @@
|
|||
let json = localStorage.getItem('AUTOSAVE'+action.split('/').join('_'));
|
||||
let obj = JSON.parse(json);
|
||||
|
||||
if (typeof obj === 'object' && obj !== null) {
|
||||
if (Object.keys(obj).length > 0) {
|
||||
for (let key in obj) {
|
||||
if(key != "action") {
|
||||
form.elements[key].value = obj[key]
|
||||
}
|
||||
}
|
||||
if (typeof obj !== 'function' && (typeof obj !== 'object' || obj === null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let div = document.createElement('div');
|
||||
let main = document.querySelector('main');
|
||||
div.innerHTML = '<div style="margin-top: 10px" class="alert alert-info" role="status">Unsaved changes were loaded.</div>';
|
||||
main.prepend(div);
|
||||
}
|
||||
}
|
||||
if (Object.keys(obj).length > 0) {
|
||||
for (let key in obj) {
|
||||
if(key != "action") {
|
||||
form.elements[key].value = obj[key];
|
||||
}
|
||||
}
|
||||
|
||||
let div = document.createElement('div');
|
||||
let main = document.querySelector('main');
|
||||
div.innerHTML = '<div style="margin-top: 10px" class="alert alert-info" role="status">Unsaved changes were loaded. <button>Reset</button></div>';
|
||||
main.prepend(div);
|
||||
}
|
||||
}
|
||||
|
||||
var doKeepAliveRequest = function() {
|
||||
let doKeepAliveRequest = function() {
|
||||
fetch('/admin/json/session/keep-alive',
|
||||
{
|
||||
method: 'POST',
|
||||
|
@ -144,8 +146,8 @@
|
|||
},
|
||||
})
|
||||
};
|
||||
|
||||
var toggleContainer = function() {
|
||||
|
||||
let toggleContainer = function() {
|
||||
let ahref = document.getElementById('toggleContainer');
|
||||
let val = ahref.text
|
||||
|
||||
|
@ -164,8 +166,8 @@
|
|||
};
|
||||
|
||||
let curPath = window.location.pathname;
|
||||
let autoSaveInterval=5*1000;
|
||||
let keepAliveInterval={{KeepAliveInterval}}*1000;
|
||||
let autoSaveInterval = 5*1000;
|
||||
let keepAliveInterval = {{KeepAliveInterval}}*1000;
|
||||
|
||||
if (curPath === "/admin/article/new" || curPath.includes("/admin/article/edit")
|
||||
|| curPath === "/admin/site/new" || curPath.includes("/admin/site/edit")) {
|
||||
|
@ -175,7 +177,6 @@
|
|||
setInterval(persistForm, autoSaveInterval);
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
|
|
|
@ -1,92 +0,0 @@
|
|||
// Copyright 2018 Lars Hoogestraat
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha512"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
const (
|
||||
//AlphaUpper all upper alphas chars
|
||||
AlphaUpper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
//AlphaLower all lowers alphas chars
|
||||
AlphaLower = "abcdefghijklmnopqrstuvwxyz"
|
||||
//AlphaUpperLower all upper and lowers aplhas chars
|
||||
AlphaUpperLower = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||
//AlphaUpperLowerNumeric all upper lowers alphas and numerics
|
||||
AlphaUpperLowerNumeric = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyz"
|
||||
//AlphaUpperLowerNumericSpecial all upper lowers alphas, numerics and special chas
|
||||
AlphaUpperLowerNumericSpecial = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz123456890" +
|
||||
"!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
|
||||
)
|
||||
|
||||
//RandomSource express which chars should be considered
|
||||
type RandomSource struct {
|
||||
CharsToGen string
|
||||
}
|
||||
|
||||
//RandomSequence returns random character with given length;
|
||||
//random source express which chars should be considered
|
||||
func (r RandomSource) RandomSequence(length int) []byte {
|
||||
result := make([]byte, length)
|
||||
for i := 0; i < length; i++ {
|
||||
char, _ := rand.Int(rand.Reader, big.NewInt(int64(len(r.CharsToGen))))
|
||||
result[i] = r.CharsToGen[int(char.Int64())]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
//RandomSecureKey returns random character with given length
|
||||
func RandomSecureKey(length int) []byte {
|
||||
k := make([]byte, length)
|
||||
if _, err := io.ReadFull(rand.Reader, k); err != nil {
|
||||
return nil
|
||||
}
|
||||
return k
|
||||
}
|
||||
|
||||
//CryptPassword bcrypts a password at given costs
|
||||
func CryptPassword(password []byte, cost int) ([]byte, error) {
|
||||
s, err := bcrypt.GenerateFromPassword(password, cost)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
//GenerateSalt generates a random salt with alphanumerics and some special characters
|
||||
func GenerateSalt() []byte {
|
||||
r := RandomSource{
|
||||
CharsToGen: AlphaUpperLowerNumericSpecial,
|
||||
}
|
||||
return r.RandomSequence(32)
|
||||
}
|
||||
|
||||
//EncodeBase64 encodes a string to base64
|
||||
func EncodeBase64(input string) string {
|
||||
return base64.StdEncoding.EncodeToString([]byte(input))
|
||||
}
|
||||
|
||||
//DecodeBase64 descodes a string to base64
|
||||
func DecodeBase64(b64 string) (string, error) {
|
||||
out, err := base64.StdEncoding.DecodeString(b64)
|
||||
return string(out), err
|
||||
}
|
||||
|
||||
func RandomHash(length int) string {
|
||||
hash := sha512.New()
|
||||
hash.Write(RandomSecureKey(length))
|
||||
|
||||
return hex.EncodeToString(hash.Sum(nil))
|
||||
}
|
136
utils/strings.go
136
utils/strings.go
|
@ -1,136 +0,0 @@
|
|||
// Copyright 2018 Lars Hoogestraat
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
//AppendString uses byte buffer to append multiple strings
|
||||
func AppendString(s ...string) string {
|
||||
var buffer bytes.Buffer
|
||||
for _, value := range s {
|
||||
buffer.WriteString(value)
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
//AppendBytes uses byte buffer to append multiple byte arrays
|
||||
func AppendBytes(s ...[]byte) []byte {
|
||||
var buffer bytes.Buffer
|
||||
for _, value := range s {
|
||||
buffer.Write(value)
|
||||
}
|
||||
return buffer.Bytes()
|
||||
}
|
||||
|
||||
//TrimmedStringIsEmpty trims spaces returns true if length is 0
|
||||
func TrimmedStringIsEmpty(s string) bool {
|
||||
return len(strings.TrimSpace(s)) == 0
|
||||
}
|
||||
|
||||
//IsOneOfStringsEmpty checks if one of the given strings is empty
|
||||
func IsOneOfStringsEmpty(s ...string) bool {
|
||||
for _, value := range s {
|
||||
if len(value) == 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
var filenameSubs = map[rune]string{
|
||||
'/': "",
|
||||
'\\': "",
|
||||
':': "",
|
||||
'*': "",
|
||||
'?': "",
|
||||
'"': "",
|
||||
'<': "",
|
||||
'>': "",
|
||||
'|': "",
|
||||
' ': "",
|
||||
}
|
||||
|
||||
func isDot(r rune) bool {
|
||||
return '.' == r
|
||||
}
|
||||
|
||||
//SanitizeFilename sanitizes a filename for safe use when serving file
|
||||
func SanitizeFilename(s string) string {
|
||||
s = strings.TrimFunc(s, unicode.IsSpace)
|
||||
s = removeControlCharacters(s)
|
||||
s = substitute(s, filenameSubs)
|
||||
s = strings.TrimLeftFunc(s, isDot)
|
||||
return s
|
||||
}
|
||||
|
||||
var slugSubs = map[rune]string{
|
||||
'&': "",
|
||||
'$': "",
|
||||
'+': "",
|
||||
',': "",
|
||||
'/': "",
|
||||
':': "",
|
||||
';': "",
|
||||
'=': "",
|
||||
'?': "",
|
||||
'@': "",
|
||||
'#': "",
|
||||
'!': "",
|
||||
'\'': "",
|
||||
'(': "",
|
||||
')': "",
|
||||
'*': "",
|
||||
'%': "",
|
||||
}
|
||||
|
||||
var multipleDashes = regexp.MustCompile(`[-]{2,}`)
|
||||
|
||||
//CreateURLSafeSlug creates a url safe slug to use in urls
|
||||
func CreateURLSafeSlug(input string, suffix int) string {
|
||||
input = removeControlCharacters(input)
|
||||
input = substitute(input, slugSubs)
|
||||
input = strings.TrimSpace(input)
|
||||
|
||||
input = strings.Replace(input, " ", "-", -1)
|
||||
|
||||
input = strings.ToLower(input)
|
||||
|
||||
input = multipleDashes.ReplaceAllString(input, "-")
|
||||
|
||||
if suffix > 0 {
|
||||
input += strconv.Itoa(suffix)
|
||||
}
|
||||
|
||||
return input
|
||||
}
|
||||
|
||||
func substitute(input string, subs map[rune]string) string {
|
||||
var b bytes.Buffer
|
||||
|
||||
for _, c := range input {
|
||||
if _, ok := subs[c]; ok {
|
||||
b.WriteString(subs[c])
|
||||
} else {
|
||||
b.WriteRune(c)
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func removeControlCharacters(input string) string {
|
||||
var b bytes.Buffer
|
||||
for _, c := range input {
|
||||
if c > 31 {
|
||||
b.WriteRune(c)
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
Loading…
Reference in New Issue