├── .gitignore ├── .travis.yml ├── BASE.sql ├── README.md ├── blog.go ├── config-sample.json ├── kayden.go ├── models └── Config.go ├── public ├── css │ └── style.css ├── favicon.ico └── img │ └── 404.jpg ├── uploads └── 01.jpg ├── utils.go └── views ├── 404.html ├── all.html ├── draft_edit.html ├── draft_new.html ├── drafts.html ├── edit.html ├── footer.html ├── header.html ├── index.html ├── login.html ├── menu.html ├── new.html ├── posts.html ├── rss.html ├── rss_header.html └── upload.html /.gitignore: -------------------------------------------------------------------------------- 1 | TODO 2 | config.json 3 | .htaccess 4 | install.go 5 | nohup.out 6 | liamka.me 7 | pkg 8 | src -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.1 4 | - 1.2 5 | - 1.3 6 | - release 7 | - tip 8 | 9 | install: 10 | - go get github.com/go-sql-driver/mysql 11 | 12 | script: 13 | - go build -v ./... -------------------------------------------------------------------------------- /BASE.sql: -------------------------------------------------------------------------------- 1 | # ************************************************************ 2 | # SQL dump for Kayden 3 | # ************************************************************ 4 | 5 | CREATE TABLE `kayden_blog_posts` ( 6 | `id` int(11) NOT NULL AUTO_INCREMENT, 7 | `title` varchar(1000) NOT NULL, 8 | `body` varchar(5000) NOT NULL, 9 | `tags` varchar(1000) NOT NULL DEFAULT '', 10 | `time` int(100) NOT NULL, 11 | PRIMARY KEY (`id`) 12 | ) ENGINE=MyISAM DEFAULT CHARSET=utf8; 13 | 14 | INSERT INTO `kayden_blog_posts` (`id`, `title`, `body`, `tags`, `time`) 15 | VALUES (1,'Hello golang!','This is test message','hello, world, ',1397480139); 16 | 17 | 18 | 19 | CREATE TABLE `kayden_blog_drafts` ( 20 | `id` int(11) NOT NULL AUTO_INCREMENT, 21 | `title` varchar(1000) NOT NULL, 22 | `body` varchar(5000) NOT NULL, 23 | `tags` varchar(1000) NOT NULL DEFAULT '', 24 | `time` int(100) NOT NULL, 25 | PRIMARY KEY (`id`) 26 | ) ENGINE=MyISAM DEFAULT CHARSET=utf8; 27 | 28 | INSERT INTO `kayden_blog_drafts` (`id`, `title`, `body`, `tags`, `time`) 29 | VALUES (1,'Test draft','Example draft','draft, tags, ',1397480139); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Keywords: Golang, go, Blog, Engine, MySQL 2 | 3 | Golang logo 4 | 5 | ### Kayden - blog engine on go(golang) 6 | 7 | Simple blog engine on go. Nothing else. 8 | 9 | [Demo here](https://liamka.me) 10 | 11 | ### Features 12 | - Fully customizable 13 | - Tags (search by tag) 14 | - Markdown 15 | - Drafts 16 | - RSS 17 | - Simple ;) 18 | 19 | ### Install 20 | 21 | 1) Rename config-sample.json to config.json and change params 22 | 23 | 2) Upload base BASE.sql 24 | 25 | 3) Install dependencies: 26 | 27 | go get github.com/go-sql-driver/mysql 28 | 29 | go get github.com/russross/blackfriday 30 | 31 | go get github.com/liamka/Superior 32 | 33 | 3) Build it and run! 34 | 35 | 4) Go to http://your-website/kayden and login 36 | 37 | ### License 38 | 39 | Kayden(blog engine) is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT) 40 | 41 | Author: [![Author](https://liamka.me/public/favicon.ico)](https://liamka.me) Kirill Kotikov 42 | 43 | ![Kayden Kross](http://i.imgur.com/JN8TQxa.jpg) 44 | -------------------------------------------------------------------------------- /blog.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "text/template" 6 | _ "github.com/go-sql-driver/mysql" 7 | "github.com/liamka/Superior" 8 | "database/sql" 9 | "./models" 10 | ) 11 | 12 | var ( 13 | db *sql.DB 14 | config models.Config 15 | v models.Vars 16 | t = template.Must(template.ParseGlob("views/*")) 17 | ) 18 | 19 | type post struct { 20 | Id int 21 | Title string 22 | Body string 23 | Tags string 24 | Time string 25 | } 26 | 27 | //////////////////////////////// 28 | // Index post 29 | //////////////////////////////// 30 | func index(w http.ResponseWriter, r *http.Request) { 31 | // Query 32 | rows, _ := db.Query("SELECT * FROM kayden_blog_posts ORDER BY id DESC LIMIT 10") 33 | defer rows.Close() 34 | posts := []post{} 35 | for rows.Next() { 36 | p := post{} 37 | rows.Scan(&p.Id, &p.Title, &p.Body, &p.Tags, &p.Time) 38 | v.Title = config.Title 39 | p.Body = ConvertMarkdownToHtml(p.Body) 40 | p.Time = timeX(p.Time) 41 | p.Tags = tagsX(p.Tags,"true") 42 | posts = append(posts, p) 43 | } 44 | t.ExecuteTemplate(w, "header", v) 45 | t.ExecuteTemplate(w, "index", posts) 46 | } 47 | 48 | //////////////////////////////// 49 | // Single post 50 | //////////////////////////////// 51 | func single(w http.ResponseWriter, r *http.Request) { 52 | // Get single 53 | id := r.URL.Path[len("/note/"):] 54 | stmt, _ := db.Prepare("SELECT * FROM kayden_blog_posts where id = ? LIMIT 1") 55 | rows, _ := stmt.Query(id) 56 | defer rows.Close() 57 | posts := []post{} 58 | for rows.Next() { 59 | p := post{} 60 | rows.Scan(&p.Id, &p.Title, &p.Body, &p.Tags, &p.Time) 61 | v.Title = config.Title + ": " + p.Title 62 | p.Body = ConvertMarkdownToHtml(p.Body) 63 | p.Time = timeX(p.Time) 64 | p.Tags = tagsX(p.Tags,"true") 65 | posts = append(posts, p) 66 | } 67 | // 404 68 | if len(posts) == 0 { 69 | http.Redirect(w, r, "/404", 302) 70 | } 71 | t.ExecuteTemplate(w, "header", v) 72 | t.ExecuteTemplate(w, "index", posts) 73 | } 74 | //////////////////////////////// 75 | // All posts 76 | //////////////////////////////// 77 | func allPosts(w http.ResponseWriter, r *http.Request) { 78 | // Query 79 | rows, _ := db.Query("SELECT * FROM kayden_blog_posts ORDER BY id DESC LIMIT 100000") 80 | defer rows.Close() 81 | posts := []post{} 82 | for rows.Next() { 83 | p := post{} 84 | rows.Scan(&p.Id, &p.Title, &p.Body, &p.Tags, &p.Time) 85 | p.Body = ConvertMarkdownToHtml(p.Body) 86 | p.Time = timeX(p.Time) 87 | p.Tags = tagsX(p.Tags,"false") 88 | posts = append(posts, p) 89 | } 90 | t.ExecuteTemplate(w, "header", v) 91 | t.ExecuteTemplate(w, "all", posts) 92 | } 93 | //////////////////////////////// 94 | // Tags 95 | //////////////////////////////// 96 | func tagPosts(w http.ResponseWriter, r *http.Request) { 97 | // Get tag 98 | tag := r.URL.Path[len("/tag/"):] 99 | stmt, _ := db.Prepare("SELECT * FROM kayden_blog_posts where tags LIKE ? ORDER BY id DESC LIMIT 1000000") 100 | rows, _ := stmt.Query("%"+tag+"%") 101 | defer rows.Close() 102 | posts := []post{} 103 | for rows.Next() { 104 | p := post{} 105 | rows.Scan(&p.Id, &p.Title, &p.Body, &p.Tags, &p.Time) 106 | p.Body = ConvertMarkdownToHtml(p.Body) 107 | p.Time = timeX(p.Time) 108 | p.Tags = tagsX(p.Tags,"true") 109 | posts = append(posts, p) 110 | } 111 | t.ExecuteTemplate(w, "header", v) 112 | t.ExecuteTemplate(w, "index", posts) 113 | } 114 | 115 | //////////////////////////////// 116 | // RSS 117 | //////////////////////////////// 118 | func rssPosts(w http.ResponseWriter, r *http.Request) { 119 | w.Header().Set("Content-Type", "application/xml") 120 | // Query 121 | rows, _ := db.Query("SELECT * FROM kayden_blog_posts ORDER BY id DESC LIMIT 10") 122 | defer rows.Close() 123 | 124 | type rssPost struct { 125 | Id int 126 | Title string 127 | Body string 128 | Tags string 129 | Time string 130 | URI string 131 | } 132 | 133 | posts := []rssPost{} 134 | k := 0 135 | var lastUpdate string 136 | for rows.Next() { 137 | p := rssPost{} 138 | rows.Scan(&p.Id, &p.Title, &p.Body, &p.Tags, &p.Time) 139 | if k == 0 { 140 | lastUpdate = timeRFC(p.Time) 141 | } 142 | p.Body = ConvertMarkdownToHtml(p.Body) 143 | p.Time = timeRFC(p.Time) 144 | p.Tags = tagsX(p.Tags,"false") 145 | p.URI = config.URI 146 | posts = append(posts, p) 147 | k++ 148 | } 149 | 150 | type Rss struct { 151 | Title string 152 | Subtitle string 153 | URI string 154 | Description string 155 | LastBuild string 156 | } 157 | 158 | rss := Rss{ 159 | Title: config.Title, 160 | URI: config.URI, 161 | Description: config.Description, 162 | LastBuild: lastUpdate, 163 | } 164 | 165 | t.ExecuteTemplate(w, "rss_header", rss) 166 | t.ExecuteTemplate(w, "rss", posts) 167 | } 168 | 169 | //////////////////////////////// 170 | // 404 171 | //////////////////////////////// 172 | func notfound(w http.ResponseWriter, r *http.Request) { 173 | t.ExecuteTemplate(w, "header", v) 174 | t.ExecuteTemplate(w, "404", nil) 175 | } 176 | 177 | //////////////////////////////// 178 | // MAIN 179 | //////////////////////////////// 180 | func main() { 181 | // Sample 182 | Superior.Print("Kayden love it!", "normal", "green") 183 | // Conf 184 | config = models.Conf() 185 | v = models.Values(config) 186 | // Open mysql 187 | db, _ = sql.Open("mysql", config.Mysql) 188 | // Invoke folders 189 | http.Handle("/public/", http.StripPrefix("/public/", http.FileServer(http.Dir("public")))) 190 | http.Handle("/uploads/", http.StripPrefix("/uploads/", http.FileServer(http.Dir("uploads")))) 191 | // Routes 192 | http.HandleFunc("/", index) 193 | http.HandleFunc("/note/", single) 194 | http.HandleFunc("/all/", allPosts) 195 | http.HandleFunc("/tag/", tagPosts) 196 | http.HandleFunc("/rss/", rssPosts) 197 | http.HandleFunc("/404/", notfound) 198 | http.HandleFunc("/kayden/login", login) 199 | http.HandleFunc("/kayden/", kayden) 200 | http.HandleFunc("/kayden/new/", newPost) 201 | http.HandleFunc("/kayden/new/save", savePost) 202 | http.HandleFunc("/kayden/edit/", editPost) 203 | http.HandleFunc("/kayden/edit/save", updatePost) 204 | http.HandleFunc("/kayden/delete/", deletePost) 205 | http.HandleFunc("/kayden/upload", uploads) 206 | http.HandleFunc("/kayden/upload/delete/", uploadsDelete) 207 | http.HandleFunc("/kayden/drafts/", drafts) 208 | http.HandleFunc("/kayden/drafts/new/", newDraft) 209 | http.HandleFunc("/kayden/drafts/new/save", saveDraft) 210 | http.HandleFunc("/kayden/drafts/edit/", editDraft) 211 | http.HandleFunc("/kayden/drafts/edit/save", updateDraft) 212 | http.HandleFunc("/kayden/drafts/delete/", deleteDraft) 213 | http.HandleFunc("/kayden/drafts/publish/", publishDraft) 214 | 215 | // Get port 216 | http.ListenAndServe(":3000", nil) 217 | } -------------------------------------------------------------------------------- /config-sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Blog title", 3 | "titleheader": "Title for header", 4 | "subtitle": "Your subtitle", 5 | "uri": "http://your-blog-url.com", 6 | "description": "Description", 7 | "keywords": "Keywords", 8 | "mysql": "user:pass@/base?charset=utf8", 9 | "cookieName": "kayden", 10 | "pass": "pass", 11 | "Social": [ 12 | {"url": "URL", "title": "TITLE #1"}, 13 | {"url": "URL", "title": "TITLE #2"} 14 | ] 15 | } -------------------------------------------------------------------------------- /kayden.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "fmt" 6 | "time" 7 | "io" 8 | "io/ioutil" 9 | "os" 10 | ) 11 | 12 | 13 | //////////////////////////////// 14 | // Login page 15 | //////////////////////////////// 16 | func login(w http.ResponseWriter, r *http.Request) { 17 | if r.Method == "GET" { 18 | t.ExecuteTemplate(w, "header", v) 19 | t.ExecuteTemplate(w, "login", nil) 20 | } else { 21 | r.ParseForm() 22 | if r.FormValue("pass") == config.Pass { 23 | expiration := time.Now().Add(365 * 24 * time.Hour) 24 | cookie := http.Cookie{Name: config.CookieName, Value: config.Pass, Expires: expiration} 25 | http.SetCookie(w, &cookie) 26 | http.Redirect(w, r, "/kayden", 302) 27 | } else { 28 | http.Redirect(w, r, "/kayden/login", 302) 29 | } 30 | } 31 | } 32 | 33 | //////////////////////////////// 34 | // Admin all posts 35 | //////////////////////////////// 36 | func kayden(w http.ResponseWriter, r *http.Request) { 37 | // Access 38 | access := CheckCookies(r) 39 | if !access { 40 | http.Redirect(w, r, "/kayden/login", 302) 41 | } 42 | 43 | // Query 44 | rows, _ := db.Query("SELECT * FROM kayden_blog_posts ORDER BY id DESC LIMIT 100000") 45 | defer rows.Close() 46 | posts := []post{} 47 | for rows.Next() { 48 | p := post{} 49 | rows.Scan(&p.Id, &p.Title, &p.Body, &p.Tags, &p.Time) 50 | 51 | posts = append(posts, p) 52 | } 53 | t.ExecuteTemplate(w, "header", v) 54 | t.ExecuteTemplate(w, "posts", posts) 55 | } 56 | 57 | //////////////////////////////// 58 | // New post 59 | //////////////////////////////// 60 | func newPost(w http.ResponseWriter, r *http.Request) { 61 | // Access 62 | access := CheckCookies(r) 63 | if !access { 64 | http.Redirect(w, r, "/kayden/login", 302) 65 | } 66 | 67 | t.ExecuteTemplate(w, "header", v) 68 | t.ExecuteTemplate(w, "new", nil) 69 | } 70 | 71 | //////////////////////////////// 72 | // Save post 73 | //////////////////////////////// 74 | func savePost(w http.ResponseWriter, r *http.Request) { 75 | // Access 76 | access := CheckCookies(r) 77 | if !access { 78 | http.Redirect(w, r, "/kayden/login", 302) 79 | } 80 | 81 | // Catch data 82 | title := r.FormValue("title") 83 | body := r.FormValue("body") 84 | tags := r.FormValue("tags") 85 | 86 | now := time.Now() 87 | time := now.Unix() 88 | 89 | // Prepare and insert 90 | stmt, _ := db.Prepare("INSERT kayden_blog_posts SET title=?,body=?,tags=?,time=?") 91 | stmt.Exec(title, body, tags, time) 92 | // Redirect 93 | http.Redirect(w, r, "/", 302) 94 | } 95 | 96 | //////////////////////////////// 97 | // Edit post 98 | //////////////////////////////// 99 | func editPost(w http.ResponseWriter, r *http.Request) { 100 | // Access 101 | access := CheckCookies(r) 102 | if !access { 103 | http.Redirect(w, r, "/kayden/login", 302) 104 | } 105 | 106 | // Get single 107 | id := r.URL.Path[len("/kayden/edit/"):] 108 | stmt, _ := db.Prepare("SELECT * FROM kayden_blog_posts where id = ? LIMIT 1") 109 | rows, _ := stmt.Query(id) 110 | defer rows.Close() 111 | posts := []post{} 112 | for rows.Next() { 113 | p := post{} 114 | rows.Scan(&p.Id, &p.Title, &p.Body, &p.Tags, &p.Time) 115 | p.Time = timeX(p.Time) 116 | posts = append(posts, p) 117 | } 118 | t.ExecuteTemplate(w, "header", v) 119 | t.ExecuteTemplate(w, "edit", posts) 120 | } 121 | 122 | //////////////////////////////// 123 | // Update post 124 | //////////////////////////////// 125 | func updatePost(w http.ResponseWriter, r *http.Request) { 126 | // Access 127 | access := CheckCookies(r) 128 | if !access { 129 | http.Redirect(w, r, "/kayden/login", 302) 130 | } 131 | 132 | // Catch data 133 | id := r.FormValue("id") 134 | title := r.FormValue("title") 135 | body := r.FormValue("body") 136 | tags := r.FormValue("tags") 137 | 138 | // Prepare and insert 139 | stmt, _ := db.Prepare("update kayden_blog_posts SET title=?,body=?,tags=? where id=?") 140 | stmt.Exec(title, body, tags, id) 141 | // Redirect 142 | http.Redirect(w, r, "/kayden", 302) 143 | } 144 | 145 | //////////////////////////////// 146 | // Delete post 147 | //////////////////////////////// 148 | func deletePost(w http.ResponseWriter, r *http.Request) { 149 | // Access 150 | access := CheckCookies(r) 151 | if !access { 152 | http.Redirect(w, r, "/kayden/login", 302) 153 | } 154 | 155 | // Get single 156 | id := r.URL.Path[len("/kayden/delete/"):] 157 | stmt, _ := db.Prepare("DELETE from kayden_blog_posts where id=?") 158 | stmt.Exec(id) 159 | http.Redirect(w, r, "/kayden", 302) 160 | } 161 | 162 | //////////////////////////////// 163 | // Upload files 164 | //////////////////////////////// 165 | func uploads(w http.ResponseWriter, r *http.Request) { 166 | type file struct { 167 | Name string 168 | } 169 | 170 | if r.Method == "GET" { 171 | filez, _ := ioutil.ReadDir("./uploads/") 172 | files := []file{} 173 | for _, fn := range filez { 174 | f := file{} 175 | f.Name = fn.Name() 176 | files = append(files, f) 177 | } 178 | t.ExecuteTemplate(w, "header", v) 179 | t.ExecuteTemplate(w, "upload", files) 180 | } else { 181 | r.ParseMultipartForm(32 << 20) 182 | file, handler, err := r.FormFile("uploadfile") 183 | if err != nil { 184 | fmt.Println(err) 185 | return 186 | } 187 | defer file.Close() 188 | f, err := os.OpenFile("./uploads/"+handler.Filename, os.O_WRONLY|os.O_CREATE, 0666) 189 | if err != nil { 190 | fmt.Println(err) 191 | return 192 | } 193 | defer f.Close() 194 | io.Copy(f, file) 195 | http.Redirect(w, r, "/kayden/upload", 302) 196 | } 197 | } 198 | 199 | //////////////////////////////// 200 | // Delete uploaded file 201 | //////////////////////////////// 202 | func uploadsDelete(w http.ResponseWriter, r *http.Request) { 203 | // Access 204 | access := CheckCookies(r) 205 | if !access { 206 | http.Redirect(w, r, "/kayden/login", 302) 207 | } 208 | 209 | // Get file name 210 | file := r.URL.Path[len("/upload/upload/delete/"):] 211 | 212 | err := os.Remove("./uploads/"+file) 213 | if err != nil { 214 | fmt.Println(err) 215 | return 216 | } 217 | 218 | http.Redirect(w, r, "/kayden/upload", 302) 219 | } 220 | 221 | //////////////////////////////// 222 | // All drafts 223 | //////////////////////////////// 224 | func drafts(w http.ResponseWriter, r *http.Request) { 225 | // Access 226 | access := CheckCookies(r) 227 | if !access { 228 | http.Redirect(w, r, "/kayden/login", 302) 229 | } 230 | 231 | // Query 232 | rows, _ := db.Query("SELECT * FROM kayden_blog_drafts ORDER BY id DESC LIMIT 100000") 233 | defer rows.Close() 234 | posts := []post{} 235 | for rows.Next() { 236 | p := post{} 237 | rows.Scan(&p.Id, &p.Title, &p.Body, &p.Tags, &p.Time) 238 | 239 | posts = append(posts, p) 240 | } 241 | t.ExecuteTemplate(w, "header", v) 242 | t.ExecuteTemplate(w, "drafts", posts) 243 | } 244 | 245 | //////////////////////////////// 246 | // New draft 247 | //////////////////////////////// 248 | func newDraft(w http.ResponseWriter, r *http.Request) { 249 | // Access 250 | access := CheckCookies(r) 251 | if !access { 252 | http.Redirect(w, r, "/kayden/login", 302) 253 | } 254 | 255 | t.ExecuteTemplate(w, "header", v) 256 | t.ExecuteTemplate(w, "draft_new", nil) 257 | } 258 | 259 | //////////////////////////////// 260 | // Save draft 261 | //////////////////////////////// 262 | func saveDraft(w http.ResponseWriter, r *http.Request) { 263 | // Access 264 | access := CheckCookies(r) 265 | if !access { 266 | http.Redirect(w, r, "/kayden/login", 302) 267 | } 268 | 269 | // Catch data 270 | title := r.FormValue("title") 271 | body := r.FormValue("body") 272 | tags := r.FormValue("tags") 273 | 274 | now := time.Now() 275 | time := now.Unix() 276 | 277 | // Prepare and insert 278 | stmt, _ := db.Prepare("INSERT kayden_blog_drafts SET title=?,body=?,tags=?,time=?") 279 | stmt.Exec(title, body, tags, time) 280 | // Redirect 281 | http.Redirect(w, r, "/kayden/drafts", 302) 282 | } 283 | 284 | //////////////////////////////// 285 | // Edit draft 286 | //////////////////////////////// 287 | func editDraft(w http.ResponseWriter, r *http.Request) { 288 | // Access 289 | access := CheckCookies(r) 290 | if !access { 291 | http.Redirect(w, r, "/kayden/login", 302) 292 | } 293 | 294 | // Get single 295 | id := r.URL.Path[len("/kayden/drafts/edit/"):] 296 | stmt, _ := db.Prepare("SELECT * FROM kayden_blog_drafts where id = ? LIMIT 1") 297 | rows, _ := stmt.Query(id) 298 | defer rows.Close() 299 | posts := []post{} 300 | for rows.Next() { 301 | p := post{} 302 | rows.Scan(&p.Id, &p.Title, &p.Body, &p.Tags, &p.Time) 303 | p.Time = timeX(p.Time) 304 | posts = append(posts, p) 305 | } 306 | t.ExecuteTemplate(w, "header", v) 307 | t.ExecuteTemplate(w, "draft_edit", posts) 308 | } 309 | 310 | //////////////////////////////// 311 | // Update draft 312 | //////////////////////////////// 313 | func updateDraft(w http.ResponseWriter, r *http.Request) { 314 | // Access 315 | access := CheckCookies(r) 316 | if !access { 317 | http.Redirect(w, r, "/kayden/login", 302) 318 | } 319 | 320 | // Catch data 321 | id := r.FormValue("id") 322 | title := r.FormValue("title") 323 | body := r.FormValue("body") 324 | tags := r.FormValue("tags") 325 | 326 | // Prepare and insert 327 | stmt, _ := db.Prepare("update kayden_blog_drafts SET title=?,body=?,tags=? where id=?") 328 | stmt.Exec(title, body, tags, id) 329 | // Redirect 330 | http.Redirect(w, r, "/kayden/drafts", 302) 331 | } 332 | 333 | //////////////////////////////// 334 | // Delete draft 335 | //////////////////////////////// 336 | func deleteDraft(w http.ResponseWriter, r *http.Request) { 337 | // Access 338 | access := CheckCookies(r) 339 | if !access { 340 | http.Redirect(w, r, "/kayden/login", 302) 341 | } 342 | 343 | // Get single 344 | id := r.URL.Path[len("/kayden/drafts/delete/"):] 345 | stmt, _ := db.Prepare("DELETE from kayden_blog_drafts where id=?") 346 | stmt.Exec(id) 347 | http.Redirect(w, r, "/kayden/drafts", 302) 348 | } 349 | 350 | //////////////////////////////// 351 | // Publish draft 352 | //////////////////////////////// 353 | func publishDraft(w http.ResponseWriter, r *http.Request) { 354 | // Access 355 | access := CheckCookies(r) 356 | if !access { 357 | http.Redirect(w, r, "/kayden/login", 302) 358 | } 359 | 360 | id := r.URL.Path[len("/kayden/drafts/publish/"):] 361 | 362 | stmt, _ := db.Prepare("SELECT * FROM kayden_blog_drafts where id = ? LIMIT 1") 363 | rows, _ := stmt.Query(id) 364 | defer rows.Close() 365 | posts := []post{} 366 | for rows.Next() { 367 | p := post{} 368 | rows.Scan(&p.Id, &p.Title, &p.Body, &p.Tags, &p.Time) 369 | p.Time = timeX(p.Time) 370 | posts = append(posts, p) 371 | } 372 | 373 | stmta, _ := db.Prepare("INSERT kayden_blog_posts SET title=?,body=?,tags=?,time=?") 374 | stmta.Exec(posts[0].Title, posts[0].Body, posts[0].Tags, posts[0].Time) 375 | 376 | stmtd, _ := db.Prepare("DELETE from kayden_blog_drafts where id=?") 377 | stmtd.Exec(id) 378 | 379 | http.Redirect(w, r, "/kayden", 302) 380 | } -------------------------------------------------------------------------------- /models/Config.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | ) 7 | 8 | type Config struct { 9 | Title string `json:"title"` 10 | Subtitle string `json:"subtitle"` 11 | TitleHeader string `json:"titleheader"` 12 | URI string `json:"uri"` 13 | Description string `json:"description"` 14 | Keywords string `json:"keywords"` 15 | Mysql string `json:"mysql"` 16 | CookieName string `json:"cookieName"` 17 | Pass string `json:"pass"` 18 | Social []struct { 19 | Url string `json:"url"` 20 | Title string `json:"title"` 21 | } `json:"social"` 22 | } 23 | 24 | type Vars struct { 25 | Title string 26 | TitleHeader string 27 | Subtitle string 28 | URI string 29 | Description string 30 | Keywords string 31 | Social []*Soc 32 | } 33 | 34 | type Soc struct { 35 | Url string 36 | Title string 37 | } 38 | 39 | var SocData struct{ 40 | Social []*Soc `json:"Social"` 41 | } 42 | 43 | func Conf() Config { 44 | var с Config 45 | configFile, _ := ioutil.ReadFile("config.json") 46 | json.Unmarshal([]byte(configFile), &с) 47 | return с 48 | } 49 | 50 | func Values(vars Config) Vars { 51 | varz, _ := json.Marshal(vars) 52 | json.Unmarshal(varz, &SocData) 53 | p := Vars{ 54 | Title: vars.Title, 55 | TitleHeader: vars.TitleHeader, 56 | Subtitle: vars.Subtitle, 57 | URI: vars.URI, 58 | Description: vars.Description, 59 | Keywords: vars.Keywords, 60 | Social: []*Soc{}, 61 | } 62 | p.Social = append(p.Social, SocData.Social...) 63 | return p 64 | } -------------------------------------------------------------------------------- /public/css/style.css: -------------------------------------------------------------------------------- 1 | /************ General */ 2 | body {background: #fff;max-width: 960px;padding: 40px;margin: 20px auto;} 3 | a {color: #1c5c76;text-decoration: underline;} 4 | 5 | /************ Header */ 6 | .header {text-align: center;padding: 0 0 10px 0;} 7 | .header h1 {margin: 0;font-size: 30px;text-shadow: -4px 4px rgba(0, 0, 0, 0.16);} 8 | .header h1 a {text-decoration: none;color: #000;font-family: Tahoma;font-weight: 700;} 9 | .header p {font-family: Georgia;} 10 | .header p.social a {margin: 4px;} 11 | 12 | /************ Content */ 13 | .content {font-family: Georgia;} 14 | .content article {margin: 0 0 80px 0;display: block;} 15 | .content article h1 a {width: 100%;font-family: Georgia;color: #000;font-size: 30px;text-decoration: none;font-weight: 100;} 16 | .content article h1 a:hover {text-decoration: underline;} 17 | .content article section {width: 100%;line-height: 1.5em;color: #3E4349;font-size: 17px;} 18 | .content article section img.big {text-align: center;margin: 0% -16% auto;display: block;width: 132%;} 19 | .content article mark {display: inline-table;padding: 3px 5px;margin: 8px 0 0 0;color: #aa9c84;background:#fff;line-height: 28px;} 20 | .content article mark a {background: #e2e9f8;color: #1c5c76;border: 1px solid transparent;border-radius: 12px;padding: 0px 6px;text-decoration: none;border-bottom: 1px dotted #999;} 21 | .content a.tag {margin: 5px;} 22 | 23 | /************ Custom */ 24 | .social a:hover {text-decoration: underline;} 25 | pre {background: #eee;padding: 7px 30px;margin: 15px -30px;line-height: 1.3em;overflow: auto;} 26 | 27 | @media only screen and (min-width: 100px) and (max-width: 800px) { 28 | body {padding: 10px;margin: 20px;} 29 | } 30 | 31 | /************ Kayden */ 32 | .kayden {font-size: 14px;} 33 | .kayden .menu a {font-size: 22px;margin-right: 14px;} 34 | .kayden .posts h2 {border: 1px dashed #DADADA;padding: 8px;} 35 | .kayden .posts h2 a {font-family: Georgia;color: #000;text-decoration: none;font-weight: 100;} 36 | .kayden .posts h2 a.options {float: right;font-style: italic;color: brown;} 37 | .kayden form input, .kayden form textarea {border: 2px solid;border-radius: 5px;margin: 10px 0;width: 98%;padding: 10px 7px;font-family: Georgia;font-size: 20px;color: #5A5A5A;} 38 | .kayden form input:focus, .kayden form textarea:focus {outline: none;} 39 | .kayden form input.new {cursor:pointer;background: #00A1E8;color: #fff;width: 30%;} 40 | .kayden form input.update {cursor:pointer;background: #6EA709;color: #fff;width: 30%;} 41 | .kayden .uploads img {} -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liamka/Kayden-blog-engine/254d53a0343b2eb5fb9c7d82d2e7d5b99c59397f/public/favicon.ico -------------------------------------------------------------------------------- /public/img/404.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liamka/Kayden-blog-engine/254d53a0343b2eb5fb9c7d82d2e7d5b99c59397f/public/img/404.jpg -------------------------------------------------------------------------------- /uploads/01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liamka/Kayden-blog-engine/254d53a0343b2eb5fb9c7d82d2e7d5b99c59397f/uploads/01.jpg -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/russross/blackfriday" 5 | "net/http" 6 | "strconv" 7 | "time" 8 | "strings" 9 | ) 10 | 11 | func ConvertMarkdownToHtml(markdawn string) string { 12 | return string(blackfriday.MarkdownBasic([]byte(markdawn))) 13 | } 14 | 15 | func CheckCookies(r *http.Request) bool { 16 | a := false 17 | for _, cookie := range r.Cookies() { 18 | if cookie.Name == config.CookieName { 19 | if cookie.Value == config.Pass { 20 | a = true 21 | } else { 22 | a = false 23 | } 24 | } 25 | } 26 | return a 27 | } 28 | 29 | func timeX(t string) string{ 30 | i, _ := strconv.ParseInt(t, 10, 64) 31 | tm := time.Unix(i, 0) 32 | Time := tm.Format("_2-01-2006") 33 | return Time 34 | } 35 | 36 | func tagsX(t string, u string) string{ 37 | t_ := strings.Split(t, ", ") 38 | var tags string 39 | var ta string 40 | for _,element := range t_ { 41 | if element == "" { 42 | continue 43 | } 44 | if u != "true" { 45 | ta += element+", " 46 | tagsz := len(ta) 47 | tags = ta[:tagsz-2] 48 | } else { 49 | tags += ""+element+"" 50 | } 51 | } 52 | return tags 53 | } 54 | 55 | func timeRFC(t string) string{ 56 | i, _ := strconv.ParseInt(t, 10, 64) 57 | tm := time.Unix(i, 0) 58 | Time := tm.Format(time.RFC1123Z) 59 | return Time 60 | } -------------------------------------------------------------------------------- /views/404.html: -------------------------------------------------------------------------------- 1 | {{ define "404" }} 2 | {{ template "header" }} 3 | 4 |
5 |

