app.go 11 KB

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