소스 검색

Make server API bit more RESTful

- Align mail related function according to REST paradigm
- Align settings related functions according to REST paradigm
Alexey Edelev 4 년 전
부모
커밋
ce41ea2b7a
6개의 변경된 파일108개의 추가작업 그리고 85개의 파일을 삭제
  1. 11 0
      db/db.go
  2. 20 12
      web/js/index.js
  3. 30 28
      web/mail.go
  4. 21 23
      web/securezone.go
  5. 23 20
      web/server.go
  6. 3 2
      web/templates/settings.html

+ 11 - 0
db/db.go

@@ -474,6 +474,17 @@ func (s *Storage) SetRead(user string, id string, read bool) error {
 	return err
 }
 
+func (s *Storage) UpdateMail(user string, id string, mailMap interface{}) error {
+	mailsCollection := s.db.Collection(qualifiedMailCollection(user))
+
+	oId, err := primitive.ObjectIDFromHex(id)
+	if err != nil {
+		return err
+	}
+	_, err = mailsCollection.UpdateOne(context.Background(), bson.M{"_id": oId}, bson.M{"$set": mailMap})
+	return err
+}
+
 func (s *Storage) GetUsers() (users []string, err error) {
 	return nil, nil
 }

+ 20 - 12
web/js/index.js

