| 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "net/http" |
| 6 | "strings" |
| 7 | |
| 8 | "github.com/stevedylandev/andromeda/pkg/auth" |
| 9 | "github.com/stevedylandev/andromeda/pkg/web" |
| 10 | ) |
| 11 | |
| 12 | func (a *App) indexHandler(w http.ResponseWriter, r *http.Request) { |
| 13 | cats, _ := listCategories(a.DB) |
| 14 | links, _ := listLinks(a.DB) |
| 15 | groups := make([]categoryGroup, 0, len(cats)) |
| 16 | for _, c := range cats { |
| 17 | group := categoryGroup{Name: c.Name} |
| 18 | for _, l := range links { |
| 19 | if l.CategoryID == c.ID { |
| 20 | group.Links = append(group.Links, l) |
| 21 | } |
| 22 | } |
| 23 | groups = append(groups, group) |
| 24 | } |
| 25 | web.Render(a.Templates, w, "index.html", indexPageData{Groups: groups}, a.Log) |
| 26 | } |
| 27 | |
| 28 | func (a *App) loginGetHandler(w http.ResponseWriter, r *http.Request) { |
| 29 | web.Render(a.Templates, w, "login.html", loginPageData{Error: r.URL.Query().Get("error")}, a.Log) |
| 30 | } |
| 31 | |
| 32 | func (a *App) loginPostHandler(w http.ResponseWriter, r *http.Request) { |
| 33 | if a.Password == "" { |
| 34 | web.RedirectWithError(w, r, "/login", "No password configured") |
| 35 | return |
| 36 | } |
| 37 | if err := r.ParseForm(); err != nil { |
| 38 | web.RedirectWithError(w, r, "/login", "Bad request") |
| 39 | return |
| 40 | } |
| 41 | if !auth.VerifyPassword(r.FormValue("password"), a.Password) { |
| 42 | web.RedirectWithError(w, r, "/login", "Invalid password") |
| 43 | return |
| 44 | } |
| 45 | token, err := a.Sessions.Create() |
| 46 | if err != nil { |
| 47 | a.Log.Error("create session failed", "err", err) |
| 48 | web.RedirectWithError(w, r, "/login", "Session error") |
| 49 | return |
| 50 | } |
| 51 | a.Sessions.PruneExpired() |
| 52 | http.SetCookie(w, a.Sessions.SessionCookie(token)) |
| 53 | http.Redirect(w, r, "/admin", http.StatusSeeOther) |
| 54 | } |
| 55 | |
| 56 | func (a *App) logoutHandler(w http.ResponseWriter, r *http.Request) { |
| 57 | if c, err := r.Cookie(a.Sessions.CookieName); err == nil && c.Value != "" { |
| 58 | a.Sessions.Delete(c.Value) |
| 59 | } |
| 60 | http.SetCookie(w, a.Sessions.ClearCookie()) |
| 61 | http.Redirect(w, r, "/login", http.StatusSeeOther) |
| 62 | } |
| 63 | |
| 64 | func (a *App) adminHandler(w http.ResponseWriter, r *http.Request) { |
| 65 | cats, _ := listCategories(a.DB) |
| 66 | raw, _ := listLinks(a.DB) |
| 67 | catName := map[int64]string{} |
| 68 | for _, c := range cats { |
| 69 | catName[c.ID] = c.Name |
| 70 | } |
| 71 | rows := make([]adminLinkRow, 0, len(raw)) |
| 72 | for _, l := range raw { |
| 73 | rows = append(rows, adminLinkRow{ShortID: l.ShortID, Title: l.Title, URL: l.URL, FaviconURL: l.FaviconURL, Category: catName[l.CategoryID]}) |
| 74 | } |
| 75 | web.Render(a.Templates, w, "admin.html", adminPageData{Success: r.URL.Query().Get("success"), Error: r.URL.Query().Get("error"), Categories: cats, Links: rows}, a.Log) |
| 76 | } |
| 77 | |
| 78 | func (a *App) adminAddCategory(w http.ResponseWriter, r *http.Request) { |
| 79 | if err := r.ParseForm(); err != nil { |
| 80 | web.RedirectWithError(w, r, "/admin", "Bad request") |
| 81 | return |
| 82 | } |
| 83 | name := strings.TrimSpace(r.FormValue("name")) |
| 84 | if name == "" { |
| 85 | web.RedirectWithError(w, r, "/admin", "Name required") |
| 86 | return |
| 87 | } |
| 88 | if _, err := createCategory(a.DB, name); err != nil { |
| 89 | a.Log.Error("create category", "err", err) |
| 90 | web.RedirectWithError(w, r, "/admin", "Failed to add category") |
| 91 | return |
| 92 | } |
| 93 | web.RedirectWithSuccess(w, r, "/admin", "Category added") |
| 94 | } |
| 95 | |
| 96 | func (a *App) adminDeleteCategory(w http.ResponseWriter, r *http.Request) { |
| 97 | _, _ = deleteCategoryByShortID(a.DB, r.PathValue("short_id")) |
| 98 | web.RedirectWithSuccess(w, r, "/admin", "Category removed") |
| 99 | } |
| 100 | |
| 101 | func (a *App) adminMoveCategory(w http.ResponseWriter, r *http.Request) { |
| 102 | dir := 0 |
| 103 | switch r.PathValue("dir") { |
| 104 | case "up": |
| 105 | dir = -1 |
| 106 | case "down": |
| 107 | dir = 1 |
| 108 | default: |
| 109 | web.RedirectWithError(w, r, "/admin", "Invalid direction") |
| 110 | return |
| 111 | } |
| 112 | if _, err := moveCategory(a.DB, r.PathValue("short_id"), dir); err != nil { |
| 113 | a.Log.Error("move category", "err", err) |
| 114 | web.RedirectWithError(w, r, "/admin", "Failed to reorder") |
| 115 | return |
| 116 | } |
| 117 | web.RedirectWithSuccess(w, r, "/admin", "Category reordered") |
| 118 | } |
| 119 | |
| 120 | func (a *App) adminAddLink(w http.ResponseWriter, r *http.Request) { |
| 121 | if err := r.ParseForm(); err != nil { |
| 122 | web.RedirectWithError(w, r, "/admin", "Bad request") |
| 123 | return |
| 124 | } |
| 125 | title := strings.TrimSpace(r.FormValue("title")) |
| 126 | url := strings.TrimSpace(r.FormValue("url")) |
| 127 | if title == "" || url == "" { |
| 128 | web.RedirectWithError(w, r, "/admin", "Title and URL required") |
| 129 | return |
| 130 | } |
| 131 | cat, err := getCategoryByName(a.DB, r.FormValue("category")) |
| 132 | if err != nil || cat == nil { |
| 133 | web.RedirectWithError(w, r, "/admin", "Unknown category") |
| 134 | return |
| 135 | } |
| 136 | link, err := createLink(a.DB, title, url, nil, cat.ID) |
| 137 | if err != nil { |
| 138 | a.Log.Error("create link", "err", err) |
| 139 | web.RedirectWithError(w, r, "/admin", "Failed to add link") |
| 140 | return |
| 141 | } |
| 142 | go func(id int64, u string) { |
| 143 | if fav := discoverFavicon(context.Background(), u); fav != "" { |
| 144 | if err := updateLinkFavicon(a.DB, id, &fav); err != nil { |
| 145 | a.Log.Error("update favicon", "err", err) |
| 146 | } |
| 147 | } |
| 148 | }(link.ID, url) |
| 149 | web.RedirectWithSuccess(w, r, "/admin", "Link added") |
| 150 | } |
| 151 | |
| 152 | func (a *App) adminDeleteLink(w http.ResponseWriter, r *http.Request) { |
| 153 | _, _ = deleteLinkByShortID(a.DB, r.PathValue("short_id")) |
| 154 | web.RedirectWithSuccess(w, r, "/admin", "Link removed") |
| 155 | } |