minor clean-up
This commit is contained in:
parent
82a94386fd
commit
4408371856
|
@ -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 ###
|
||||
|
|
|
@ -9,7 +9,6 @@ import (
|
|||
"crypto/rand"
|
||||
"crypto/sha512"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
|
@ -38,20 +37,23 @@ 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())]
|
||||
}
|
||||
fmt.Println(result)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -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 ###########
|
||||
|
|
13
go.mod
13
go.mod
|
@ -1,16 +1,15 @@
|
|||
module git.hoogi.eu/snafu/go-blog
|
||||
|
||||
go 1.15
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
git.hoogi.eu/snafu/cfg v1.0.6
|
||||
git.hoogi.eu/snafu/session v1.3.0
|
||||
github.com/felixge/httpsnoop v1.0.2 // indirect
|
||||
github.com/gorilla/csrf v1.7.0
|
||||
github.com/gorilla/csrf v1.7.1
|
||||
github.com/gorilla/handlers v1.5.1
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/justinas/alice v1.2.0
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.8
|
||||
github.com/microcosm-cc/bluemonday v1.0.15
|
||||
github.com/russross/blackfriday/v2 v2.1.0
|
||||
|
@ -20,6 +19,12 @@ require (
|
|||
golang.org/x/net v0.0.0-20210716203947-853a461950ff // indirect
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/gorilla/css v1.0.0 // indirect
|
||||
github.com/gorilla/securecookie v1.1.1 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
)
|
||||
|
|
11
go.sum
11
go.sum
|
@ -5,7 +5,6 @@ git.hoogi.eu/snafu/session v1.3.0/go.mod h1:kgRDrnHcKc9H18G9533BXy6qO+81eBf6e9gk
|
|||
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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
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=
|
||||
|
@ -14,6 +13,8 @@ github.com/felixge/httpsnoop v1.0.2 h1:+nS9g82KMXccJ/wp0zyRW9ZBHFETmMGtkk+2CTTrW
|
|||
github.com/felixge/httpsnoop v1.0.2/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/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
||||
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
||||
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
|
||||
|
@ -24,12 +25,6 @@ github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyC
|
|||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
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/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
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/microcosm-cc/bluemonday v1.0.15 h1:J4uN+qPng9rvkBZBoBb8YGR+ijuklIMpSOZZLjYpbeY=
|
||||
|
@ -68,8 +63,6 @@ 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/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
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=
|
||||
|
|
5
main.go
5
main.go
|
@ -130,12 +130,11 @@ func main() {
|
|||
MaxHeaderBytes: 1 << 20,
|
||||
}
|
||||
|
||||
logger.Log.Infof("server will start at %s on port %d", config.Server.Address, config.Server.Port)
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ type JSONHandler struct {
|
|||
type JHandler func(*AppContext, http.ResponseWriter, *http.Request) (*models.JSONData, error)
|
||||
|
||||
func (fn JSONHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
logWithIP := logger.Log.WithField("ip", getIP(r))
|
||||
code := http.StatusOK
|
||||
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
|
@ -35,15 +36,14 @@ func (fn JSONHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
|||
code = e.HTTPStatus
|
||||
default:
|
||||
code = http.StatusInternalServerError
|
||||
logger.Log.Error(e)
|
||||
}
|
||||
|
||||
logger.Log.Error(err)
|
||||
logWithIP.Error(err)
|
||||
|
||||
j, err := json.Marshal(err)
|
||||
|
||||
if err != nil {
|
||||
logger.Log.Error(err)
|
||||
logWithIP.Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ func (fn JSONHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
|||
_, err = rw.Write(j)
|
||||
|
||||
if err != nil {
|
||||
logger.Log.Error(err)
|
||||
logWithIP.Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
@ -63,6 +63,7 @@ func (fn JSONHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
|||
j, err := json.Marshal(data)
|
||||
|
||||
if err != nil {
|
||||
logWithIP.Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
|
@ -73,6 +74,7 @@ func (fn JSONHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
|||
_, err = rw.Write(j)
|
||||
|
||||
if err != nil {
|
||||
logWithIP.Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
|
|
|
@ -23,9 +23,13 @@ import (
|
|||
)
|
||||
|
||||
// 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
|
||||
|
@ -36,7 +40,8 @@ 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
|
||||
|
|
|
@ -119,17 +119,19 @@ func (fn TemplateHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
|||
// 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)
|
||||
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)
|
||||
logWithIP.Errorf("error while executing the template %v", err)
|
||||
return
|
||||
}
|
||||
return
|
||||
|
@ -138,7 +140,7 @@ func (ctx AppContext) AuthHandler(handler http.Handler) http.Handler {
|
|||
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)
|
||||
if err := ctx.Templates.ExecuteTemplate(rw, "admin/login", map[string]interface{}{
|
||||
|
@ -146,7 +148,7 @@ func (ctx AppContext) AuthHandler(handler http.Handler) http.Handler {
|
|||
"state": r.URL.EscapedPath(),
|
||||
csrf.TemplateTag: csrf.TemplateField(r),
|
||||
}); err != nil {
|
||||
logger.Log.Errorf("error while executing the template %v", err)
|
||||
logWithIP.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -156,7 +158,7 @@ func (ctx AppContext) AuthHandler(handler http.Handler) http.Handler {
|
|||
u, err := ctx.UserService.GetByID(userid)
|
||||
|
||||
if err != nil {
|
||||
logger.Log.Error(err)
|
||||
logWithIP.Error(err)
|
||||
rw.WriteHeader(http.StatusUnauthorized)
|
||||
if err := ctx.Templates.ExecuteTemplate(rw, "admin/login", map[string]interface{}{
|
||||
"ErrorMsg": "Please provide login credentials.",
|
||||
|
@ -174,17 +176,19 @@ func (ctx AppContext) AuthHandler(handler http.Handler) http.Handler {
|
|||
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)
|
||||
logWithIP.Error(err)
|
||||
if err := ctx.Templates.ExecuteTemplate(rw, "admin/error", map[string]interface{}{
|
||||
"ErrorMsg": "An internal server error occurred",
|
||||
}); err != nil {
|
||||
logger.Log.Errorf("error while executing the template %v", err)
|
||||
logWithIP.Errorf("error while executing the template %v", err)
|
||||
return
|
||||
}
|
||||
return
|
||||
|
@ -195,7 +199,7 @@ func (ctx AppContext) RequireAdmin(handler http.Handler) http.Handler {
|
|||
"ErrorMsg": "You have not the permissions to execute this action",
|
||||
"currentUser": u,
|
||||
}); err != nil {
|
||||
logger.Log.Errorf("error while executing the template %v", err)
|
||||
logWithIP.Errorf("error while executing the template %v", err)
|
||||
return
|
||||
}
|
||||
return
|
||||
|
@ -203,6 +207,7 @@ func (ctx AppContext) RequireAdmin(handler http.Handler) http.Handler {
|
|||
|
||||
handler.ServeHTTP(rw, r)
|
||||
}
|
||||
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
|
||||
|
|
|
@ -19,23 +19,25 @@ type XMLHandler struct {
|
|||
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, err := xml.Marshal(err)
|
||||
|
||||
if err != nil {
|
||||
logger.Log.Error(err)
|
||||
logWithIP.Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = rw.Write(x); err != nil {
|
||||
logger.Log.Error(err)
|
||||
logWithIP.Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
@ -46,6 +48,7 @@ func (fn XMLHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
|||
x, err2 := xml.MarshalIndent(h.Data, "", "\t")
|
||||
|
||||
if err2 != nil {
|
||||
logWithIP.Error(err)
|
||||
http.Error(rw, err2.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
|
||||
|
|
|
@ -145,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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,8 +73,8 @@ 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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -82,6 +83,7 @@ func (cs CategoryService) GetByID(id int, fc FilterCriteria) (*Category, error)
|
|||
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
|
||||
}
|
||||
|
||||
|
@ -100,9 +102,7 @@ func (cs CategoryService) List(fc FilterCriteria) ([]Category, error) {
|
|||
func (cs CategoryService) Create(c *Category) (int, error) {
|
||||
for i := 0; i < 10; i++ {
|
||||
c.Slug = slug.CreateURLSafeSlug(c.Name, i)
|
||||
_, err := cs.Datasource.GetBySlug(c.Slug, AllCategories)
|
||||
|
||||
if err != nil {
|
||||
if _, err := cs.Datasource.GetBySlug(c.Slug, AllCategories); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
break
|
||||
}
|
||||
|
@ -114,12 +114,7 @@ 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
|
||||
|
|
|
@ -173,14 +173,8 @@ func (rdb SQLiteCategoryDatasource) GetBySlug(slug string, fc FilterCriteria) (*
|
|||
}
|
||||
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -191,5 +185,6 @@ func (rdb SQLiteCategoryDatasource) Delete(categoryID int) error {
|
|||
if _, err := rdb.SQLConn.Exec("DELETE FROM category WHERE id=?", categoryID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -67,12 +67,16 @@ func (f *File) validate() error {
|
|||
|
||||
func (f File) randomFilename() string {
|
||||
var buf bytes.Buffer
|
||||
|
||||
sanFilename := sanitizeFilename(f.FileInfo.Name)
|
||||
|
||||
if len(sanFilename) == 0 {
|
||||
sanFilename = "unnamed"
|
||||
}
|
||||
|
||||
buf.WriteString(sanFilename)
|
||||
buf.WriteString(f.FileInfo.Extension)
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
|
@ -124,8 +128,8 @@ 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
|
||||
// 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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -101,7 +101,8 @@ func (rdb SQLiteFileDatasource) Create(f *File) (int, error) {
|
|||
}
|
||||
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -142,9 +143,7 @@ func (rdb SQLiteFileDatasource) List(u *User, p *Pagination) ([]File, error) {
|
|||
}
|
||||
|
||||
defer func() {
|
||||
err := rows.Close()
|
||||
|
||||
if err != nil {
|
||||
if err := rows.Close(); err != nil {
|
||||
logger.Log.Error(err)
|
||||
}
|
||||
}()
|
||||
|
|
|
@ -132,13 +132,7 @@ func (ss SiteService) Publish(siteID int) error {
|
|||
return err
|
||||
}
|
||||
|
||||
err = ss.Datasource.Publish(s)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return ss.Datasource.Publish(s)
|
||||
}
|
||||
|
||||
// Create creates a site
|
||||
|
|
|
@ -120,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
|
||||
}
|
||||
|
||||
|
@ -145,7 +146,8 @@ func (rdb SQLiteSiteDatasource) Publish(s *Site) error {
|
|||
|
||||
// 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(?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
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 {
|
||||
|
@ -212,18 +214,13 @@ func (rdb SQLiteSiteDatasource) Order(id int, d Direction) error {
|
|||
}
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -293,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
|
||||
}
|
||||
|
||||
|
|
|
@ -109,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
|
||||
}
|
||||
}
|
||||
|
@ -145,11 +141,13 @@ func (us UserService) duplicateMail(mail string) error {
|
|||
|
||||
func (us UserService) duplicateUsername(username string) error {
|
||||
user, err := us.Datasource.GetByUsername(username)
|
||||
|
||||
if err != nil {
|
||||
if !errors.Is(err, sql.ErrNoRows) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if user != nil {
|
||||
return httperror.New(http.StatusUnprocessableEntity,
|
||||
fmt.Sprintf("The username %s already exists.", username),
|
||||
|
@ -186,6 +184,7 @@ func (us UserService) GetByID(userID int) (*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 errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, httperror.NotFound("user", err)
|
||||
|
@ -353,9 +352,9 @@ func (us UserService) Authenticate(u *User, loginMethod settings.LoginMethod) (*
|
|||
}
|
||||
|
||||
if err != nil {
|
||||
//Do some extra work
|
||||
bcrypt.CompareHashAndPassword([]byte("$2a$12$bQlRnXTNZMp6kCyoAlnf3uZW5vtmSj9CHP7pYplRUVK2n0C5xBHBa"), password)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
//Do some extra work
|
||||
bcrypt.CompareHashAndPassword([]byte("$2a$12$bQlRnXTNZMp6kCyoAlnf3uZW5vtmSj9CHP7pYplRUVK2n0C5xBHBa"), password)
|
||||
return nil, httperror.New(http.StatusUnauthorized, "Your username or password is invalid.", err)
|
||||
}
|
||||
return nil, err
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"git.hoogi.eu/snafu/go-blog/mail"
|
||||
)
|
||||
|
||||
// TODO: refactor
|
||||
// UserInvite represents a new invited user
|
||||
type UserInvite struct {
|
||||
ID int
|
||||
|
@ -50,13 +51,7 @@ type UserInviteService struct {
|
|||
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) {
|
||||
|
|
|
@ -12,7 +12,7 @@ type SQLiteUserDatasource struct {
|
|||
SQLConn *sql.DB
|
||||
}
|
||||
|
||||
// List returns a list of user
|
||||
// List returns a list of users
|
||||
func (rdb SQLiteUserDatasource) List(p *Pagination) ([]User, error) {
|
||||
var stmt strings.Builder
|
||||
var args []interface{}
|
||||
|
@ -53,7 +53,7 @@ func (rdb SQLiteUserDatasource) List(p *Pagination) ([]User, error) {
|
|||
return users, nil
|
||||
}
|
||||
|
||||
// Get gets an user by his userID
|
||||
// Get gets a user by his userID
|
||||
func (rdb SQLiteUserDatasource) Get(userID int) (*User, error) {
|
||||
var u User
|
||||
|
||||
|
@ -67,7 +67,7 @@ func (rdb SQLiteUserDatasource) Get(userID int) (*User, error) {
|
|||
return &u, nil
|
||||
}
|
||||
|
||||
// GetByMail gets an user by his mail, includes the password and salt
|
||||
// GetByMail gets a user by his mail, includes the password and salt
|
||||
func (rdb SQLiteUserDatasource) GetByMail(mail string) (*User, error) {
|
||||
var u User
|
||||
|
||||
|
@ -78,7 +78,7 @@ func (rdb SQLiteUserDatasource) GetByMail(mail string) (*User, error) {
|
|||
return &u, nil
|
||||
}
|
||||
|
||||
// GetByUsername gets an user by his username, includes the password and salt
|
||||
// GetByUsername gets a user by his username, includes the password and salt
|
||||
func (rdb SQLiteUserDatasource) GetByUsername(username string) (*User, error) {
|
||||
var u User
|
||||
|
||||
|
@ -89,7 +89,7 @@ func (rdb SQLiteUserDatasource) GetByUsername(username string) (*User, error) {
|
|||
return &u, nil
|
||||
}
|
||||
|
||||
// Create creates an new user
|
||||
// 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)
|
||||
|
@ -103,6 +103,7 @@ func (rdb SQLiteUserDatasource) Create(u *User) (int, error) {
|
|||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
return int(i), nil
|
||||
}
|
||||
|
||||
|
@ -116,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)
|
||||
|
|
|
@ -288,9 +288,7 @@ func (cfg *Settings) GenerateCSRF() (bool, error) {
|
|||
// create a random csrf token
|
||||
b = crypt.AlphaUpperLowerNumericSpecial.RandomSequence(32)
|
||||
|
||||
err := ioutil.WriteFile(csrfTokenFilename, b, 0640)
|
||||
|
||||
if err != nil {
|
||||
if err := ioutil.WriteFile(csrfTokenFilename, b, 0640); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
|
|
|
@ -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}}
|
||||
|
|
Loading…
Reference in New Issue