github.com/rpdict/ponzu@v0.10.1-0.20190226054626-477f29d6bf5e/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/rpdict/ponzu/management/editor" 16 "github.com/rpdict/ponzu/management/format" 17 "github.com/rpdict/ponzu/management/manager" 18 "github.com/rpdict/ponzu/system/addon" 19 "github.com/rpdict/ponzu/system/admin/config" 20 "github.com/rpdict/ponzu/system/admin/upload" 21 "github.com/rpdict/ponzu/system/admin/user" 22 "github.com/rpdict/ponzu/system/api" 23 "github.com/rpdict/ponzu/system/api/analytics" 24 "github.com/rpdict/ponzu/system/db" 25 "github.com/rpdict/ponzu/system/item" 26 "github.com/rpdict/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 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 } 1568 1569 post := ` 1570 <li class="col s12"> 1571 ` + link + ` 1572 <span class="post-detail">Updated: ` + updatedTime + `</span> 1573 <span class="publish-date right">` + publishTime + `</span> 1574 1575 <form enctype="multipart/form-data" class="quick-delete-post __ponzu right" action="/admin/edit/delete" method="post"> 1576 <span>Delete</span> 1577 <input type="hidden" name="id" value="` + cid + `" /> 1578 <input type="hidden" name="type" value="` + typeName + status + `" /> 1579 </form> 1580 </li>` 1581 1582 return []byte(post) 1583 } 1584 1585 func approveContentHandler(res http.ResponseWriter, req *http.Request) { 1586 if req.Method != http.MethodPost { 1587 res.WriteHeader(http.StatusMethodNotAllowed) 1588 errView, err := Error405() 1589 if err != nil { 1590 return 1591 } 1592 1593 res.Write(errView) 1594 return 1595 } 1596 1597 err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB 1598 if err != nil { 1599 res.WriteHeader(http.StatusInternalServerError) 1600 errView, err := Error500() 1601 if err != nil { 1602 return 1603 } 1604 1605 res.Write(errView) 1606 return 1607 } 1608 1609 pendingID := req.FormValue("id") 1610 1611 t := req.FormValue("type") 1612 if strings.Contains(t, "__") { 1613 t = strings.Split(t, "__")[0] 1614 } 1615 1616 post := item.Types[t]() 1617 1618 // run hooks 1619 hook, ok := post.(item.Hookable) 1620 if !ok { 1621 log.Println("Type", t, "does not implement item.Hookable or embed item.Item.") 1622 res.WriteHeader(http.StatusBadRequest) 1623 errView, err := Error400() 1624 if err != nil { 1625 return 1626 } 1627 1628 res.Write(errView) 1629 return 1630 } 1631 1632 // check if we have a Mergeable 1633 m, ok := post.(editor.Mergeable) 1634 if !ok { 1635 log.Println("Content type", t, "must implement editor.Mergeable before it can be approved.") 1636 res.WriteHeader(http.StatusBadRequest) 1637 errView, err := Error400() 1638 if err != nil { 1639 return 1640 } 1641 1642 res.Write(errView) 1643 return 1644 } 1645 1646 dec := schema.NewDecoder() 1647 dec.IgnoreUnknownKeys(true) 1648 dec.SetAliasTag("json") 1649 err = dec.Decode(post, req.Form) 1650 if err != nil { 1651 log.Println("Error decoding post form for content approval:", t, err) 1652 res.WriteHeader(http.StatusInternalServerError) 1653 errView, err := Error500() 1654 if err != nil { 1655 return 1656 } 1657 1658 res.Write(errView) 1659 return 1660 } 1661 1662 err = hook.BeforeApprove(res, req) 1663 if err != nil { 1664 log.Println("Error running BeforeApprove hook in approveContentHandler for:", t, err) 1665 return 1666 } 1667 1668 // call its Approve method 1669 err = m.Approve(res, req) 1670 if err != nil { 1671 log.Println("Error running Approve method in approveContentHandler for:", t, err) 1672 return 1673 } 1674 1675 err = hook.AfterApprove(res, req) 1676 if err != nil { 1677 log.Println("Error running AfterApprove hook in approveContentHandler for:", t, err) 1678 return 1679 } 1680 1681 err = hook.BeforeSave(res, req) 1682 if err != nil { 1683 log.Println("Error running BeforeSave hook in approveContentHandler for:", t, err) 1684 return 1685 } 1686 1687 // Store the content in the bucket t 1688 id, err := db.SetContent(t+":-1", req.Form) 1689 if err != nil { 1690 log.Println("Error storing content in approveContentHandler for:", t, err) 1691 res.WriteHeader(http.StatusInternalServerError) 1692 errView, err := Error500() 1693 if err != nil { 1694 return 1695 } 1696 1697 res.Write(errView) 1698 return 1699 } 1700 1701 // set the target in the context so user can get saved value from db in hook 1702 ctx := context.WithValue(req.Context(), "target", fmt.Sprintf("%s:%d", t, id)) 1703 req = req.WithContext(ctx) 1704 1705 err = hook.AfterSave(res, req) 1706 if err != nil { 1707 log.Println("Error running AfterSave hook in approveContentHandler for:", t, err) 1708 return 1709 } 1710 1711 if pendingID != "" { 1712 err = db.DeleteContent(req.FormValue("type") + ":" + pendingID) 1713 if err != nil { 1714 log.Println("Failed to remove content after approval:", err) 1715 } 1716 } 1717 1718 // redirect to the new approved content's editor 1719 redir := req.URL.Scheme + req.URL.Host + strings.TrimSuffix(req.URL.Path, "/approve") 1720 redir += fmt.Sprintf("?type=%s&id=%d", t, id) 1721 http.Redirect(res, req, redir, http.StatusFound) 1722 } 1723 1724 func editHandler(res http.ResponseWriter, req *http.Request) { 1725 switch req.Method { 1726 case http.MethodGet: 1727 q := req.URL.Query() 1728 i := q.Get("id") 1729 t := q.Get("type") 1730 status := q.Get("status") 1731 1732 contentType, ok := item.Types[t] 1733 if !ok { 1734 fmt.Fprintf(res, item.ErrTypeNotRegistered.Error(), t) 1735 return 1736 } 1737 post := contentType() 1738 1739 if i != "" { 1740 if status == "pending" { 1741 t = t + "__pending" 1742 } 1743 1744 data, err := db.Content(t + ":" + i) 1745 if err != nil { 1746 log.Println(err) 1747 res.WriteHeader(http.StatusInternalServerError) 1748 errView, err := Error500() 1749 if err != nil { 1750 return 1751 } 1752 1753 res.Write(errView) 1754 return 1755 } 1756 1757 if len(data) < 1 || data == nil { 1758 res.WriteHeader(http.StatusNotFound) 1759 errView, err := Error404() 1760 if err != nil { 1761 return 1762 } 1763 1764 res.Write(errView) 1765 return 1766 } 1767 1768 err = json.Unmarshal(data, post) 1769 if err != nil { 1770 log.Println(err) 1771 res.WriteHeader(http.StatusInternalServerError) 1772 errView, err := Error500() 1773 if err != nil { 1774 return 1775 } 1776 1777 res.Write(errView) 1778 return 1779 } 1780 } else { 1781 item, ok := post.(item.Identifiable) 1782 if !ok { 1783 log.Println("Content type", t, "doesn't implement item.Identifiable") 1784 return 1785 } 1786 1787 item.SetItemID(-1) 1788 } 1789 1790 m, err := manager.Manage(post.(editor.Editable), t) 1791 if err != nil { 1792 log.Println(err) 1793 res.WriteHeader(http.StatusInternalServerError) 1794 errView, err := Error500() 1795 if err != nil { 1796 return 1797 } 1798 1799 res.Write(errView) 1800 return 1801 } 1802 1803 adminView, err := Admin(m) 1804 if err != nil { 1805 log.Println(err) 1806 res.WriteHeader(http.StatusInternalServerError) 1807 return 1808 } 1809 1810 res.Header().Set("Content-Type", "text/html") 1811 res.Write(adminView) 1812 1813 case http.MethodPost: 1814 err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB 1815 if err != nil { 1816 log.Println(err) 1817 res.WriteHeader(http.StatusInternalServerError) 1818 errView, err := Error500() 1819 if err != nil { 1820 return 1821 } 1822 1823 res.Write(errView) 1824 return 1825 } 1826 1827 cid := req.FormValue("id") 1828 t := req.FormValue("type") 1829 ts := req.FormValue("timestamp") 1830 up := req.FormValue("updated") 1831 1832 // create a timestamp if one was not set 1833 if ts == "" { 1834 ts = fmt.Sprintf("%d", int64(time.Nanosecond)*time.Now().UTC().UnixNano()/int64(time.Millisecond)) 1835 req.PostForm.Set("timestamp", ts) 1836 } 1837 1838 if up == "" { 1839 req.PostForm.Set("updated", ts) 1840 } 1841 1842 urlPaths, err := upload.StoreFiles(req) 1843 if err != nil { 1844 log.Println(err) 1845 res.WriteHeader(http.StatusInternalServerError) 1846 errView, err := Error500() 1847 if err != nil { 1848 return 1849 } 1850 1851 res.Write(errView) 1852 return 1853 } 1854 1855 for name, urlPath := range urlPaths { 1856 req.PostForm.Set(name, urlPath) 1857 } 1858 1859 // check for any multi-value fields (ex. checkbox fields) 1860 // and correctly format for db storage. Essentially, we need 1861 // fieldX.0: value1, fieldX.1: value2 => fieldX: []string{value1, value2} 1862 fieldOrderValue := make(map[string]map[string][]string) 1863 for k, v := range req.PostForm { 1864 if strings.Contains(k, ".") { 1865 fo := strings.Split(k, ".") 1866 1867 // put the order and the field value into map 1868 field := string(fo[0]) 1869 order := string(fo[1]) 1870 if len(fieldOrderValue[field]) == 0 { 1871 fieldOrderValue[field] = make(map[string][]string) 1872 } 1873 1874 // orderValue is 0:[?type=Thing&id=1] 1875 orderValue := fieldOrderValue[field] 1876 orderValue[order] = v 1877 fieldOrderValue[field] = orderValue 1878 1879 // discard the post form value with name.N 1880 req.PostForm.Del(k) 1881 } 1882 1883 } 1884 1885 // add/set the key & value to the post form in order 1886 for f, ov := range fieldOrderValue { 1887 for i := 0; i < len(ov); i++ { 1888 position := fmt.Sprintf("%d", i) 1889 fieldValue := ov[position] 1890 1891 if req.PostForm.Get(f) == "" { 1892 for i, fv := range fieldValue { 1893 if i == 0 { 1894 req.PostForm.Set(f, fv) 1895 } else { 1896 req.PostForm.Add(f, fv) 1897 } 1898 } 1899 } else { 1900 for _, fv := range fieldValue { 1901 req.PostForm.Add(f, fv) 1902 } 1903 } 1904 } 1905 } 1906 1907 pt := t 1908 if strings.Contains(t, "__") { 1909 pt = strings.Split(t, "__")[0] 1910 } 1911 1912 p, ok := item.Types[pt] 1913 if !ok { 1914 log.Println("Type", t, "is not a content type. Cannot edit or save.") 1915 res.WriteHeader(http.StatusBadRequest) 1916 errView, err := Error400() 1917 if err != nil { 1918 return 1919 } 1920 1921 res.Write(errView) 1922 return 1923 } 1924 1925 post := p() 1926 hook, ok := post.(item.Hookable) 1927 if !ok { 1928 log.Println("Type", pt, "does not implement item.Hookable or embed item.Item.") 1929 res.WriteHeader(http.StatusBadRequest) 1930 errView, err := Error400() 1931 if err != nil { 1932 return 1933 } 1934 1935 res.Write(errView) 1936 return 1937 } 1938 1939 // Let's be nice and make a proper item for the Hookable methods 1940 dec := schema.NewDecoder() 1941 dec.IgnoreUnknownKeys(true) 1942 dec.SetAliasTag("json") 1943 err = dec.Decode(post, req.PostForm) 1944 if err != nil { 1945 log.Println("Error decoding post form for edit handler:", t, err) 1946 res.WriteHeader(http.StatusBadRequest) 1947 errView, err := Error400() 1948 if err != nil { 1949 return 1950 } 1951 1952 res.Write(errView) 1953 return 1954 } 1955 1956 if cid == "-1" { 1957 err = hook.BeforeAdminCreate(res, req) 1958 if err != nil { 1959 log.Println("Error running BeforeAdminCreate method in editHandler for:", t, err) 1960 return 1961 } 1962 } else { 1963 err = hook.BeforeAdminUpdate(res, req) 1964 if err != nil { 1965 log.Println("Error running BeforeAdminUpdate method in editHandler for:", t, err) 1966 return 1967 } 1968 } 1969 1970 err = hook.BeforeSave(res, req) 1971 if err != nil { 1972 log.Println("Error running BeforeSave method in editHandler for:", t, err) 1973 return 1974 } 1975 1976 id, err := db.SetContent(t+":"+cid, req.PostForm) 1977 if err != nil { 1978 log.Println(err) 1979 res.WriteHeader(http.StatusInternalServerError) 1980 errView, err := Error500() 1981 if err != nil { 1982 return 1983 } 1984 1985 res.Write(errView) 1986 return 1987 } 1988 1989 // set the target in the context so user can get saved value from db in hook 1990 ctx := context.WithValue(req.Context(), "target", fmt.Sprintf("%s:%d", t, id)) 1991 req = req.WithContext(ctx) 1992 1993 err = hook.AfterSave(res, req) 1994 if err != nil { 1995 log.Println("Error running AfterSave method in editHandler for:", t, err) 1996 return 1997 } 1998 1999 if cid == "-1" { 2000 err = hook.AfterAdminCreate(res, req) 2001 if err != nil { 2002 log.Println("Error running AfterAdminUpdate method in editHandler for:", t, err) 2003 return 2004 } 2005 } else { 2006 err = hook.AfterAdminUpdate(res, req) 2007 if err != nil { 2008 log.Println("Error running AfterAdminUpdate method in editHandler for:", t, err) 2009 return 2010 } 2011 } 2012 2013 scheme := req.URL.Scheme 2014 host := req.URL.Host 2015 path := req.URL.Path 2016 sid := fmt.Sprintf("%d", id) 2017 redir := scheme + host + path + "?type=" + pt + "&id=" + sid 2018 2019 if req.URL.Query().Get("status") == "pending" { 2020 redir += "&status=pending" 2021 } 2022 2023 http.Redirect(res, req, redir, http.StatusFound) 2024 2025 default: 2026 res.WriteHeader(http.StatusMethodNotAllowed) 2027 } 2028 } 2029 2030 func deleteHandler(res http.ResponseWriter, req *http.Request) { 2031 if req.Method != http.MethodPost { 2032 res.WriteHeader(http.StatusMethodNotAllowed) 2033 return 2034 } 2035 2036 err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB 2037 if err != nil { 2038 log.Println(err) 2039 res.WriteHeader(http.StatusInternalServerError) 2040 errView, err := Error500() 2041 if err != nil { 2042 return 2043 } 2044 2045 res.Write(errView) 2046 return 2047 } 2048 2049 id := req.FormValue("id") 2050 t := req.FormValue("type") 2051 ct := t 2052 2053 if id == "" || t == "" { 2054 res.WriteHeader(http.StatusBadRequest) 2055 return 2056 } 2057 2058 // catch specifier suffix from delete form value 2059 if strings.Contains(t, "__") { 2060 spec := strings.Split(t, "__") 2061 ct = spec[0] 2062 } 2063 2064 p, ok := item.Types[ct] 2065 if !ok { 2066 log.Println("Type", t, "does not implement item.Hookable or embed item.Item.") 2067 res.WriteHeader(http.StatusBadRequest) 2068 errView, err := Error400() 2069 if err != nil { 2070 return 2071 } 2072 2073 res.Write(errView) 2074 return 2075 } 2076 2077 post := p() 2078 hook, ok := post.(item.Hookable) 2079 if !ok { 2080 log.Println("Type", t, "does not implement item.Hookable or embed item.Item.") 2081 res.WriteHeader(http.StatusBadRequest) 2082 errView, err := Error400() 2083 if err != nil { 2084 return 2085 } 2086 2087 res.Write(errView) 2088 return 2089 } 2090 2091 data, err := db.Content(t + ":" + id) 2092 if err != nil { 2093 log.Println("Error in db.Content ", t+":"+id, err) 2094 return 2095 } 2096 2097 err = json.Unmarshal(data, post) 2098 if err != nil { 2099 log.Println("Error unmarshalling ", t, "=", id, err, " Hooks will be called on a zero-value.") 2100 } 2101 2102 reject := req.URL.Query().Get("reject") 2103 if reject == "true" { 2104 err = hook.BeforeReject(res, req) 2105 if err != nil { 2106 log.Println("Error running BeforeReject method in deleteHandler for:", t, err) 2107 return 2108 } 2109 } 2110 2111 err = hook.BeforeAdminDelete(res, req) 2112 if err != nil { 2113 log.Println("Error running BeforeAdminDelete method in deleteHandler for:", t, err) 2114 return 2115 } 2116 2117 err = hook.BeforeDelete(res, req) 2118 if err != nil { 2119 log.Println("Error running BeforeDelete method in deleteHandler for:", t, err) 2120 return 2121 } 2122 2123 err = db.DeleteContent(t + ":" + id) 2124 if err != nil { 2125 log.Println(err) 2126 res.WriteHeader(http.StatusInternalServerError) 2127 return 2128 } 2129 2130 err = hook.AfterDelete(res, req) 2131 if err != nil { 2132 log.Println("Error running AfterDelete method in deleteHandler for:", t, err) 2133 return 2134 } 2135 2136 err = hook.AfterAdminDelete(res, req) 2137 if err != nil { 2138 log.Println("Error running AfterDelete method in deleteHandler for:", t, err) 2139 return 2140 } 2141 2142 if reject == "true" { 2143 err = hook.AfterReject(res, req) 2144 if err != nil { 2145 log.Println("Error running AfterReject method in deleteHandler for:", t, err) 2146 return 2147 } 2148 } 2149 2150 redir := strings.TrimSuffix(req.URL.Scheme+req.URL.Host+req.URL.Path, "/edit/delete") 2151 redir = redir + "/contents?type=" + ct 2152 http.Redirect(res, req, redir, http.StatusFound) 2153 } 2154 2155 func deleteUploadHandler(res http.ResponseWriter, req *http.Request) { 2156 if req.Method != http.MethodPost { 2157 res.WriteHeader(http.StatusMethodNotAllowed) 2158 return 2159 } 2160 2161 err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB 2162 if err != nil { 2163 log.Println(err) 2164 res.WriteHeader(http.StatusInternalServerError) 2165 errView, err := Error500() 2166 if err != nil { 2167 return 2168 } 2169 2170 res.Write(errView) 2171 return 2172 } 2173 2174 id := req.FormValue("id") 2175 t := "__uploads" 2176 2177 if id == "" || t == "" { 2178 res.WriteHeader(http.StatusBadRequest) 2179 return 2180 } 2181 2182 post := interface{}(&item.FileUpload{}) 2183 hook, ok := post.(item.Hookable) 2184 if !ok { 2185 log.Println("Type", t, "does not implement item.Hookable or embed item.Item.") 2186 res.WriteHeader(http.StatusBadRequest) 2187 errView, err := Error400() 2188 if err != nil { 2189 return 2190 } 2191 2192 res.Write(errView) 2193 return 2194 } 2195 2196 err = hook.BeforeDelete(res, req) 2197 if err != nil { 2198 log.Println("Error running BeforeDelete method in deleteHandler for:", t, err) 2199 return 2200 } 2201 2202 err = db.DeleteUpload(t + ":" + id) 2203 if err != nil { 2204 log.Println(err) 2205 res.WriteHeader(http.StatusInternalServerError) 2206 return 2207 } 2208 2209 err = hook.AfterDelete(res, req) 2210 if err != nil { 2211 log.Println("Error running AfterDelete method in deleteHandler for:", t, err) 2212 return 2213 } 2214 2215 redir := "/admin/uploads" 2216 http.Redirect(res, req, redir, http.StatusFound) 2217 } 2218 2219 func editUploadHandler(res http.ResponseWriter, req *http.Request) { 2220 switch req.Method { 2221 case http.MethodGet: 2222 q := req.URL.Query() 2223 i := q.Get("id") 2224 t := "__uploads" 2225 2226 post := &item.FileUpload{} 2227 2228 if i != "" { 2229 data, err := db.Upload(t + ":" + i) 2230 if err != nil { 2231 log.Println(err) 2232 res.WriteHeader(http.StatusInternalServerError) 2233 errView, err := Error500() 2234 if err != nil { 2235 return 2236 } 2237 2238 res.Write(errView) 2239 return 2240 } 2241 2242 if len(data) < 1 || data == nil { 2243 res.WriteHeader(http.StatusNotFound) 2244 errView, err := Error404() 2245 if err != nil { 2246 return 2247 } 2248 2249 res.Write(errView) 2250 return 2251 } 2252 2253 err = json.Unmarshal(data, post) 2254 if err != nil { 2255 log.Println(err) 2256 res.WriteHeader(http.StatusInternalServerError) 2257 errView, err := Error500() 2258 if err != nil { 2259 return 2260 } 2261 2262 res.Write(errView) 2263 return 2264 } 2265 } else { 2266 it, ok := interface{}(post).(item.Identifiable) 2267 if !ok { 2268 log.Println("Content type", t, "doesn't implement item.Identifiable") 2269 return 2270 } 2271 2272 it.SetItemID(-1) 2273 } 2274 2275 m, err := manager.Manage(interface{}(post).(editor.Editable), t) 2276 if err != nil { 2277 log.Println(err) 2278 res.WriteHeader(http.StatusInternalServerError) 2279 errView, err := Error500() 2280 if err != nil { 2281 return 2282 } 2283 2284 res.Write(errView) 2285 return 2286 } 2287 2288 adminView, err := Admin(m) 2289 if err != nil { 2290 log.Println(err) 2291 res.WriteHeader(http.StatusInternalServerError) 2292 return 2293 } 2294 2295 res.Header().Set("Content-Type", "text/html") 2296 res.Write(adminView) 2297 2298 case http.MethodPost: 2299 err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB 2300 if err != nil { 2301 log.Println(err) 2302 res.WriteHeader(http.StatusInternalServerError) 2303 errView, err := Error500() 2304 if err != nil { 2305 return 2306 } 2307 2308 res.Write(errView) 2309 return 2310 } 2311 2312 t := req.FormValue("type") 2313 pt := "__uploads" 2314 ts := req.FormValue("timestamp") 2315 up := req.FormValue("updated") 2316 2317 // create a timestamp if one was not set 2318 if ts == "" { 2319 ts = fmt.Sprintf("%d", int64(time.Nanosecond)*time.Now().UTC().UnixNano()/int64(time.Millisecond)) 2320 req.PostForm.Set("timestamp", ts) 2321 } 2322 2323 if up == "" { 2324 req.PostForm.Set("updated", ts) 2325 } 2326 2327 post := interface{}(&item.FileUpload{}) 2328 hook, ok := post.(item.Hookable) 2329 if !ok { 2330 log.Println("Type", pt, "does not implement item.Hookable or embed item.Item.") 2331 res.WriteHeader(http.StatusBadRequest) 2332 errView, err := Error400() 2333 if err != nil { 2334 return 2335 } 2336 2337 res.Write(errView) 2338 return 2339 } 2340 2341 err = hook.BeforeSave(res, req) 2342 if err != nil { 2343 log.Println("Error running BeforeSave method in editHandler for:", t, err) 2344 return 2345 } 2346 2347 // StoreFiles has the SetUpload call (which is equivalent of SetContent in other handlers) 2348 urlPaths, err := upload.StoreFiles(req) 2349 if err != nil { 2350 log.Println(err) 2351 res.WriteHeader(http.StatusInternalServerError) 2352 errView, err := Error500() 2353 if err != nil { 2354 return 2355 } 2356 2357 res.Write(errView) 2358 return 2359 } 2360 2361 for name, urlPath := range urlPaths { 2362 req.PostForm.Set(name, urlPath) 2363 } 2364 2365 // check for any multi-value fields (ex. checkbox fields) 2366 // and correctly format for db storage. Essentially, we need 2367 // fieldX.0: value1, fieldX.1: value2 => fieldX: []string{value1, value2} 2368 fieldOrderValue := make(map[string]map[string][]string) 2369 ordVal := make(map[string][]string) 2370 for k, v := range req.PostForm { 2371 if strings.Contains(k, ".") { 2372 fo := strings.Split(k, ".") 2373 2374 // put the order and the field value into map 2375 field := string(fo[0]) 2376 order := string(fo[1]) 2377 fieldOrderValue[field] = ordVal 2378 2379 // orderValue is 0:[?type=Thing&id=1] 2380 orderValue := fieldOrderValue[field] 2381 orderValue[order] = v 2382 fieldOrderValue[field] = orderValue 2383 2384 // discard the post form value with name.N 2385 req.PostForm.Del(k) 2386 } 2387 2388 } 2389 2390 // add/set the key & value to the post form in order 2391 for f, ov := range fieldOrderValue { 2392 for i := 0; i < len(ov); i++ { 2393 position := fmt.Sprintf("%d", i) 2394 fieldValue := ov[position] 2395 2396 if req.PostForm.Get(f) == "" { 2397 for i, fv := range fieldValue { 2398 if i == 0 { 2399 req.PostForm.Set(f, fv) 2400 } else { 2401 req.PostForm.Add(f, fv) 2402 } 2403 } 2404 } else { 2405 for _, fv := range fieldValue { 2406 req.PostForm.Add(f, fv) 2407 } 2408 } 2409 } 2410 } 2411 2412 err = hook.AfterSave(res, req) 2413 if err != nil { 2414 log.Println("Error running AfterSave method in editHandler for:", t, err) 2415 return 2416 } 2417 2418 scheme := req.URL.Scheme 2419 host := req.URL.Host 2420 redir := scheme + host + "/admin/uploads" 2421 http.Redirect(res, req, redir, http.StatusFound) 2422 2423 case http.MethodPut: 2424 urlPaths, err := upload.StoreFiles(req) 2425 if err != nil { 2426 log.Println("Couldn't store file uploads.", err) 2427 res.WriteHeader(http.StatusInternalServerError) 2428 return 2429 } 2430 2431 res.Header().Set("Content-Type", "application/json") 2432 res.Write([]byte(`{"data": [{"url": "` + urlPaths["file"] + `"}]}`)) 2433 default: 2434 res.WriteHeader(http.StatusMethodNotAllowed) 2435 return 2436 } 2437 } 2438 2439 /* 2440 func editUploadHandler(res http.ResponseWriter, req *http.Request) { 2441 if req.Method != http.MethodPost { 2442 res.WriteHeader(http.StatusMethodNotAllowed) 2443 return 2444 } 2445 2446 urlPaths, err := upload.StoreFiles(req) 2447 if err != nil { 2448 log.Println("Couldn't store file uploads.", err) 2449 res.WriteHeader(http.StatusInternalServerError) 2450 return 2451 } 2452 2453 res.Header().Set("Content-Type", "application/json") 2454 res.Write([]byte(`{"data": [{"url": "` + urlPaths["file"] + `"}]}`)) 2455 } 2456 */ 2457 2458 func searchHandler(res http.ResponseWriter, req *http.Request) { 2459 q := req.URL.Query() 2460 t := q.Get("type") 2461 search := q.Get("q") 2462 status := q.Get("status") 2463 var specifier string 2464 2465 if t == "" || search == "" { 2466 http.Redirect(res, req, req.URL.Scheme+req.URL.Host+"/admin", http.StatusFound) 2467 return 2468 } 2469 2470 if status == "pending" { 2471 specifier = "__" + status 2472 } 2473 2474 posts := db.ContentAll(t + specifier) 2475 b := &bytes.Buffer{} 2476 pt, ok := item.Types[t] 2477 if !ok { 2478 res.WriteHeader(http.StatusBadRequest) 2479 return 2480 } 2481 2482 post := pt() 2483 2484 p := post.(editor.Editable) 2485 2486 html := `<div class="col s9 card"> 2487 <div class="card-content"> 2488 <div class="row"> 2489 <div class="card-title col s7">` + t + ` Results</div> 2490 <form class="col s4" action="/admin/contents/search" method="get"> 2491 <div class="input-field post-search inline"> 2492 <label class="active">Search:</label> 2493 <i class="right material-icons search-icon">search</i> 2494 <input class="search" name="q" type="text" placeholder="Within all ` + t + ` fields" class="search"/> 2495 <input type="hidden" name="type" value="` + t + `" /> 2496 <input type="hidden" name="status" value="` + status + `" /> 2497 </div> 2498 </form> 2499 </div> 2500 <ul class="posts row">` 2501 2502 for i := range posts { 2503 // skip posts that don't have any matching search criteria 2504 match := strings.ToLower(search) 2505 all := strings.ToLower(string(posts[i])) 2506 if !strings.Contains(all, match) { 2507 continue 2508 } 2509 2510 err := json.Unmarshal(posts[i], &p) 2511 if err != nil { 2512 log.Println("Error unmarshal search result json into", t, err, posts[i]) 2513 2514 post := `<li class="col s12">Error decoding data. Possible file corruption.</li>` 2515 _, err = b.Write([]byte(post)) 2516 if err != nil { 2517 log.Println(err) 2518 2519 res.WriteHeader(http.StatusInternalServerError) 2520 errView, err := Error500() 2521 if err != nil { 2522 log.Println(err) 2523 } 2524 2525 res.Write(errView) 2526 return 2527 } 2528 continue 2529 } 2530 2531 post := adminPostListItem(p, t, status) 2532 _, err = b.Write([]byte(post)) 2533 if err != nil { 2534 log.Println(err) 2535 2536 res.WriteHeader(http.StatusInternalServerError) 2537 errView, err := Error500() 2538 if err != nil { 2539 log.Println(err) 2540 } 2541 2542 res.Write(errView) 2543 return 2544 } 2545 } 2546 2547 _, err := b.WriteString(`</ul></div></div>`) 2548 if err != nil { 2549 log.Println(err) 2550 2551 res.WriteHeader(http.StatusInternalServerError) 2552 errView, err := Error500() 2553 if err != nil { 2554 log.Println(err) 2555 } 2556 2557 res.Write(errView) 2558 return 2559 } 2560 2561 script := ` 2562 <script> 2563 $(function() { 2564 var del = $('.quick-delete-post.__ponzu span'); 2565 del.on('click', function(e) { 2566 if (confirm("[Ponzu] Please confirm:\n\nAre you sure you want to delete this post?\nThis cannot be undone.")) { 2567 $(e.target).parent().submit(); 2568 } 2569 }); 2570 }); 2571 2572 // disable link from being clicked if parent is 'disabled' 2573 $(function() { 2574 $('ul.pagination li.disabled a').on('click', function(e) { 2575 e.preventDefault(); 2576 }); 2577 }); 2578 </script> 2579 ` 2580 2581 btn := `<div class="col s3"> 2582 <a href="/admin/edit?type=` + t + `" class="btn new-post waves-effect waves-light"> 2583 New ` + t + ` 2584 </a>` 2585 2586 html += b.String() + script + btn + `</div></div>` 2587 2588 adminView, err := Admin([]byte(html)) 2589 if err != nil { 2590 log.Println(err) 2591 res.WriteHeader(http.StatusInternalServerError) 2592 return 2593 } 2594 2595 res.Header().Set("Content-Type", "text/html") 2596 res.Write(adminView) 2597 } 2598 2599 func uploadSearchHandler(res http.ResponseWriter, req *http.Request) { 2600 q := req.URL.Query() 2601 t := "__uploads" 2602 search := q.Get("q") 2603 status := q.Get("status") 2604 2605 if t == "" || search == "" { 2606 http.Redirect(res, req, req.URL.Scheme+req.URL.Host+"/admin", http.StatusFound) 2607 return 2608 } 2609 2610 posts := db.UploadAll() 2611 b := &bytes.Buffer{} 2612 p := interface{}(&item.FileUpload{}).(editor.Editable) 2613 2614 html := `<div class="col s9 card"> 2615 <div class="card-content"> 2616 <div class="row"> 2617 <div class="card-title col s7">Uploads Results</div> 2618 <form class="col s4" action="/admin/uploads/search" method="get"> 2619 <div class="input-field post-search inline"> 2620 <label class="active">Search:</label> 2621 <i class="right material-icons search-icon">search</i> 2622 <input class="search" name="q" type="text" placeholder="Within all Upload fields" class="search"/> 2623 <input type="hidden" name="type" value="` + t + `" /> 2624 </div> 2625 </form> 2626 </div> 2627 <ul class="posts row">` 2628 2629 for i := range posts { 2630 // skip posts that don't have any matching search criteria 2631 match := strings.ToLower(search) 2632 all := strings.ToLower(string(posts[i])) 2633 if !strings.Contains(all, match) { 2634 continue 2635 } 2636 2637 err := json.Unmarshal(posts[i], &p) 2638 if err != nil { 2639 log.Println("Error unmarshal search result json into", t, err, posts[i]) 2640 2641 post := `<li class="col s12">Error decoding data. Possible file corruption.</li>` 2642 _, err = b.Write([]byte(post)) 2643 if err != nil { 2644 log.Println(err) 2645 2646 res.WriteHeader(http.StatusInternalServerError) 2647 errView, err := Error500() 2648 if err != nil { 2649 log.Println(err) 2650 } 2651 2652 res.Write(errView) 2653 return 2654 } 2655 continue 2656 } 2657 2658 post := adminPostListItem(p, t, status) 2659 _, err = b.Write([]byte(post)) 2660 if err != nil { 2661 log.Println(err) 2662 2663 res.WriteHeader(http.StatusInternalServerError) 2664 errView, err := Error500() 2665 if err != nil { 2666 log.Println(err) 2667 } 2668 2669 res.Write(errView) 2670 return 2671 } 2672 } 2673 2674 _, err := b.WriteString(`</ul></div></div>`) 2675 if err != nil { 2676 log.Println(err) 2677 2678 res.WriteHeader(http.StatusInternalServerError) 2679 errView, err := Error500() 2680 if err != nil { 2681 log.Println(err) 2682 } 2683 2684 res.Write(errView) 2685 return 2686 } 2687 2688 btn := `<div class="col s3"><a href="/admin/edit/upload" class="btn new-post waves-effect waves-light">New Upload</a></div></div>` 2689 html = html + b.String() + btn 2690 2691 adminView, err := Admin([]byte(html)) 2692 if err != nil { 2693 log.Println(err) 2694 res.WriteHeader(http.StatusInternalServerError) 2695 return 2696 } 2697 2698 res.Header().Set("Content-Type", "text/html") 2699 res.Write(adminView) 2700 } 2701 2702 func addonsHandler(res http.ResponseWriter, req *http.Request) { 2703 switch req.Method { 2704 case http.MethodGet: 2705 all := db.AddonAll() 2706 list := &bytes.Buffer{} 2707 2708 for i := range all { 2709 v := adminAddonListItem(all[i]) 2710 _, err := list.Write(v) 2711 if err != nil { 2712 log.Println("Error writing bytes to addon list view:", err) 2713 res.WriteHeader(http.StatusInternalServerError) 2714 errView, err := Error500() 2715 if err != nil { 2716 log.Println(err) 2717 return 2718 } 2719 2720 res.Write(errView) 2721 return 2722 } 2723 } 2724 2725 html := &bytes.Buffer{} 2726 open := `<div class="col s9 card"> 2727 <div class="card-content"> 2728 <div class="row"> 2729 <div class="card-title col s7">Addons</div> 2730 </div> 2731 <ul class="posts row">` 2732 2733 _, err := html.WriteString(open) 2734 if err != nil { 2735 log.Println("Error writing open html to addon html view:", err) 2736 res.WriteHeader(http.StatusInternalServerError) 2737 errView, err := Error500() 2738 if err != nil { 2739 log.Println(err) 2740 return 2741 } 2742 2743 res.Write(errView) 2744 return 2745 } 2746 2747 _, err = html.Write(list.Bytes()) 2748 if err != nil { 2749 log.Println("Error writing list bytes to addon html view:", err) 2750 res.WriteHeader(http.StatusInternalServerError) 2751 errView, err := Error500() 2752 if err != nil { 2753 log.Println(err) 2754 return 2755 } 2756 2757 res.Write(errView) 2758 return 2759 } 2760 2761 _, err = html.WriteString(`</ul></div></div>`) 2762 if err != nil { 2763 log.Println("Error writing close html to addon html view:", err) 2764 res.WriteHeader(http.StatusInternalServerError) 2765 errView, err := Error500() 2766 if err != nil { 2767 log.Println(err) 2768 return 2769 } 2770 2771 res.Write(errView) 2772 return 2773 } 2774 2775 if html.Len() == 0 { 2776 _, err := html.WriteString(`<p>No addons available.</p>`) 2777 if err != nil { 2778 log.Println("Error writing default addon html to admin view:", err) 2779 res.WriteHeader(http.StatusInternalServerError) 2780 errView, err := Error500() 2781 if err != nil { 2782 log.Println(err) 2783 return 2784 } 2785 2786 res.Write(errView) 2787 return 2788 } 2789 } 2790 2791 view, err := Admin(html.Bytes()) 2792 if err != nil { 2793 log.Println("Error writing addon html to admin view:", err) 2794 res.WriteHeader(http.StatusInternalServerError) 2795 errView, err := Error500() 2796 if err != nil { 2797 log.Println(err) 2798 return 2799 } 2800 2801 res.Write(errView) 2802 return 2803 } 2804 2805 res.Write(view) 2806 2807 case http.MethodPost: 2808 err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB 2809 if err != nil { 2810 log.Println(err) 2811 res.WriteHeader(http.StatusInternalServerError) 2812 errView, err := Error500() 2813 if err != nil { 2814 return 2815 } 2816 2817 res.Write(errView) 2818 return 2819 } 2820 2821 id := req.PostFormValue("id") 2822 action := strings.ToLower(req.PostFormValue("action")) 2823 2824 at, ok := addon.Types[id] 2825 if !ok { 2826 log.Println("Error: no addon type found for:", id) 2827 log.Println(err) 2828 res.WriteHeader(http.StatusNotFound) 2829 errView, err := Error404() 2830 if err != nil { 2831 return 2832 } 2833 2834 res.Write(errView) 2835 return 2836 } 2837 2838 b, err := db.Addon(id) 2839 if err == db.ErrNoAddonExists { 2840 log.Println(err) 2841 res.WriteHeader(http.StatusNotFound) 2842 errView, err := Error404() 2843 if err != nil { 2844 return 2845 } 2846 2847 res.Write(errView) 2848 return 2849 } 2850 if err != nil { 2851 log.Println(err) 2852 res.WriteHeader(http.StatusInternalServerError) 2853 errView, err := Error500() 2854 if err != nil { 2855 return 2856 } 2857 2858 res.Write(errView) 2859 return 2860 } 2861 2862 adn := at() 2863 err = json.Unmarshal(b, adn) 2864 if err != nil { 2865 log.Println(err) 2866 res.WriteHeader(http.StatusInternalServerError) 2867 errView, err := Error500() 2868 if err != nil { 2869 return 2870 } 2871 2872 res.Write(errView) 2873 return 2874 } 2875 2876 h, ok := adn.(item.Hookable) 2877 if !ok { 2878 log.Println("Addon", adn, "does not implement the item.Hookable interface or embed item.Item") 2879 return 2880 } 2881 2882 switch action { 2883 case "enable": 2884 err := h.BeforeEnable(res, req) 2885 if err != nil { 2886 log.Println(err) 2887 res.WriteHeader(http.StatusInternalServerError) 2888 errView, err := Error500() 2889 if err != nil { 2890 return 2891 } 2892 2893 res.Write(errView) 2894 return 2895 } 2896 2897 err = addon.Enable(id) 2898 if err != nil { 2899 log.Println(err) 2900 res.WriteHeader(http.StatusInternalServerError) 2901 errView, err := Error500() 2902 if err != nil { 2903 return 2904 } 2905 2906 res.Write(errView) 2907 return 2908 } 2909 2910 err = h.AfterEnable(res, req) 2911 if err != nil { 2912 log.Println(err) 2913 res.WriteHeader(http.StatusInternalServerError) 2914 errView, err := Error500() 2915 if err != nil { 2916 return 2917 } 2918 2919 res.Write(errView) 2920 return 2921 } 2922 2923 case "disable": 2924 err := h.BeforeDisable(res, req) 2925 if err != nil { 2926 log.Println(err) 2927 res.WriteHeader(http.StatusInternalServerError) 2928 errView, err := Error500() 2929 if err != nil { 2930 return 2931 } 2932 2933 res.Write(errView) 2934 return 2935 } 2936 2937 err = addon.Disable(id) 2938 if err != nil { 2939 log.Println(err) 2940 res.WriteHeader(http.StatusInternalServerError) 2941 errView, err := Error500() 2942 if err != nil { 2943 return 2944 } 2945 2946 res.Write(errView) 2947 return 2948 } 2949 2950 err = h.AfterDisable(res, req) 2951 if err != nil { 2952 log.Println(err) 2953 res.WriteHeader(http.StatusInternalServerError) 2954 errView, err := Error500() 2955 if err != nil { 2956 return 2957 } 2958 2959 res.Write(errView) 2960 return 2961 } 2962 default: 2963 res.WriteHeader(http.StatusBadRequest) 2964 errView, err := Error400() 2965 if err != nil { 2966 return 2967 } 2968 2969 res.Write(errView) 2970 return 2971 } 2972 2973 http.Redirect(res, req, req.URL.String(), http.StatusFound) 2974 2975 default: 2976 res.WriteHeader(http.StatusBadRequest) 2977 errView, err := Error400() 2978 if err != nil { 2979 log.Println(err) 2980 return 2981 } 2982 2983 res.Write(errView) 2984 return 2985 } 2986 } 2987 2988 func addonHandler(res http.ResponseWriter, req *http.Request) { 2989 switch req.Method { 2990 case http.MethodGet: 2991 id := req.FormValue("id") 2992 2993 data, err := db.Addon(id) 2994 if err != nil { 2995 log.Println(err) 2996 res.WriteHeader(http.StatusInternalServerError) 2997 errView, err := Error500() 2998 if err != nil { 2999 return 3000 } 3001 3002 res.Write(errView) 3003 return 3004 } 3005 3006 _, ok := addon.Types[id] 3007 if !ok { 3008 log.Println("Addon: ", id, "is not found in addon.Types map") 3009 res.WriteHeader(http.StatusNotFound) 3010 errView, err := Error404() 3011 if err != nil { 3012 return 3013 } 3014 3015 res.Write(errView) 3016 return 3017 } 3018 3019 m, err := addon.Manage(data, id) 3020 if err != nil { 3021 log.Println(err) 3022 res.WriteHeader(http.StatusInternalServerError) 3023 errView, err := Error500() 3024 if err != nil { 3025 return 3026 } 3027 3028 res.Write(errView) 3029 return 3030 } 3031 3032 addonView, err := Admin(m) 3033 if err != nil { 3034 log.Println(err) 3035 res.WriteHeader(http.StatusInternalServerError) 3036 return 3037 } 3038 3039 res.Header().Set("Content-Type", "text/html") 3040 res.Write(addonView) 3041 3042 case http.MethodPost: 3043 // save req.Form 3044 err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB 3045 if err != nil { 3046 log.Println(err) 3047 res.WriteHeader(http.StatusInternalServerError) 3048 errView, err := Error500() 3049 if err != nil { 3050 return 3051 } 3052 3053 res.Write(errView) 3054 return 3055 } 3056 3057 name := req.FormValue("addon_name") 3058 id := req.FormValue("addon_reverse_dns") 3059 3060 at, ok := addon.Types[id] 3061 if !ok { 3062 log.Println("Error: addon", name, "has no record in addon.Types map at", id) 3063 res.WriteHeader(http.StatusBadRequest) 3064 errView, err := Error400() 3065 if err != nil { 3066 return 3067 } 3068 3069 res.Write(errView) 3070 return 3071 } 3072 3073 // if Hookable, call BeforeSave prior to saving 3074 h, ok := at().(item.Hookable) 3075 if ok { 3076 err := h.BeforeSave(res, req) 3077 if err != nil { 3078 log.Println("Error running BeforeSave method in addonHandler for:", id, err) 3079 return 3080 } 3081 } 3082 3083 err = db.SetAddon(req.Form, at()) 3084 if err != nil { 3085 log.Println("Error saving addon:", name, err) 3086 res.WriteHeader(http.StatusInternalServerError) 3087 errView, err := Error500() 3088 if err != nil { 3089 return 3090 } 3091 3092 res.Write(errView) 3093 return 3094 } 3095 3096 http.Redirect(res, req, "/admin/addon?id="+id, http.StatusFound) 3097 3098 default: 3099 res.WriteHeader(http.StatusBadRequest) 3100 errView, err := Error405() 3101 if err != nil { 3102 log.Println(err) 3103 return 3104 } 3105 3106 res.Write(errView) 3107 return 3108 } 3109 } 3110 3111 func adminAddonListItem(data []byte) []byte { 3112 id := gjson.GetBytes(data, "addon_reverse_dns").String() 3113 status := gjson.GetBytes(data, "addon_status").String() 3114 name := gjson.GetBytes(data, "addon_name").String() 3115 author := gjson.GetBytes(data, "addon_author").String() 3116 authorURL := gjson.GetBytes(data, "addon_author_url").String() 3117 version := gjson.GetBytes(data, "addon_version").String() 3118 3119 var action string 3120 var buttonClass string 3121 if status != addon.StatusEnabled { 3122 action = "Enable" 3123 buttonClass = "green" 3124 } else { 3125 action = "Disable" 3126 buttonClass = "red" 3127 } 3128 3129 a := ` 3130 <li class="col s12"> 3131 <div class="row"> 3132 <div class="col s9"> 3133 <a class="addon-name" href="/admin/addon?id=` + id + `" alt="Configure '` + name + `'">` + name + `</a> 3134 <span class="addon-meta addon-author">by: <a href="` + authorURL + `">` + author + `</a></span> 3135 <span class="addon-meta addon-version">version: ` + version + `</span> 3136 </div> 3137 3138 <div class="col s3"> 3139 <form enctype="multipart/form-data" class="quick-` + strings.ToLower(action) + `-addon __ponzu right" action="/admin/addons" method="post"> 3140 <button class="btn waves-effect waves-effect-light ` + buttonClass + `">` + action + `</button> 3141 <input type="hidden" name="id" value="` + id + `" /> 3142 <input type="hidden" name="action" value="` + action + `" /> 3143 </form> 3144 </div> 3145 </div> 3146 </li>` 3147 3148 return []byte(a) 3149 }