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" { writeErr(w, 405, "Method Not Allowed") return } err := r.ParseMultipartForm(4 << 20) if err != nil { writeErr(w, 500, "Failed to retrieve form data") return } var buf bytes.Buffer md := r.PostForm.Get("raw") err = mdParser.Convert([]byte(md), &buf) if err != nil { writeErr(w, 500, fmt.Sprintf("Failed to compile markdown into html: %v", err)) return } 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() { err := godotenv.Load() if err != nil { log.Fatal("Failed to load env") } mdParser = goldmark.New( goldmark.WithExtensions( extension.GFM, extension.Footnote, extension.Typographer, ), goldmark.WithParserOptions( parser.WithAutoHeadingID(), ), goldmark.WithRendererOptions( html.WithHardWraps(), ), ) 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) appPort, err := strconv.ParseUint(os.Getenv("parser_port"), 10, 64) if err != nil { appPort = 5006 } srv := &http.Server{ Addr: fmt.Sprintf(":%d", appPort), Handler: router, } err = srv.ListenAndServe() log.Fatal(err) }