diff --git a/built-in/admin/admin-angular.js b/built-in/admin/admin-angular.js index 440df421..9bea0676 100644 --- a/built-in/admin/admin-angular.js +++ b/built-in/admin/admin-angular.js @@ -21,14 +21,20 @@ adminApp.config(function($routeProvider) { }). otherwise({ redirectTo: '/' - }); + }); }); //service for sharing the markdown content across controllers adminApp.factory('sharingService', function(){ - - return { shared: { post: {}, blog: {}, user: {}, infiniteScrollFactory: null, selected: "" } } - + return { + shared: { + post: {}, + blog: {}, + user: {}, + infiniteScrollFactory: null, + selected: '' + } + } }); //directive to handle visual selection of images @@ -97,7 +103,7 @@ adminApp.controller('ContentCtrl', function ($scope, $http, $sce, $location, inf $location.url('/edit/' + postId); }; $scope.deletePost = function(postId, postTitle) { - if (confirm('Are you sure you want to delete post "' + postTitle + '"?')) { + if (confirm('Are you sure you want to delete the post "' + postTitle + '"?')) { $http.delete('/admin/api/post/' + postId).success(function(data) { //delete post from array for (var i = 0; i < $scope.infiniteScrollFactory.items.length; i++) { diff --git a/built-in/admin/settings.html b/built-in/admin/settings.html index d6bb19d6..d5e6fb75 100644 --- a/built-in/admin/settings.html +++ b/built-in/admin/settings.html @@ -88,6 +88,18 @@

Navigation

User {{shared.user.Name}}

