nthmail/pkg/web_server/main.go
Guilherme Rugai Freire 48e7b5a9a5
integrate web and mail server
now, there is only one binary that starts both servers,
making them use the same SQL connection.

this commit also added some `defer tx.Commit()` to ensure
all the transactions were closed
2024-07-10 16:56:46 -03:00

219 lines
4.8 KiB
Go

package web_server
import (
"database/sql"
"errors"
"fmt"
"log"
"net/http"
"os"
"strconv"
"time"
"github.com/GRFreire/nthmail/pkg/mail_utils"
"github.com/GRFreire/nthmail/pkg/rig"
"github.com/go-chi/chi"
_ "github.com/mattn/go-sqlite3"
)
func Start(db *sql.DB) error {
server := &ServerResouces{}
server.db = db
domain, exists := os.LookupEnv("MAIL_SERVER_DOMAIN")
if !exists {
domain = "localhost"
}
server.domain = domain
var port int
var err error
port_str, exists := os.LookupEnv("WEB_SERVER_PORT")
if exists {
port, err = strconv.Atoi(port_str)
if err != nil {
return errors.New("env:WEB_SERVER_PORT is not a number")
}
} else {
port = 3000
}
router := server.Routes()
log.Println("Listening on port", port)
err = http.ListenAndServe(fmt.Sprintf(":%d", port), router)
if err != nil {
return err
}
return nil
}
type ServerResouces struct {
db *sql.DB
domain string
}
type db_mail struct {
Id int
Arrived_at int64
Rcpt_addr, From_addr string
Data []byte
}
func (sr ServerResouces) Routes() chi.Router {
router := chi.NewRouter()
router.Get("/", func(res http.ResponseWriter, req *http.Request) {
page := index_page()
page.Render(req.Context(), res)
})
router.Get("/random", func(res http.ResponseWriter, req *http.Request) {
inbox_name := rig.GenerateRandomInboxName()
inbox_addr := fmt.Sprintf("/%s@%s", inbox_name, sr.domain)
http.Redirect(res, req, inbox_addr, 307)
})
router.Get("/{rcpt-addr}", sr.handleInbox)
router.Get("/{rcpt-addr}/{mail-id}", sr.handleMail)
return router
}
func (sr ServerResouces) handleInbox(res http.ResponseWriter, req *http.Request) {
rcpt_addr := chi.URLParam(req, "rcpt-addr")
if len(rcpt_addr) == 0 {
res.WriteHeader(404)
res.Write([]byte("inbox not found"))
return
}
tx, err := sr.db.Begin()
if err != nil {
res.WriteHeader(500)
res.Write([]byte("internal server error"))
log.Println("could not begin db transaction")
return
}
defer tx.Commit()
stmt, err := tx.Prepare("SELECT mails.id, mails.arrived_at, mails.rcpt_addr, mails.from_addr, mails.data FROM mails WHERE mails.rcpt_addr = ?")
if err != nil {
res.WriteHeader(500)
res.Write([]byte("internal server error"))
log.Println("could not prepare db stmt")
return
}
defer stmt.Close()
rows, err := stmt.Query(rcpt_addr)
if err != nil {
res.WriteHeader(500)
res.Write([]byte("internal server error"))
log.Println("could not query db stmt")
return
}
defer rows.Close()
var mails []mail_utils.Mail_obj
for rows.Next() {
var m db_mail
err = rows.Scan(&m.Id, &m.Arrived_at, &m.Rcpt_addr, &m.From_addr, &m.Data)
if err != nil {
res.WriteHeader(500)
res.Write([]byte("internal server error"))
log.Println("could not scan db row")
return
}
mail_obj, err := mail_utils.Parse_mail(m.Data, true)
mail_obj.Date = time.Unix(m.Arrived_at, 0)
mail_obj.Id = m.Id
if err != nil {
res.WriteHeader(500)
res.Write([]byte("internal server error"))
log.Println("could not parse mail")
log.Println(err)
return
}
mails = append(mails, mail_obj)
}
body := inbox_body(rcpt_addr, mails)
body.Render(req.Context(), res)
}
func (sr ServerResouces) handleMail(res http.ResponseWriter, req *http.Request) {
rcpt_addr := chi.URLParam(req, "rcpt-addr")
if len(rcpt_addr) == 0 {
res.WriteHeader(404)
res.Write([]byte("inbox not found"))
return
}
mail_id := chi.URLParam(req, "mail-id")
if len(rcpt_addr) == 0 {
res.WriteHeader(404)
res.Write([]byte("mail not found"))
return
}
tx, err := sr.db.Begin()
if err != nil {
res.WriteHeader(500)
res.Write([]byte("internal server error"))
log.Println("could not begin db transaction")
return
}
defer tx.Commit()
stmt, err := tx.Prepare("SELECT mails.id, mails.arrived_at, mails.rcpt_addr, mails.from_addr, mails.data FROM mails WHERE mails.rcpt_addr = ? AND mails.id = ?")
if err != nil {
res.WriteHeader(500)
res.Write([]byte("internal server error"))
log.Println("could not prepare db stmt")
return
}
defer stmt.Close()
row := stmt.QueryRow(rcpt_addr, mail_id)
format, f_pref := mail_utils.Parse_mime_format(req.URL.Query().Get("format"))
var m db_mail
err = row.Scan(&m.Id, &m.Arrived_at, &m.Rcpt_addr, &m.From_addr, &m.Data)
if err != nil {
res.WriteHeader(500)
res.Write([]byte("internal server error"))
log.Println("could not scan db row")
return
}
mail_obj, err := mail_utils.Parse_mail(m.Data, false)
mail_obj.Date = time.Unix(m.Arrived_at, 0)
mail_obj.Id = m.Id
if err != nil {
res.WriteHeader(500)
res.Write([]byte("internal server error"))
log.Println("could not parse mail")
log.Println(err)
return
}
mail_obj = mail_utils.Set_format_index(mail_obj, format, f_pref)
body := mail_body_comp(rcpt_addr, mail_obj)
body.Render(req.Context(), res)
}