@@ -195,10 +195,8 @@ function onHashChanged() {
 function requestMail(mailId) {
     if (mailId != "") {
         $.ajax({
-            url: '/mail',
-            data: {
-                mailId: mailId
-            },
+            url: '/mail/' + mailId,
+            type: 'GET',
             success: function(result) {
                 currentMail = mailId;
                 if ($('#readListIcon'+mailId)) {
@@ -305,9 +303,9 @@ function localDate(elementToChange, timestamp) {
 
 function setRead(mailId, read) {
     $.ajax({
-        url: '/setRead',
-        data: {mailId: mailId,
-               read: read},
+        url: '/mail/'+mailId,
+        type: 'PATCH',
+        data: {read: read},
         success: function(result) {
             if (read) {
                 if ($('#readIcon'+mailId)) {
@@ -342,10 +340,16 @@ function toggleRead(mailId) {
 }
 
 function removeMail(mailId, callback) {
-    var url = currentFolder != 'Trash' ? '/remove' : '/delete';
+    var method = 'PATCH';
+    var data = { trash: 'true' };
+    if (currentFolder == 'Trash') {
+        method = 'DELETE';
+        data = null;
+    }
     $.ajax({
-        url: url,
-        data: {mailId: mailId},
+        url: '/mail/'+mailId,
+        type: method,
+        data: data,
         success: function() {
             removeFromSelectionList(mailId);
             $('#mail'+mailId).remove();
@@ -356,14 +360,16 @@ function removeMail(mailId, callback) {
             folderStat('Trash');//TODO: receive statistic from websocket
         },
         error: function(jqXHR, textStatus, errorThrown) {
+            showToast(Severity.Critical, 'Unable to remove mail: ' + errorThrown + ' ' + textStatus);
         }
     });
 }
 
 function restoreMail(mailId, callback) {
     $.ajax({
-        url: '/restore',
-        data: {mailId: mailId},
+        url: '/mail/'+mailId,
+        type: 'PATCH',
+        data: {trash: 'false'},
         success: function() {
             if (currentFolder == 'Trash') {
                 $('#mail'+mailId).remove();
@@ -377,6 +383,7 @@ function restoreMail(mailId, callback) {
             }
         },
         error: function(jqXHR, textStatus, errorThrown) {
+            showToast(Severity.Critical, 'Unable to restore mail: ' + errorThrown + ' ' + textStatus);
         }
     });
 }
@@ -384,6 +391,7 @@ function restoreMail(mailId, callback) {
 function downloadAttachment(attachmentId, filename) {
     $.ajax({
         url: '/attachment/' + attachmentId,
+        type: 'GET',
         xhrFields: {
             responseType: 'blob'
         },

+ 30 - 28
web/mail.go

@@ -36,31 +36,25 @@ import (
 	"git.semlanik.org/semlanik/gostfix/utils"
 )
 
-func (s *Server) handleMailRequest(w http.ResponseWriter, r *http.Request) {
+func (s *Server) handleMailRequest(w http.ResponseWriter, r *http.Request, mailId string) {
 	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":
+	switch r.Method {
+	case "GET":
 		s.handleMailDetails(w, user, mailId)
-	case "/setRead":
-		s.handleSetRead(w, r, user, mailId)
-	case "/remove":
-		s.handleRemove(w, user, mailId)
-	case "/restore":
-		s.handleRestore(w, user, mailId)
-	case "/delete":
-		s.handleDelete(w, user, mailId)
+	case "DELETE":
+		s.handleMailDelete(w, user, mailId)
+	case "PATCH":
+		s.handleMailUpdate(w, r, user, mailId)
 	}
 }
 
@@ -101,28 +95,36 @@ func (s *Server) handleMailDetails(w http.ResponseWriter, user, mailId string) {
 	}))
 }
 
-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) handleMailUpdate(w http.ResponseWriter, r *http.Request, user, mailId string) {
+	updateMap := map[string]interface{}{}
 
-func (s *Server) handleRemove(w http.ResponseWriter, user, mailId string) {
-	err := s.storage.MoveMail(user, mailId, common.Trash)
-	if err != nil {
-		s.error(http.StatusInternalServerError, "Could not move email to trash", w)
+	if r.FormValue("read") == "true" {
+		updateMap["read"] = true
+	} else if r.FormValue("read") == "false" {
+		updateMap["read"] = false
+	}
+
+	if r.FormValue("trash") == "true" {
+		updateMap["trash"] = true
+	} else if r.FormValue("trash") == "false" {
+		updateMap["trash"] = false
+	}
+
+	if len(updateMap) == 0 {
+		s.error(http.StatusBadRequest, "Unable to proccess mail", w)
+		return
 	}
-}
 
-func (s *Server) handleRestore(w http.ResponseWriter, user, mailId string) {
-	err := s.storage.RestoreMail(user, mailId)
+	err := s.storage.UpdateMail(user, mailId, &updateMap)
 	if err != nil {
-		s.error(http.StatusInternalServerError, "Could not move email to trash", w)
+		s.error(http.StatusInternalServerError, "Unable to proccess mail", w)
+		return
 	}
+	w.WriteHeader(http.StatusOK)
+	w.Write([]byte{})
 }
 
-func (s *Server) handleDelete(w http.ResponseWriter, user, mailId string) {
+func (s *Server) handleMailDelete(w http.ResponseWriter, user, mailId string) {
 	log.Printf("Delete mail")
 	err := s.storage.DeleteMail(user, mailId)
 	if err != nil {

+ 21 - 23
web/securezone.go

@@ -39,35 +39,33 @@ func (s *Server) handleSecureZone(w http.ResponseWriter, r *http.Request) {
 		s.error(http.StatusUnauthorized, "You are not allowed to access this function", w)
 		return
 	}
-
-	switch r.URL.Path {
-	case "/settings":
-		s.handleSettings(w, user)
-	case "/update":
-		s.handleUpdate(w, r, user)
-		// case "/admin":
-		// case "/addUser":
-		// 	//TODO:
-		// case "/removeUser":
-		// 	//TODO:
-		// case "/changeUser":
-		// 	//TODO:
-	}
+	s.error(http.StatusNotImplemented, "Admin panel is not implemented", w)
 }
 
-func (s *Server) handleSettings(w http.ResponseWriter, user string) {
-	info, err := s.storage.GetUserInfo(user)
-	if err != nil {
-		s.error(http.StatusInternalServerError, "Unable to obtain user information", w)
+func (s *Server) handleSettings(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
 	}
-	fmt.Fprintf(w, s.templater.ExecuteSettings(&struct {
-		Version  string
-		FullName string
-	}{common.Version, info.FullName}))
+
+	switch r.Method {
+	case "GET":
+		info, err := s.storage.GetUserInfo(user)
+		if err != nil {
+			s.error(http.StatusInternalServerError, "Unable to obtain user information", w)
+			return
+		}
+		fmt.Fprintf(w, s.templater.ExecuteSettings(&struct {
+			Version  string
+			FullName string
+		}{common.Version, info.FullName}))
+	case "PATCH":
+		s.handleSettingsUpdate(w, r, user)
+	}
 }
 
-func (s *Server) handleUpdate(w http.ResponseWriter, r *http.Request, user string) {
+func (s *Server) handleSettingsUpdate(w http.ResponseWriter, r *http.Request, user string) {
 	if err := r.ParseForm(); err != nil {
 		s.error(http.StatusUnauthorized, "Password entered is invalid", w)
 		return

+ 23 - 20
web/server.go

@@ -31,6 +31,7 @@ import (
 	"log"
 	"net/http"
 	"strconv"
+	"strings"
 
 	auth "git.semlanik.org/semlanik/gostfix/auth"
 	common "git.semlanik.org/semlanik/gostfix/common"
@@ -105,6 +106,7 @@ func (s *Server) Run() {
 
 func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	fmt.Println(r.URL.Path)
+	fmt.Println(r.Method)
 	if utils.StartsWith(r.URL.Path, "/css/") ||
 		utils.StartsWith(r.URL.Path, "/assets/") ||
 		utils.StartsWith(r.URL.Path, "/js/") {
@@ -129,30 +131,31 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 
 		s.handleMailboxRequest(path, user, mailbox, w, r)
 	} else {
-		switch r.URL.Path {
-		case "/login":
+		urlParts := strings.Split(r.URL.Path, "/")
+		if len(urlParts) < 2 {
+			http.Redirect(w, r, "/m0", http.StatusTemporaryRedirect)
+			return
+		}
+
+		fmt.Println("urlParts:" + urlParts[1])
+		switch urlParts[1] {
+		case "login":
 			s.handleLogin(w, r)
-		case "/logout":
+		case "logout":
 			s.handleLogout(w, r)
-		case "/register":
+		case "register":
 			s.handleRegister(w, r)
-		case "/checkEmail":
+		case "checkEmail":
 			s.handleCheckEmail(w, r)
-		case "/mail":
-			fallthrough
-		case "/setRead":
-			fallthrough
-		case "/remove":
-			fallthrough
-		case "/restore":
-			fallthrough
-		case "/delete":
-			s.handleMailRequest(w, r)
-		case "/settings":
-			fallthrough
-		case "/update":
-			fallthrough
-		case "/admin":
+		case "mail":
+			if len(urlParts) == 3 {
+				s.handleMailRequest(w, r, urlParts[2])
+			} else {
+				//TODO: return mail list here
+			}
+		case "settings":
+			s.handleSettings(w, r)
+		case "admin":
 			s.handleSecureZone(w, r)
 		default:
 			http.Redirect(w, r, "/m0", http.StatusTemporaryRedirect)

+ 3 - 2
web/templates/settings.html

@@ -21,7 +21,8 @@
             function update() {
                 var formValue = $('#updateForm').serialize()
                 $.ajax({
-                    url: "/update",
+                    url: "/settings",
+                    type: "PATCH",
                     data: formValue,
                     success: function(result) {
                         showToast(Severity.Normal, "User information updated successfully")
@@ -70,7 +71,7 @@
                                         <input id="oldPasswordField" name="oldPassword" type="password" required maxlength="28" autocomplete="off">
                                         <span class="highlight"></span>
                                         <span class="bar"></span>
-                                        <label>Old password</label>
+                                        <label>Current password</label>
                                     </div>
                                     <div id="updateButton" class="btn materialLevel1" style="margin-bottom: 30px;" onclick="update();">Update</div>
                                 </form>