+
+ +
+ +
+
+
+ +
+ +
+
diff --git a/database/initialization.go b/database/initialization.go index fca7d948..512a69c6 100644 --- a/database/initialization.go +++ b/database/initialization.go @@ -5,6 +5,7 @@ import ( "github.com/kabukky/journey/database/migration" "github.com/kabukky/journey/filenames" "github.com/kabukky/journey/helpers" + "github.com/kabukky/journey/structure" _ "github.com/mattn/go-sqlite3" "github.com/twinj/uuid" "time" @@ -147,5 +148,97 @@ func Initialize() error { if err != nil { return err } + err = checkBlogSettings() + if err != nil { + return err + } + return nil +} + +// Function to check and insert any missing blog settings into the database (settings could be missing if migrating from Ghost). +func checkBlogSettings() error { + tempBlog := structure.Blog{} + // Check for title + row := readDB.QueryRow(stmtRetrieveBlog, "title") + err := row.Scan(&tempBlog.Title) + if err != nil { + // Insert title + err = insertSettingString("title", "My Blog", "blog", time.Now(), 1) + if err != nil { + return err + } + } + // Check for description + row = readDB.QueryRow(stmtRetrieveBlog, "description") + err = row.Scan(&tempBlog.Description) + if err != nil { + // Insert description + err = insertSettingString("description", "Just another Blog", "blog", time.Now(), 1) + if err != nil { + return err + } + } + // Check for email + var email []byte + row = readDB.QueryRow(stmtRetrieveBlog, "email") + err = row.Scan(&email) + if err != nil { + // Insert email + err = insertSettingString("email", "", "blog", time.Now(), 1) + if err != nil { + return err + } + } + // Check for logo + row = readDB.QueryRow(stmtRetrieveBlog, "logo") + err = row.Scan(&tempBlog.Logo) + if err != nil { + // Insert logo + err = insertSettingString("logo", "/public/images/blog-logo.jpg", "blog", time.Now(), 1) + if err != nil { + return err + } + } + // Check for cover + row = readDB.QueryRow(stmtRetrieveBlog, "cover") + err = row.Scan(&tempBlog.Cover) + if err != nil { + // Insert cover + err = insertSettingString("cover", "/public/images/blog-cover.jpg", "blog", time.Now(), 1) + if err != nil { + return err + } + } + // Check for postsPerPage + row = readDB.QueryRow(stmtRetrieveBlog, "postsPerPage") + err = row.Scan(&tempBlog.PostsPerPage) + if err != nil { + // Insert postsPerPage + err = insertSettingInt64("postsPerPage", 5, "blog", time.Now(), 1) + if err != nil { + return err + } + } + // Check for activeTheme + row = readDB.QueryRow(stmtRetrieveBlog, "activeTheme") + err = row.Scan(&tempBlog.ActiveTheme) + if err != nil { + // Insert activeTheme + err = insertSettingString("activeTheme", "promenade", "theme", time.Now(), 1) + if err != nil { + return err + } + } + // Check for navigation + var navigation []byte + row = readDB.QueryRow(stmtRetrieveBlog, "navigation") + err = row.Scan(&navigation) + if err != nil { + // Insert navigation + err = insertSettingString("navigation", "[{\"label\":\"Home\", \"url\":\"/\"}]", "blog", time.Now(), 1) + if err != nil { + return err + } + } return nil } diff --git a/database/insertion.go b/database/insertion.go index f6cd7402..1db1b7e6 100644 --- a/database/insertion.go +++ b/database/insertion.go @@ -11,6 +11,7 @@ const stmtInsertUser = "INSERT INTO users (id, uuid, name, slug, password, email const stmtInsertRoleUser = "INSERT INTO roles_users (id, role_id, user_id) VALUES (?, ?, ?)" const stmtInsertTag = "INSERT INTO tags (id, uuid, name, slug, created_at, created_by, updated_at, updated_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?)" const stmtInsertPostTag = "INSERT INTO posts_tags (id, post_id, tag_id) VALUES (?, ?, ?)" +const stmtInsertSetting = "INSERT INTO settings (id, uuid, key, value, type, created_at, created_by, updated_at, updated_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)" func InsertPost(title []byte, slug string, markdown []byte, html []byte, featured bool, isPage bool, published bool, image []byte, created_at time.Time, created_by int64) (int64, error) { @@ -106,3 +107,31 @@ func InsertPostTag(post_id int64, tag_id int64) error { } return writeDB.Commit() } + +func insertSettingString(key string, value string, setting_type string, created_at time.Time, created_by int64) error { + writeDB, err := readDB.Begin() + if err != nil { + writeDB.Rollback() + return err + } + _, err = writeDB.Exec(stmtInsertSetting, nil, uuid.Formatter(uuid.NewV4(), uuid.CleanHyphen), key, value, setting_type, created_at, created_by, created_at, created_by) + if err != nil { + writeDB.Rollback() + return err + } + return writeDB.Commit() +} + +func insertSettingInt64(key string, value int64, setting_type string, created_at time.Time, created_by int64) error { + writeDB, err := readDB.Begin() + if err != nil { + writeDB.Rollback() + return err + } + _, err = writeDB.Exec(stmtInsertSetting, nil, uuid.Formatter(uuid.NewV4(), uuid.CleanHyphen), key, value, setting_type, created_at, created_by, created_at, created_by) + if err != nil { + writeDB.Rollback() + return err + } + return writeDB.Commit() +} diff --git a/database/update.go b/database/update.go index 41e98f4f..53f5ee59 100644 --- a/database/update.go +++ b/database/update.go @@ -7,7 +7,7 @@ import ( const stmtUpdatePost = "UPDATE posts SET title = ?, slug = ?, markdown = ?, html = ?, featured = ?, page = ?, status = ?, image = ?, updated_at = ?, updated_by = ? WHERE id = ?" const stmtUpdatePostPublished = "UPDATE posts SET title = ?, slug = ?, markdown = ?, html = ?, featured = ?, page = ?, status = ?, image = ?, updated_at = ?, updated_by = ?, published_at = ?, published_by = ? WHERE id = ?" const stmtUpdateSettings = "UPDATE settings SET value = ?, updated_at = ?, updated_by = ? WHERE key = ?" -const stmtUpdateUser = "UPDATE users SET email = ?, image = ?, cover = ?, bio = ?, website = ?, location = ?, updated_at = ?, updated_by = ? WHERE id = ?" +const stmtUpdateUser = "UPDATE users SET name = ?, slug = ?, email = ?, image = ?, cover = ?, bio = ?, website = ?, location = ?, updated_at = ?, updated_by = ? WHERE id = ?" const stmtUpdateLastLogin = "UPDATE users SET last_login = ? WHERE id = ?" const stmtUpdateUserPassword = "UPDATE users SET password = ?, updated_at = ?, updated_by = ? WHERE id = ?" @@ -103,13 +103,13 @@ func UpdateActiveTheme(activeTheme string, updated_at time.Time, updated_by int6 return writeDB.Commit() } -func UpdateUser(id int64, email []byte, image []byte, cover []byte, bio []byte, website []byte, location []byte, updated_at time.Time, updated_by int64) error { +func UpdateUser(id int64, name []byte, slug string, email []byte, image []byte, cover []byte, bio []byte, website []byte, location []byte, updated_at time.Time, updated_by int64) error { writeDB, err := readDB.Begin() if err != nil { writeDB.Rollback() return err } - _, err = writeDB.Exec(stmtUpdateUser, email, image, cover, bio, website, location, updated_at, updated_by, id) + _, err = writeDB.Exec(stmtUpdateUser, name, slug, email, image, cover, bio, website, location, updated_at, updated_by, id) if err != nil { writeDB.Rollback() return err diff --git a/server/admin.go b/server/admin.go index a16af771..99db4032 100644 --- a/server/admin.go +++ b/server/admin.go @@ -52,6 +52,7 @@ type JsonBlog struct { type JsonUser struct { Id int64 Name string + Slug string Email string Image string Cover string @@ -86,15 +87,7 @@ func postLoginHandler(w http.ResponseWriter, r *http.Request, _ map[string]strin password := r.FormValue("password") if name != "" && password != "" { if authentication.LoginIsCorrect(name, password) { - authentication.SetSession(name, w) - userId, err := getUserId(name) - if err != nil { - log.Println("Couldn't get id of logged in user:", err) - } - err = database.UpdateLastLogin(time.Now(), userId) - if err != nil { - log.Println("Couldn't update last login date of a user:", err) - } + logInUser(name, w) } else { log.Println("Failed login attempt for user " + name) } @@ -597,13 +590,13 @@ func getApiUserHandler(w http.ResponseWriter, r *http.Request, params map[string http.Error(w, "You don't have permission to access this data.", http.StatusForbidden) return } - author, err := database.RetrieveUser(userIdToGet) + user, err := database.RetrieveUser(userIdToGet) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } - authorJson := JsonUser{Id: author.Id, Name: string(author.Name), Email: string(author.Email), Image: string(author.Image), Cover: string(author.Cover), Bio: string(author.Bio), Website: string(author.Website), Location: string(author.Location)} - json, err := json.Marshal(authorJson) + userJson := userToJson(user) + json, err := json.Marshal(userJson) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -633,19 +626,34 @@ func patchApiUserHandler(w http.ResponseWriter, r *http.Request, _ map[string]st http.Error(w, err.Error(), http.StatusInternalServerError) return } - // Make sure user id is over 0 and E-Mail is included. + // Make sure user id is over 0 if json.Id < 1 { http.Error(w, "Wrong user id.", http.StatusInternalServerError) return - } else if json.Email == "" { - http.Error(w, "Email needs to be included.", http.StatusInternalServerError) - return } else if userId != json.Id { // Make sure the authenticated user is only changing his/her own data. TODO: Make sure the user is admin when multiple users have been introduced http.Error(w, "You don't have permission to change this data.", http.StatusInternalServerError) return } - author := structure.User{Id: json.Id, Email: []byte(json.Email), Image: []byte(json.Image), Cover: []byte(json.Cover), Bio: []byte(json.Bio), Website: []byte(json.Website), Location: []byte(json.Location)} - err = methods.UpdateUser(&author, userId) + // Get old user data to compare + tempUser, err := database.RetrieveUser(json.Id) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + // Make sure user email is provided + if json.Email == "" { + json.Email = string(tempUser.Email) + } + // Make sure user name is provided + if json.Name == "" { + json.Name = string(tempUser.Name) + } + // Make sure user slug is provided + if json.Slug == "" { + json.Slug = tempUser.Slug + } + user := structure.User{Id: json.Id, Name: []byte(json.Name), Slug: json.Slug, Email: []byte(json.Email), Image: []byte(json.Image), Cover: []byte(json.Cover), Bio: []byte(json.Bio), Website: []byte(json.Website), Location: []byte(json.Location)} + err = methods.UpdateUser(&user, userId) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -656,12 +664,16 @@ func patchApiUserHandler(w http.ResponseWriter, r *http.Request, _ map[string]st http.Error(w, err.Error(), http.StatusInternalServerError) return } - err = database.UpdateUserPassword(author.Id, encryptedPassword, time.Now(), json.Id) + err = database.UpdateUserPassword(user.Id, encryptedPassword, time.Now(), json.Id) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } + // Check if the user name was changed. If so, update the session cookie to the new user name. + if json.Name != string(tempUser.Name) { + logInUser(json.Name, w) + } w.WriteHeader(http.StatusOK) w.Write([]byte("User settings updated!")) return @@ -703,6 +715,18 @@ func getUserId(userName string) (int64, error) { return user.Id, nil } +func logInUser(name string, w http.ResponseWriter) { + authentication.SetSession(name, w) + userId, err := getUserId(name) + if err != nil { + log.Println("Couldn't get id of logged in user:", err) + } + err = database.UpdateLastLogin(time.Now(), userId) + if err != nil { + log.Println("Couldn't update last login date of a user:", err) + } +} + func postsToJson(posts []structure.Post) *[]JsonPost { jsonPosts := make([]JsonPost, len(posts)) for index, _ := range posts { @@ -745,6 +769,20 @@ func blogToJson(blog *structure.Blog) *JsonBlog { return &jsonBlog } +func userToJson(user *structure.User) *JsonUser { + var jsonUser JsonUser + jsonUser.Id = user.Id + jsonUser.Name = string(user.Name) + jsonUser.Slug = user.Slug + jsonUser.Email = string(user.Email) + jsonUser.Image = string(user.Image) + jsonUser.Cover = string(user.Cover) + jsonUser.Bio = string(user.Bio) + jsonUser.Website = string(user.Website) + jsonUser.Location = string(user.Location) + return &jsonUser +} + func InitializeAdmin(router *httptreemux.TreeMux) { // For admin panel router.GET("/admin/", adminHandler) diff --git a/structure/methods/user.go b/structure/methods/user.go index 7a40d101..d0b33ad7 100644 --- a/structure/methods/user.go +++ b/structure/methods/user.go @@ -19,7 +19,7 @@ func SaveUser(u *structure.User, hashedPassword string, createdBy int64) error { } func UpdateUser(u *structure.User, updatedById int64) error { - err := database.UpdateUser(u.Id, u.Email, u.Image, u.Cover, u.Bio, u.Website, u.Location, time.Now(), updatedById) + err := database.UpdateUser(u.Id, u.Name, u.Slug, u.Email, u.Image, u.Cover, u.Bio, u.Website, u.Location, time.Now(), updatedById) if err != nil { return err } diff --git a/templates/rss.go b/templates/rss.go index e34f6050..cedecab4 100644 --- a/templates/rss.go +++ b/templates/rss.go @@ -5,27 +5,30 @@ import ( "github.com/gorilla/feeds" "github.com/kabukky/journey/database" "github.com/kabukky/journey/structure" + "github.com/kabukky/journey/structure/methods" "net/http" "time" ) func ShowIndexRss(writer http.ResponseWriter) error { + // Read lock global blog + methods.Blog.RLock() + defer methods.Blog.RUnlock() // 15 posts in rss for now posts, err := database.RetrievePostsForIndex(15, 0) if err != nil { return err } - blog, err := database.RetrieveBlog() - if err != nil { - return err - } - blogData := &structure.RequestData{Posts: posts, Blog: blog} + blogData := &structure.RequestData{Posts: posts, Blog: methods.Blog} feed := createFeed(blogData) err = feed.WriteRss(writer) return err } func ShowTagRss(writer http.ResponseWriter, slug string) error { + // Read lock global blog + methods.Blog.RLock() + defer methods.Blog.RUnlock() tag, err := database.RetrieveTagBySlug(slug) if err != nil { return err @@ -35,17 +38,16 @@ func ShowTagRss(writer http.ResponseWriter, slug string) error { if err != nil { return err } - blog, err := database.RetrieveBlog() - if err != nil { - return err - } - blogData := &structure.RequestData{Posts: posts, Blog: blog} + blogData := &structure.RequestData{Posts: posts, Blog: methods.Blog} feed := createFeed(blogData) err = feed.WriteRss(writer) return err } func ShowAuthorRss(writer http.ResponseWriter, slug string) error { + // Read lock global blog + methods.Blog.RLock() + defer methods.Blog.RUnlock() author, err := database.RetrieveUserBySlug(slug) if err != nil { return err @@ -55,11 +57,7 @@ func ShowAuthorRss(writer http.ResponseWriter, slug string) error { if err != nil { return err } - blog, err := database.RetrieveBlog() - if err != nil { - return err - } - blogData := &structure.RequestData{Posts: posts, Blog: blog} + blogData := &structure.RequestData{Posts: posts, Blog: methods.Blog} feed := createFeed(blogData) err = feed.WriteRss(writer) return err @@ -68,8 +66,8 @@ func ShowAuthorRss(writer http.ResponseWriter, slug string) error { func createFeed(values *structure.RequestData) *feeds.Feed { now := time.Now() feed := &feeds.Feed{ - Title: string(makeCdata(values.Blog.Title)), - Description: string(makeCdata(values.Blog.Description)), + Title: string(values.Blog.Title), + Description: string(values.Blog.Description), Link: &feeds.Link{Href: string(values.Blog.Url)}, Created: now, } @@ -81,8 +79,8 @@ func createFeed(values *structure.RequestData) *feeds.Feed { buffer.WriteString("/") buffer.WriteString(values.Posts[i].Slug) feed.Items = append(feed.Items, &feeds.Item{ - Title: string(makeCdata(values.Posts[i].Title)), - Description: string(makeCdata(values.Posts[i].Html)), + Title: string(values.Posts[i].Title), + Description: string(values.Posts[i].Html), Link: &feeds.Link{Href: buffer.String()}, Id: string(values.Posts[i].Uuid), Author: &feeds.Author{Name: string(values.Posts[i].Author.Name), Email: ""}, @@ -93,11 +91,3 @@ func createFeed(values *structure.RequestData) *feeds.Feed { return feed } - -func makeCdata(input []byte) []byte { - var buffer bytes.Buffer - buffer.WriteString("") - return buffer.Bytes() -}