From 1692aea0951f1d46af01211bd3e32920c443505f Mon Sep 17 00:00:00 2001 From: Samuel Johnson Date: Sun, 23 Nov 2025 23:52:30 -0500 Subject: Add markdown parsing endpoint --- cmd/parser/main.go | 109 ++++++++++++++++++++++++++++++++++++---- cmd/web/main.go | 1 + go.mod | 10 ++++ go.sum | 21 ++++++++ internal/context/environment.go | 1 + 5 files changed, 131 insertions(+), 11 deletions(-) diff --git a/cmd/parser/main.go b/cmd/parser/main.go index 3933935..c9c80c8 100644 --- a/cmd/parser/main.go +++ b/cmd/parser/main.go @@ -2,48 +2,86 @@ package main import ( "bytes" + "context" + "database/sql" "fmt" "log" "net/http" "os" "strconv" + "time" "github.com/joho/godotenv" "github.com/yuin/goldmark" "github.com/yuin/goldmark/extension" "github.com/yuin/goldmark/parser" "github.com/yuin/goldmark/renderer/html" + "golang.org/x/crypto/bcrypt" + + _ "github.com/jackc/pgx/v5/stdlib" ) +var db *sql.DB var mdParser goldmark.Markdown +func writeErr(w http.ResponseWriter, errcode int, msg string) { + w.WriteHeader(errcode) + w.Write([]byte(msg)) + return +} + func mdServe(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { - w.WriteHeader(405) - w.Write([]byte("Method Not Allowed")) + writeErr(w, 405, "Method Not Allowed") return } - err := r.ParseForm() + err := r.ParseMultipartForm(4 << 20) if err != nil { - w.WriteHeader(500) - w.Write([]byte("Failed to retrieve form data")) + writeErr(w, 500, "Failed to retrieve form data") return } var buf bytes.Buffer - - log.Print(r.Form) + md := r.PostForm.Get("raw") - log.Print(md) + err = mdParser.Convert([]byte(md), &buf) if err != nil { - w.WriteHeader(500) - w.Write([]byte("Failed to compile markdown into html")) + writeErr(w, 500, fmt.Sprintf("Failed to compile markdown into html: %v", err)) return } - w.Write([]byte(md)) + ctx, cancel := context.WithTimeout(r.Context(), 5 * time.Second) + defer cancel() + + tx, err := db.BeginTx(ctx, nil) + if err != nil { + writeErr(w, 500, fmt.Sprintf("Failed to initialize transaction: %v", err)) + return + } + defer tx.Rollback() + + stmt, err := tx.PrepareContext(ctx, "INSERT INTO posts (name, content) VALUES ($1, $2);") + if err != nil { + writeErr(w, 500, fmt.Sprintf("Failed to prepare DB statement: %v", err)) + return + } + defer stmt.Close() + + _, err = stmt.ExecContext(ctx, os.Getenv("webmaster"), buf.Bytes()) + if err != nil { + writeErr(w, 500, fmt.Sprintf("Failed to execute statement: %v", err)) + return + } + + err = tx.Commit() + if err != nil { + writeErr(w, 500, fmt.Sprintf("Failed to commit DB transaction: %v", err)) + } + + w.WriteHeader(200) + w.Write([]byte("Successfully parsed and stored markdown")) } func main() { @@ -66,6 +104,55 @@ func main() { ), ) + host := "localhost" + port := 5432 + dbName := os.Getenv("db_name") + user := os.Getenv("db_user") + pass := os.Getenv("db_pass") + connStr := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", host, port, user, pass, dbName) + + db, err = sql.Open("pgx", connStr) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to connect to DB: %v\n", err) + os.Exit(1) + } + defer db.Close() + + _, table_check := db.Query("SELECT * FROM posts;") + if table_check != nil { + _, err = db.Exec("CREATE TABLE posts (id SERIAL PRIMARY KEY, name VARCHAR(50) NOT NULL, time DATE DEFAULT CURRENT_DATE, content TEXT NOT NULL);") + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to create posts table: %v\n", err) + os.Exit(1) + } + } + + webmaster := os.Getenv("webmaster") + passOne := os.Getenv("blog_pass1") + passTwo := os.Getenv("blog_pass2") + + _, table_check = db.Query("SELECT * FROM users;") + if table_check != nil { + _, err = db.Exec("CREATE TABLE logins (id SERIAL PRIMARY KEY, name VARCHAR(50) NOT NULL, time DATE DEFAULT CURRENT_DATE, pass_one TEXT NOT NULL, pass_two TEXT NOT NULL);") + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to create logins table: %v\n", err) + os.Exit(1) + } + + hashOne, errHashOne := bcrypt.GenerateFromPassword([]byte(passOne), 12) + hashTwo, errHashTwo := bcrypt.GenerateFromPassword([]byte(passTwo), 12) + if errHashOne != nil || errHashTwo != nil { + fmt.Fprintf(os.Stderr, "Failed to hash password") + os.Exit(1) + } + + _, err = db.Exec(fmt.Sprintf("INSERT INTO logins (name, pass_one, pass_two) VALUES ('%s', '%s', '%s');", webmaster, hashOne, hashTwo)) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to add webmaster to logins table: %v\n", err) + os.Exit(1) + } + } + router := http.NewServeMux() router.HandleFunc("/", mdServe) diff --git a/cmd/web/main.go b/cmd/web/main.go index 246abeb..619cc93 100644 --- a/cmd/web/main.go +++ b/cmd/web/main.go @@ -27,6 +27,7 @@ func main() { app.AudioDir = os.Getenv("audio_dir") app.Env.Webmaster = os.Getenv("webmaster") + app.Env.Db.Name = os.Getenv("db_name") app.Env.Db.Username = os.Getenv("db_user") app.Env.Db.Password = os.Getenv("db_pass") app.Env.AppPort, err = strconv.ParseUint(os.Getenv("web_port"), 10, 64) diff --git a/go.mod b/go.mod index bf24c47..82a8fd3 100644 --- a/go.mod +++ b/go.mod @@ -6,3 +6,13 @@ require ( github.com/joho/godotenv v1.5.1 github.com/yuin/goldmark v1.7.11 ) + +require ( + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.7.6 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + golang.org/x/crypto v0.37.0 // indirect + golang.org/x/sync v0.13.0 // indirect + golang.org/x/text v0.24.0 // indirect +) diff --git a/go.sum b/go.sum index 6274e37..1a6445d 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,25 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk= +github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/yuin/goldmark v1.7.11 h1:ZCxLyDMtz0nT2HFfsYG8WZ47Trip2+JyLysKcMYE5bo= github.com/yuin/goldmark v1.7.11/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/context/environment.go b/internal/context/environment.go index 8329ac2..7d52978 100644 --- a/internal/context/environment.go +++ b/internal/context/environment.go @@ -1,6 +1,7 @@ package context type DbCredentials struct { + Name string Username string Password string } -- cgit v1.2.3