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" "paterissa.net/mblog/internal" _ "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, http.StatusMethodNotAllowed, "Method Not Allowed") return } err := r.ParseMultipartForm(4 << 20) if err != nil { writeErr(w, http.StatusUnprocessableEntity, fmt.Sprintf("Failed to retrieve form data: %v", err)) return } var longBuf bytes.Buffer var shortBuf bytes.Buffer md := r.Form.Get("raw") userId := r.Form.Get("user_id") shouldCreateShort := len(md) > 255 err = mdParser.Convert([]byte(md), &longBuf) if err != nil { writeErr(w, http.StatusUnprocessableEntity, fmt.Sprintf("Failed to compile markdown into html: %v", err)) return } var shortMd string if shouldCreateShort { shortMd = md[0:249] err = mdParser.Convert([]byte(shortMd), &shortBuf) if err != nil { writeErr(w, http.StatusUnprocessableEntity, 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, http.StatusInternalServerError, fmt.Sprintf("Failed to initialize transaction: %v", err)) return } defer tx.Rollback() stmt, err := tx.PrepareContext(ctx, "INSERT INTO posts (user_id, brief, content) VALUES ($1, $2, $3);") if err != nil { writeErr(w, http.StatusInternalServerError, fmt.Sprintf("Failed to prepare DB statement: %v", err)) return } defer stmt.Close() userIdInt, err := strconv.ParseUint(userId, 10, 64) if err != nil { writeErr(w, http.StatusUnprocessableEntity, fmt.Sprintf("Failed to parse userId: %v\n", err)) return } if shouldCreateShort { _, err = stmt.ExecContext(ctx, userIdInt, shortBuf.Bytes(), longBuf.Bytes()) } else { _, err = stmt.ExecContext(ctx, userIdInt, longBuf.Bytes(), longBuf.Bytes()) } if err != nil { writeErr(w, http.StatusInternalServerError, fmt.Sprintf("Failed to execute statement: %v", err)) return } err = tx.Commit() if err != nil { writeErr(w, http.StatusInternalServerError, fmt.Sprintf("Failed to commit DB transaction: %v", err)) return } } 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 := os.Getenv("db_host") port, err := strconv.ParseUint(os.Getenv("db_port"), 10, 64) if err != nil { 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() internal.Migrate(db, os.Getenv("webmaster"), os.Getenv("blog_pass1"), os.Getenv("blog_pass2")) 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) }