From 3c237fc659c2829042407697ca7aa3e1442a5719 Mon Sep 17 00:00:00 2001 From: Samuel Johnson Date: Tue, 25 Nov 2025 19:47:20 -0500 Subject: Add post editing interface --- cmd/web/handlers/blog.go | 206 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 191 insertions(+), 15 deletions(-) (limited to 'cmd/web/handlers/blog.go') diff --git a/cmd/web/handlers/blog.go b/cmd/web/handlers/blog.go index 573e52f..8d9a811 100644 --- a/cmd/web/handlers/blog.go +++ b/cmd/web/handlers/blog.go @@ -1,10 +1,16 @@ package handlers import ( + "bytes" "database/sql" "html/template" + "io" "log" + "mime/multipart" "net/http" + "os" + "strconv" + "time" "paterissa.net/mblog/internal/models" ) @@ -13,9 +19,167 @@ type blogContext struct { err *log.Logger db *sql.DB + Post models.Post Rows []models.Post Name string IsAuth bool + Offset int +} + +func (ctx *blogContext) viewPost(w http.ResponseWriter, r *http.Request) { + ctx.Rows = []models.Post{} + postId := r.URL.Query().Get("id") + + stmt, err := ctx.db.Prepare("SELECT p.id, u.name, p.time, p.brief, p.content FROM posts p INNER JOIN logins u ON p.user_id = u.id WHERE p.id = $1;") + 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 + } + + row := stmt.QueryRow(postId) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Internal Error")) + ctx.err.Printf("Could not load post from DB: %v\n", err) + return + } + + var p models.Post + err = row.Scan(&p.Id, &p.Name, &p.Time, &p.Brief, &p.Content) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Internal Error")) + ctx.err.Printf("Could not load post from DB: %v\n", err) + return + } + + p.FormattedTime = p.Time.Format(time.ANSIC) + ctx.Post = p + + files := []string{ + "ui/html/base.tmpl.html", + "ui/html/music_player.tmpl.html", + "ui/html/pages/post.tmpl.html", + } + + compiled, err := template.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) post(w http.ResponseWriter, r *http.Request) { + if r.Method != "POST" { + w.WriteHeader(http.StatusMethodNotAllowed) + w.Write([]byte("Method Not Allowed")) + return + } + + cookie, err := r.Cookie("paterissa_session_token") + if err != nil { + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte("Unauthorized")) + return + } + + stmt, err := ctx.db.Prepare("SELECT * FROM cookies WHERE content = $1;") + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Internal Error")) + ctx.err.Printf("Could not load cookies from DB: %v\n", err) + return + } + + var id uint64 + var content string + var userId uint64 + var expiration time.Time + + row := stmt.QueryRow(cookie.Value) + err = row.Scan(&id, &content, &userId, &expiration) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Internal Error")) + return; + } + + err = r.ParseMultipartForm(4 << 20) + if err != nil { + w.WriteHeader(http.StatusUnprocessableEntity) + w.Write([]byte("Failed to retrieve form data")) + ctx.err.Printf("Could not parse request form: %v\n", err) + return + } + + var buffer bytes.Buffer + boundary := "----internal-parser-req" + writer := multipart.NewWriter(&buffer) + writer.SetBoundary(boundary) + writer.WriteField("user_id", strconv.Itoa(int(userId))) + + part, err := writer.CreateFormField("raw") + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Internal Error")) + ctx.err.Printf("Could not create form field: %v\n", err) + return + } + + _, err = part.Write([]byte(r.Form.Get("raw"))) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Internal Error")) + ctx.err.Printf("Could not create form field: %v\n", err) + return + } + + writer.Close() + + proxyReq, err := http.NewRequest(r.Method, "http://127.0.0.1:" + os.Getenv("parser_port"), bytes.NewReader(buffer.Bytes())) + proxyReq.Header = make(http.Header) + for key, val := range r.Header { + if key != "Content-Length" { + proxyReq.Header[key] = val; + } + } + + proxyReq.Header.Set("Content-Type", writer.FormDataContentType()) + + resp, err := http.DefaultClient.Do(proxyReq) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Internal Error")) + ctx.err.Printf("Fail response from parser: %v\n", err) + return + } + if resp.StatusCode != http.StatusOK { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Internal Error")) + + body, err := io.ReadAll(resp.Body) + if err == nil { + ctx.err.Printf("Fail response from parser: %s\n", body) + } + + return + } + + defer resp.Body.Close() + + return } func (ctx *blogContext) index(w http.ResponseWriter, r *http.Request) { @@ -32,15 +196,13 @@ func (ctx *blogContext) index(w http.ResponseWriter, r *http.Request) { ctx.IsAuth = true; } - offset := r.URL.Query().Get("offset") - if offset == "" { - offset = "20" - } + ctx.Offset, _ = strconv.Atoi(r.URL.Query().Get("offset")) - rows, err := ctx.db.Query("SELECT p.id, u.name, p.time, p.content FROM posts p INNER JOIN logins u ON p.user_id = u.id WHERE p.id < " + offset + " ORDER BY p.id DESC LIMIT 20;") + rows, err := ctx.db.Query("SELECT p.id, u.name, p.time, p.brief FROM posts p INNER JOIN logins u ON p.user_id = u.id WHERE p.id > " + strconv.Itoa(ctx.Offset) + " ORDER BY p.id DESC LIMIT 20;") if err != nil { - ctx.err.Print(err.Error()) - http.Error(w, "Internal Server Error", 500) + 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() @@ -48,12 +210,15 @@ func (ctx *blogContext) index(w http.ResponseWriter, r *http.Request) { for rows.Next() { var p models.Post - if err = rows.Scan(&p.Id, &p.Name, &p.Time, &p.Content); err != nil { - ctx.err.Print(err.Error()) - http.Error(w, "Internal Server Error", 500) + 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) } @@ -63,17 +228,28 @@ func (ctx *blogContext) index(w http.ResponseWriter, r *http.Request) { "ui/html/pages/index.tmpl.html", } - compiled, err := template.ParseFiles(files...) + 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 { - ctx.err.Print(err.Error()) - http.Error(w, "Internal Server Error", 500) + 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 { - ctx.err.Print(err.Error()) - http.Error(w, "Internal Server Error", 500) + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Internal Error")) + ctx.err.Printf("Could not execute template: %v\n", err) return } } -- cgit v1.2.3