minor clean-up

This commit is contained in:
Lars Hoogestraat 2021-09-08 22:26:41 +02:00
parent 82a94386fd
commit 4408371856
27 changed files with 135 additions and 147 deletions

View File

@ -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 ###

View File

View File

@ -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
}

View File

@ -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
View File

@ -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
View File

@ -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=

View File

@ -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()
}

View File

@ -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

View File

@ -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

View File

@ -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)
}

View File

@ -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
}

View File

@ -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

View File

@ -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
}
}

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}
}()

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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) {

View File

@ -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)

View File

@ -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
}

View File

@ -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>

View File

@ -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>

View File

@ -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}}