Запись не найдена

6 |
7 |
8 | 9 |
все записи 10 | {{ template "footer" }} 11 | 12 | {{ end }} -------------------------------------------------------------------------------- /views/all.html: -------------------------------------------------------------------------------- 1 | {{ define "all" }} 2 | {{ template "header" }} 3 | 4 |

Все записи

5 | 6 | {{ range . }} 7 |

8 | {{ .Title }} ({{ .Tags }}) 9 |

10 | {{ end }} 11 | 12 |
все записи 13 | {{ template "footer" }} 14 | 15 | {{ end }} -------------------------------------------------------------------------------- /views/draft_edit.html: -------------------------------------------------------------------------------- 1 | {{ define "draft_edit" }} 2 | 3 | {{ template "header" }} 4 | 5 |
6 | 7 | {{ template "menu" }} 8 | 9 | {{ range . }} 10 |
11 | 12 | 13 | 14 | 15 | 16 |
17 | {{ end }} 18 | 19 | {{ template "footer" }} 20 | 21 | {{ end }} -------------------------------------------------------------------------------- /views/draft_new.html: -------------------------------------------------------------------------------- 1 | {{ define "draft_new" }} 2 | 3 | {{ template "header" }} 4 | 5 |
6 | 7 | {{ template "menu" }} 8 | 9 |
10 | 11 | 12 | 13 | 14 |
15 | 16 |
17 | 18 | {{ template "footer" }} 19 | 20 | {{ end }} -------------------------------------------------------------------------------- /views/drafts.html: -------------------------------------------------------------------------------- 1 | {{ define "drafts" }} 2 | {{ template "header" }} 3 | 4 |
5 | 6 | {{ template "menu" }} 7 | 8 |
9 | 10 | {{ range . }} 11 |
12 |

