aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/web/handlers/blog.go81
-rw-r--r--cmd/web/handlers/routes.go1
-rw-r--r--internal/dbmigrations.go5
-rw-r--r--static/app.css28
-rw-r--r--ui/html/pages/index.tmpl.html18
-rw-r--r--ui/html/pages/login.tmpl.html6
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>