mirror of
https://github.com/GRFreire/nthmail.git
synced 2026-01-09 04:49:39 +00:00
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
219 lines
4.8 KiB
Go
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)
|
|
}
|