{{ .Title }} [delete draft] [publish draft]

13 |
14 | {{ end }} 15 | 16 |
17 | 18 |
19 | 20 | {{ template "footer" }} 21 | 22 | {{ end }} -------------------------------------------------------------------------------- /views/edit.html: -------------------------------------------------------------------------------- 1 | {{ define "edit" }} 2 | 3 | {{ template "header" }} 4 | 5 |
6 | 7 | {{ template "menu" }} 8 | 9 | {{ range . }} 10 |
11 | 12 | 13 | 14 | 15 | 16 |
17 | {{ end }} 18 | 19 | {{ template "footer" }} 20 | 21 | {{ end }} -------------------------------------------------------------------------------- /views/footer.html: -------------------------------------------------------------------------------- 1 | {{ define "footer" }} 2 |
3 | 4 | 5 | {{ end }} -------------------------------------------------------------------------------- /views/header.html: -------------------------------------------------------------------------------- 1 | {{ define "header" }} 2 | 3 | 4 | {{.Title}} 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |

{{.TitleHeader}}

25 |

{{.Subtitle}}

26 | 33 |
34 |
35 | {{ end }} -------------------------------------------------------------------------------- /views/index.html: -------------------------------------------------------------------------------- 1 | {{ define "index" }} 2 | 3 | {{ range . }} 4 |
5 |

