app.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. package main
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "fmt"
  6. "html/template"
  7. "log"
  8. "net/http"
  9. "strconv"
  10. "strings"
  11. "github.com/Masterminds/sprig/v3"
  12. )
  13. const (
  14. SecretKey = "hypermedia rocks"
  15. )
  16. var (
  17. /*
  18. @app.route("/")
  19. def index():
  20. */
  21. RouteFront = http.RedirectHandler("/contacts", http.StatusFound)
  22. /*
  23. @app.route("/contacts")
  24. def contacts():
  25. */
  26. MakeRouteContacts = func(templates *template.Template) http.HandlerFunc {
  27. return func(w http.ResponseWriter, r *http.Request) {
  28. search := r.URL.Query().Get("q")
  29. page, err := strconv.Atoi(r.URL.Query().Get("page"))
  30. if err != nil || page == 0 {
  31. page = 1
  32. }
  33. var contactsSet []Contact
  34. if search != "" {
  35. contactsSet = (&Contact{}).Search(search)
  36. if r.Header.Get("HX-Trigger") == "search" {
  37. templates.ExecuteTemplate(w, "rows.html", anyMap{"contacts": contactsSet})
  38. return
  39. }
  40. } else {
  41. contactsSet = (&Contact{}).All(page)
  42. }
  43. templates.ExecuteTemplate(w, "index.html", anyMap{
  44. "contacts": contactsSet,
  45. "archiver": NewArchiver(),
  46. })
  47. }
  48. }
  49. /*
  50. @app.route("/contacts/archive", methods=["POST"])
  51. def start_archive():
  52. */
  53. MakeStartArchive = func(templates *template.Template) http.HandlerFunc {
  54. return func(w http.ResponseWriter, r *http.Request) {
  55. archiver := NewArchiver()
  56. archiver.Run()
  57. templates.ExecuteTemplate(w, "archive_ui.html", anyMap{"archiver": archiver})
  58. }
  59. }
  60. /*
  61. @app.route("/contacts/archive", methods=["GET"])
  62. def archive_status():
  63. */
  64. MakeArchiveStatus = func(templates *template.Template) http.HandlerFunc {
  65. return func(w http.ResponseWriter, r *http.Request) {
  66. archiver := NewArchiver()
  67. templates.ExecuteTemplate(w, "archive_ui.html", anyMap{"archiver": archiver})
  68. }
  69. }
  70. /*
  71. @app.route("/contacts/archive/file", methods=["GET"])
  72. def archive_content():
  73. */
  74. ArchiveContent http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
  75. archiver := NewArchiver()
  76. fileName := archiver.ArchiveFile()
  77. w.Header().Set("Content-Disposition", fmt.Sprintf(`attachement; filename="%s"`, fileName))
  78. http.ServeFile(w, r, fileName)
  79. }
  80. /*
  81. @app.route("/contacts/archive", methods=["DELETE"])
  82. def reset_archive():
  83. */
  84. MakeResetArchive = func(templates *template.Template) http.HandlerFunc {
  85. return func(w http.ResponseWriter, r *http.Request) {
  86. archiver := NewArchiver()
  87. archiver.Reset()
  88. templates.ExecuteTemplate(w, "archive_ui.html", anyMap{"archiver": archiver})
  89. }
  90. }
  91. /*
  92. @app.route("/contacts/count")
  93. def contacts_count():
  94. */
  95. ContactsCount http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
  96. count := (&Contact{}).Count()
  97. _, _ = fmt.Fprintf(w, "(%d total Contacts)", count)
  98. }
  99. /*
  100. @app.route("/contacts/new", methods=['GET'])
  101. def contacts_new_get():
  102. */
  103. MakeContactsNewGet = func(templates *template.Template) http.HandlerFunc {
  104. return func(w http.ResponseWriter, r *http.Request) {
  105. templates.ExecuteTemplate(w, "new.html", anyMap{"contact": NewContact(nil)})
  106. }
  107. }
  108. /*
  109. @app.route("/contacts/new", methods=['POST'])
  110. def contacts_new():
  111. */
  112. MakeContactsNew = func(templates *template.Template) http.HandlerFunc {
  113. return func(w http.ResponseWriter, r *http.Request) {
  114. c := NewContact(anyMap{
  115. "first": r.FormValue("first_name"),
  116. "last": r.FormValue("last_name"),
  117. "email": r.FormValue("phone"),
  118. "phone": r.FormValue("email"),
  119. })
  120. if c.Save() {
  121. flash("Created New Contact!")
  122. http.Redirect(w, r, "/contacts", http.StatusFound)
  123. return
  124. } else {
  125. templates.ExecuteTemplate(w, "new.html", anyMap{"contact": c})
  126. }
  127. }
  128. }
  129. /*
  130. @app.route("/contacts/<contact_id>")
  131. def contacts_view(contact_id=0):
  132. */
  133. MakeContactsView = func(templates *template.Template) http.HandlerFunc {
  134. return func(w http.ResponseWriter, r *http.Request) {
  135. contact_id, _ := strconv.Atoi(r.PathValue("contact_id"))
  136. contact := (&Contact{}).Find(uint64(contact_id))
  137. templates.ExecuteTemplate(w, "show.html", anyMap{"contact": contact})
  138. }
  139. }
  140. /*
  141. @app.route("/contacts/<contact_id>/edit", methods=["GET"])
  142. def contacts_edit_get(contact_id=0):
  143. */
  144. MakeContactsEditGet = func(templates *template.Template) http.HandlerFunc {
  145. return func(w http.ResponseWriter, r *http.Request) {
  146. contact_id, _ := strconv.Atoi(r.PathValue("contact_id"))
  147. contact := (&Contact{}).Find(uint64(contact_id))
  148. templates.ExecuteTemplate(w, "edit.html", anyMap{"contact": contact})
  149. }
  150. }
  151. /*
  152. @app.route("/contacts/<contact_id>/edit", methods=["POST"])
  153. def contacts_edit_post(contact_id=0):
  154. */
  155. MakeContactsEditPost = func(templates *template.Template) http.HandlerFunc {
  156. return func(w http.ResponseWriter, request *http.Request) {
  157. contact_id, _ := strconv.Atoi(request.PathValue("contact_id"))
  158. c := (&Contact{}).Find(uint64(contact_id))
  159. c.Update(
  160. request.FormValue("first_name"),
  161. request.FormValue("last_name"),
  162. request.FormValue("phone"),
  163. request.FormValue("email"),
  164. )
  165. if c.Save() {
  166. flash("Updated Contact!")
  167. http.Redirect(w, request, fmt.Sprintf("/contacts/%d", contact_id), http.StatusFound)
  168. return
  169. } else {
  170. templates.ExecuteTemplate(w, "edit.html", anyMap{"contact": c})
  171. }
  172. }
  173. }
  174. /*
  175. @app.route("/contacts/<contact_id>/email", methods=["GET"])
  176. def contacts_email_get(contact_id=0):
  177. */
  178. ContactsEmailGet http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
  179. contact_id, _ := strconv.Atoi(r.PathValue("contact_id"))
  180. c := (&Contact{}).Find(uint64(contact_id))
  181. c.Email = r.FormValue("email")
  182. c.validate()
  183. // FIXME find what this is really expected to do in the original Python.
  184. for _, err := range c.Errors {
  185. err := err.Error()
  186. if strings.Contains(err, "email") {
  187. fmt.Fprintln(w, err)
  188. }
  189. }
  190. }
  191. /*
  192. @app.route("/contacts/<contact_id>", methods=["DELETE"])
  193. def contacts_delete(contact_id=0):
  194. */
  195. ContactsDelete http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
  196. contact_id, _ := strconv.Atoi(r.PathValue("contact_id"))
  197. contact := (&Contact{}).Find(uint64(contact_id))
  198. contact.Delete()
  199. if r.Header.Get("HX-Trigger") == "delete-btn" {
  200. flash("Deleted Contact!")
  201. http.Redirect(w, r, "/contacts", http.StatusSeeOther)
  202. return
  203. } else {
  204. return
  205. }
  206. }
  207. /*
  208. @app.route("/contacts/", methods=["DELETE"])
  209. def contacts_delete_all():
  210. */
  211. MakeContactsDeleteAll = func(templates *template.Template) http.HandlerFunc {
  212. return func(w http.ResponseWriter, r *http.Request) {
  213. sContactIDs := strings.Split(r.FormValue("selected_contact_ids"), ",")
  214. for _, sContactID := range sContactIDs {
  215. if contactID, err := strconv.Atoi(sContactID); contactID > 0 && err == nil {
  216. contact := (&Contact{}).Find(uint64(contactID))
  217. contact.Delete()
  218. }
  219. }
  220. flash("Deleted contacts!")
  221. contacts_set := (&Contact{}).All(1)
  222. templates.ExecuteTemplate(w, "index.html", anyMap{"contacts": contacts_set})
  223. }
  224. }
  225. /*
  226. # ===========================================================
  227. # JSON Data API
  228. # ===========================================================
  229. */
  230. /*
  231. @app.route("/api/v1/contacts", methods=["GET"])
  232. def json_contacts():
  233. */
  234. JSONContacts http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
  235. contacts_set := (&Contact{}).All(1)
  236. enc := json.NewEncoder(w)
  237. if err := enc.Encode(anyMap{"contacts": contacts_set}); err != nil {
  238. http.Error(w, err.Error(), http.StatusInternalServerError)
  239. return
  240. }
  241. }
  242. /*
  243. @app.route("/api/v1/contacts", methods=["POST"])
  244. def json_contacts_new():
  245. */
  246. JSONContactsNew http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
  247. c := NewContact(anyMap{
  248. "first": r.FormValue("first_°name"),
  249. "last": r.FormValue("last_name"),
  250. "phone": r.FormValue("phone"),
  251. "email": r.FormValue("email"),
  252. })
  253. enc := json.NewEncoder(w)
  254. if c.Save() {
  255. if err := enc.Encode(c); err != nil {
  256. http.Error(w, err.Error(), http.StatusInternalServerError)
  257. return
  258. }
  259. } else {
  260. w.WriteHeader(http.StatusBadRequest)
  261. if err := enc.Encode(anyMap{"errors": c.Errors}); err != nil {
  262. http.Error(w, err.Error(), http.StatusInternalServerError)
  263. return
  264. }
  265. }
  266. }
  267. /*
  268. @app.route("/api/v1/contacts/<contact_id>", methods=["GET"])
  269. def json_contacts_view(contact_id=0):
  270. */
  271. JSONContactsEdit http.HandlerFunc = func(w http.ResponseWriter, request *http.Request) {
  272. contact_id, _ := strconv.Atoi(request.PathValue("contact_id"))
  273. c := (&Contact{}).Find(uint64(contact_id))
  274. c.Update(
  275. request.FormValue("first_name"),
  276. request.FormValue("last_name"),
  277. request.FormValue("phone"),
  278. request.FormValue("email"),
  279. )
  280. enc := json.NewEncoder(w)
  281. if c.Save() {
  282. if err := enc.Encode(c); err != nil {
  283. http.Error(w, err.Error(), http.StatusInternalServerError)
  284. return
  285. }
  286. } else {
  287. w.WriteHeader(http.StatusBadRequest)
  288. if err := enc.Encode(anyMap{"errors": c.Errors}); err != nil {
  289. http.Error(w, err.Error(), http.StatusInternalServerError)
  290. return
  291. }
  292. }
  293. }
  294. /*
  295. @app.route("/api/v1/contacts/<contact_id>", methods=["DELETE"])
  296. def json_contacts_delete(contact_id=0):
  297. */
  298. JSONContactsDelete http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
  299. contact_id, _ := strconv.Atoi(r.PathValue("contact_id"))
  300. contact := (&Contact{}).Find(uint64(contact_id))
  301. contact.Delete()
  302. enc := json.NewEncoder(w)
  303. _ = enc.Encode(anyMap{"success": true})
  304. }
  305. )
  306. /*
  307. *
  308. TODO implement me
  309. */
  310. func flash(message string) {
  311. }
  312. func setupRoutes(mux *http.ServeMux, templates *template.Template) {
  313. mux.Handle("/", RouteFront)
  314. mux.Handle("/contacts", MakeRouteContacts(templates))
  315. mux.Handle("POST /contacts/archive", MakeStartArchive(templates))
  316. mux.Handle("GET /contacts/archive", MakeArchiveStatus(templates))
  317. mux.Handle("GET /contacts/archive/file", ArchiveContent)
  318. mux.Handle("DELETE /contacts/archive", MakeResetArchive(templates))
  319. mux.Handle("GET /contacts/count", ContactsCount)
  320. mux.Handle("GET /contacts/new", MakeContactsNewGet(templates))
  321. mux.Handle("POST /contacts/new", MakeContactsNew(templates))
  322. mux.Handle("GET /contacts/{contact_id}", MakeContactsView(templates))
  323. mux.Handle("GET /contacts/{contact_id}/edit", MakeContactsEditGet(templates))
  324. mux.Handle("POST /contacts/{contact_id}/edit", MakeContactsEditPost(templates))
  325. mux.Handle("GET /contacts/{contact_id}/email", ContactsEmailGet)
  326. mux.Handle("DELETE /contacts/{contact_id}", ContactsDelete)
  327. mux.Handle("DELETE /contacts/", MakeContactsDeleteAll(templates))
  328. mux.Handle("GET /api/v1/contacts", JSONContacts)
  329. mux.Handle("POST /api/v1/contacts", JSONContactsNew)
  330. mux.Handle("GET /api/v1/contacts/{contact_id}", JSONContactsEdit)
  331. mux.Handle("DELETE /api/v1/contacts/{contact_id}", JSONContactsDelete)
  332. }
  333. func main() {
  334. mux := http.NewServeMux()
  335. templates := template.Must(
  336. template.
  337. New("htmx-app").
  338. Funcs(sprig.FuncMap()). // Arithmetic, etc.
  339. Funcs(template.FuncMap{
  340. "get_flashed_messages": func() string { return "some message" },
  341. }).
  342. ParseGlob("./templates/*.gohtml"),
  343. )
  344. setupRoutes(mux, templates)
  345. (&Contact{}).LoadDB()
  346. if err := http.ListenAndServe(":8080", mux); err != nil {
  347. if !errors.Is(err, http.ErrServerClosed) {
  348. log.Printf("Server error: %v\n", err)
  349. return
  350. }
  351. }
  352. log.Println("Server shutdown")
  353. }