Browse Source

Refacor request handling

- Split requests to 3 types: mailbox, mail, common
- Provide common pre-handling for each request type
- Fix attachments path usage
- Update look and feel
Alexey Edelev 5 years ago
parent
commit
24c8ccd69f

+ 15 - 6
db/db.go

@@ -30,6 +30,7 @@ import (
 	"crypto/sha1"
 	"encoding/hex"
 	"errors"
+	"fmt"
 	"log"
 	"time"
 
@@ -272,10 +273,10 @@ func (s *Storage) SaveMail(email, folder string, m *common.Mail) error {
 	return nil
 }
 
-func (s *Storage) RemoveMail(user string, messageId string) error {
+func (s *Storage) RemoveMail(user string, mailId string) error {
 	mailsCollection := s.db.Collection(qualifiedMailCollection(user))
 
-	oId, err := primitive.ObjectIDFromHex(messageId)
+	oId, err := primitive.ObjectIDFromHex(mailId)
 	if err != nil {
 		return err
 	}
@@ -314,11 +315,11 @@ func (s *Storage) MailList(user, email, folder string, frame common.Frame) ([]*c
 			log.Printf("Unable to read database mail record: %s", err)
 			continue
 		}
-		log.Printf("Add message: %s", result.Id)
+		// fmt.Printf("Add mail: %s", result.Id)
 		headers = append(headers, result)
 	}
 
-	log.Printf("Mails read from database: %v", headers)
+	// fmt.Printf("Mails read from database: %v", headers)
 	return headers, nil
 }
 
@@ -392,8 +393,16 @@ func (s *Storage) GetUsers() (users []string, err error) {
 	return nil, nil
 }
 
-func (s *Storage) GetEmails(user []string) (emails []string, err error) {
-	return nil, nil
+func (s *Storage) GetEmails(user string) (emails []string, err error) {
+	fmt.Printf("user: %s\n", user)
+	result := &struct {
+		Email []string
+	}{}
+	err = s.emailsCollection.FindOne(context.Background(), bson.M{"user": user}).Decode(result)
+	if err != nil {
+		return nil, err
+	}
+	return result.Email, nil
 }
 
 func (s *Storage) GetAllEmails() (emails []string, err error) {

+ 8 - 0
scanner/mailscanner.go

@@ -71,6 +71,14 @@ func NewMailScanner() (ms *MailScanner) {
 		return
 	}
 
+	if !utils.DirectoryExists(config.ConfigInstance().AttachmentsPath) {
+		err = os.Mkdir(config.ConfigInstance().AttachmentsPath, 0755)
+		if err != nil {
+			log.Fatal(err)
+			return
+		}
+	}
+
 	ms = &MailScanner{
 		watcher:       watcher,
 		storage:       storage,

+ 5 - 2
scanner/parser.go

@@ -35,6 +35,7 @@ import (
 	"time"
 
 	"git.semlanik.org/semlanik/gostfix/common"
+	"git.semlanik.org/semlanik/gostfix/config"
 	utils "git.semlanik.org/semlanik/gostfix/utils"
 	"github.com/google/uuid"
 	enmime "github.com/jhillyerd/enmime"
@@ -159,8 +160,9 @@ func (pd *parseData) parseHeader(headerRaw string) {
 			if err == nil {
 				pd.email.Header.Date = time.Unix()
 				pd.mandatoryHeaders |= DateHeaderMask
+			} else {
+				log.Printf("Invalid date format %s, %s", strings.Trim(capture[2], " \t"), err)
 			}
-			log.Printf("Invalid date format %s, %s", strings.Trim(capture[2], " \t"), err)
 		case "content-type":
 			pd.previousHeader = &pd.bodyContentType
 		default:
@@ -196,9 +198,10 @@ func (pd *parseData) parseBody() {
 	for _, attachment := range en.Attachments {
 		uuid := uuid.New()
 		fileName := hex.EncodeToString(uuid[:])
-		attachmentFile, err := os.Create(fileName)
+		attachmentFile, err := os.Create(config.ConfigInstance().AttachmentsPath + "/" + fileName)
 		log.Printf("Attachment found %s\n", fileName)
 		if err != nil {
+			log.Printf("Unable to save attachment %s %s\n", fileName, err)
 			continue
 		}
 		pd.email.Body.Attachments = append(pd.email.Body.Attachments, &common.AttachmentHeader{

+ 9 - 0
utils/regexp.go

@@ -37,6 +37,7 @@ const (
 	BoundaryStartRegExp = "^--(.*)"
 	BoundaryEndRegExp   = "^--(.*)--$"
 	BoundaryRegExp      = "boundary=\"(.*)\""
+	MailboxRegExp       = "^/m(\\d+)/?(.*)"
 )
 
 const (
@@ -68,6 +69,7 @@ type regExpUtils struct {
 	BoundaryStartFinder *regexp.Regexp
 	BoundaryEndFinder   *regexp.Regexp
 	BoundaryFinder      *regexp.Regexp
+	MailboxFinder       *regexp.Regexp
 }
 
 func newRegExpUtils() (*regExpUtils, error) {
@@ -113,6 +115,12 @@ func newRegExpUtils() (*regExpUtils, error) {
 		return nil, err
 	}
 
+	mailboxFinder, err := regexp.Compile(MailboxRegExp)
+	if err != nil {
+		log.Fatalf("Invalid regexp %s\n", err)
+		return nil, err
+	}
+
 	ru := &regExpUtils{
 		EmailChecker:        emailChecker,
 		HeaderFinder:        headerFinder,
@@ -121,6 +129,7 @@ func newRegExpUtils() (*regExpUtils, error) {
 		BoundaryEndFinder:   boundaryEndFinder,
 		BoundaryFinder:      boundaryFinder,
 		DomainChecker:       domainChecker,
+		MailboxFinder:       mailboxFinder,
 	}
 
 	return ru, nil

+ 1 - 1
web/css/styles.css

@@ -90,7 +90,7 @@ tr.unread {
     font-size: 10pt;
 }
 
-.messageBody {
+.mailBody {
     border-radius: 3pt;
     border-width: 1pt;
     border-style: solid;

+ 73 - 36
web/js/index.js

@@ -26,6 +26,9 @@
 var detailsUrl = "details/"
 var updateTimerId = null
 var updateInterval = 5000
+var mailbox = ""
+var mailboxRegex = /^(\/m\d+)/g
+
 $(document).ready(function(){
     $.ajaxSetup({
         global: false,
@@ -33,10 +36,21 @@ $(document).ready(function(){
     })
     $(window).bind('hashchange', requestDetails)
     requestDetails()
+
+    urlPaths = mailboxRegex.exec($(location).attr('pathname'))
+    if (urlPaths != null && urlPaths.length === 2) {
+        mailbox = urlPaths[0]
+    } else {
+        mailbox = ""
+    }
+
+    loadFolders()
     loadStatusLine()
-    clearInterval(updateTimerId)
-    // updateMessageList()
-    updateTimerId = setInterval(updateMessageList, updateInterval)
+    updateMailList()
+    if(mailbox != "") {
+        clearInterval(updateTimerId)
+        updateTimerId = setInterval(updateMailList, updateInterval)
+    }
 })
 
 function openEmail(id) {
@@ -46,14 +60,14 @@ function openEmail(id) {
 function requestDetails() {
     var hashLocation = window.location.hash
     if (hashLocation.startsWith("#" + detailsUrl)) {
-        var messageId = hashLocation.replace(/^#details\//, "")
-        if (messageId != "") {
+        var mailId = hashLocation.replace(/^#details\//, "")
+        if (mailId != "") {
             $.ajax({
-                url: "/messageDetails",
-                data: {messageId: messageId},
+                url: "/mail",
+                data: {mailId: mailId},
                 success: function(result) {
-                    $("#mail"+messageId).removeClass("unread")
-                    $("#mail"+messageId).addClass("read")
+                    $("#mail"+mailId).removeClass("unread")
+                    $("#mail"+mailId).addClass("read")
                     $("#details").html(result);
                     setDetailsVisible(true);
                 },
@@ -68,13 +82,30 @@ function requestDetails() {
     }
 }
 
+function loadFolders() {
+    if (mailbox == "") {
+        $("#folders").html("Unable to load folder list")
+        return
+    }
+
+    $.ajax({
+        url: mailbox + "/folders",
+        success: function(result) {
+            $("#folders").html(result)
+        },
+        error: function(jqXHR, textStatus, errorThrown) {
+            //TODO: some toast message here once implemented
+        }
+    })
+}
+
 function closeDetails() {
     window.location.hash = ""
 }
 
 function loadStatusLine() {
     $.ajax({
-        url: "/statusLine",
+        url: mailbox + "/statusLine",
         success: function(result) {
             $("#statusLine").html(result)
         },
@@ -103,24 +134,24 @@ function localDate(timestamp) {
     return dateString
 }
 
-function setRead(messageId, read) {
+function setRead(mailId, read) {
     $.ajax({
         url: "/setRead",
-        data: {messageId: messageId,
+        data: {mailId: mailId,
                read: read},
         success: function(result) {
             if (read) {
-                if ($("#readIcon"+messageId)) {
-                    $("#readIcon"+messageId).attr("src", "/assets/read.svg")
+                if ($("#readIcon"+mailId)) {
+                    $("#readIcon"+mailId).attr("src", "/assets/read.svg")
                 }
-                $("#mail"+messageId).removeClass("unread")
-                $("#mail"+messageId).addClass("read")
+                $("#mail"+mailId).removeClass("unread")
+                $("#mail"+mailId).addClass("read")
             } else {
-                if ($("#readIcon"+messageId)) {
-                    $("#readIcon"+messageId).attr("src", "/assets/unread.svg")
+                if ($("#readIcon"+mailId)) {
+                    $("#readIcon"+mailId).attr("src", "/assets/unread.svg")
                 }
-                $("#mail"+messageId).removeClass("read")
-                $("#mail"+messageId).addClass("unread")
+                $("#mail"+mailId).removeClass("read")
+                $("#mail"+mailId).addClass("unread")
             }
         },
         error: function(jqXHR, textStatus, errorThrown) {
@@ -128,18 +159,18 @@ function setRead(messageId, read) {
     })
 }
 
-function toggleRead(messageId) {
-    if ($("#readIcon"+messageId)) {
-        setRead(messageId, $("#readIcon"+messageId).attr("src") == "/assets/unread.svg")
+function toggleRead(mailId) {
+    if ($("#readIcon"+mailId)) {
+        setRead(mailId, $("#readIcon"+mailId).attr("src") == "/assets/unread.svg")
     }
 }
 
-function removeMail(messageId) {
+function removeMail(mailId) {
     $.ajax({
         url: "/remove",
-        data: {messageId: messageId},
+        data: {mailId: mailId},
         success: function(result) {
-            $("#mail"+messageId).remove();
+            $("#mail"+mailId).remove();
             closeDetails()
         },
         error: function(jqXHR, textStatus, errorThrown) {
@@ -150,29 +181,35 @@ function removeMail(messageId) {
 function setDetailsVisible(visible) {
     if (visible) {
         $("#details").show()
-        $("#messageList").css({pointerEvents: "none"})
+        $("#mailList").css({pointerEvents: "none"})
         clearInterval(updateTimerId)
     } else {
         $("#details").hide()
         $("#details").html("")
-        $("#messageList").css({pointerEvents: "auto"})
-        updateTimerId = setInterval(updateMessageList, updateInterval)
-        updateMessageList()
+        $("#mailList").css({pointerEvents: "auto"})
+        updateTimerId = setInterval(updateMailList, updateInterval)
     }
 }
 
-function updateMessageList() {
+function updateMailList() {
+    if (mailbox == "") {
+        if($("#mailList")) {
+            $("#mailList").html("Unable to load message list")
+        }
+        return
+    }
+
     $.ajax({
-        url: "/messageList",
+        url: mailbox + "/mailList",
         success: function(result) {
-            if($("#messageList")) {
+            if($("#mailList")) {
                 // console.log("result: " + result)
-                $("#messageList").html(result)
+                $("#mailList").html(result)
             }
         },
         error: function(jqXHR, textStatus, errorThrown) {
-            if($("#messageList")) {
-                $("#messageList").html(textStatus)
+            if($("#mailList")) {
+                $("#mailList").html(textStatus)
             }
         }
     })

+ 91 - 0
web/mail.go

@@ -0,0 +1,91 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2020 Alexey Edelev <semlanik@gmail.com>
+ *
+ * This file is part of gostfix project https://git.semlanik.org/semlanik/gostfix
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this
+ * software and associated documentation files (the "Software"), to deal in the Software
+ * without restriction, including without limitation the rights to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+ * to permit persons to whom the Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies
+ * or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+ * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package web
+
+import (
+	"fmt"
+	template "html/template"
+	"net/http"
+)
+
+func (s *Server) handleMailRequest(w http.ResponseWriter, r *http.Request) {
+	user, token := s.extractAuth(w, r)
+	if !s.authenticator.Verify(user, token) {
+		s.error(http.StatusUnauthorized, "You are not allowed to access this function", w)
+		return
+	}
+
+	mailId := r.FormValue("mailId")
+
+	if mailId == "" {
+		s.error(http.StatusBadRequest, "Invalid mail id requested", w)
+		return
+	}
+
+	switch r.URL.Path {
+	case "/mail":
+		s.handleMailDetails(w, user, mailId)
+	case "/setRead":
+		s.handleSetRead(w, r, user, mailId)
+	case "/remove":
+		s.handleRemove(w, user, mailId)
+	}
+}
+
+func (s *Server) handleMailDetails(w http.ResponseWriter, user, mailId string) {
+	mail, err := s.storage.GetMail(user, mailId)
+	if err != nil {
+		s.error(http.StatusInternalServerError, "Unable to read mail", w)
+		return
+	}
+
+	s.storage.SetRead(user, mailId, true)
+	fmt.Fprint(w, s.templater.ExecuteDetails(&struct {
+		From    string
+		To      string
+		Subject string
+		Text    template.HTML
+		MailId  string
+		Read    bool
+	}{
+		From:    mail.Header.From,
+		To:      mail.Header.To,
+		Subject: mail.Header.Subject,
+		Text:    template.HTML(mail.Body.RichText),
+		MailId:  mailId,
+	}))
+}
+
+func (s *Server) handleSetRead(w http.ResponseWriter, r *http.Request, user, mailId string) {
+	read := r.FormValue("read") == "true"
+	s.storage.SetRead(user, mailId, read)
+	w.WriteHeader(http.StatusOK)
+	w.Write([]byte{})
+}
+
+func (s *Server) handleRemove(w http.ResponseWriter, user, mailId string) {
+	s.storage.RemoveMail(user, mailId)
+}

+ 126 - 0
web/mailbox.go

@@ -0,0 +1,126 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2020 Alexey Edelev <semlanik@gmail.com>
+ *
+ * This file is part of gostfix project https://git.semlanik.org/semlanik/gostfix
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this
+ * software and associated documentation files (the "Software"), to deal in the Software
+ * without restriction, including without limitation the rights to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+ * to permit persons to whom the Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies
+ * or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+ * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package web
+
+import (
+	"fmt"
+	template "html/template"
+	"log"
+	"net/http"
+
+	common "git.semlanik.org/semlanik/gostfix/common"
+)
+
+func (s *Server) handleMailbox(w http.ResponseWriter, user, email string) {
+	mailList, err := s.storage.MailList(user, email, "Inbox", common.Frame{Skip: 0, Limit: 0})
+
+	if err != nil {
+		s.error(http.StatusInternalServerError, "Couldn't read email database", w)
+		return
+	}
+
+	fmt.Fprint(w, s.templater.ExecuteIndex(&struct {
+		Folders  template.HTML
+		MailList template.HTML
+		Version  template.HTML
+	}{
+		MailList: template.HTML(s.templater.ExecuteMailList(mailList)),
+		Folders:  "Folders",
+		Version:  common.Version,
+	}))
+}
+
+func (s *Server) handleMailboxRequest(path, user string, mailbox int, w http.ResponseWriter, r *http.Request) {
+	log.Printf("Handle mailbox %s", path)
+	emails, err := s.storage.GetEmails(user)
+
+	if err != nil || len(emails) <= 0 {
+		s.error(http.StatusInternalServerError, "Unable to access mailbox", w)
+		return
+	}
+
+	if len(emails) <= mailbox {
+		if path == "" {
+			http.Redirect(w, r, "/m0", http.StatusTemporaryRedirect)
+		} else {
+			s.error(http.StatusInternalServerError, "Unable to access mailbox", w)
+		}
+		return
+	}
+
+	switch path {
+	case "":
+		s.handleMailbox(w, user, emails[mailbox])
+	case "folders":
+		s.handleFolders(w, user, emails[mailbox])
+	case "statusLine":
+		s.handleStatusLine(w, user, emails[mailbox])
+	case "mailList":
+		s.handleMailList(w, user, emails[mailbox])
+	default:
+		http.Redirect(w, r, "/m0", http.StatusTemporaryRedirect)
+	}
+}
+
+func (s *Server) handleFolders(w http.ResponseWriter, user, email string) {
+	folders := []string{"Inbox", "Trash"}
+	fmt.Fprintf(w, s.templater.ExecuteFolders(folders))
+}
+
+func (s *Server) handleMailList(w http.ResponseWriter, user, email string) {
+	mailList, err := s.storage.MailList(user, email, "Inbox", common.Frame{Skip: 0, Limit: 0})
+
+	if err != nil {
+		s.error(http.StatusInternalServerError, "Couldn't read email database", w)
+		return
+	}
+
+	fmt.Fprint(w, s.templater.ExecuteMailList(mailList))
+}
+
+func (s *Server) handleStatusLine(w http.ResponseWriter, user, email string) {
+	info, err := s.storage.GetUserInfo(user)
+	if err != nil {
+		s.error(http.StatusInternalServerError, "Could not read user info", w)
+		return
+	}
+
+	unread, total, err := s.storage.GetEmailStats(user, email)
+	if err != nil {
+		s.error(http.StatusInternalServerError, "Could not read user stats", w)
+		return
+	}
+
+	fmt.Fprint(w, s.templater.ExecuteStatusLine(&struct {
+		Name   string
+		Unread int
+		Total  int
+	}{
+		Name:   info.FullName,
+		Unread: unread,
+		Total:  total,
+	}))
+}

+ 26 - 154
web/server.go

@@ -27,9 +27,9 @@ package web
 
 import (
 	"fmt"
-	template "html/template"
 	"log"
 	"net/http"
+	"strconv"
 
 	auth "git.semlanik.org/semlanik/gostfix/auth"
 	common "git.semlanik.org/semlanik/gostfix/common"
@@ -103,28 +103,37 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		utils.StartsWith(r.URL.Path, "/assets/") ||
 		utils.StartsWith(r.URL.Path, "/js/") {
 		s.fileServer.ServeHTTP(w, r)
+	} else if cap := utils.RegExpUtilsInstance().MailboxFinder.FindStringSubmatch(r.URL.Path); len(cap) == 3 {
+		user, token := s.extractAuth(w, r)
+		if !s.authenticator.Verify(user, token) {
+			s.logout(w, r)
+			http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
+			return
+		}
+
+		mailbox, err := strconv.Atoi(cap[1])
+		if err != nil || mailbox < 0 {
+			http.Redirect(w, r, "/m0", http.StatusTemporaryRedirect)
+			return
+		}
+
+		path := cap[2]
+
+		s.handleMailboxRequest(path, user, mailbox, w, r)
 	} else {
 		switch r.URL.Path {
 		case "/login":
 			s.handleLogin(w, r)
 		case "/logout":
 			s.handleLogout(w, r)
-		case "/messageDetails":
-			s.handleMessageDetails(w, r)
-		case "/statusLine":
-			s.handleStatusLine(w, r)
-		case "/":
-			s.handleMailbox(w, r)
-		case "/mailbox":
-			s.handleMailbox(w, r)
+		case "/mail":
+			fallthrough
 		case "/setRead":
-			s.handleSetRead(w, r)
+			fallthrough
 		case "/remove":
-			s.handleRemove(w, r)
-		case "/messageList":
-			s.handleMessageList(w, r)
+			s.handleMailRequest(w, r)
 		default:
-			s.error(http.StatusBadRequest, "Invalid request", w, r)
+			http.Redirect(w, r, "/m0", http.StatusTemporaryRedirect)
 		}
 	}
 }
@@ -143,7 +152,7 @@ func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) {
 
 	//Check if user already logged in and entered login page accidently
 	if s.authenticator.Verify(s.extractAuth(w, r)) {
-		http.Redirect(w, r, "/mailbox", http.StatusTemporaryRedirect)
+		http.Redirect(w, r, "/m0", http.StatusTemporaryRedirect)
 		return
 	}
 
@@ -159,143 +168,6 @@ func (s *Server) handleLogout(w http.ResponseWriter, r *http.Request) {
 	http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
 }
 
-func (s *Server) handleMessageDetails(w http.ResponseWriter, r *http.Request) {
-	//TODO: Not implemented yet. Need database mail storage implemented first
-	user, token := s.extractAuth(w, r)
-	if !s.authenticator.Verify(user, token) {
-		fmt.Fprint(w, "")
-		return
-	}
-
-	messageId := r.FormValue("messageId")
-
-	mail, err := s.storage.GetMail(user, messageId)
-	if err != nil {
-		s.error(http.StatusInternalServerError, "Unable to read message", w, r)
-		return
-	}
-
-	s.storage.SetRead(user, messageId, true)
-	fmt.Fprint(w, s.templater.ExecuteDetails(&struct {
-		From      string
-		To        string
-		Subject   string
-		Text      template.HTML
-		MessageId string
-		Read      bool
-	}{
-		From:      mail.Header.From,
-		To:        mail.Header.To,
-		Subject:   mail.Header.Subject,
-		Text:      template.HTML(mail.Body.RichText),
-		MessageId: messageId,
-	}))
-}
-
-func (s *Server) handleStatusLine(w http.ResponseWriter, r *http.Request) {
-	//TODO: Not implemented yet. Need database mail storage implemented first
-	user, token := s.extractAuth(w, r)
-	if !s.authenticator.Verify(user, token) {
-		fmt.Fprint(w, "")
-		return
-	}
-
-	info, err := s.storage.GetUserInfo(user)
-	if err != nil {
-		s.error(http.StatusInternalServerError, "Could not read user info", w, r)
-		return
-	}
-
-	unread, total, err := s.storage.GetEmailStats(user, user)
-	if err != nil {
-		s.error(http.StatusInternalServerError, "Could not read user stats", w, r)
-		return
-	}
-
-	fmt.Fprint(w, s.templater.ExecuteStatusLine(&struct {
-		Name   string
-		Unread int
-		Total  int
-	}{
-		Name:   info.FullName,
-		Unread: unread,
-		Total:  total,
-	}))
-}
-
-func (s *Server) handleMailbox(w http.ResponseWriter, r *http.Request) {
-	user, token := s.extractAuth(w, r)
-	if !s.authenticator.Verify(user, token) {
-		s.logout(w, r)
-		http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
-		return
-	}
-
-	mailList, err := s.storage.MailList(user, user, "Inbox", common.Frame{Skip: 0, Limit: 0})
-
-	if err != nil {
-		s.error(http.StatusInternalServerError, "Couldn't read email database", w, r)
-		return
-	}
-
-	fmt.Fprint(w, s.templater.ExecuteIndex(&struct {
-		Folders  template.HTML
-		MailList template.HTML
-		Version  template.HTML
-	}{
-		MailList: template.HTML(s.templater.ExecuteMailList(mailList)),
-		Folders:  "Folders",
-		Version:  common.Version,
-	}))
-}
-
-func (s *Server) handleSetRead(w http.ResponseWriter, r *http.Request) {
-	user, token := s.extractAuth(w, r)
-	if !s.authenticator.Verify(user, token) {
-		s.logout(w, r)
-		s.error(http.StatusUnauthorized, "Unknown user credentials", w, r)
-		return
-	}
-
-	read := r.FormValue("read") == "true"
-	messageId := r.FormValue("messageId")
-	fmt.Printf("SetRead %s, %s, %v\n", user, messageId, read)
-	s.storage.SetRead(user, messageId, read)
-	w.WriteHeader(http.StatusOK)
-	fmt.Fprint(w, "Test")
-}
-
-func (s *Server) handleRemove(w http.ResponseWriter, r *http.Request) {
-	user, token := s.extractAuth(w, r)
-	if !s.authenticator.Verify(user, token) {
-		s.logout(w, r)
-		s.error(http.StatusUnauthorized, "Unknown user credentials", w, r)
-		return
-	}
-
-	messageId := r.FormValue("messageId")
-
-	s.storage.RemoveMail(user, messageId)
-}
-
-func (s *Server) handleMessageList(w http.ResponseWriter, r *http.Request) {
-	user, token := s.extractAuth(w, r)
-	if !s.authenticator.Verify(user, token) {
-		s.logout(w, r)
-		http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
-		return
-	}
-
-	mailList, err := s.storage.MailList(user, user, "Inbox", common.Frame{Skip: 0, Limit: 0})
-
-	if err != nil {
-		s.error(http.StatusInternalServerError, "Couldn't read email database", w, r)
-		return
-	}
-
-	fmt.Fprint(w, s.templater.ExecuteMailList(mailList))
-}
-
 func (s *Server) logout(w http.ResponseWriter, r *http.Request) {
 	fmt.Println("logout")
 
@@ -310,10 +182,10 @@ func (s *Server) login(user, token string, w http.ResponseWriter, r *http.Reques
 	session.Values["user"] = user
 	session.Values["token"] = token
 	session.Save(r, w)
-	http.Redirect(w, r, "/mailbox", http.StatusTemporaryRedirect)
+	http.Redirect(w, r, "/m0", http.StatusTemporaryRedirect)
 }
 
-func (s *Server) error(code int, text string, w http.ResponseWriter, r *http.Request) {
+func (s *Server) error(code int, text string, w http.ResponseWriter) {
 	w.WriteHeader(code)
 	fmt.Fprint(w, s.templater.ExecuteError(&struct {
 		Code    int

+ 12 - 0
web/templater.go

@@ -39,6 +39,7 @@ const (
 	ErrorTemplateName      = "error.html"
 	LoginTemplateName      = "login.html"
 	StatusLineTemplateName = "statusline.html"
+	FoldersTemplateName    = "folders.html"
 )
 
 type Templater struct {
@@ -48,6 +49,7 @@ type Templater struct {
 	errorTemplate      *template.Template
 	loginTemplate      *template.Template
 	statusLineTemplate *template.Template
+	foldersTemaplate   *template.Template
 }
 
 func NewTemplater(templatesPath string) (t *Templater) {
@@ -82,6 +84,11 @@ func NewTemplater(templatesPath string) (t *Templater) {
 		log.Fatal(err)
 	}
 
+	folders, err := parseTemplate(templatesPath + "/" + FoldersTemplateName)
+	if err != nil {
+		log.Fatal(err)
+	}
+
 	t = &Templater{
 		indexTemplate:      index,
 		mailListTemplate:   maillist,
@@ -89,6 +96,7 @@ func NewTemplater(templatesPath string) (t *Templater) {
 		errorTemplate:      errors,
 		loginTemplate:      login,
 		statusLineTemplate: statusLine,
+		foldersTemaplate:   folders,
 	}
 	return
 }
@@ -126,6 +134,10 @@ func (t *Templater) ExecuteStatusLine(data interface{}) string {
 	return executeTemplateCommon(t.statusLineTemplate, data)
 }
 
+func (t *Templater) ExecuteFolders(data interface{}) string {
+	return executeTemplateCommon(t.foldersTemaplate, data)
+}
+
 func executeTemplateCommon(t *template.Template, values interface{}) string {
 	buffer := &bytes.Buffer{}
 	err := t.Execute(buffer, values)

+ 4 - 4
web/templates/details.html

@@ -5,13 +5,13 @@
             <span class="secondaryText">To: {{.To}}</span></br>
             <span class="primaryText">Subject: {{.Subject}}</span></br>
         </div>
-        <img id="readIcon{{.MessageId}}" class="iconBtn" style="width: 20pt; margin-right: 10pt;" onclick="toggleRead({{.MessageId}});" src="assets/read.svg"/>
-        <img id="deleteIcon" class="iconBtn" style="width: 20pt; margin-right: 10pt;" onclick="removeMail({{.MessageId}});" src="assets/remove.svg"/>
+        <img id="readIcon{{.MailId}}" class="iconBtn" style="width: 20pt; margin-right: 10pt;" onclick="toggleRead({{.MailId}});" src="/assets/read.svg"/>
+        <img id="deleteIcon" class="iconBtn" style="width: 20pt; margin-right: 10pt;" onclick="removeMail({{.MailId}});" src="/assets/remove.svg"/>
         <div class="btn materialLevel1" style="width: 40pt; right: 0pt; top: 0pt; margin: auto;" onclick="closeDetails();">
-            <img src="assets/back.svg" style="width: 20pt"/>
+            <img src="/assets/back.svg" style="width: 20pt"/>
         </div>
     </div>
-    <div class="scrollable messageBody" style="margin-top: 10pt;">
+    <div class="scrollable mailBody" style="margin-top: 10pt;">
         <div style="padding: 5pt;">{{.Text}}</div>
     </div>
 </div>

+ 6 - 6
web/templates/error.html

@@ -3,15 +3,15 @@
     <head>
         <meta charset="utf-8"/>
         <link href="https://fonts.googleapis.com/css?family=Titillium+Web&display=swap" rel="stylesheet">
-        <link type="text/css" href="css/index.css" rel="stylesheet">
-        <link type="text/css" href="css/styles.css" rel="stylesheet">
-        <link type="text/css" href="css/controls.css" rel="stylesheet">
-        <script src="js/jquery-3.4.1.min.js"></script>
-        <script src="js/index.js" type="text/javascript"></script>
+        <link type="text/css" href="/css/index.css" rel="stylesheet">
+        <link type="text/css" href="/css/styles.css" rel="stylesheet">
+        <link type="text/css" href="/css/controls.css" rel="stylesheet">
+        <script src="/js/jquery-3.4.1.min.js"></script>
+        <title>Gostfix mail {{.Version}}</title>
     </head>
     <body>
         <div style="width: 100%; height: 100%; display:flex; justify-content: center; align-items: center;">
-            <img src="assets/error.svg" height="100pt"/>
+            <img src="/assets/error.svg" height="100pt"/>
             <div style="display: block;">
                 <span style="font-size: 24pt;">{{.Code}}</span></br>
                 <span style="font-size: 14pt;">{{.Text}}</span>

+ 1 - 1
web/templates/folders.html

@@ -1,5 +1,5 @@
 <div>
     {{range .}}
-    <div class="folderBtn">{{.Folder.Name}}</div>
+    <div class="folderBtn">{{.}}</div>
     {{end}}
 </div>

+ 8 - 7
web/templates/index.html

@@ -3,20 +3,21 @@
     <head>
         <meta charset="utf-8"/>
         <link href="https://fonts.googleapis.com/css?family=Titillium+Web&display=swap" rel="stylesheet">
-        <link type="text/css" href="css/index.css" rel="stylesheet">
-        <link type="text/css" href="css/styles.css" rel="stylesheet">
-        <link type="text/css" href="css/controls.css" rel="stylesheet">
-        <script src="js/jquery-3.4.1.min.js"></script>
-        <script src="js/index.js" type="text/javascript"></script>
+        <link type="text/css" href="/css/index.css" rel="stylesheet">
+        <link type="text/css" href="/css/styles.css" rel="stylesheet">
+        <link type="text/css" href="/css/controls.css" rel="stylesheet">
+        <script src="/js/jquery-3.4.1.min.js"></script>
+        <script src="/js/index.js" type="text/javascript"></script>
+        <title>Gostfix mail {{.Version}}</title>
     </head>
     <body>
         <div class="main">
             <div class="foldersBox">
                 <div class="btn materialLevel1" style="width: 110pt">New email</div>
-                {{.Folders}}
+                <div id="folders"></div>
             </div>
             <div id="statusLine"></div>
-            <div id="messageList" class="materialLevel1 contentBox"></div>
+            <div id="mailList" class="materialLevel1 contentBox"></div>
             <div id="details" class="materialLevel2 contentBox emailDetails" style="display: none;">
             </div>
         </div>

+ 5 - 5
web/templates/login.html

@@ -3,11 +3,11 @@
     <head>
         <meta charset="utf-8"/>
         <link href="https://fonts.googleapis.com/css?family=Titillium+Web&display=swap" rel="stylesheet">
-        <link type="text/css" href="css/index.css" rel="stylesheet">
-        <link type="text/css" href="css/styles.css" rel="stylesheet">
-        <link type="text/css" href="css/controls.css" rel="stylesheet">
-        <script src="js/jquery-3.4.1.min.js"></script>
-        <script src="js/index.js" type="text/javascript"></script>
+        <link type="text/css" href="/css/index.css" rel="stylesheet">
+        <link type="text/css" href="/css/styles.css" rel="stylesheet">
+        <link type="text/css" href="/css/controls.css" rel="stylesheet">
+        <script src="/js/jquery-3.4.1.min.js"></script>
+        <title>Gostfix mail {{.Version}}</title>
     </head>
     <body>
         <div style="width: 100%; height: 100%; display:flex; justify-content: center; align-items: center;">