{{ .Title }}

6 |
{{ .Body }}
7 | {{ .Time }} {{ .Tags }} 8 |
9 | {{ end }} 10 | 11 |
все записи 12 | {{ template "footer" }} 13 | 14 | {{ end }} -------------------------------------------------------------------------------- /views/login.html: -------------------------------------------------------------------------------- 1 | {{ define "login" }} 2 | 3 | {{ template "header" }} 4 | 5 |
6 | 7 |
8 | 9 |
10 | 11 |
12 | 13 |
14 | {{ template "footer" }} 15 | 16 | {{ end }} -------------------------------------------------------------------------------- /views/menu.html: -------------------------------------------------------------------------------- 1 | {{ define "menu" }} 2 | 3 | 10 | 11 | {{ end }} -------------------------------------------------------------------------------- /views/new.html: -------------------------------------------------------------------------------- 1 | {{ define "new" }} 2 | 3 | {{ template "header" }} 4 | 5 |
6 | 7 | {{ template "menu" }} 8 | 9 |
10 | 11 | 12 | 13 | 14 |
15 | 16 |
17 | 18 | {{ template "footer" }} 19 | 20 | {{ end }} -------------------------------------------------------------------------------- /views/posts.html: -------------------------------------------------------------------------------- 1 | {{ define "posts" }} 2 | {{ template "header" }} 3 | 4 |
5 | 6 | {{ template "menu" }} 7 | 8 |
9 | 10 | {{ range . }} 11 |
12 |

