github.com/soulteary/pocket-bookcase@v0.0.0-20240428065142-0b5a9a0fc98a/internal/webserver/handler-api-ext.go (about) 1 package webserver 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io" 8 "log" 9 "net/http" 10 "os" 11 fp "path/filepath" 12 "strconv" 13 14 "github.com/julienschmidt/httprouter" 15 "github.com/soulteary/pocket-bookcase/internal/core" 16 "github.com/soulteary/pocket-bookcase/internal/model" 17 ) 18 19 // ApiInsertViaExtension is handler for POST /api/bookmarks/ext 20 func (h *Handler) ApiInsertViaExtension(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 21 ctx := r.Context() 22 23 // Make sure session still valid 24 err := h.validateSession(r) 25 checkError(err) 26 27 // Decode request 28 request := model.BookmarkDTO{} 29 err = json.NewDecoder(r.Body).Decode(&request) 30 checkError(err) 31 32 // Clean up bookmark URL 33 request.URL, err = core.RemoveUTMParams(request.URL) 34 if err != nil { 35 panic(fmt.Errorf("failed to clean URL: %v", err)) 36 } 37 38 // Check if bookmark already exists. 39 book, exist, err := h.DB.GetBookmark(ctx, 0, request.URL) 40 if err != nil { 41 panic(fmt.Errorf("failed to get bookmark, URL: %v", err)) 42 } 43 44 // If it already exists, we need to set ID and tags. 45 if exist { 46 book.HTML = request.HTML 47 48 mapOldTags := map[string]model.Tag{} 49 for _, oldTag := range book.Tags { 50 mapOldTags[oldTag.Name] = oldTag 51 } 52 53 for _, newTag := range request.Tags { 54 if _, tagExist := mapOldTags[newTag.Name]; !tagExist { 55 book.Tags = append(book.Tags, newTag) 56 } 57 } 58 } else if request.Title == "" { 59 request.Title = request.URL 60 } 61 62 // Since we are using extension, the extension might send the HTML content 63 // so no need to download it again here. However, if it's empty, it might be not HTML file 64 // so we download it here. 65 var contentType string 66 var contentBuffer io.Reader 67 68 if request.HTML == "" { 69 contentBuffer, contentType, _ = core.DownloadBookmark(request.URL) 70 } else { 71 contentType = "text/html; charset=UTF-8" 72 contentBuffer = bytes.NewBufferString(request.HTML) 73 } 74 75 // Save the bookmark with whatever we already have downloaded 76 // since we need the ID in order to download the archive 77 // Only when old bookmark is not exists. 78 if !exist { 79 books, err := h.DB.SaveBookmarks(ctx, true, request) 80 if err != nil { 81 log.Printf("error saving bookmark before downloading content: %s", err) 82 return 83 } 84 book = books[0] 85 } 86 87 // At this point the web page already downloaded. 88 // Time to process it. 89 if contentBuffer != nil { 90 book.CreateArchive = true 91 request := core.ProcessRequest{ 92 DataDir: h.DataDir, 93 Bookmark: book, 94 Content: contentBuffer, 95 ContentType: contentType, 96 } 97 98 var isFatalErr bool 99 book, isFatalErr, err = core.ProcessBookmark(h.dependencies, request) 100 101 if tmp, ok := contentBuffer.(io.ReadCloser); ok { 102 tmp.Close() 103 } 104 105 // If we can't process or update the saved bookmark, just log it and continue on with the 106 // request. 107 if err != nil && isFatalErr { 108 log.Printf("failed to process bookmark: %v", err) 109 } else if _, err := h.DB.SaveBookmarks(ctx, false, book); err != nil { 110 log.Printf("error saving bookmark after downloading content: %s", err) 111 } 112 } 113 114 // Return the new bookmark 115 w.Header().Set("Content-Type", "application/json") 116 err = json.NewEncoder(w).Encode(&book) 117 checkError(err) 118 } 119 120 // ApiDeleteViaExtension is handler for DELETE /api/bookmark/ext 121 func (h *Handler) ApiDeleteViaExtension(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 122 ctx := r.Context() 123 124 // Make sure session still valid 125 err := h.validateSession(r) 126 checkError(err) 127 128 // Decode request 129 request := model.BookmarkDTO{} 130 err = json.NewDecoder(r.Body).Decode(&request) 131 checkError(err) 132 133 // Check if bookmark already exists. 134 book, exist, err := h.DB.GetBookmark(ctx, 0, request.URL) 135 checkError(err) 136 137 if exist { 138 // Delete bookmarks 139 err = h.DB.DeleteBookmarks(ctx, book.ID) 140 checkError(err) 141 142 // Delete thumbnail image and archives from local disk 143 strID := strconv.Itoa(book.ID) 144 imgPath := fp.Join(h.DataDir, "thumb", strID) 145 archivePath := fp.Join(h.DataDir, "archive", strID) 146 147 os.Remove(imgPath) 148 os.Remove(archivePath) 149 } 150 151 fmt.Fprint(w, 1) 152 }