diff options
| -rw-r--r-- | cmd/web/handlers/blog.go | 81 | ||||
| -rw-r--r-- | cmd/web/handlers/routes.go | 1 | ||||
| -rw-r--r-- | internal/dbmigrations.go | 5 | ||||
| -rw-r--r-- | static/app.css | 28 | ||||
| -rw-r--r-- | ui/html/pages/index.tmpl.html | 18 | ||||
| -rw-r--r-- | ui/html/pages/login.tmpl.html | 6 |
6 files changed, 123 insertions, 16 deletions
diff --git a/cmd/web/handlers/blog.go b/cmd/web/handlers/blog.go index 472b270..51b5771 100644 --- a/cmd/web/handlers/blog.go +++ b/cmd/web/handlers/blog.go @@ -24,6 +24,7 @@ type blogContext struct { Rows []models.Post Comments []models.Comment Name string + SearchTerm string IsAuth bool PagePopulated bool Offset int @@ -315,10 +316,90 @@ func (ctx *blogContext) post(w http.ResponseWriter, r *http.Request) { return } +func (ctx *blogContext) search(w http.ResponseWriter, r *http.Request) { + ctx.Rows = []models.Post{} + ctx.IsAuth = false + ctx.PagePopulated = false + + _, err := r.Cookie("paterissa_session_token") + if err == nil { + ctx.IsAuth = true + } + + ctx.Offset, _ = strconv.Atoi(r.URL.Query().Get("offset")) + ctx.SearchTerm = r.URL.Query().Get("query") + + stmt, err := ctx.db.Prepare("SELECT p.id, u.name, p.time, p.brief FROM posts p INNER JOIN logins u ON p.user_id = u.id WHERE word_similarity($1, p.brief) > 0.1 ORDER BY word_similarity($1, p.brief) DESC LIMIT 20 OFFSET $2;") + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Internal Error")) + ctx.err.Printf("Could not prepare statement for DB: %v\n", err) + return + } + defer stmt.Close() + + rows, err := stmt.Query(ctx.SearchTerm, strconv.Itoa(ctx.Offset)) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Internal Error")) + ctx.err.Printf("Could not load posts from DB: %v\n", err) + return + } + defer rows.Close() + + for rows.Next() { + var p models.Post + + if err = rows.Scan(&p.Id, &p.Name, &p.Time, &p.Brief); err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Internal Error")) + ctx.err.Printf("Could not load row: %v\n", err) + return + } + + p.FormattedTime = p.Time.Format(time.ANSIC) + + ctx.Rows = append(ctx.Rows, p) + ctx.PagePopulated = true + } + + files := []string{ + "ui/html/base.tmpl.html", + "ui/html/music_player.tmpl.html", + "ui/html/pages/index.tmpl.html", + } + + funcMap := template.FuncMap{ + "add": func(a int, b int) int { + return a + b + }, + "sub": func(a int, b int) int { + return a - b + }, + } + + compiled, err := template.New("blog").Funcs(funcMap).ParseFiles(files...) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Internal Error")) + ctx.err.Printf("Could not parse template: %v\n", err) + return + } + + err = compiled.ExecuteTemplate(w, "base", ctx) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Internal Error")) + ctx.err.Printf("Could not execute template: %v\n", err) + return + } +} + func (ctx *blogContext) index(w http.ResponseWriter, r *http.Request) { ctx.Rows = []models.Post{} ctx.IsAuth = false ctx.PagePopulated = false + ctx.SearchTerm = "" if r.URL.Path != "/" { http.NotFound(w, r) diff --git a/cmd/web/handlers/routes.go b/cmd/web/handlers/routes.go index f6d8dc3..074e3e9 100644 --- a/cmd/web/handlers/routes.go +++ b/cmd/web/handlers/routes.go @@ -45,6 +45,7 @@ func RegisterEndpoints(app types.Application, db *sql.DB) *http.ServeMux { blogRouter := http.NewServeMux() blogRouter.HandleFunc("/", auth.CheckAndInvalidate(blog.index)) + blogRouter.HandleFunc("/search", auth.CheckAndInvalidate(blog.search)) blogRouter.HandleFunc("/post/new", auth.Resolve(blog.post)) blogRouter.HandleFunc("/post", blog.viewPost) blogRouter.HandleFunc("/comments/verify", auth.Resolve(blog.verifyComment)) diff --git a/internal/dbmigrations.go b/internal/dbmigrations.go index 41180c7..7ece2b8 100644 --- a/internal/dbmigrations.go +++ b/internal/dbmigrations.go @@ -64,4 +64,9 @@ func Migrate(db *sql.DB, webmaster string, passOne string, passTwo string) { fmt.Fprintf(os.Stderr, "Unable to create comments table: %v\n", err) } } + + _, err := db.Exec("CREATE EXTENSION IF NOT EXISTS pg_trgm;") + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to enable pg_trgm: %v\n", err) + } } diff --git a/static/app.css b/static/app.css index 4e26607..c9b482d 100644 --- a/static/app.css +++ b/static/app.css @@ -27,7 +27,7 @@ pre { } .rss { - margin-left: 1.5em; + margin-left: 1.75em; } .lead { @@ -64,6 +64,11 @@ pre { margin-right: 1.5em; } +.right_nofloat { + margin: auto; + margin-right: 1.5em; +} + a { color: inherit; position: relative; @@ -146,6 +151,7 @@ iframe { justify-content: center; align-content: center; margin: 2em; + margin-left: 1.5em; } .inner_pane { @@ -228,21 +234,21 @@ iframe { padding: 0.5em; } -.login_form { +.flex_center { display: flex; - flex-direction: column; + justify-content: center; } -.login_form > input { - margin: auto; - margin-top: 0.5em; - margin-bottom: 2em; - width: 95%; +.login_form { + width: 75%; + display: grid; + grid-template-columns: 1fr 9fr; + gap: 1em; } -.url_form > input { - margin-right: 2em; - margin-left: 2em; +.login_form div { + margin: auto; + margin-right: 0em; } .markdown_form { diff --git a/ui/html/pages/index.tmpl.html b/ui/html/pages/index.tmpl.html index fa5a9bb..5c91ab4 100644 --- a/ui/html/pages/index.tmpl.html +++ b/ui/html/pages/index.tmpl.html @@ -14,6 +14,12 @@ <a href="/login" class="nav_tag right">Login</a> {{end}} </div> + <div class="flex_end right_nofloat"> + <form action="/search" method="GET"> + <input type="search" id="search_bar" name="query" placeholder="Search posts..." aria-label="Search for posts"> + <button type="submit" id="search-button">Search</button> + </form> + </div> {{if .IsAuth}} <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.css"> <script src="https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.js"></script> @@ -47,10 +53,18 @@ </a> <div class="right"> {{if ne .Offset 0}} - <a href="/?offset={{sub .Offset 10}}" class="nav_tag">Previous</a> + {{if eq .SearchTerm ""}} + <a href="/?offset={{sub .Offset 10}}" class="nav_tag">Previous</a> + {{else}} + <a href="/search?offset={{sub .Offset 10}}&query={{.SearchTerm}}" class="nav_tag">Previous</a> + {{end}} {{end}} {{if .PagePopulated}} - <a href="/?offset={{add .Offset 10}}">Next</a> + {{if eq .SearchTerm ""}} + <a href="/?offset={{add .Offset 10}}">Next</a> + {{else}} + <a href="/search?offset={{add .Offset 10}}&query={{.SearchTerm}}">Next</a> + {{end}} {{end}} </div> </div> diff --git a/ui/html/pages/login.tmpl.html b/ui/html/pages/login.tmpl.html index 01dc804..9751ee8 100644 --- a/ui/html/pages/login.tmpl.html +++ b/ui/html/pages/login.tmpl.html @@ -5,8 +5,8 @@ <div class="topline"> <h2 class="login_header">Log In</h2> </div> - <div class="card"> - <form class="login_form center" action="/login" method="POST"> + <div class="card flex_center"> + <form class="login_form" action="/login" method="POST"> <label for="user">Username:</label> <input type="text" id="user" name="user"> <label for="pass_one">Password:</label> @@ -14,7 +14,7 @@ <label for="pass_two">Password:</label> <input type="text" id="pass_two" name="pass_two"> <br> - <div class="right"> + <div> <input type="submit" value="Submit"> </div> </form> |
