github.com/bosssauce/ponzu@v0.11.1-0.20200102001432-9bc41b703131/system/admin/handlers.go (about) 1 package admin 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/base64" 7 "encoding/json" 8 "fmt" 9 "log" 10 "net/http" 11 "strconv" 12 "strings" 13 "time" 14 15 "github.com/ponzu-cms/ponzu/management/editor" 16 "github.com/ponzu-cms/ponzu/management/format" 17 "github.com/ponzu-cms/ponzu/management/manager" 18 "github.com/ponzu-cms/ponzu/system/addon" 19 "github.com/ponzu-cms/ponzu/system/admin/config" 20 "github.com/ponzu-cms/ponzu/system/admin/upload" 21 "github.com/ponzu-cms/ponzu/system/admin/user" 22 "github.com/ponzu-cms/ponzu/system/api" 23 "github.com/ponzu-cms/ponzu/system/api/analytics" 24 "github.com/ponzu-cms/ponzu/system/db" 25 "github.com/ponzu-cms/ponzu/system/item" 26 "github.com/ponzu-cms/ponzu/system/search" 27 28 "github.com/gorilla/schema" 29 emailer "github.com/nilslice/email" 30 "github.com/nilslice/jwt" 31 "github.com/tidwall/gjson" 32 ) 33 34 func adminHandler(res http.ResponseWriter, req *http.Request) { 35 view, err := Dashboard() 36 if err != nil { 37 log.Println(err) 38 res.WriteHeader(http.StatusInternalServerError) 39 return 40 } 41 42 res.Header().Set("Content-Type", "text/html") 43 res.Write(view) 44 } 45 46 func initHandler(res http.ResponseWriter, req *http.Request) { 47 if db.SystemInitComplete() { 48 http.Redirect(res, req, req.URL.Scheme+req.URL.Host+"/admin", http.StatusFound) 49 return 50 } 51 52 switch req.Method { 53 case http.MethodGet: 54 view, err := Init() 55 if err != nil { 56 log.Println(err) 57 res.WriteHeader(http.StatusInternalServerError) 58 return 59 } 60 res.Header().Set("Content-Type", "text/html") 61 res.Write(view) 62 63 case http.MethodPost: 64 err := req.ParseForm() 65 if err != nil { 66 log.Println(err) 67 res.WriteHeader(http.StatusInternalServerError) 68 return 69 } 70 71 // get the site name from post to encode and use as secret 72 name := []byte(req.FormValue("name") + db.NewEtag()) 73 secret := base64.StdEncoding.EncodeToString(name) 74 req.Form.Set("client_secret", secret) 75 76 // generate an Etag to use for response caching 77 etag := db.NewEtag() 78 req.Form.Set("etag", etag) 79 80 // create and save admin user 81 email := strings.ToLower(req.FormValue("email")) 82 password := req.FormValue("password") 83 usr, err := user.New(email, password) 84 if err != nil { 85 log.Println(err) 86 res.WriteHeader(http.StatusInternalServerError) 87 return 88 } 89 90 _, err = db.SetUser(usr) 91 if err != nil { 92 log.Println(err) 93 res.WriteHeader(http.StatusInternalServerError) 94 return 95 } 96 97 // set HTTP port which should be previously added to config cache 98 port := db.ConfigCache("http_port").(string) 99 req.Form.Set("http_port", port) 100 101 // set initial user email as admin_email and make config 102 req.Form.Set("admin_email", email) 103 err = db.SetConfig(req.Form) 104 if err != nil { 105 log.Println(err) 106 res.WriteHeader(http.StatusInternalServerError) 107 return 108 } 109 110 // add _token cookie for login persistence 111 week := time.Now().Add(time.Hour * 24 * 7) 112 claims := map[string]interface{}{ 113 "exp": week.Unix(), 114 "user": usr.Email, 115 } 116 117 jwt.Secret([]byte(secret)) 118 token, err := jwt.New(claims) 119 if err != nil { 120 log.Println(err) 121 res.WriteHeader(http.StatusInternalServerError) 122 return 123 } 124 125 http.SetCookie(res, &http.Cookie{ 126 Name: "_token", 127 Value: token, 128 Expires: week, 129 Path: "/", 130 }) 131 132 redir := strings.TrimSuffix(req.URL.String(), "/init") 133 http.Redirect(res, req, redir, http.StatusFound) 134 135 default: 136 res.WriteHeader(http.StatusMethodNotAllowed) 137 } 138 } 139 140 func configHandler(res http.ResponseWriter, req *http.Request) { 141 switch req.Method { 142 case http.MethodGet: 143 data, err := db.ConfigAll() 144 if err != nil { 145 log.Println(err) 146 res.WriteHeader(http.StatusInternalServerError) 147 return 148 } 149 150 c := &config.Config{} 151 152 err = json.Unmarshal(data, c) 153 if err != nil { 154 log.Println(err) 155 res.WriteHeader(http.StatusInternalServerError) 156 return 157 } 158 159 cfg, err := c.MarshalEditor() 160 if err != nil { 161 log.Println(err) 162 res.WriteHeader(http.StatusInternalServerError) 163 return 164 } 165 166 adminView, err := Admin(cfg) 167 if err != nil { 168 log.Println(err) 169 res.WriteHeader(http.StatusInternalServerError) 170 return 171 } 172 173 res.Header().Set("Content-Type", "text/html") 174 res.Write(adminView) 175 176 case http.MethodPost: 177 err := req.ParseForm() 178 if err != nil { 179 log.Println(err) 180 res.WriteHeader(http.StatusInternalServerError) 181 return 182 } 183 184 err = db.SetConfig(req.Form) 185 if err != nil { 186 log.Println(err) 187 res.WriteHeader(http.StatusInternalServerError) 188 return 189 } 190 191 http.Redirect(res, req, req.URL.String(), http.StatusFound) 192 193 default: 194 res.WriteHeader(http.StatusMethodNotAllowed) 195 } 196 197 } 198 199 func backupHandler(res http.ResponseWriter, req *http.Request) { 200 ctx, cancel := context.WithCancel(context.Background()) 201 defer cancel() 202 203 switch req.URL.Query().Get("source") { 204 case "system": 205 err := db.Backup(ctx, res) 206 if err != nil { 207 log.Println("Failed to run backup on system:", err) 208 res.WriteHeader(http.StatusInternalServerError) 209 return 210 } 211 212 case "analytics": 213 err := analytics.Backup(ctx, res) 214 if err != nil { 215 log.Println("Failed to run backup on analytics:", err) 216 res.WriteHeader(http.StatusInternalServerError) 217 return 218 } 219 220 case "uploads": 221 err := upload.Backup(ctx, res) 222 if err != nil { 223 log.Println("Failed to run backup on uploads:", err) 224 res.WriteHeader(http.StatusInternalServerError) 225 return 226 } 227 228 case "search": 229 err := search.Backup(ctx, res) 230 if err != nil { 231 log.Println("Failed to run backup on search:", err) 232 res.WriteHeader(http.StatusInternalServerError) 233 return 234 } 235 236 default: 237 res.WriteHeader(http.StatusBadRequest) 238 } 239 } 240 241 func configUsersHandler(res http.ResponseWriter, req *http.Request) { 242 switch req.Method { 243 case http.MethodGet: 244 view, err := UsersList(req) 245 if err != nil { 246 log.Println(err) 247 res.WriteHeader(http.StatusInternalServerError) 248 errView, err := Error500() 249 if err != nil { 250 return 251 } 252 253 res.Write(errView) 254 return 255 } 256 257 res.Write(view) 258 259 case http.MethodPost: 260 // create new user 261 err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB 262 if err != nil { 263 log.Println(err) 264 res.WriteHeader(http.StatusInternalServerError) 265 errView, err := Error500() 266 if err != nil { 267 return 268 } 269 270 res.Write(errView) 271 return 272 } 273 274 email := strings.ToLower(req.FormValue("email")) 275 password := req.PostFormValue("password") 276 277 if email == "" || password == "" { 278 res.WriteHeader(http.StatusInternalServerError) 279 errView, err := Error500() 280 if err != nil { 281 return 282 } 283 284 res.Write(errView) 285 return 286 } 287 288 usr, err := user.New(email, password) 289 if err != nil { 290 log.Println(err) 291 res.WriteHeader(http.StatusInternalServerError) 292 return 293 } 294 295 _, err = db.SetUser(usr) 296 if err != nil { 297 log.Println(err) 298 res.WriteHeader(http.StatusInternalServerError) 299 errView, err := Error500() 300 if err != nil { 301 return 302 } 303 304 res.Write(errView) 305 return 306 } 307 308 http.Redirect(res, req, req.URL.String(), http.StatusFound) 309 310 default: 311 res.WriteHeader(http.StatusMethodNotAllowed) 312 } 313 } 314 315 func configUsersEditHandler(res http.ResponseWriter, req *http.Request) { 316 switch req.Method { 317 case http.MethodPost: 318 err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB 319 if err != nil { 320 log.Println(err) 321 res.WriteHeader(http.StatusInternalServerError) 322 errView, err := Error500() 323 if err != nil { 324 return 325 } 326 327 res.Write(errView) 328 return 329 } 330 331 // check if user to be edited is current user 332 j, err := db.CurrentUser(req) 333 if err != nil { 334 log.Println(err) 335 res.WriteHeader(http.StatusInternalServerError) 336 errView, err := Error500() 337 if err != nil { 338 return 339 } 340 341 res.Write(errView) 342 return 343 } 344 345 usr := &user.User{} 346 err = json.Unmarshal(j, usr) 347 if err != nil { 348 log.Println(err) 349 res.WriteHeader(http.StatusInternalServerError) 350 errView, err := Error500() 351 if err != nil { 352 return 353 } 354 355 res.Write(errView) 356 return 357 } 358 359 // check if password matches 360 password := req.PostFormValue("password") 361 362 if !user.IsUser(usr, password) { 363 log.Println("Unexpected user/password combination for", usr.Email) 364 res.WriteHeader(http.StatusBadRequest) 365 errView, err := Error405() 366 if err != nil { 367 return 368 } 369 370 res.Write(errView) 371 return 372 } 373 374 email := strings.ToLower(req.PostFormValue("email")) 375 newPassword := req.PostFormValue("new_password") 376 var updatedUser *user.User 377 if newPassword != "" { 378 updatedUser, err = user.New(email, newPassword) 379 if err != nil { 380 log.Println(err) 381 res.WriteHeader(http.StatusInternalServerError) 382 return 383 } 384 } else { 385 updatedUser, err = user.New(email, password) 386 if err != nil { 387 log.Println(err) 388 res.WriteHeader(http.StatusInternalServerError) 389 return 390 } 391 } 392 393 // set the ID to the same ID as current user 394 updatedUser.ID = usr.ID 395 396 // set user in db 397 err = db.UpdateUser(usr, updatedUser) 398 if err != nil { 399 log.Println(err) 400 res.WriteHeader(http.StatusInternalServerError) 401 errView, err := Error500() 402 if err != nil { 403 return 404 } 405 406 res.Write(errView) 407 return 408 } 409 410 // create new token 411 week := time.Now().Add(time.Hour * 24 * 7) 412 claims := map[string]interface{}{ 413 "exp": week, 414 "user": updatedUser.Email, 415 } 416 token, err := jwt.New(claims) 417 if err != nil { 418 log.Println(err) 419 res.WriteHeader(http.StatusInternalServerError) 420 errView, err := Error500() 421 if err != nil { 422 return 423 } 424 425 res.Write(errView) 426 return 427 } 428 429 // add token to cookie +1 week expiration 430 cookie := &http.Cookie{ 431 Name: "_token", 432 Value: token, 433 Expires: week, 434 Path: "/", 435 } 436 http.SetCookie(res, cookie) 437 438 // add new token cookie to the request 439 req.AddCookie(cookie) 440 441 http.Redirect(res, req, strings.TrimSuffix(req.URL.String(), "/edit"), http.StatusFound) 442 443 default: 444 res.WriteHeader(http.StatusMethodNotAllowed) 445 } 446 } 447 448 func configUsersDeleteHandler(res http.ResponseWriter, req *http.Request) { 449 switch req.Method { 450 case http.MethodPost: 451 err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB 452 if err != nil { 453 log.Println(err) 454 res.WriteHeader(http.StatusInternalServerError) 455 errView, err := Error500() 456 if err != nil { 457 return 458 } 459 460 res.Write(errView) 461 return 462 } 463 464 // do not allow current user to delete themselves 465 j, err := db.CurrentUser(req) 466 if err != nil { 467 log.Println(err) 468 res.WriteHeader(http.StatusInternalServerError) 469 errView, err := Error500() 470 if err != nil { 471 return 472 } 473 474 res.Write(errView) 475 return 476 } 477 478 usr := &user.User{} 479 err = json.Unmarshal(j, &usr) 480 if err != nil { 481 log.Println(err) 482 res.WriteHeader(http.StatusInternalServerError) 483 errView, err := Error500() 484 if err != nil { 485 return 486 } 487 488 res.Write(errView) 489 return 490 } 491 492 email := strings.ToLower(req.PostFormValue("email")) 493 494 if usr.Email == email { 495 log.Println(err) 496 res.WriteHeader(http.StatusBadRequest) 497 errView, err := Error405() 498 if err != nil { 499 return 500 } 501 502 res.Write(errView) 503 return 504 } 505 506 // delete existing user 507 err = db.DeleteUser(email) 508 if err != nil { 509 log.Println(err) 510 res.WriteHeader(http.StatusInternalServerError) 511 errView, err := Error500() 512 if err != nil { 513 return 514 } 515 516 res.Write(errView) 517 return 518 } 519 520 http.Redirect(res, req, strings.TrimSuffix(req.URL.String(), "/delete"), http.StatusFound) 521 522 default: 523 res.WriteHeader(http.StatusMethodNotAllowed) 524 } 525 } 526 527 func loginHandler(res http.ResponseWriter, req *http.Request) { 528 if !db.SystemInitComplete() { 529 redir := req.URL.Scheme + req.URL.Host + "/admin/init" 530 http.Redirect(res, req, redir, http.StatusFound) 531 return 532 } 533 534 switch req.Method { 535 case http.MethodGet: 536 if user.IsValid(req) { 537 http.Redirect(res, req, req.URL.Scheme+req.URL.Host+"/admin", http.StatusFound) 538 return 539 } 540 541 view, err := Login() 542 if err != nil { 543 log.Println(err) 544 res.WriteHeader(http.StatusInternalServerError) 545 return 546 } 547 548 res.Header().Set("Content-Type", "text/html") 549 res.Write(view) 550 551 case http.MethodPost: 552 if user.IsValid(req) { 553 http.Redirect(res, req, req.URL.Scheme+req.URL.Host+"/admin", http.StatusFound) 554 return 555 } 556 557 err := req.ParseForm() 558 if err != nil { 559 log.Println(err) 560 http.Redirect(res, req, req.URL.String(), http.StatusFound) 561 return 562 } 563 564 // check email & password 565 j, err := db.User(strings.ToLower(req.FormValue("email"))) 566 if err != nil { 567 log.Println(err) 568 http.Redirect(res, req, req.URL.String(), http.StatusFound) 569 return 570 } 571 572 if j == nil { 573 http.Redirect(res, req, req.URL.String(), http.StatusFound) 574 return 575 } 576 577 usr := &user.User{} 578 err = json.Unmarshal(j, usr) 579 if err != nil { 580 log.Println(err) 581 http.Redirect(res, req, req.URL.String(), http.StatusFound) 582 return 583 } 584 585 if !user.IsUser(usr, req.FormValue("password")) { 586 http.Redirect(res, req, req.URL.String(), http.StatusFound) 587 return 588 } 589 // create new token 590 week := time.Now().Add(time.Hour * 24 * 7) 591 claims := map[string]interface{}{ 592 "exp": week, 593 "user": usr.Email, 594 } 595 token, err := jwt.New(claims) 596 if err != nil { 597 log.Println(err) 598 http.Redirect(res, req, req.URL.String(), http.StatusFound) 599 return 600 } 601 602 // add it to cookie +1 week expiration 603 http.SetCookie(res, &http.Cookie{ 604 Name: "_token", 605 Value: token, 606 Expires: week, 607 Path: "/", 608 }) 609 610 http.Redirect(res, req, strings.TrimSuffix(req.URL.String(), "/login"), http.StatusFound) 611 } 612 } 613 614 func logoutHandler(res http.ResponseWriter, req *http.Request) { 615 http.SetCookie(res, &http.Cookie{ 616 Name: "_token", 617 Expires: time.Unix(0, 0), 618 Value: "", 619 Path: "/", 620 }) 621 622 http.Redirect(res, req, req.URL.Scheme+req.URL.Host+"/admin/login", http.StatusFound) 623 } 624 625 func forgotPasswordHandler(res http.ResponseWriter, req *http.Request) { 626 switch req.Method { 627 case http.MethodGet: 628 view, err := ForgotPassword() 629 if err != nil { 630 res.WriteHeader(http.StatusInternalServerError) 631 errView, err := Error500() 632 if err != nil { 633 return 634 } 635 636 res.Write(errView) 637 return 638 } 639 640 res.Write(view) 641 642 case http.MethodPost: 643 err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB 644 if err != nil { 645 log.Println(err) 646 res.WriteHeader(http.StatusInternalServerError) 647 errView, err := Error500() 648 if err != nil { 649 return 650 } 651 652 res.Write(errView) 653 return 654 } 655 656 // check email for user, if no user return Error 657 email := strings.ToLower(req.FormValue("email")) 658 if email == "" { 659 res.WriteHeader(http.StatusBadRequest) 660 log.Println("Failed account recovery. No email address submitted.") 661 return 662 } 663 664 _, err = db.User(email) 665 if err == db.ErrNoUserExists { 666 res.WriteHeader(http.StatusBadRequest) 667 log.Println("No user exists.", err) 668 return 669 } 670 671 if err != db.ErrNoUserExists && err != nil { 672 res.WriteHeader(http.StatusInternalServerError) 673 log.Println("Error:", err) 674 return 675 } 676 677 // create temporary key to verify user 678 key, err := db.SetRecoveryKey(email) 679 if err != nil { 680 res.WriteHeader(http.StatusInternalServerError) 681 log.Println("Failed to set account recovery key.", err) 682 return 683 } 684 685 domain, err := db.Config("domain") 686 if err != nil { 687 res.WriteHeader(http.StatusInternalServerError) 688 log.Println("Failed to get domain from configuration.", err) 689 return 690 } 691 692 body := fmt.Sprintf(` 693 There has been an account recovery request made for the user with email: 694 %s 695 696 To recover your account, please go to http://%s/admin/recover/key and enter 697 this email address along with the following secret key: 698 699 %s 700 701 If you did not make the request, ignore this message and your password 702 will remain as-is. 703 704 705 Thank you, 706 Ponzu CMS at %s 707 708 `, email, domain, key, domain) 709 710 msg := emailer.Message{ 711 To: email, 712 From: fmt.Sprintf("ponzu@%s", domain), 713 Subject: fmt.Sprintf("Account Recovery [%s]", domain), 714 Body: body, 715 } 716 717 go func() { 718 err = msg.Send() 719 if err != nil { 720 log.Println("Failed to send message to:", msg.To, "about", msg.Subject, "Error:", err) 721 } 722 }() 723 724 // redirect to /admin/recover/key and send email with key and URL 725 http.Redirect(res, req, req.URL.Scheme+req.URL.Host+"/admin/recover/key", http.StatusFound) 726 727 default: 728 res.WriteHeader(http.StatusMethodNotAllowed) 729 errView, err := Error405() 730 if err != nil { 731 return 732 } 733 734 res.Write(errView) 735 return 736 } 737 } 738 739 func recoveryKeyHandler(res http.ResponseWriter, req *http.Request) { 740 switch req.Method { 741 case http.MethodGet: 742 view, err := RecoveryKey() 743 if err != nil { 744 res.WriteHeader(http.StatusInternalServerError) 745 return 746 } 747 748 res.Write(view) 749 750 case http.MethodPost: 751 err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB 752 if err != nil { 753 log.Println("Error parsing recovery key form:", err) 754 755 res.WriteHeader(http.StatusInternalServerError) 756 res.Write([]byte("Error, please go back and try again.")) 757 return 758 } 759 760 // check for email & key match 761 email := strings.ToLower(req.FormValue("email")) 762 key := req.FormValue("key") 763 764 var actual string 765 if actual, err = db.RecoveryKey(email); err != nil || actual == "" { 766 log.Println("Error getting recovery key from database:", err) 767 768 res.WriteHeader(http.StatusInternalServerError) 769 res.Write([]byte("Error, please go back and try again.")) 770 return 771 } 772 773 if key != actual { 774 log.Println("Bad recovery key submitted:", key) 775 776 res.WriteHeader(http.StatusBadRequest) 777 res.Write([]byte("Error, please go back and try again.")) 778 return 779 } 780 781 // set user with new password 782 password := req.FormValue("password") 783 usr := &user.User{} 784 u, err := db.User(email) 785 if err != nil { 786 log.Println("Error finding user by email:", email, err) 787 788 res.WriteHeader(http.StatusInternalServerError) 789 res.Write([]byte("Error, please go back and try again.")) 790 return 791 } 792 793 if u == nil { 794 log.Println("No user found with email:", email) 795 796 res.WriteHeader(http.StatusBadRequest) 797 res.Write([]byte("Error, please go back and try again.")) 798 return 799 } 800 801 err = json.Unmarshal(u, usr) 802 if err != nil { 803 log.Println("Error decoding user from database:", err) 804 805 res.WriteHeader(http.StatusInternalServerError) 806 res.Write([]byte("Error, please go back and try again.")) 807 return 808 } 809 810 update, err := user.New(email, password) 811 if err != nil { 812 log.Println(err) 813 814 res.WriteHeader(http.StatusInternalServerError) 815 res.Write([]byte("Error, please go back and try again.")) 816 return 817 } 818 819 update.ID = usr.ID 820 821 err = db.UpdateUser(usr, update) 822 if err != nil { 823 log.Println("Error updating user:", err) 824 825 res.WriteHeader(http.StatusInternalServerError) 826 res.Write([]byte("Error, please go back and try again.")) 827 return 828 } 829 830 // redirect to /admin/login 831 redir := req.URL.Scheme + req.URL.Host + "/admin/login" 832 http.Redirect(res, req, redir, http.StatusFound) 833 834 default: 835 res.WriteHeader(http.StatusMethodNotAllowed) 836 return 837 } 838 } 839 840 func uploadContentsHandler(res http.ResponseWriter, req *http.Request) { 841 q := req.URL.Query() 842 843 order := strings.ToLower(q.Get("order")) 844 if order != "asc" { 845 order = "desc" 846 } 847 848 pt := interface{}(&item.FileUpload{}) 849 850 p, ok := pt.(editor.Editable) 851 if !ok { 852 res.WriteHeader(http.StatusInternalServerError) 853 errView, err := Error500() 854 if err != nil { 855 return 856 } 857 858 res.Write(errView) 859 return 860 } 861 862 count, err := strconv.Atoi(q.Get("count")) // int: determines number of posts to return (10 default, -1 is all) 863 if err != nil { 864 if q.Get("count") == "" { 865 count = 10 866 } else { 867 res.WriteHeader(http.StatusInternalServerError) 868 errView, err := Error500() 869 if err != nil { 870 return 871 } 872 873 res.Write(errView) 874 return 875 } 876 } 877 878 offset, err := strconv.Atoi(q.Get("offset")) // int: multiplier of count for pagination (0 default) 879 if err != nil { 880 if q.Get("offset") == "" { 881 offset = 0 882 } else { 883 res.WriteHeader(http.StatusInternalServerError) 884 errView, err := Error500() 885 if err != nil { 886 return 887 } 888 889 res.Write(errView) 890 return 891 } 892 } 893 894 opts := db.QueryOptions{ 895 Count: count, 896 Offset: offset, 897 Order: order, 898 } 899 900 b := &bytes.Buffer{} 901 var total int 902 var posts [][]byte 903 904 html := `<div class="col s9 card"> 905 <div class="card-content"> 906 <div class="row"> 907 <div class="col s8"> 908 <div class="row"> 909 <div class="card-title col s7">Uploaded Items</div> 910 <div class="col s5 input-field inline"> 911 <select class="browser-default __ponzu sort-order"> 912 <option value="DESC">New to Old</option> 913 <option value="ASC">Old to New</option> 914 </select> 915 <label class="active">Sort:</label> 916 </div> 917 <script> 918 $(function() { 919 var sort = $('select.__ponzu.sort-order'); 920 921 sort.on('change', function() { 922 var path = window.location.pathname; 923 var s = sort.val(); 924 925 window.location.replace(path + '?order=' + s); 926 }); 927 928 var order = getParam('order'); 929 if (order !== '') { 930 sort.val(order); 931 } 932 933 }); 934 </script> 935 </div> 936 </div> 937 <form class="col s4" action="/admin/uploads/search" method="get"> 938 <div class="input-field post-search inline"> 939 <label class="active">Search:</label> 940 <i class="right material-icons search-icon">search</i> 941 <input class="search" name="q" type="text" placeholder="Within all Upload fields" class="search"/> 942 <input type="hidden" name="type" value="__uploads" /> 943 </div> 944 </form> 945 </div>` 946 947 t := "__uploads" 948 status := "" 949 total, posts = db.Query(t, opts) 950 951 for i := range posts { 952 err := json.Unmarshal(posts[i], &p) 953 if err != nil { 954 log.Println("Error unmarshal json into", t, err, string(posts[i])) 955 956 post := `<li class="col s12">Error decoding data. Possible file corruption.</li>` 957 _, err := b.Write([]byte(post)) 958 if err != nil { 959 log.Println(err) 960 961 res.WriteHeader(http.StatusInternalServerError) 962 errView, err := Error500() 963 if err != nil { 964 log.Println(err) 965 } 966 967 res.Write(errView) 968 return 969 } 970 continue 971 } 972 973 post := adminPostListItem(p, t, status) 974 _, err = b.Write(post) 975 if err != nil { 976 log.Println(err) 977 978 res.WriteHeader(http.StatusInternalServerError) 979 errView, err := Error500() 980 if err != nil { 981 log.Println(err) 982 } 983 984 res.Write(errView) 985 return 986 } 987 } 988 989 html += `<ul class="posts row">` 990 991 _, err = b.Write([]byte(`</ul>`)) 992 if err != nil { 993 log.Println(err) 994 995 res.WriteHeader(http.StatusInternalServerError) 996 errView, err := Error500() 997 if err != nil { 998 log.Println(err) 999 } 1000 1001 res.Write(errView) 1002 return 1003 } 1004 1005 statusDisabled := "disabled" 1006 prevStatus := "" 1007 nextStatus := "" 1008 // total may be less than 10 (default count), so reset count to match total 1009 if total < count { 1010 count = total 1011 } 1012 // nothing previous to current list 1013 if offset == 0 { 1014 prevStatus = statusDisabled 1015 } 1016 // nothing after current list 1017 if (offset+1)*count >= total { 1018 nextStatus = statusDisabled 1019 } 1020 1021 // set up pagination values 1022 urlFmt := req.URL.Path + "?count=%d&offset=%d&&order=%s" 1023 prevURL := fmt.Sprintf(urlFmt, count, offset-1, order) 1024 nextURL := fmt.Sprintf(urlFmt, count, offset+1, order) 1025 start := 1 + count*offset 1026 end := start + count - 1 1027 1028 if total < end { 1029 end = total 1030 } 1031 1032 pagination := fmt.Sprintf(` 1033 <ul class="pagination row"> 1034 <li class="col s2 waves-effect %s"><a href="%s"><i class="material-icons">chevron_left</i></a></li> 1035 <li class="col s8">%d to %d of %d</li> 1036 <li class="col s2 waves-effect %s"><a href="%s"><i class="material-icons">chevron_right</i></a></li> 1037 </ul> 1038 `, prevStatus, prevURL, start, end, total, nextStatus, nextURL) 1039 1040 // show indicator that a collection of items will be listed implicitly, but 1041 // that none are created yet 1042 if total < 1 { 1043 pagination = ` 1044 <ul class="pagination row"> 1045 <li class="col s2 waves-effect disabled"><a href="#"><i class="material-icons">chevron_left</i></a></li> 1046 <li class="col s8">0 to 0 of 0</li> 1047 <li class="col s2 waves-effect disabled"><a href="#"><i class="material-icons">chevron_right</i></a></li> 1048 </ul> 1049 ` 1050 } 1051 1052 _, err = b.Write([]byte(pagination + `</div></div>`)) 1053 if err != nil { 1054 log.Println(err) 1055 1056 res.WriteHeader(http.StatusInternalServerError) 1057 errView, err := Error500() 1058 if err != nil { 1059 log.Println(err) 1060 } 1061 1062 res.Write(errView) 1063 return 1064 } 1065 1066 script := ` 1067 <script> 1068 $(function() { 1069 var del = $('.quick-delete-post.__ponzu span'); 1070 del.on('click', function(e) { 1071 if (confirm("[Ponzu] Please confirm:\n\nAre you sure you want to delete this post?\nThis cannot be undone.")) { 1072 $(e.target).parent().submit(); 1073 } 1074 }); 1075 }); 1076 1077 // disable link from being clicked if parent is 'disabled' 1078 $(function() { 1079 $('ul.pagination li.disabled a').on('click', function(e) { 1080 e.preventDefault(); 1081 }); 1082 }); 1083 </script> 1084 ` 1085 1086 btn := `<div class="col s3"><a href="/admin/edit/upload" class="btn new-post waves-effect waves-light">New Upload</a></div></div>` 1087 html = html + b.String() + script + btn 1088 1089 adminView, err := Admin([]byte(html)) 1090 if err != nil { 1091 log.Println(err) 1092 res.WriteHeader(http.StatusInternalServerError) 1093 return 1094 } 1095 1096 res.Header().Set("Content-Type", "text/html") 1097 res.Write(adminView) 1098 } 1099 1100 func contentsHandler(res http.ResponseWriter, req *http.Request) { 1101 q := req.URL.Query() 1102 t := q.Get("type") 1103 if t == "" { 1104 res.WriteHeader(http.StatusBadRequest) 1105 errView, err := Error400() 1106 if err != nil { 1107 return 1108 } 1109 1110 res.Write(errView) 1111 return 1112 } 1113 1114 order := strings.ToLower(q.Get("order")) 1115 if order != "asc" { 1116 order = "desc" 1117 } 1118 1119 status := q.Get("status") 1120 1121 if _, ok := item.Types[t]; !ok { 1122 res.WriteHeader(http.StatusBadRequest) 1123 errView, err := Error400() 1124 if err != nil { 1125 return 1126 } 1127 1128 res.Write(errView) 1129 return 1130 } 1131 1132 pt := item.Types[t]() 1133 1134 p, ok := pt.(editor.Editable) 1135 if !ok { 1136 res.WriteHeader(http.StatusInternalServerError) 1137 errView, err := Error500() 1138 if err != nil { 1139 return 1140 } 1141 1142 res.Write(errView) 1143 return 1144 } 1145 1146 var hasExt bool 1147 _, ok = pt.(api.Createable) 1148 if ok { 1149 hasExt = true 1150 } 1151 1152 count, err := strconv.Atoi(q.Get("count")) // int: determines number of posts to return (10 default, -1 is all) 1153 if err != nil { 1154 if q.Get("count") == "" { 1155 count = 10 1156 } else { 1157 res.WriteHeader(http.StatusInternalServerError) 1158 errView, err := Error500() 1159 if err != nil { 1160 return 1161 } 1162 1163 res.Write(errView) 1164 return 1165 } 1166 } 1167 1168 offset, err := strconv.Atoi(q.Get("offset")) // int: multiplier of count for pagination (0 default) 1169 if err != nil { 1170 if q.Get("offset") == "" { 1171 offset = 0 1172 } else { 1173 res.WriteHeader(http.StatusInternalServerError) 1174 errView, err := Error500() 1175 if err != nil { 1176 return 1177 } 1178 1179 res.Write(errView) 1180 return 1181 } 1182 } 1183 1184 opts := db.QueryOptions{ 1185 Count: count, 1186 Offset: offset, 1187 Order: order, 1188 } 1189 1190 var specifier string 1191 if status == "public" || status == "" { 1192 specifier = "__sorted" 1193 } else if status == "pending" { 1194 specifier = "__pending" 1195 } 1196 1197 b := &bytes.Buffer{} 1198 var total int 1199 var posts [][]byte 1200 1201 html := `<div class="col s9 card"> 1202 <div class="card-content"> 1203 <div class="row"> 1204 <div class="col s8"> 1205 <div class="row"> 1206 <div class="card-title col s7">` + t + ` Items</div> 1207 <div class="col s5 input-field inline"> 1208 <select class="browser-default __ponzu sort-order"> 1209 <option value="DESC">New to Old</option> 1210 <option value="ASC">Old to New</option> 1211 </select> 1212 <label class="active">Sort:</label> 1213 </div> 1214 <script> 1215 $(function() { 1216 var sort = $('select.__ponzu.sort-order'); 1217 1218 sort.on('change', function() { 1219 var path = window.location.pathname; 1220 var s = sort.val(); 1221 var t = getParam('type'); 1222 var status = getParam('status'); 1223 1224 if (status == "") { 1225 status = "public"; 1226 } 1227 1228 window.location.replace(path + '?type=' + t + '&order=' + s + '&status=' + status); 1229 }); 1230 1231 var order = getParam('order'); 1232 if (order !== '') { 1233 sort.val(order); 1234 } 1235 1236 }); 1237 </script> 1238 </div> 1239 </div> 1240 <form class="col s4" action="/admin/contents/search" method="get"> 1241 <div class="input-field post-search inline"> 1242 <label class="active">Search:</label> 1243 <i class="right material-icons search-icon">search</i> 1244 <input class="search" name="q" type="text" placeholder="Within all ` + t + ` fields" class="search"/> 1245 <input type="hidden" name="type" value="` + t + `" /> 1246 <input type="hidden" name="status" value="` + status + `" /> 1247 </div> 1248 </form> 1249 </div>` 1250 if hasExt { 1251 if status == "" { 1252 q.Set("status", "public") 1253 } 1254 1255 // always start from top of results when changing public/pending 1256 q.Del("count") 1257 q.Del("offset") 1258 1259 q.Set("status", "public") 1260 publicURL := req.URL.Path + "?" + q.Encode() 1261 1262 q.Set("status", "pending") 1263 pendingURL := req.URL.Path + "?" + q.Encode() 1264 1265 switch status { 1266 case "public", "": 1267 // get __sorted posts of type t from the db 1268 total, posts = db.Query(t+specifier, opts) 1269 1270 html += `<div class="row externalable"> 1271 <span class="description">Status:</span> 1272 <span class="active">Public</span> 1273 | 1274 <a href="` + pendingURL + `">Pending</a> 1275 </div>` 1276 1277 for i := range posts { 1278 err := json.Unmarshal(posts[i], &p) 1279 if err != nil { 1280 log.Println("Error unmarshal json into", t, err, string(posts[i])) 1281 1282 post := `<li class="col s12">Error decoding data. Possible file corruption.</li>` 1283 _, err := b.Write([]byte(post)) 1284 if err != nil { 1285 log.Println(err) 1286 1287 res.WriteHeader(http.StatusInternalServerError) 1288 errView, err := Error500() 1289 if err != nil { 1290 log.Println(err) 1291 } 1292 1293 res.Write(errView) 1294 return 1295 } 1296 1297 continue 1298 } 1299 1300 post := adminPostListItem(p, t, status) 1301 _, err = b.Write(post) 1302 if err != nil { 1303 log.Println(err) 1304 1305 res.WriteHeader(http.StatusInternalServerError) 1306 errView, err := Error500() 1307 if err != nil { 1308 log.Println(err) 1309 } 1310 1311 res.Write(errView) 1312 return 1313 } 1314 } 1315 1316 case "pending": 1317 // get __pending posts of type t from the db 1318 total, posts = db.Query(t+"__pending", opts) 1319 1320 html += `<div class="row externalable"> 1321 <span class="description">Status:</span> 1322 <a href="` + publicURL + `">Public</a> 1323 | 1324 <span class="active">Pending</span> 1325 </div>` 1326 1327 for i := len(posts) - 1; i >= 0; i-- { 1328 err := json.Unmarshal(posts[i], &p) 1329 if err != nil { 1330 log.Println("Error unmarshal json into", t, err, string(posts[i])) 1331 1332 post := `<li class="col s12">Error decoding data. Possible file corruption.</li>` 1333 _, err := b.Write([]byte(post)) 1334 if err != nil { 1335 log.Println(err) 1336 1337 res.WriteHeader(http.StatusInternalServerError) 1338 errView, err := Error500() 1339 if err != nil { 1340 log.Println(err) 1341 } 1342 1343 res.Write(errView) 1344 return 1345 } 1346 continue 1347 } 1348 1349 post := adminPostListItem(p, t, status) 1350 _, err = b.Write(post) 1351 if err != nil { 1352 log.Println(err) 1353 1354 res.WriteHeader(http.StatusInternalServerError) 1355 errView, err := Error500() 1356 if err != nil { 1357 log.Println(err) 1358 } 1359 1360 res.Write(errView) 1361 return 1362 } 1363 } 1364 } 1365 1366 } else { 1367 total, posts = db.Query(t+specifier, opts) 1368 1369 for i := range posts { 1370 err := json.Unmarshal(posts[i], &p) 1371 if err != nil { 1372 log.Println("Error unmarshal json into", t, err, string(posts[i])) 1373 1374 post := `<li class="col s12">Error decoding data. Possible file corruption.</li>` 1375 _, err := b.Write([]byte(post)) 1376 if err != nil { 1377 log.Println(err) 1378 1379 res.WriteHeader(http.StatusInternalServerError) 1380 errView, err := Error500() 1381 if err != nil { 1382 log.Println(err) 1383 } 1384 1385 res.Write(errView) 1386 return 1387 } 1388 continue 1389 } 1390 1391 post := adminPostListItem(p, t, status) 1392 _, err = b.Write(post) 1393 if err != nil { 1394 log.Println(err) 1395 1396 res.WriteHeader(http.StatusInternalServerError) 1397 errView, err := Error500() 1398 if err != nil { 1399 log.Println(err) 1400 } 1401 1402 res.Write(errView) 1403 return 1404 } 1405 } 1406 } 1407 1408 html += `<ul class="posts row">` 1409 1410 _, err = b.Write([]byte(`</ul>`)) 1411 if err != nil { 1412 log.Println(err) 1413 1414 res.WriteHeader(http.StatusInternalServerError) 1415 errView, err := Error500() 1416 if err != nil { 1417 log.Println(err) 1418 } 1419 1420 res.Write(errView) 1421 return 1422 } 1423 1424 statusDisabled := "disabled" 1425 prevStatus := "" 1426 nextStatus := "" 1427 // total may be less than 10 (default count), so reset count to match total 1428 if total < count { 1429 count = total 1430 } 1431 // nothing previous to current list 1432 if offset == 0 { 1433 prevStatus = statusDisabled 1434 } 1435 // nothing after current list 1436 if (offset+1)*count >= total { 1437 nextStatus = statusDisabled 1438 } 1439 1440 // set up pagination values 1441 urlFmt := req.URL.Path + "?count=%d&offset=%d&&order=%s&status=%s&type=%s" 1442 prevURL := fmt.Sprintf(urlFmt, count, offset-1, order, status, t) 1443 nextURL := fmt.Sprintf(urlFmt, count, offset+1, order, status, t) 1444 start := 1 + count*offset 1445 end := start + count - 1 1446 1447 if total < end { 1448 end = total 1449 } 1450 1451 pagination := fmt.Sprintf(` 1452 <ul class="pagination row"> 1453 <li class="col s2 waves-effect %s"><a href="%s"><i class="material-icons">chevron_left</i></a></li> 1454 <li class="col s8">%d to %d of %d</li> 1455 <li class="col s2 waves-effect %s"><a href="%s"><i class="material-icons">chevron_right</i></a></li> 1456 </ul> 1457 `, prevStatus, prevURL, start, end, total, nextStatus, nextURL) 1458 1459 // show indicator that a collection of items will be listed implicitly, but 1460 // that none are created yet 1461 if total < 1 { 1462 pagination = ` 1463 <ul class="pagination row"> 1464 <li class="col s2 waves-effect disabled"><a href="#"><i class="material-icons">chevron_left</i></a></li> 1465 <li class="col s8">0 to 0 of 0</li> 1466 <li class="col s2 waves-effect disabled"><a href="#"><i class="material-icons">chevron_right</i></a></li> 1467 </ul> 1468 ` 1469 } 1470 1471 _, err = b.Write([]byte(pagination + `</div></div>`)) 1472 if err != nil { 1473 log.Println(err) 1474 1475 res.WriteHeader(http.StatusInternalServerError) 1476 errView, err := Error500() 1477 if err != nil { 1478 log.Println(err) 1479 } 1480 1481 res.Write(errView) 1482 return 1483 } 1484 1485 script := ` 1486 <script> 1487 $(function() { 1488 var del = $('.quick-delete-post.__ponzu span'); 1489 del.on('click', function(e) { 1490 if (confirm("[Ponzu] Please confirm:\n\nAre you sure you want to delete this post?\nThis cannot be undone.")) { 1491 $(e.target).parent().submit(); 1492 } 1493 }); 1494 }); 1495 1496 // disable link from being clicked if parent is 'disabled' 1497 $(function() { 1498 $('ul.pagination li.disabled a').on('click', function(e) { 1499 e.preventDefault(); 1500 }); 1501 }); 1502 </script> 1503 ` 1504 1505 btn := `<div class="col s3"> 1506 <a href="/admin/edit?type=` + t + `" class="btn new-post waves-effect waves-light"> 1507 New ` + t + ` 1508 </a>` 1509 1510 if _, ok := pt.(format.CSVFormattable); ok { 1511 btn += `<br/> 1512 <a href="/admin/contents/export?type=` + t + `&format=csv" class="green darken-4 btn export-post waves-effect waves-light"> 1513 <i class="material-icons left">system_update_alt</i> 1514 CSV 1515 </a>` 1516 } 1517 1518 html += b.String() + script + btn + `</div></div>` 1519 1520 adminView, err := Admin([]byte(html)) 1521 if err != nil { 1522 log.Println(err) 1523 res.WriteHeader(http.StatusInternalServerError) 1524 return 1525 } 1526 1527 res.Header().Set("Content-Type", "text/html") 1528 res.Write(adminView) 1529 } 1530 1531 // adminPostListItem is a helper to create the li containing a post. 1532 // p is the asserted post as an Editable, t is the Type of the post. 1533 // specifier is passed to append a name to a namespace like __pending 1534 func adminPostListItem(e editor.Editable, typeName, status string) []byte { 1535 s, ok := e.(item.Sortable) 1536 if !ok { 1537 log.Println("Content type", typeName, "doesn't implement item.Sortable") 1538 post := `<li class="col s12">Error retreiving data. Your data type doesn't implement necessary interfaces. (item.Sortable)</li>` 1539 return []byte(post) 1540 } 1541 1542 i, ok := e.(item.Identifiable) 1543 if !ok { 1544 log.Println("Content type", typeName, "doesn't implement item.Identifiable") 1545 post := `<li class="col s12">Error retreiving data. Your data type doesn't implement necessary interfaces. (item.Identifiable)</li>` 1546 return []byte(post) 1547 } 1548 1549 // use sort to get other info to display in admin UI post list 1550 tsTime := time.Unix(int64(s.Time()/1000), 0) 1551 upTime := time.Unix(int64(s.Touch()/1000), 0) 1552 updatedTime := upTime.Format("01/02/06 03:04 PM") 1553 publishTime := tsTime.Format("01/02/06") 1554 1555 cid := fmt.Sprintf("%d", i.ItemID()) 1556 1557 switch status { 1558 case "public", "": 1559 status = "" 1560 default: 1561 status = "__" + status 1562 } 1563 action := "/admin/edit/delete" 1564 link := `<a href="/admin/edit?type=` + typeName + `&status=` + strings.TrimPrefix(status, "__") + `&id=` + cid + `">` + i.String() + `</a>` 1565 if strings.HasPrefix(typeName, "__") { 1566 link = `<a href="/admin/edit/upload?id=` + cid + `">` + i.String() + `</a>` 1567 action = "/admin/edit/upload/delete" 1568 } 1569 1570 post := ` 1571 <li class="col s12"> 1572 ` + link + ` 1573 <span class="post-detail">Updated: ` + updatedTime + `</span> 1574 <span class="publish-date right">` + publishTime + `</span> 1575 1576 <form enctype="multipart/form-data" class="quick-delete-post __ponzu right" action="` + action + `" method="post"> 1577 <span>Delete</span> 1578 <input type="hidden" name="id" value="` + cid + `" /> 1579 <input type="hidden" name="type" value="` + typeName + status + `" /> 1580 </form> 1581 </li>` 1582 1583 return []byte(post) 1584 } 1585 1586 func approveContentHandler(res http.ResponseWriter, req *http.Request) { 1587 if req.Method != http.MethodPost { 1588 res.WriteHeader(http.StatusMethodNotAllowed) 1589 errView, err := Error405() 1590 if err != nil { 1591 return 1592 } 1593 1594 res.Write(errView) 1595 return 1596 } 1597 1598 err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB 1599 if err != nil { 1600 res.WriteHeader(http.StatusInternalServerError) 1601 errView, err := Error500() 1602 if err != nil { 1603 return 1604 } 1605 1606 res.Write(errView) 1607 return 1608 } 1609 1610 pendingID := req.FormValue("id") 1611 1612 t := req.FormValue("type") 1613 if strings.Contains(t, "__") { 1614 t = strings.Split(t, "__")[0] 1615 } 1616 1617 post := item.Types[t]() 1618 1619 // run hooks 1620 hook, ok := post.(item.Hookable) 1621 if !ok { 1622 log.Println("Type", t, "does not implement item.Hookable or embed item.Item.") 1623 res.WriteHeader(http.StatusBadRequest) 1624 errView, err := Error400() 1625 if err != nil { 1626 return 1627 } 1628 1629 res.Write(errView) 1630 return 1631 } 1632 1633 // check if we have a Mergeable 1634 m, ok := post.(editor.Mergeable) 1635 if !ok { 1636 log.Println("Content type", t, "must implement editor.Mergeable before it can be approved.") 1637 res.WriteHeader(http.StatusBadRequest) 1638 errView, err := Error400() 1639 if err != nil { 1640 return 1641 } 1642 1643 res.Write(errView) 1644 return 1645 } 1646 1647 dec := schema.NewDecoder() 1648 dec.IgnoreUnknownKeys(true) 1649 dec.SetAliasTag("json") 1650 err = dec.Decode(post, req.Form) 1651 if err != nil { 1652 log.Println("Error decoding post form for content approval:", t, err) 1653 res.WriteHeader(http.StatusInternalServerError) 1654 errView, err := Error500() 1655 if err != nil { 1656 return 1657 } 1658 1659 res.Write(errView) 1660 return 1661 } 1662 1663 err = hook.BeforeApprove(res, req) 1664 if err != nil { 1665 log.Println("Error running BeforeApprove hook in approveContentHandler for:", t, err) 1666 return 1667 } 1668 1669 // call its Approve method 1670 err = m.Approve(res, req) 1671 if err != nil { 1672 log.Println("Error running Approve method in approveContentHandler for:", t, err) 1673 return 1674 } 1675 1676 err = hook.AfterApprove(res, req) 1677 if err != nil { 1678 log.Println("Error running AfterApprove hook in approveContentHandler for:", t, err) 1679 return 1680 } 1681 1682 err = hook.BeforeSave(res, req) 1683 if err != nil { 1684 log.Println("Error running BeforeSave hook in approveContentHandler for:", t, err) 1685 return 1686 } 1687 1688 // Store the content in the bucket t 1689 id, err := db.SetContent(t+":-1", req.Form) 1690 if err != nil { 1691 log.Println("Error storing content in approveContentHandler for:", t, err) 1692 res.WriteHeader(http.StatusInternalServerError) 1693 errView, err := Error500() 1694 if err != nil { 1695 return 1696 } 1697 1698 res.Write(errView) 1699 return 1700 } 1701 1702 // set the target in the context so user can get saved value from db in hook 1703 ctx := context.WithValue(req.Context(), "target", fmt.Sprintf("%s:%d", t, id)) 1704 req = req.WithContext(ctx) 1705 1706 err = hook.AfterSave(res, req) 1707 if err != nil { 1708 log.Println("Error running AfterSave hook in approveContentHandler for:", t, err) 1709 return 1710 } 1711 1712 if pendingID != "" { 1713 err = db.DeleteContent(req.FormValue("type") + ":" + pendingID) 1714 if err != nil { 1715 log.Println("Failed to remove content after approval:", err) 1716 } 1717 } 1718 1719 // redirect to the new approved content's editor 1720 redir := req.URL.Scheme + req.URL.Host + strings.TrimSuffix(req.URL.Path, "/approve") 1721 redir += fmt.Sprintf("?type=%s&id=%d", t, id) 1722 http.Redirect(res, req, redir, http.StatusFound) 1723 } 1724 1725 func editHandler(res http.ResponseWriter, req *http.Request) { 1726 switch req.Method { 1727 case http.MethodGet: 1728 q := req.URL.Query() 1729 i := q.Get("id") 1730 t := q.Get("type") 1731 status := q.Get("status") 1732 1733 contentType, ok := item.Types[t] 1734 if !ok { 1735 fmt.Fprintf(res, item.ErrTypeNotRegistered.Error(), t) 1736 return 1737 } 1738 post := contentType() 1739 1740 if i != "" { 1741 if status == "pending" { 1742 t = t + "__pending" 1743 } 1744 1745 data, err := db.Content(t + ":" + i) 1746 if err != nil { 1747 log.Println(err) 1748 res.WriteHeader(http.StatusInternalServerError) 1749 errView, err := Error500() 1750 if err != nil { 1751 return 1752 } 1753 1754 res.Write(errView) 1755 return 1756 } 1757 1758 if len(data) < 1 || data == nil { 1759 res.WriteHeader(http.StatusNotFound) 1760 errView, err := Error404() 1761 if err != nil { 1762 return 1763 } 1764 1765 res.Write(errView) 1766 return 1767 } 1768 1769 err = json.Unmarshal(data, post) 1770 if err != nil { 1771 log.Println(err) 1772 res.WriteHeader(http.StatusInternalServerError) 1773 errView, err := Error500() 1774 if err != nil { 1775 return 1776 } 1777 1778 res.Write(errView) 1779 return 1780 } 1781 } else { 1782 item, ok := post.(item.Identifiable) 1783 if !ok { 1784 log.Println("Content type", t, "doesn't implement item.Identifiable") 1785 return 1786 } 1787 1788 item.SetItemID(-1) 1789 } 1790 1791 m, err := manager.Manage(post.(editor.Editable), t) 1792 if err != nil { 1793 log.Println(err) 1794 res.WriteHeader(http.StatusInternalServerError) 1795 errView, err := Error500() 1796 if err != nil { 1797 return 1798 } 1799 1800 res.Write(errView) 1801 return 1802 } 1803 1804 adminView, err := Admin(m) 1805 if err != nil { 1806 log.Println(err) 1807 res.WriteHeader(http.StatusInternalServerError) 1808 return 1809 } 1810 1811 res.Header().Set("Content-Type", "text/html") 1812 res.Write(adminView) 1813 1814 case http.MethodPost: 1815 err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB 1816 if err != nil { 1817 log.Println(err) 1818 res.WriteHeader(http.StatusInternalServerError) 1819 errView, err := Error500() 1820 if err != nil { 1821 return 1822 } 1823 1824 res.Write(errView) 1825 return 1826 } 1827 1828 cid := req.FormValue("id") 1829 t := req.FormValue("type") 1830 ts := req.FormValue("timestamp") 1831 up := req.FormValue("updated") 1832 1833 // create a timestamp if one was not set 1834 if ts == "" { 1835 ts = fmt.Sprintf("%d", int64(time.Nanosecond)*time.Now().UTC().UnixNano()/int64(time.Millisecond)) 1836 req.PostForm.Set("timestamp", ts) 1837 } 1838 1839 if up == "" { 1840 req.PostForm.Set("updated", ts) 1841 } 1842 1843 urlPaths, err := upload.StoreFiles(req) 1844 if err != nil { 1845 log.Println(err) 1846 res.WriteHeader(http.StatusInternalServerError) 1847 errView, err := Error500() 1848 if err != nil { 1849 return 1850 } 1851 1852 res.Write(errView) 1853 return 1854 } 1855 1856 for name, urlPath := range urlPaths { 1857 req.PostForm.Set(name, urlPath) 1858 } 1859 1860 // check for any multi-value fields (ex. checkbox fields) 1861 // and correctly format for db storage. Essentially, we need 1862 // fieldX.0: value1, fieldX.1: value2 => fieldX: []string{value1, value2} 1863 fieldOrderValue := make(map[string]map[string][]string) 1864 for k, v := range req.PostForm { 1865 if strings.Contains(k, ".") { 1866 fo := strings.Split(k, ".") 1867 1868 // put the order and the field value into map 1869 field := string(fo[0]) 1870 order := string(fo[1]) 1871 if len(fieldOrderValue[field]) == 0 { 1872 fieldOrderValue[field] = make(map[string][]string) 1873 } 1874 1875 // orderValue is 0:[?type=Thing&id=1] 1876 orderValue := fieldOrderValue[field] 1877 orderValue[order] = v 1878 fieldOrderValue[field] = orderValue 1879 1880 // discard the post form value with name.N 1881 req.PostForm.Del(k) 1882 } 1883 1884 } 1885 1886 // add/set the key & value to the post form in order 1887 for f, ov := range fieldOrderValue { 1888 for i := 0; i < len(ov); i++ { 1889 position := fmt.Sprintf("%d", i) 1890 fieldValue := ov[position] 1891 1892 if req.PostForm.Get(f) == "" { 1893 for i, fv := range fieldValue { 1894 if i == 0 { 1895 req.PostForm.Set(f, fv) 1896 } else { 1897 req.PostForm.Add(f, fv) 1898 } 1899 } 1900 } else { 1901 for _, fv := range fieldValue { 1902 req.PostForm.Add(f, fv) 1903 } 1904 } 1905 } 1906 } 1907 1908 pt := t 1909 if strings.Contains(t, "__") { 1910 pt = strings.Split(t, "__")[0] 1911 } 1912 1913 p, ok := item.Types[pt] 1914 if !ok { 1915 log.Println("Type", t, "is not a content type. Cannot edit or save.") 1916 res.WriteHeader(http.StatusBadRequest) 1917 errView, err := Error400() 1918 if err != nil { 1919 return 1920 } 1921 1922 res.Write(errView) 1923 return 1924 } 1925 1926 post := p() 1927 hook, ok := post.(item.Hookable) 1928 if !ok { 1929 log.Println("Type", pt, "does not implement item.Hookable or embed item.Item.") 1930 res.WriteHeader(http.StatusBadRequest) 1931 errView, err := Error400() 1932 if err != nil { 1933 return 1934 } 1935 1936 res.Write(errView) 1937 return 1938 } 1939 1940 // Let's be nice and make a proper item for the Hookable methods 1941 dec := schema.NewDecoder() 1942 dec.IgnoreUnknownKeys(true) 1943 dec.SetAliasTag("json") 1944 err = dec.Decode(post, req.PostForm) 1945 if err != nil { 1946 log.Println("Error decoding post form for edit handler:", t, err) 1947 res.WriteHeader(http.StatusBadRequest) 1948 errView, err := Error400() 1949 if err != nil { 1950 return 1951 } 1952 1953 res.Write(errView) 1954 return 1955 } 1956 1957 if cid == "-1" { 1958 err = hook.BeforeAdminCreate(res, req) 1959 if err != nil { 1960 log.Println("Error running BeforeAdminCreate method in editHandler for:", t, err) 1961 return 1962 } 1963 } else { 1964 err = hook.BeforeAdminUpdate(res, req) 1965 if err != nil { 1966 log.Println("Error running BeforeAdminUpdate method in editHandler for:", t, err) 1967 return 1968 } 1969 } 1970 1971 err = hook.BeforeSave(res, req) 1972 if err != nil { 1973 log.Println("Error running BeforeSave method in editHandler for:", t, err) 1974 return 1975 } 1976 1977 id, err := db.SetContent(t+":"+cid, req.PostForm) 1978 if err != nil { 1979 log.Println(err) 1980 res.WriteHeader(http.StatusInternalServerError) 1981 errView, err := Error500() 1982 if err != nil { 1983 return 1984 } 1985 1986 res.Write(errView) 1987 return 1988 } 1989 1990 // set the target in the context so user can get saved value from db in hook 1991 ctx := context.WithValue(req.Context(), "target", fmt.Sprintf("%s:%d", t, id)) 1992 req = req.WithContext(ctx) 1993 1994 err = hook.AfterSave(res, req) 1995 if err != nil { 1996 log.Println("Error running AfterSave method in editHandler for:", t, err) 1997 return 1998 } 1999 2000 if cid == "-1" { 2001 err = hook.AfterAdminCreate(res, req) 2002 if err != nil { 2003 log.Println("Error running AfterAdminUpdate method in editHandler for:", t, err) 2004 return 2005 } 2006 } else { 2007 err = hook.AfterAdminUpdate(res, req) 2008 if err != nil { 2009 log.Println("Error running AfterAdminUpdate method in editHandler for:", t, err) 2010 return 2011 } 2012 } 2013 2014 scheme := req.URL.Scheme 2015 host := req.URL.Host 2016 path := req.URL.Path 2017 sid := fmt.Sprintf("%d", id) 2018 redir := scheme + host + path + "?type=" + pt + "&id=" + sid 2019 2020 if req.URL.Query().Get("status") == "pending" { 2021 redir += "&status=pending" 2022 } 2023 2024 http.Redirect(res, req, redir, http.StatusFound) 2025 2026 default: 2027 res.WriteHeader(http.StatusMethodNotAllowed) 2028 } 2029 } 2030 2031 func deleteHandler(res http.ResponseWriter, req *http.Request) { 2032 if req.Method != http.MethodPost { 2033 res.WriteHeader(http.StatusMethodNotAllowed) 2034 return 2035 } 2036 2037 err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB 2038 if err != nil { 2039 log.Println(err) 2040 res.WriteHeader(http.StatusInternalServerError) 2041 errView, err := Error500() 2042 if err != nil { 2043 return 2044 } 2045 2046 res.Write(errView) 2047 return 2048 } 2049 2050 id := req.FormValue("id") 2051 t := req.FormValue("type") 2052 ct := t 2053 2054 if id == "" || t == "" { 2055 res.WriteHeader(http.StatusBadRequest) 2056 return 2057 } 2058 2059 // catch specifier suffix from delete form value 2060 if strings.Contains(t, "__") { 2061 spec := strings.Split(t, "__") 2062 ct = spec[0] 2063 } 2064 2065 p, ok := item.Types[ct] 2066 if !ok { 2067 log.Println("Type", t, "does not implement item.Hookable or embed item.Item.") 2068 res.WriteHeader(http.StatusBadRequest) 2069 errView, err := Error400() 2070 if err != nil { 2071 return 2072 } 2073 2074 res.Write(errView) 2075 return 2076 } 2077 2078 post := p() 2079 hook, ok := post.(item.Hookable) 2080 if !ok { 2081 log.Println("Type", t, "does not implement item.Hookable or embed item.Item.") 2082 res.WriteHeader(http.StatusBadRequest) 2083 errView, err := Error400() 2084 if err != nil { 2085 return 2086 } 2087 2088 res.Write(errView) 2089 return 2090 } 2091 2092 data, err := db.Content(t + ":" + id) 2093 if err != nil { 2094 log.Println("Error in db.Content ", t+":"+id, err) 2095 return 2096 } 2097 2098 err = json.Unmarshal(data, post) 2099 if err != nil { 2100 log.Println("Error unmarshalling ", t, "=", id, err, " Hooks will be called on a zero-value.") 2101 } 2102 2103 reject := req.URL.Query().Get("reject") 2104 if reject == "true" { 2105 err = hook.BeforeReject(res, req) 2106 if err != nil { 2107 log.Println("Error running BeforeReject method in deleteHandler for:", t, err) 2108 return 2109 } 2110 } 2111 2112 err = hook.BeforeAdminDelete(res, req) 2113 if err != nil { 2114 log.Println("Error running BeforeAdminDelete method in deleteHandler for:", t, err) 2115 return 2116 } 2117 2118 err = hook.BeforeDelete(res, req) 2119 if err != nil { 2120 log.Println("Error running BeforeDelete method in deleteHandler for:", t, err) 2121 return 2122 } 2123 2124 err = db.DeleteContent(t + ":" + id) 2125 if err != nil { 2126 log.Println(err) 2127 res.WriteHeader(http.StatusInternalServerError) 2128 return 2129 } 2130 2131 err = hook.AfterDelete(res, req) 2132 if err != nil { 2133 log.Println("Error running AfterDelete method in deleteHandler for:", t, err) 2134 return 2135 } 2136 2137 err = hook.AfterAdminDelete(res, req) 2138 if err != nil { 2139 log.Println("Error running AfterDelete method in deleteHandler for:", t, err) 2140 return 2141 } 2142 2143 if reject == "true" { 2144 err = hook.AfterReject(res, req) 2145 if err != nil { 2146 log.Println("Error running AfterReject method in deleteHandler for:", t, err) 2147 return 2148 } 2149 } 2150 2151 redir := strings.TrimSuffix(req.URL.Scheme+req.URL.Host+req.URL.Path, "/edit/delete") 2152 redir = redir + "/contents?type=" + ct 2153 http.Redirect(res, req, redir, http.StatusFound) 2154 } 2155 2156 func deleteUploadHandler(res http.ResponseWriter, req *http.Request) { 2157 if req.Method != http.MethodPost { 2158 res.WriteHeader(http.StatusMethodNotAllowed) 2159 return 2160 } 2161 2162 err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB 2163 if err != nil { 2164 log.Println(err) 2165 res.WriteHeader(http.StatusInternalServerError) 2166 errView, err := Error500() 2167 if err != nil { 2168 return 2169 } 2170 2171 res.Write(errView) 2172 return 2173 } 2174 2175 id := req.FormValue("id") 2176 t := "__uploads" 2177 2178 if id == "" || t == "" { 2179 res.WriteHeader(http.StatusBadRequest) 2180 return 2181 } 2182 2183 post := interface{}(&item.FileUpload{}) 2184 hook, ok := post.(item.Hookable) 2185 if !ok { 2186 log.Println("Type", t, "does not implement item.Hookable or embed item.Item.") 2187 res.WriteHeader(http.StatusBadRequest) 2188 errView, err := Error400() 2189 if err != nil { 2190 return 2191 } 2192 2193 res.Write(errView) 2194 return 2195 } 2196 2197 err = hook.BeforeDelete(res, req) 2198 if err != nil { 2199 log.Println("Error running BeforeDelete method in deleteHandler for:", t, err) 2200 return 2201 } 2202 2203 dbTarget := t + ":" + id 2204 2205 // delete from file system, if good, we continue to delete 2206 // from database, if bad error 500 2207 err = deleteUploadFromDisk(dbTarget) 2208 if err != nil { 2209 log.Println(err) 2210 res.WriteHeader(http.StatusInternalServerError) 2211 return 2212 } 2213 2214 err = db.DeleteUpload(dbTarget) 2215 if err != nil { 2216 log.Println(err) 2217 res.WriteHeader(http.StatusInternalServerError) 2218 return 2219 } 2220 2221 err = hook.AfterDelete(res, req) 2222 if err != nil { 2223 log.Println("Error running AfterDelete method in deleteHandler for:", t, err) 2224 return 2225 } 2226 2227 redir := "/admin/uploads" 2228 http.Redirect(res, req, redir, http.StatusFound) 2229 } 2230 2231 func editUploadHandler(res http.ResponseWriter, req *http.Request) { 2232 switch req.Method { 2233 case http.MethodGet: 2234 q := req.URL.Query() 2235 i := q.Get("id") 2236 t := "__uploads" 2237 2238 post := &item.FileUpload{} 2239 2240 if i != "" { 2241 data, err := db.Upload(t + ":" + i) 2242 if err != nil { 2243 log.Println(err) 2244 res.WriteHeader(http.StatusInternalServerError) 2245 errView, err := Error500() 2246 if err != nil { 2247 return 2248 } 2249 2250 res.Write(errView) 2251 return 2252 } 2253 2254 if len(data) < 1 || data == nil { 2255 res.WriteHeader(http.StatusNotFound) 2256 errView, err := Error404() 2257 if err != nil { 2258 return 2259 } 2260 2261 res.Write(errView) 2262 return 2263 } 2264 2265 err = json.Unmarshal(data, post) 2266 if err != nil { 2267 log.Println(err) 2268 res.WriteHeader(http.StatusInternalServerError) 2269 errView, err := Error500() 2270 if err != nil { 2271 return 2272 } 2273 2274 res.Write(errView) 2275 return 2276 } 2277 } else { 2278 it, ok := interface{}(post).(item.Identifiable) 2279 if !ok { 2280 log.Println("Content type", t, "doesn't implement item.Identifiable") 2281 return 2282 } 2283 2284 it.SetItemID(-1) 2285 } 2286 2287 m, err := manager.Manage(interface{}(post).(editor.Editable), t) 2288 if err != nil { 2289 log.Println(err) 2290 res.WriteHeader(http.StatusInternalServerError) 2291 errView, err := Error500() 2292 if err != nil { 2293 return 2294 } 2295 2296 res.Write(errView) 2297 return 2298 } 2299 2300 adminView, err := Admin(m) 2301 if err != nil { 2302 log.Println(err) 2303 res.WriteHeader(http.StatusInternalServerError) 2304 return 2305 } 2306 2307 res.Header().Set("Content-Type", "text/html") 2308 res.Write(adminView) 2309 2310 case http.MethodPost: 2311 err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB 2312 if err != nil { 2313 log.Println(err) 2314 res.WriteHeader(http.StatusInternalServerError) 2315 errView, err := Error500() 2316 if err != nil { 2317 return 2318 } 2319 2320 res.Write(errView) 2321 return 2322 } 2323 2324 t := req.FormValue("type") 2325 pt := "__uploads" 2326 ts := req.FormValue("timestamp") 2327 up := req.FormValue("updated") 2328 2329 // create a timestamp if one was not set 2330 if ts == "" { 2331 ts = fmt.Sprintf("%d", int64(time.Nanosecond)*time.Now().UTC().UnixNano()/int64(time.Millisecond)) 2332 req.PostForm.Set("timestamp", ts) 2333 } 2334 2335 if up == "" { 2336 req.PostForm.Set("updated", ts) 2337 } 2338 2339 post := interface{}(&item.FileUpload{}) 2340 hook, ok := post.(item.Hookable) 2341 if !ok { 2342 log.Println("Type", pt, "does not implement item.Hookable or embed item.Item.") 2343 res.WriteHeader(http.StatusBadRequest) 2344 errView, err := Error400() 2345 if err != nil { 2346 return 2347 } 2348 2349 res.Write(errView) 2350 return 2351 } 2352 2353 err = hook.BeforeSave(res, req) 2354 if err != nil { 2355 log.Println("Error running BeforeSave method in editHandler for:", t, err) 2356 return 2357 } 2358 2359 // StoreFiles has the SetUpload call (which is equivalent of SetContent in other handlers) 2360 urlPaths, err := upload.StoreFiles(req) 2361 if err != nil { 2362 log.Println(err) 2363 res.WriteHeader(http.StatusInternalServerError) 2364 errView, err := Error500() 2365 if err != nil { 2366 return 2367 } 2368 2369 res.Write(errView) 2370 return 2371 } 2372 2373 for name, urlPath := range urlPaths { 2374 req.PostForm.Set(name, urlPath) 2375 } 2376 2377 // check for any multi-value fields (ex. checkbox fields) 2378 // and correctly format for db storage. Essentially, we need 2379 // fieldX.0: value1, fieldX.1: value2 => fieldX: []string{value1, value2} 2380 fieldOrderValue := make(map[string]map[string][]string) 2381 ordVal := make(map[string][]string) 2382 for k, v := range req.PostForm { 2383 if strings.Contains(k, ".") { 2384 fo := strings.Split(k, ".") 2385 2386 // put the order and the field value into map 2387 field := string(fo[0]) 2388 order := string(fo[1]) 2389 fieldOrderValue[field] = ordVal 2390 2391 // orderValue is 0:[?type=Thing&id=1] 2392 orderValue := fieldOrderValue[field] 2393 orderValue[order] = v 2394 fieldOrderValue[field] = orderValue 2395 2396 // discard the post form value with name.N 2397 req.PostForm.Del(k) 2398 } 2399 2400 } 2401 2402 // add/set the key & value to the post form in order 2403 for f, ov := range fieldOrderValue { 2404 for i := 0; i < len(ov); i++ { 2405 position := fmt.Sprintf("%d", i) 2406 fieldValue := ov[position] 2407 2408 if req.PostForm.Get(f) == "" { 2409 for i, fv := range fieldValue { 2410 if i == 0 { 2411 req.PostForm.Set(f, fv) 2412 } else { 2413 req.PostForm.Add(f, fv) 2414 } 2415 } 2416 } else { 2417 for _, fv := range fieldValue { 2418 req.PostForm.Add(f, fv) 2419 } 2420 } 2421 } 2422 } 2423 2424 err = hook.AfterSave(res, req) 2425 if err != nil { 2426 log.Println("Error running AfterSave method in editHandler for:", t, err) 2427 return 2428 } 2429 2430 scheme := req.URL.Scheme 2431 host := req.URL.Host 2432 redir := scheme + host + "/admin/uploads" 2433 http.Redirect(res, req, redir, http.StatusFound) 2434 2435 case http.MethodPut: 2436 urlPaths, err := upload.StoreFiles(req) 2437 if err != nil { 2438 log.Println("Couldn't store file uploads.", err) 2439 res.WriteHeader(http.StatusInternalServerError) 2440 return 2441 } 2442 2443 res.Header().Set("Content-Type", "application/json") 2444 res.Write([]byte(`{"data": [{"url": "` + urlPaths["file"] + `"}]}`)) 2445 default: 2446 res.WriteHeader(http.StatusMethodNotAllowed) 2447 return 2448 } 2449 } 2450 2451 /* 2452 func editUploadHandler(res http.ResponseWriter, req *http.Request) { 2453 if req.Method != http.MethodPost { 2454 res.WriteHeader(http.StatusMethodNotAllowed) 2455 return 2456 } 2457 2458 urlPaths, err := upload.StoreFiles(req) 2459 if err != nil { 2460 log.Println("Couldn't store file uploads.", err) 2461 res.WriteHeader(http.StatusInternalServerError) 2462 return 2463 } 2464 2465 res.Header().Set("Content-Type", "application/json") 2466 res.Write([]byte(`{"data": [{"url": "` + urlPaths["file"] + `"}]}`)) 2467 } 2468 */ 2469 2470 func searchHandler(res http.ResponseWriter, req *http.Request) { 2471 q := req.URL.Query() 2472 t := q.Get("type") 2473 search := q.Get("q") 2474 status := q.Get("status") 2475 var specifier string 2476 2477 if t == "" || search == "" { 2478 http.Redirect(res, req, req.URL.Scheme+req.URL.Host+"/admin", http.StatusFound) 2479 return 2480 } 2481 2482 if status == "pending" { 2483 specifier = "__" + status 2484 } 2485 2486 posts := db.ContentAll(t + specifier) 2487 b := &bytes.Buffer{} 2488 pt, ok := item.Types[t] 2489 if !ok { 2490 res.WriteHeader(http.StatusBadRequest) 2491 return 2492 } 2493 2494 post := pt() 2495 2496 p := post.(editor.Editable) 2497 2498 html := `<div class="col s9 card"> 2499 <div class="card-content"> 2500 <div class="row"> 2501 <div class="card-title col s7">` + t + ` Results</div> 2502 <form class="col s4" action="/admin/contents/search" method="get"> 2503 <div class="input-field post-search inline"> 2504 <label class="active">Search:</label> 2505 <i class="right material-icons search-icon">search</i> 2506 <input class="search" name="q" type="text" placeholder="Within all ` + t + ` fields" class="search"/> 2507 <input type="hidden" name="type" value="` + t + `" /> 2508 <input type="hidden" name="status" value="` + status + `" /> 2509 </div> 2510 </form> 2511 </div> 2512 <ul class="posts row">` 2513 2514 for i := range posts { 2515 // skip posts that don't have any matching search criteria 2516 match := strings.ToLower(search) 2517 all := strings.ToLower(string(posts[i])) 2518 if !strings.Contains(all, match) { 2519 continue 2520 } 2521 2522 err := json.Unmarshal(posts[i], &p) 2523 if err != nil { 2524 log.Println("Error unmarshal search result json into", t, err, posts[i]) 2525 2526 post := `<li class="col s12">Error decoding data. Possible file corruption.</li>` 2527 _, err = b.Write([]byte(post)) 2528 if err != nil { 2529 log.Println(err) 2530 2531 res.WriteHeader(http.StatusInternalServerError) 2532 errView, err := Error500() 2533 if err != nil { 2534 log.Println(err) 2535 } 2536 2537 res.Write(errView) 2538 return 2539 } 2540 continue 2541 } 2542 2543 post := adminPostListItem(p, t, status) 2544 _, err = b.Write([]byte(post)) 2545 if err != nil { 2546 log.Println(err) 2547 2548 res.WriteHeader(http.StatusInternalServerError) 2549 errView, err := Error500() 2550 if err != nil { 2551 log.Println(err) 2552 } 2553 2554 res.Write(errView) 2555 return 2556 } 2557 } 2558 2559 _, err := b.WriteString(`</ul></div></div>`) 2560 if err != nil { 2561 log.Println(err) 2562 2563 res.WriteHeader(http.StatusInternalServerError) 2564 errView, err := Error500() 2565 if err != nil { 2566 log.Println(err) 2567 } 2568 2569 res.Write(errView) 2570 return 2571 } 2572 2573 script := ` 2574 <script> 2575 $(function() { 2576 var del = $('.quick-delete-post.__ponzu span'); 2577 del.on('click', function(e) { 2578 if (confirm("[Ponzu] Please confirm:\n\nAre you sure you want to delete this post?\nThis cannot be undone.")) { 2579 $(e.target).parent().submit(); 2580 } 2581 }); 2582 }); 2583 2584 // disable link from being clicked if parent is 'disabled' 2585 $(function() { 2586 $('ul.pagination li.disabled a').on('click', function(e) { 2587 e.preventDefault(); 2588 }); 2589 }); 2590 </script> 2591 ` 2592 2593 btn := `<div class="col s3"> 2594 <a href="/admin/edit?type=` + t + `" class="btn new-post waves-effect waves-light"> 2595 New ` + t + ` 2596 </a>` 2597 2598 html += b.String() + script + btn + `</div></div>` 2599 2600 adminView, err := Admin([]byte(html)) 2601 if err != nil { 2602 log.Println(err) 2603 res.WriteHeader(http.StatusInternalServerError) 2604 return 2605 } 2606 2607 res.Header().Set("Content-Type", "text/html") 2608 res.Write(adminView) 2609 } 2610 2611 func uploadSearchHandler(res http.ResponseWriter, req *http.Request) { 2612 q := req.URL.Query() 2613 t := "__uploads" 2614 search := q.Get("q") 2615 status := q.Get("status") 2616 2617 if t == "" || search == "" { 2618 http.Redirect(res, req, req.URL.Scheme+req.URL.Host+"/admin", http.StatusFound) 2619 return 2620 } 2621 2622 posts := db.UploadAll() 2623 b := &bytes.Buffer{} 2624 p := interface{}(&item.FileUpload{}).(editor.Editable) 2625 2626 html := `<div class="col s9 card"> 2627 <div class="card-content"> 2628 <div class="row"> 2629 <div class="card-title col s7">Uploads Results</div> 2630 <form class="col s4" action="/admin/uploads/search" method="get"> 2631 <div class="input-field post-search inline"> 2632 <label class="active">Search:</label> 2633 <i class="right material-icons search-icon">search</i> 2634 <input class="search" name="q" type="text" placeholder="Within all Upload fields" class="search"/> 2635 <input type="hidden" name="type" value="` + t + `" /> 2636 </div> 2637 </form> 2638 </div> 2639 <ul class="posts row">` 2640 2641 for i := range posts { 2642 // skip posts that don't have any matching search criteria 2643 match := strings.ToLower(search) 2644 all := strings.ToLower(string(posts[i])) 2645 if !strings.Contains(all, match) { 2646 continue 2647 } 2648 2649 err := json.Unmarshal(posts[i], &p) 2650 if err != nil { 2651 log.Println("Error unmarshal search result json into", t, err, posts[i]) 2652 2653 post := `<li class="col s12">Error decoding data. Possible file corruption.</li>` 2654 _, err = b.Write([]byte(post)) 2655 if err != nil { 2656 log.Println(err) 2657 2658 res.WriteHeader(http.StatusInternalServerError) 2659 errView, err := Error500() 2660 if err != nil { 2661 log.Println(err) 2662 } 2663 2664 res.Write(errView) 2665 return 2666 } 2667 continue 2668 } 2669 2670 post := adminPostListItem(p, t, status) 2671 _, err = b.Write([]byte(post)) 2672 if err != nil { 2673 log.Println(err) 2674 2675 res.WriteHeader(http.StatusInternalServerError) 2676 errView, err := Error500() 2677 if err != nil { 2678 log.Println(err) 2679 } 2680 2681 res.Write(errView) 2682 return 2683 } 2684 } 2685 2686 _, err := b.WriteString(`</ul></div></div>`) 2687 if err != nil { 2688 log.Println(err) 2689 2690 res.WriteHeader(http.StatusInternalServerError) 2691 errView, err := Error500() 2692 if err != nil { 2693 log.Println(err) 2694 } 2695 2696 res.Write(errView) 2697 return 2698 } 2699 2700 btn := `<div class="col s3"><a href="/admin/edit/upload" class="btn new-post waves-effect waves-light">New Upload</a></div></div>` 2701 html = html + b.String() + btn 2702 2703 adminView, err := Admin([]byte(html)) 2704 if err != nil { 2705 log.Println(err) 2706 res.WriteHeader(http.StatusInternalServerError) 2707 return 2708 } 2709 2710 res.Header().Set("Content-Type", "text/html") 2711 res.Write(adminView) 2712 } 2713 2714 func addonsHandler(res http.ResponseWriter, req *http.Request) { 2715 switch req.Method { 2716 case http.MethodGet: 2717 all := db.AddonAll() 2718 list := &bytes.Buffer{} 2719 2720 for i := range all { 2721 v := adminAddonListItem(all[i]) 2722 _, err := list.Write(v) 2723 if err != nil { 2724 log.Println("Error writing bytes to addon list view:", err) 2725 res.WriteHeader(http.StatusInternalServerError) 2726 errView, err := Error500() 2727 if err != nil { 2728 log.Println(err) 2729 return 2730 } 2731 2732 res.Write(errView) 2733 return 2734 } 2735 } 2736 2737 html := &bytes.Buffer{} 2738 open := `<div class="col s9 card"> 2739 <div class="card-content"> 2740 <div class="row"> 2741 <div class="card-title col s7">Addons</div> 2742 </div> 2743 <ul class="posts row">` 2744 2745 _, err := html.WriteString(open) 2746 if err != nil { 2747 log.Println("Error writing open html to addon html view:", err) 2748 res.WriteHeader(http.StatusInternalServerError) 2749 errView, err := Error500() 2750 if err != nil { 2751 log.Println(err) 2752 return 2753 } 2754 2755 res.Write(errView) 2756 return 2757 } 2758 2759 _, err = html.Write(list.Bytes()) 2760 if err != nil { 2761 log.Println("Error writing list bytes to addon html view:", err) 2762 res.WriteHeader(http.StatusInternalServerError) 2763 errView, err := Error500() 2764 if err != nil { 2765 log.Println(err) 2766 return 2767 } 2768 2769 res.Write(errView) 2770 return 2771 } 2772 2773 _, err = html.WriteString(`</ul></div></div>`) 2774 if err != nil { 2775 log.Println("Error writing close html to addon html view:", err) 2776 res.WriteHeader(http.StatusInternalServerError) 2777 errView, err := Error500() 2778 if err != nil { 2779 log.Println(err) 2780 return 2781 } 2782 2783 res.Write(errView) 2784 return 2785 } 2786 2787 if html.Len() == 0 { 2788 _, err := html.WriteString(`<p>No addons available.</p>`) 2789 if err != nil { 2790 log.Println("Error writing default addon html to admin view:", err) 2791 res.WriteHeader(http.StatusInternalServerError) 2792 errView, err := Error500() 2793 if err != nil { 2794 log.Println(err) 2795 return 2796 } 2797 2798 res.Write(errView) 2799 return 2800 } 2801 } 2802 2803 view, err := Admin(html.Bytes()) 2804 if err != nil { 2805 log.Println("Error writing addon html to admin view:", err) 2806 res.WriteHeader(http.StatusInternalServerError) 2807 errView, err := Error500() 2808 if err != nil { 2809 log.Println(err) 2810 return 2811 } 2812 2813 res.Write(errView) 2814 return 2815 } 2816 2817 res.Write(view) 2818 2819 case http.MethodPost: 2820 err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB 2821 if err != nil { 2822 log.Println(err) 2823 res.WriteHeader(http.StatusInternalServerError) 2824 errView, err := Error500() 2825 if err != nil { 2826 return 2827 } 2828 2829 res.Write(errView) 2830 return 2831 } 2832 2833 id := req.PostFormValue("id") 2834 action := strings.ToLower(req.PostFormValue("action")) 2835 2836 at, ok := addon.Types[id] 2837 if !ok { 2838 log.Println("Error: no addon type found for:", id) 2839 log.Println(err) 2840 res.WriteHeader(http.StatusNotFound) 2841 errView, err := Error404() 2842 if err != nil { 2843 return 2844 } 2845 2846 res.Write(errView) 2847 return 2848 } 2849 2850 b, err := db.Addon(id) 2851 if err == db.ErrNoAddonExists { 2852 log.Println(err) 2853 res.WriteHeader(http.StatusNotFound) 2854 errView, err := Error404() 2855 if err != nil { 2856 return 2857 } 2858 2859 res.Write(errView) 2860 return 2861 } 2862 if err != nil { 2863 log.Println(err) 2864 res.WriteHeader(http.StatusInternalServerError) 2865 errView, err := Error500() 2866 if err != nil { 2867 return 2868 } 2869 2870 res.Write(errView) 2871 return 2872 } 2873 2874 adn := at() 2875 err = json.Unmarshal(b, adn) 2876 if err != nil { 2877 log.Println(err) 2878 res.WriteHeader(http.StatusInternalServerError) 2879 errView, err := Error500() 2880 if err != nil { 2881 return 2882 } 2883 2884 res.Write(errView) 2885 return 2886 } 2887 2888 h, ok := adn.(item.Hookable) 2889 if !ok { 2890 log.Println("Addon", adn, "does not implement the item.Hookable interface or embed item.Item") 2891 return 2892 } 2893 2894 switch action { 2895 case "enable": 2896 err := h.BeforeEnable(res, req) 2897 if err != nil { 2898 log.Println(err) 2899 res.WriteHeader(http.StatusInternalServerError) 2900 errView, err := Error500() 2901 if err != nil { 2902 return 2903 } 2904 2905 res.Write(errView) 2906 return 2907 } 2908 2909 err = addon.Enable(id) 2910 if err != nil { 2911 log.Println(err) 2912 res.WriteHeader(http.StatusInternalServerError) 2913 errView, err := Error500() 2914 if err != nil { 2915 return 2916 } 2917 2918 res.Write(errView) 2919 return 2920 } 2921 2922 err = h.AfterEnable(res, req) 2923 if err != nil { 2924 log.Println(err) 2925 res.WriteHeader(http.StatusInternalServerError) 2926 errView, err := Error500() 2927 if err != nil { 2928 return 2929 } 2930 2931 res.Write(errView) 2932 return 2933 } 2934 2935 case "disable": 2936 err := h.BeforeDisable(res, req) 2937 if err != nil { 2938 log.Println(err) 2939 res.WriteHeader(http.StatusInternalServerError) 2940 errView, err := Error500() 2941 if err != nil { 2942 return 2943 } 2944 2945 res.Write(errView) 2946 return 2947 } 2948 2949 err = addon.Disable(id) 2950 if err != nil { 2951 log.Println(err) 2952 res.WriteHeader(http.StatusInternalServerError) 2953 errView, err := Error500() 2954 if err != nil { 2955 return 2956 } 2957 2958 res.Write(errView) 2959 return 2960 } 2961 2962 err = h.AfterDisable(res, req) 2963 if err != nil { 2964 log.Println(err) 2965 res.WriteHeader(http.StatusInternalServerError) 2966 errView, err := Error500() 2967 if err != nil { 2968 return 2969 } 2970 2971 res.Write(errView) 2972 return 2973 } 2974 default: 2975 res.WriteHeader(http.StatusBadRequest) 2976 errView, err := Error400() 2977 if err != nil { 2978 return 2979 } 2980 2981 res.Write(errView) 2982 return 2983 } 2984 2985 http.Redirect(res, req, req.URL.String(), http.StatusFound) 2986 2987 default: 2988 res.WriteHeader(http.StatusBadRequest) 2989 errView, err := Error400() 2990 if err != nil { 2991 log.Println(err) 2992 return 2993 } 2994 2995 res.Write(errView) 2996 return 2997 } 2998 } 2999 3000 func addonHandler(res http.ResponseWriter, req *http.Request) { 3001 switch req.Method { 3002 case http.MethodGet: 3003 id := req.FormValue("id") 3004 3005 data, err := db.Addon(id) 3006 if err != nil { 3007 log.Println(err) 3008 res.WriteHeader(http.StatusInternalServerError) 3009 errView, err := Error500() 3010 if err != nil { 3011 return 3012 } 3013 3014 res.Write(errView) 3015 return 3016 } 3017 3018 _, ok := addon.Types[id] 3019 if !ok { 3020 log.Println("Addon: ", id, "is not found in addon.Types map") 3021 res.WriteHeader(http.StatusNotFound) 3022 errView, err := Error404() 3023 if err != nil { 3024 return 3025 } 3026 3027 res.Write(errView) 3028 return 3029 } 3030 3031 m, err := addon.Manage(data, id) 3032 if err != nil { 3033 log.Println(err) 3034 res.WriteHeader(http.StatusInternalServerError) 3035 errView, err := Error500() 3036 if err != nil { 3037 return 3038 } 3039 3040 res.Write(errView) 3041 return 3042 } 3043 3044 addonView, err := Admin(m) 3045 if err != nil { 3046 log.Println(err) 3047 res.WriteHeader(http.StatusInternalServerError) 3048 return 3049 } 3050 3051 res.Header().Set("Content-Type", "text/html") 3052 res.Write(addonView) 3053 3054 case http.MethodPost: 3055 // save req.Form 3056 err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB 3057 if err != nil { 3058 log.Println(err) 3059 res.WriteHeader(http.StatusInternalServerError) 3060 errView, err := Error500() 3061 if err != nil { 3062 return 3063 } 3064 3065 res.Write(errView) 3066 return 3067 } 3068 3069 name := req.FormValue("addon_name") 3070 id := req.FormValue("addon_reverse_dns") 3071 3072 at, ok := addon.Types[id] 3073 if !ok { 3074 log.Println("Error: addon", name, "has no record in addon.Types map at", id) 3075 res.WriteHeader(http.StatusBadRequest) 3076 errView, err := Error400() 3077 if err != nil { 3078 return 3079 } 3080 3081 res.Write(errView) 3082 return 3083 } 3084 3085 // if Hookable, call BeforeSave prior to saving 3086 h, ok := at().(item.Hookable) 3087 if ok { 3088 err := h.BeforeSave(res, req) 3089 if err != nil { 3090 log.Println("Error running BeforeSave method in addonHandler for:", id, err) 3091 return 3092 } 3093 } 3094 3095 err = db.SetAddon(req.Form, at()) 3096 if err != nil { 3097 log.Println("Error saving addon:", name, err) 3098 res.WriteHeader(http.StatusInternalServerError) 3099 errView, err := Error500() 3100 if err != nil { 3101 return 3102 } 3103 3104 res.Write(errView) 3105 return 3106 } 3107 3108 http.Redirect(res, req, "/admin/addon?id="+id, http.StatusFound) 3109 3110 default: 3111 res.WriteHeader(http.StatusBadRequest) 3112 errView, err := Error405() 3113 if err != nil { 3114 log.Println(err) 3115 return 3116 } 3117 3118 res.Write(errView) 3119 return 3120 } 3121 } 3122 3123 func adminAddonListItem(data []byte) []byte { 3124 id := gjson.GetBytes(data, "addon_reverse_dns").String() 3125 status := gjson.GetBytes(data, "addon_status").String() 3126 name := gjson.GetBytes(data, "addon_name").String() 3127 author := gjson.GetBytes(data, "addon_author").String() 3128 authorURL := gjson.GetBytes(data, "addon_author_url").String() 3129 version := gjson.GetBytes(data, "addon_version").String() 3130 3131 var action string 3132 var buttonClass string 3133 if status != addon.StatusEnabled { 3134 action = "Enable" 3135 buttonClass = "green" 3136 } else { 3137 action = "Disable" 3138 buttonClass = "red" 3139 } 3140 3141 a := ` 3142 <li class="col s12"> 3143 <div class="row"> 3144 <div class="col s9"> 3145 <a class="addon-name" href="/admin/addon?id=` + id + `" alt="Configure '` + name + `'">` + name + `</a> 3146 <span class="addon-meta addon-author">by: <a href="` + authorURL + `">` + author + `</a></span> 3147 <span class="addon-meta addon-version">version: ` + version + `</span> 3148 </div> 3149 3150 <div class="col s3"> 3151 <form enctype="multipart/form-data" class="quick-` + strings.ToLower(action) + `-addon __ponzu right" action="/admin/addons" method="post"> 3152 <button class="btn waves-effect waves-effect-light ` + buttonClass + `">` + action + `</button> 3153 <input type="hidden" name="id" value="` + id + `" /> 3154 <input type="hidden" name="action" value="` + action + `" /> 3155 </form> 3156 </div> 3157 </div> 3158 </li>` 3159 3160 return []byte(a) 3161 }