{{ .Title }} delete

13 |
14 | {{ end }} 15 | 16 |
17 | 18 |
19 | 20 | {{ template "footer" }} 21 | 22 | {{ end }} -------------------------------------------------------------------------------- /views/rss.html: -------------------------------------------------------------------------------- 1 | {{ define "rss" }} 2 | {{range .}} 3 | 4 | {{ .Title }} 5 | {{.URI}}/note/{{ .Id }} 6 | 7 | {{ .Time }} 8 | {{.URI}}/note/{{ .Id }} 9 | 10 | {{end}} 11 | {{ end }} -------------------------------------------------------------------------------- /views/rss_header.html: -------------------------------------------------------------------------------- 1 | {{ define "rss_header" }} 2 | {{.Title}} 3 | {{.URI}} 4 | 5 | {{.LastBuild}} 6 | {{.URI}}{{ end }} -------------------------------------------------------------------------------- /views/upload.html: -------------------------------------------------------------------------------- 1 | {{ define "upload" }} 2 | 3 | {{ template "header" }} 4 | 5 |
6 | 7 | {{ template "menu" }} 8 | 9 |
10 | 11 | 12 | 13 |
14 | 15 | {{ range . }} 16 |
{{ .Name }}

Delete
17 |

18 | {{end}} 19 | 20 |
21 | 22 | {{ template "footer" }} 23 | 24 | {{ end }} --------------------------------------------------------------------------------