template migration working + remove settings on logout
authorPhiTux <redacted>
Wed, 16 Jul 2025 17:31:55 +0000 (19:31 +0200)
committerPhiTux <redacted>
Wed, 16 Jul 2025 17:31:55 +0000 (19:31 +0200)
backend/handlers/additional.go
backend/handlers/logs.go
backend/handlers/users.go
backend/main.go
backend/utils/migration.go
frontend/src/lib/helpers.js
frontend/src/routes/+layout.svelte
frontend/src/routes/login/+page.svelte

index 42da002f493d910eb4d6ed21ac733e8567f321bf..31dfcc6b8f10b670f340fd447b1aaf4e334d4493 100644 (file)
@@ -5,6 +5,10 @@ import (
        "fmt"
        "io"
        "net/http"
+       "os"
+       "path/filepath"
+       "regexp"
+       "sort"
        "strconv"
        "strings"
 
@@ -435,155 +439,6 @@ func RemoveTagFromLog(w http.ResponseWriter, r *http.Request) {
        })
 }
 
-// LoadMonthForReading handles loading a month for reading
-func LoadMonthForReading(w http.ResponseWriter, r *http.Request) {
-       // Get user ID and derived key from context
-       userID, ok := r.Context().Value(utils.UserIDKey).(int)
-       if !ok {
-               http.Error(w, "Unauthorized", http.StatusUnauthorized)
-               return
-       }
-       derivedKey, ok := r.Context().Value(utils.DerivedKeyKey).(string)
-       if !ok {
-               http.Error(w, "Unauthorized", http.StatusUnauthorized)
-               return
-       }
-
-       // Get parameters from URL
-       monthStr := r.URL.Query().Get("month")
-       if monthStr == "" {
-               http.Error(w, "Missing month parameter", http.StatusBadRequest)
-               return
-       }
-       month, err := strconv.Atoi(monthStr)
-       if err != nil {
-               http.Error(w, "Invalid month parameter", http.StatusBadRequest)
-               return
-       }
-
-       yearStr := r.URL.Query().Get("year")
-       if yearStr == "" {
-               http.Error(w, "Missing year parameter", http.StatusBadRequest)
-               return
-       }
-       year, err := strconv.Atoi(yearStr)
-       if err != nil {
-               http.Error(w, "Invalid year parameter", http.StatusBadRequest)
-               return
-       }
-
-       // Get month data
-       content, err := utils.GetMonth(userID, year, month)
-       if err != nil {
-               http.Error(w, fmt.Sprintf("Error retrieving month data: %v", err), http.StatusInternalServerError)
-               return
-       }
-
-       // Get encryption key
-       encKey, err := utils.GetEncryptionKey(userID, derivedKey)
-       if err != nil {
-               http.Error(w, fmt.Sprintf("Error getting encryption key: %v", err), http.StatusInternalServerError)
-               return
-       }
-
-       // Check if days exist
-       days, ok := content["days"].([]any)
-       if !ok {
-               utils.JSONResponse(w, http.StatusOK, []any{})
-               return
-       }
-
-       // Process days
-       result := []any{}
-       for _, dayInterface := range days {
-               day, ok := dayInterface.(map[string]any)
-               if !ok {
-                       continue
-               }
-
-               dayNum, ok := day["day"].(float64)
-               if !ok {
-                       continue
-               }
-
-               // Create result day
-               resultDay := map[string]any{
-                       "day": int(dayNum),
-               }
-
-               // Decrypt text and date_written
-               if text, ok := day["text"].(string); ok && text != "" {
-                       decryptedText, err := utils.DecryptText(text, encKey)
-                       if err != nil {
-                               http.Error(w, fmt.Sprintf("Error decrypting text: %v", err), http.StatusInternalServerError)
-                               return
-                       }
-                       resultDay["text"] = decryptedText
-
-                       if dateWritten, ok := day["date_written"].(string); ok && dateWritten != "" {
-                               decryptedDate, err := utils.DecryptText(dateWritten, encKey)
-                               if err != nil {
-                                       http.Error(w, fmt.Sprintf("Error decrypting date_written: %v", err), http.StatusInternalServerError)
-                                       return
-                               }
-                               resultDay["date_written"] = decryptedDate
-                       }
-               }
-
-               // Get tags
-               if tags, ok := day["tags"].([]any); ok && len(tags) > 0 {
-                       resultDay["tags"] = tags
-               }
-
-               // Decrypt filenames if files exist
-               if filesList, ok := day["files"].([]any); ok && len(filesList) > 0 {
-                       files := []any{}
-                       for _, fileInterface := range filesList {
-                               file, ok := fileInterface.(map[string]any)
-                               if !ok {
-                                       continue
-                               }
-
-                               if encFilename, ok := file["enc_filename"].(string); ok {
-                                       decryptedFilename, err := utils.DecryptText(encFilename, encKey)
-                                       if err != nil {
-                                               http.Error(w, fmt.Sprintf("Error decrypting filename: %v", err), http.StatusInternalServerError)
-                                               return
-                                       }
-                                       fileCopy := make(map[string]any)
-                                       for k, v := range file {
-                                               fileCopy[k] = v
-                                       }
-                                       fileCopy["filename"] = decryptedFilename
-                                       files = append(files, fileCopy)
-                               }
-                       }
-                       resultDay["files"] = files
-               }
-
-               // Add day to result if it has content
-               if _, hasText := resultDay["text"]; hasText {
-                       result = append(result, resultDay)
-               } else if _, hasFiles := resultDay["files"]; hasFiles {
-                       result = append(result, resultDay)
-               } else if _, hasTags := resultDay["tags"]; hasTags {
-                       result = append(result, resultDay)
-               }
-       }
-
-       // Sort by day
-       /*
-               sort.Slice(result, func(i, j int) bool {
-                       dayI := result[i].(map[string]any)["day"].(int)
-                       dayJ := result[j].(map[string]any)["day"].(int)
-                       return dayI < dayJ
-               })
-       */
-
-       // Return result
-       utils.JSONResponse(w, http.StatusOK, result)
-}
-
 // UploadFile handles uploading a file
 func UploadFile(w http.ResponseWriter, r *http.Request) {
        // Get user ID and derived key from context
@@ -940,140 +795,6 @@ func DeleteFile(w http.ResponseWriter, r *http.Request) {
        })
 }
 
-// GetHistory handles retrieving log history
-func GetHistory(w http.ResponseWriter, r *http.Request) {
-       // Get user ID and derived key from context
-       userID, ok := r.Context().Value(utils.UserIDKey).(int)
-       if !ok {
-               http.Error(w, "Unauthorized", http.StatusUnauthorized)
-               return
-       }
-       derivedKey, ok := r.Context().Value(utils.DerivedKeyKey).(string)
-       if !ok {
-               http.Error(w, "Unauthorized", http.StatusUnauthorized)
-               return
-       }
-
-       // Get parameters
-       dayStr := r.URL.Query().Get("day")
-       if dayStr == "" {
-               http.Error(w, "Missing day parameter", http.StatusBadRequest)
-               return
-       }
-       day, err := strconv.Atoi(dayStr)
-       if err != nil {
-               http.Error(w, "Invalid day parameter", http.StatusBadRequest)
-               return
-       }
-
-       monthStr := r.URL.Query().Get("month")
-       if monthStr == "" {
-               http.Error(w, "Missing month parameter", http.StatusBadRequest)
-               return
-       }
-       month, err := strconv.Atoi(monthStr)
-       if err != nil {
-               http.Error(w, "Invalid month parameter", http.StatusBadRequest)
-               return
-       }
-
-       yearStr := r.URL.Query().Get("year")
-       if yearStr == "" {
-               http.Error(w, "Missing year parameter", http.StatusBadRequest)
-               return
-       }
-       year, err := strconv.Atoi(yearStr)
-       if err != nil {
-               http.Error(w, "Invalid year parameter", http.StatusBadRequest)
-               return
-       }
-
-       // Get encryption key
-       encKey, err := utils.GetEncryptionKey(userID, derivedKey)
-       if err != nil {
-               http.Error(w, fmt.Sprintf("Error getting encryption key: %v", err), http.StatusInternalServerError)
-               return
-       }
-
-       // Get month data
-       content, err := utils.GetMonth(userID, year, month)
-       if err != nil {
-               http.Error(w, fmt.Sprintf("Error retrieving month data: %v", err), http.StatusInternalServerError)
-               return
-       }
-
-       // Check if days exist
-       days, ok := content["days"].([]any)
-       if !ok {
-               utils.JSONResponse(w, http.StatusOK, []any{})
-               return
-       }
-
-       // Find day
-       for _, dayInterface := range days {
-               dayObj, ok := dayInterface.(map[string]any)
-               if !ok {
-                       continue
-               }
-
-               dayNum, ok := dayObj["day"].(float64)
-               if !ok || int(dayNum) != day {
-                       continue
-               }
-
-               // Check for history
-               history, ok := dayObj["history"].([]any)
-               if !ok || len(history) == 0 {
-                       utils.JSONResponse(w, http.StatusOK, []any{})
-                       return
-               }
-
-               // Decrypt history entries
-               result := []any{}
-               for _, historyInterface := range history {
-                       historyEntry, ok := historyInterface.(map[string]any)
-                       if !ok {
-                               continue
-                       }
-
-                       text, ok := historyEntry["text"].(string)
-                       if !ok {
-                               continue
-                       }
-
-                       dateWritten, ok := historyEntry["date_written"].(string)
-                       if !ok {
-                               continue
-                       }
-
-                       // Decrypt text and date
-                       decryptedText, err := utils.DecryptText(text, encKey)
-                       if err != nil {
-                               http.Error(w, fmt.Sprintf("Error decrypting history text: %v", err), http.StatusInternalServerError)
-                               return
-                       }
-
-                       decryptedDate, err := utils.DecryptText(dateWritten, encKey)
-                       if err != nil {
-                               http.Error(w, fmt.Sprintf("Error decrypting history date: %v", err), http.StatusInternalServerError)
-                               return
-                       }
-
-                       result = append(result, map[string]any{
-                               "text":         decryptedText,
-                               "date_written": decryptedDate,
-                       })
-               }
-
-               // Return history
-               utils.JSONResponse(w, http.StatusOK, result)
-               return
-       }
-
-       // Day not found
-       utils.JSONResponse(w, http.StatusOK, []any{})
-}
-
 // BookmarkDay handles bookmarking a day
 func BookmarkDay(w http.ResponseWriter, r *http.Request) {
        // Get user ID from context
@@ -1146,11 +867,11 @@ func BookmarkDay(w http.ResponseWriter, r *http.Request) {
 
                // Day found, toggle bookmark
                dayFound = true
-               if bookmark, ok := dayObj["bookmarked"].(bool); ok && bookmark {
-                       dayObj["bookmarked"] = false
+               if bookmark, ok := dayObj["isBookmarked"].(bool); ok && bookmark {
+                       dayObj["isBookmarked"] = false
                        bookmarked = false
                } else {
-                       dayObj["bookmarked"] = true
+                       dayObj["isBookmarked"] = true
                }
                days[i] = dayObj
                break
@@ -1159,8 +880,8 @@ func BookmarkDay(w http.ResponseWriter, r *http.Request) {
        if !dayFound {
                // Create new day with bookmark
                days = append(days, map[string]any{
-                       "day":        day,
-                       "bookmarked": true,
+                       "day":          day,
+                       "isBookmarked": true,
                })
        }
 
@@ -1325,3 +1046,461 @@ func SearchTag(w http.ResponseWriter, r *http.Request) {
        // Return results
        utils.JSONResponse(w, http.StatusOK, results)
 }
+
+// GetTags handles retrieving a user's tags
+func GetTags(w http.ResponseWriter, r *http.Request) {
+       // Get user ID and derived key from context
+       userID, ok := r.Context().Value(utils.UserIDKey).(int)
+       if !ok {
+               http.Error(w, "Unauthorized", http.StatusUnauthorized)
+               return
+       }
+       derivedKey, ok := r.Context().Value(utils.DerivedKeyKey).(string)
+       if !ok {
+               http.Error(w, "Unauthorized", http.StatusUnauthorized)
+               return
+       }
+
+       // Get tags
+       content, err := utils.GetTags(userID)
+       if err != nil {
+               http.Error(w, fmt.Sprintf("Error retrieving tags: %v", err), http.StatusInternalServerError)
+               return
+       }
+
+       // If no tags, return empty array
+       if tags, ok := content["tags"].([]any); !ok || len(tags) == 0 {
+               utils.JSONResponse(w, http.StatusOK, []any{})
+               return
+       }
+
+       // Get encryption key
+       encKey, err := utils.GetEncryptionKey(userID, derivedKey)
+       if err != nil {
+               http.Error(w, fmt.Sprintf("Error getting encryption key: %v", err), http.StatusInternalServerError)
+               return
+       }
+
+       // Decrypt tag data
+       tags := content["tags"].([]any)
+       result := []any{}
+
+       for _, tagInterface := range tags {
+               tag, ok := tagInterface.(map[string]any)
+               if !ok {
+                       continue
+               }
+
+               // Decrypt icon, name, and color
+               if encIcon, ok := tag["icon"].(string); ok {
+                       decryptedIcon, err := utils.DecryptText(encIcon, encKey)
+                       if err != nil {
+                               http.Error(w, fmt.Sprintf("Error decrypting tag icon: %v", err), http.StatusInternalServerError)
+                               return
+                       }
+                       tag["icon"] = decryptedIcon
+               }
+
+               if encName, ok := tag["name"].(string); ok {
+                       decryptedName, err := utils.DecryptText(encName, encKey)
+                       if err != nil {
+                               http.Error(w, fmt.Sprintf("Error decrypting tag name: %v", err), http.StatusInternalServerError)
+                               return
+                       }
+                       tag["name"] = decryptedName
+               }
+
+               if encColor, ok := tag["color"].(string); ok {
+                       decryptedColor, err := utils.DecryptText(encColor, encKey)
+                       if err != nil {
+                               http.Error(w, fmt.Sprintf("Error decrypting tag color: %v", err), http.StatusInternalServerError)
+                               return
+                       }
+                       tag["color"] = decryptedColor
+               }
+
+               result = append(result, tag)
+       }
+
+       // Return tags
+       utils.JSONResponse(w, http.StatusOK, result)
+}
+
+// TagRequest represents a tag request
+type TagRequest struct {
+       Icon  string `json:"icon"`
+       Name  string `json:"name"`
+       Color string `json:"color"`
+}
+
+// SaveTags handles saving a new tag
+func SaveTags(w http.ResponseWriter, r *http.Request) {
+       // Get user ID and derived key from context
+       userID, ok := r.Context().Value(utils.UserIDKey).(int)
+       if !ok {
+               http.Error(w, "Unauthorized", http.StatusUnauthorized)
+               return
+       }
+       derivedKey, ok := r.Context().Value(utils.DerivedKeyKey).(string)
+       if !ok {
+               http.Error(w, "Unauthorized", http.StatusUnauthorized)
+               return
+       }
+
+       // Parse request body
+       var req TagRequest
+       if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+               http.Error(w, "Invalid request body", http.StatusBadRequest)
+               return
+       }
+
+       // Get tags
+       content, err := utils.GetTags(userID)
+       if err != nil {
+               http.Error(w, fmt.Sprintf("Error retrieving tags: %v", err), http.StatusInternalServerError)
+               return
+       }
+
+       // Create tags array if it doesn't exist
+       if _, ok := content["tags"]; !ok {
+               content["tags"] = []any{}
+       }
+       if _, ok := content["next_id"]; !ok {
+               content["next_id"] = 1
+       }
+
+       // Get encryption key
+       encKey, err := utils.GetEncryptionKey(userID, derivedKey)
+       if err != nil {
+               http.Error(w, fmt.Sprintf("Error getting encryption key: %v", err), http.StatusInternalServerError)
+               return
+       }
+
+       // Encrypt tag data
+       encIcon, err := utils.EncryptText(req.Icon, encKey)
+       if err != nil {
+               http.Error(w, fmt.Sprintf("Error encrypting tag icon: %v", err), http.StatusInternalServerError)
+               return
+       }
+
+       encName, err := utils.EncryptText(req.Name, encKey)
+       if err != nil {
+               http.Error(w, fmt.Sprintf("Error encrypting tag name: %v", err), http.StatusInternalServerError)
+               return
+       }
+
+       encColor, err := utils.EncryptText(req.Color, encKey)
+       if err != nil {
+               http.Error(w, fmt.Sprintf("Error encrypting tag color: %v", err), http.StatusInternalServerError)
+               return
+       }
+
+       // Create new tag
+       nextID, ok := content["next_id"].(float64)
+       if !ok {
+               nextID = 1
+       }
+
+       newTag := map[string]any{
+               "id":    int(nextID),
+               "icon":  encIcon,
+               "name":  encName,
+               "color": encColor,
+       }
+
+       // Add tag to tags array
+       tags, ok := content["tags"].([]any)
+       if !ok {
+               tags = []any{}
+       }
+       tags = append(tags, newTag)
+       content["tags"] = tags
+       content["next_id"] = nextID + 1
+
+       // Write tags
+       if err := utils.WriteTags(userID, content); err != nil {
+               http.Error(w, fmt.Sprintf("Error writing tags: %v", err), http.StatusInternalServerError)
+               return
+       }
+
+       // Return success
+       utils.JSONResponse(w, http.StatusOK, map[string]bool{
+               "success": true,
+       })
+}
+
+// Helper functions for search
+func getStartIndex(text string, index int) int {
+       if index == 0 {
+               return 0
+       }
+
+       for i := 0; i < 3; i++ {
+               startIndex := strings.LastIndex(text[:index-1], " ")
+               index = startIndex
+               if startIndex == -1 {
+                       return 0
+               }
+       }
+
+       return index + 1
+}
+
+func getEndIndex(text string, index int) int {
+       if index == len(text)-1 {
+               return len(text)
+       }
+
+       for i := 0; i < 3; i++ {
+               endIndex := strings.Index(text[index+1:], " ")
+               if endIndex == -1 {
+                       return len(text)
+               }
+               index = index + 1 + endIndex
+       }
+
+       return index
+}
+
+func getContext(text, searchString string, exact bool) string {
+       // Replace whitespace with non-breaking space
+       re := regexp.MustCompile(`\s+`)
+       text = re.ReplaceAllString(text, " ")
+
+       var pos int
+       if exact {
+               pos = strings.Index(text, searchString)
+       } else {
+               pos = strings.Index(strings.ToLower(text), strings.ToLower(searchString))
+       }
+
+       if pos == -1 {
+               return "<em>Dailytxt: Error formatting...</em>"
+       }
+
+       start := getStartIndex(text, pos)
+       end := getEndIndex(text, pos+len(searchString)-1)
+       return text[start:pos] + "<b>" + text[pos:pos+len(searchString)] + "</b>" + text[pos+len(searchString):end]
+}
+
+// Search handles searching logs for text
+func Search(w http.ResponseWriter, r *http.Request) {
+       // Get user ID and derived key from context
+       userID, ok := r.Context().Value(utils.UserIDKey).(int)
+       if !ok {
+               http.Error(w, "Unauthorized", http.StatusUnauthorized)
+               return
+       }
+       derivedKey, ok := r.Context().Value(utils.DerivedKeyKey).(string)
+       if !ok {
+               http.Error(w, "Unauthorized", http.StatusUnauthorized)
+               return
+       }
+
+       // Get query parameter
+       searchString := r.URL.Query().Get("searchString")
+       if searchString == "" {
+               http.Error(w, "Missing search parameter", http.StatusBadRequest)
+               return
+       }
+
+       // Get encryption key
+       encKey, err := utils.GetEncryptionKey(userID, derivedKey)
+       if err != nil {
+               http.Error(w, fmt.Sprintf("Error getting encryption key: %v", err), http.StatusInternalServerError)
+               return
+       }
+
+       // Get user directory
+       userDir := filepath.Join(utils.Settings.DataPath, strconv.Itoa(userID))
+       results := []any{}
+
+       // Traverse all years and months
+       yearEntries, err := os.ReadDir(userDir)
+       if err != nil {
+               http.Error(w, fmt.Sprintf("Error reading user directory: %v", err), http.StatusInternalServerError)
+               return
+       }
+
+       // Regex to match year directories (4 digits)
+       yearRegex := regexp.MustCompile(`^\d{4}$`)
+
+       for _, yearEntry := range yearEntries {
+               if !yearEntry.IsDir() || !yearRegex.MatchString(yearEntry.Name()) {
+                       continue
+               }
+               year := yearEntry.Name()
+
+               // Read month files in year directory
+               yearDir := filepath.Join(userDir, year)
+               monthEntries, err := os.ReadDir(yearDir)
+               if err != nil {
+                       continue
+               }
+
+               // Regex to match month files (2 digits + .json)
+               monthRegex := regexp.MustCompile(`^(\d{2})\.json$`)
+
+               for _, monthEntry := range monthEntries {
+                       if monthEntry.IsDir() {
+                               continue
+                       }
+
+                       matches := monthRegex.FindStringSubmatch(monthEntry.Name())
+                       if len(matches) != 2 {
+                               continue
+                       }
+                       month := matches[1]
+
+                       // Get month content
+                       monthInt, _ := strconv.Atoi(month)
+                       yearInt, _ := strconv.Atoi(year)
+                       content, err := utils.GetMonth(userID, yearInt, monthInt)
+                       if err != nil {
+                               continue
+                       }
+
+                       days, ok := content["days"].([]any)
+                       if !ok {
+                               continue
+                       }
+
+                       // Process each day
+                       for _, dayInterface := range days {
+                               dayLog, ok := dayInterface.(map[string]any)
+                               if !ok {
+                                       continue
+                               }
+
+                               dayNum, ok := dayLog["day"].(float64)
+                               if !ok {
+                                       continue
+                               }
+                               day := int(dayNum)
+
+                               // Check text
+                               if text, ok := dayLog["text"].(string); ok {
+                                       decryptedText, err := utils.DecryptText(text, encKey)
+                                       if err != nil {
+                                               continue
+                                       }
+
+                                       // Apply search logic
+                                       if strings.HasPrefix(searchString, "\"") && strings.HasSuffix(searchString, "\"") {
+                                               // Exact match
+                                               searchTerm := searchString[1 : len(searchString)-1]
+                                               if strings.Contains(decryptedText, searchTerm) {
+                                                       context := getContext(decryptedText, searchTerm, true)
+                                                       results = append(results, map[string]any{
+                                                               "year":  year,
+                                                               "month": month,
+                                                               "day":   day,
+                                                               "text":  context,
+                                                       })
+                                               }
+                                       } else if strings.Contains(searchString, "|") {
+                                               // OR search
+                                               words := strings.Split(searchString, "|")
+                                               for _, word := range words {
+                                                       wordTrimmed := strings.TrimSpace(word)
+                                                       if strings.Contains(strings.ToLower(decryptedText), strings.ToLower(wordTrimmed)) {
+                                                               context := getContext(decryptedText, wordTrimmed, false)
+                                                               results = append(results, map[string]any{
+                                                                       "year":  year,
+                                                                       "month": month,
+                                                                       "day":   day,
+                                                                       "text":  context,
+                                                               })
+                                                               break
+                                                       }
+                                               }
+                                       } else if strings.Contains(searchString, " ") {
+                                               // AND search
+                                               words := strings.Split(searchString, " ")
+                                               allWordsMatch := true
+                                               for _, word := range words {
+                                                       wordTrimmed := strings.TrimSpace(word)
+                                                       if !strings.Contains(strings.ToLower(decryptedText), strings.ToLower(wordTrimmed)) {
+                                                               allWordsMatch = false
+                                                               break
+                                                       }
+                                               }
+                                               if allWordsMatch {
+                                                       context := getContext(decryptedText, strings.TrimSpace(words[0]), false)
+                                                       results = append(results, map[string]any{
+                                                               "year":  year,
+                                                               "month": month,
+                                                               "day":   day,
+                                                               "text":  context,
+                                                       })
+                                               }
+                                       } else {
+                                               // Simple search
+                                               if strings.Contains(strings.ToLower(decryptedText), strings.ToLower(searchString)) {
+                                                       context := getContext(decryptedText, searchString, false)
+                                                       results = append(results, map[string]any{
+                                                               "year":  year,
+                                                               "month": month,
+                                                               "day":   day,
+                                                               "text":  context,
+                                                       })
+                                               }
+                                       }
+                               }
+
+                               // Check filenames
+                               if files, ok := dayLog["files"].([]any); ok {
+                                       for _, fileInterface := range files {
+                                               file, ok := fileInterface.(map[string]any)
+                                               if !ok {
+                                                       continue
+                                               }
+
+                                               if encFilename, ok := file["enc_filename"].(string); ok {
+                                                       decryptedFilename, err := utils.DecryptText(encFilename, encKey)
+                                                       if err != nil {
+                                                               continue
+                                                       }
+
+                                                       if strings.Contains(strings.ToLower(decryptedFilename), strings.ToLower(searchString)) {
+                                                               context := "📎 " + decryptedFilename
+                                                               results = append(results, map[string]any{
+                                                                       "year":  year,
+                                                                       "month": month,
+                                                                       "day":   day,
+                                                                       "text":  context,
+                                                               })
+                                                               break
+                                                       }
+                                               }
+                                       }
+                               }
+                       }
+               }
+       }
+
+       // Sort results by date
+       sort.Slice(results, func(i, j int) bool {
+               ri := results[i].(map[string]any)
+               rj := results[j].(map[string]any)
+
+               yearI, _ := strconv.Atoi(ri["year"].(string))
+               yearJ, _ := strconv.Atoi(rj["year"].(string))
+               if yearI != yearJ {
+                       return yearI < yearJ
+               }
+
+               monthI, _ := strconv.Atoi(ri["month"].(string))
+               monthJ, _ := strconv.Atoi(rj["month"].(string))
+               if monthI != monthJ {
+                       return monthI < monthJ
+               }
+
+               dayI := ri["day"].(int)
+               dayJ := rj["day"].(int)
+               return dayI < dayJ
+       })
+
+       // Return results
+       utils.JSONResponse(w, http.StatusOK, results)
+}
index e014e08db299af704a822c18ece3ea1bb59f1515..a611c044f4a4f95b82a426acfb6e794303b00600 100644 (file)
@@ -5,10 +5,6 @@ import (
        "fmt"
        "html"
        "net/http"
-       "os"
-       "path/filepath"
-       "regexp"
-       "sort"
        "strconv"
        "strings"
 
@@ -381,7 +377,7 @@ func GetMarkedDays(w http.ResponseWriter, r *http.Request) {
                        }
 
                        // Check if bookmarked
-                       if bookmarked, ok := day["bookmarked"].(bool); ok && bookmarked {
+                       if bookmarked, ok := day["isBookmarked"].(bool); ok && bookmarked {
                                daysBookmarked = append(daysBookmarked, int(dayNum))
                        }
                }
@@ -395,188 +391,6 @@ func GetMarkedDays(w http.ResponseWriter, r *http.Request) {
        })
 }
 
-// GetTags handles retrieving a user's tags
-func GetTags(w http.ResponseWriter, r *http.Request) {
-       // Get user ID and derived key from context
-       userID, ok := r.Context().Value(utils.UserIDKey).(int)
-       if !ok {
-               http.Error(w, "Unauthorized", http.StatusUnauthorized)
-               return
-       }
-       derivedKey, ok := r.Context().Value(utils.DerivedKeyKey).(string)
-       if !ok {
-               http.Error(w, "Unauthorized", http.StatusUnauthorized)
-               return
-       }
-
-       // Get tags
-       content, err := utils.GetTags(userID)
-       if err != nil {
-               http.Error(w, fmt.Sprintf("Error retrieving tags: %v", err), http.StatusInternalServerError)
-               return
-       }
-
-       // If no tags, return empty array
-       if tags, ok := content["tags"].([]any); !ok || len(tags) == 0 {
-               utils.JSONResponse(w, http.StatusOK, []any{})
-               return
-       }
-
-       // Get encryption key
-       encKey, err := utils.GetEncryptionKey(userID, derivedKey)
-       if err != nil {
-               http.Error(w, fmt.Sprintf("Error getting encryption key: %v", err), http.StatusInternalServerError)
-               return
-       }
-
-       // Decrypt tag data
-       tags := content["tags"].([]any)
-       result := []any{}
-
-       for _, tagInterface := range tags {
-               tag, ok := tagInterface.(map[string]any)
-               if !ok {
-                       continue
-               }
-
-               // Decrypt icon, name, and color
-               if encIcon, ok := tag["icon"].(string); ok {
-                       decryptedIcon, err := utils.DecryptText(encIcon, encKey)
-                       if err != nil {
-                               http.Error(w, fmt.Sprintf("Error decrypting tag icon: %v", err), http.StatusInternalServerError)
-                               return
-                       }
-                       tag["icon"] = decryptedIcon
-               }
-
-               if encName, ok := tag["name"].(string); ok {
-                       decryptedName, err := utils.DecryptText(encName, encKey)
-                       if err != nil {
-                               http.Error(w, fmt.Sprintf("Error decrypting tag name: %v", err), http.StatusInternalServerError)
-                               return
-                       }
-                       tag["name"] = decryptedName
-               }
-
-               if encColor, ok := tag["color"].(string); ok {
-                       decryptedColor, err := utils.DecryptText(encColor, encKey)
-                       if err != nil {
-                               http.Error(w, fmt.Sprintf("Error decrypting tag color: %v", err), http.StatusInternalServerError)
-                               return
-                       }
-                       tag["color"] = decryptedColor
-               }
-
-               result = append(result, tag)
-       }
-
-       // Return tags
-       utils.JSONResponse(w, http.StatusOK, result)
-}
-
-// TagRequest represents a tag request
-type TagRequest struct {
-       Icon  string `json:"icon"`
-       Name  string `json:"name"`
-       Color string `json:"color"`
-}
-
-// SaveTags handles saving a new tag
-func SaveTags(w http.ResponseWriter, r *http.Request) {
-       // Get user ID and derived key from context
-       userID, ok := r.Context().Value(utils.UserIDKey).(int)
-       if !ok {
-               http.Error(w, "Unauthorized", http.StatusUnauthorized)
-               return
-       }
-       derivedKey, ok := r.Context().Value(utils.DerivedKeyKey).(string)
-       if !ok {
-               http.Error(w, "Unauthorized", http.StatusUnauthorized)
-               return
-       }
-
-       // Parse request body
-       var req TagRequest
-       if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
-               http.Error(w, "Invalid request body", http.StatusBadRequest)
-               return
-       }
-
-       // Get tags
-       content, err := utils.GetTags(userID)
-       if err != nil {
-               http.Error(w, fmt.Sprintf("Error retrieving tags: %v", err), http.StatusInternalServerError)
-               return
-       }
-
-       // Create tags array if it doesn't exist
-       if _, ok := content["tags"]; !ok {
-               content["tags"] = []any{}
-       }
-       if _, ok := content["next_id"]; !ok {
-               content["next_id"] = 1
-       }
-
-       // Get encryption key
-       encKey, err := utils.GetEncryptionKey(userID, derivedKey)
-       if err != nil {
-               http.Error(w, fmt.Sprintf("Error getting encryption key: %v", err), http.StatusInternalServerError)
-               return
-       }
-
-       // Encrypt tag data
-       encIcon, err := utils.EncryptText(req.Icon, encKey)
-       if err != nil {
-               http.Error(w, fmt.Sprintf("Error encrypting tag icon: %v", err), http.StatusInternalServerError)
-               return
-       }
-
-       encName, err := utils.EncryptText(req.Name, encKey)
-       if err != nil {
-               http.Error(w, fmt.Sprintf("Error encrypting tag name: %v", err), http.StatusInternalServerError)
-               return
-       }
-
-       encColor, err := utils.EncryptText(req.Color, encKey)
-       if err != nil {
-               http.Error(w, fmt.Sprintf("Error encrypting tag color: %v", err), http.StatusInternalServerError)
-               return
-       }
-
-       // Create new tag
-       nextID, ok := content["next_id"].(float64)
-       if !ok {
-               nextID = 1
-       }
-
-       newTag := map[string]any{
-               "id":    int(nextID),
-               "icon":  encIcon,
-               "name":  encName,
-               "color": encColor,
-       }
-
-       // Add tag to tags array
-       tags, ok := content["tags"].([]any)
-       if !ok {
-               tags = []any{}
-       }
-       tags = append(tags, newTag)
-       content["tags"] = tags
-       content["next_id"] = nextID + 1
-
-       // Write tags
-       if err := utils.WriteTags(userID, content); err != nil {
-               http.Error(w, fmt.Sprintf("Error writing tags: %v", err), http.StatusInternalServerError)
-               return
-       }
-
-       // Return success
-       utils.JSONResponse(w, http.StatusOK, map[string]bool{
-               "success": true,
-       })
-}
-
 // TemplatesRequest represents a templates request
 type TemplatesRequest struct {
        Templates []struct {
@@ -828,62 +642,148 @@ func GetOnThisDay(w http.ResponseWriter, r *http.Request) {
        utils.JSONResponse(w, http.StatusOK, results)
 }
 
-// Helper functions for search
-func getStartIndex(text string, index int) int {
-       if index == 0 {
-               return 0
+// LoadMonthForReading handles loading a month for reading
+func LoadMonthForReading(w http.ResponseWriter, r *http.Request) {
+       // Get user ID and derived key from context
+       userID, ok := r.Context().Value(utils.UserIDKey).(int)
+       if !ok {
+               http.Error(w, "Unauthorized", http.StatusUnauthorized)
+               return
+       }
+       derivedKey, ok := r.Context().Value(utils.DerivedKeyKey).(string)
+       if !ok {
+               http.Error(w, "Unauthorized", http.StatusUnauthorized)
+               return
        }
 
-       for i := 0; i < 3; i++ {
-               startIndex := strings.LastIndex(text[:index-1], " ")
-               index = startIndex
-               if startIndex == -1 {
-                       return 0
-               }
+       // Get parameters from URL
+       monthStr := r.URL.Query().Get("month")
+       if monthStr == "" {
+               http.Error(w, "Missing month parameter", http.StatusBadRequest)
+               return
+       }
+       month, err := strconv.Atoi(monthStr)
+       if err != nil {
+               http.Error(w, "Invalid month parameter", http.StatusBadRequest)
+               return
        }
 
-       return index + 1
-}
+       yearStr := r.URL.Query().Get("year")
+       if yearStr == "" {
+               http.Error(w, "Missing year parameter", http.StatusBadRequest)
+               return
+       }
+       year, err := strconv.Atoi(yearStr)
+       if err != nil {
+               http.Error(w, "Invalid year parameter", http.StatusBadRequest)
+               return
+       }
 
-func getEndIndex(text string, index int) int {
-       if index == len(text)-1 {
-               return len(text)
+       // Get month data
+       content, err := utils.GetMonth(userID, year, month)
+       if err != nil {
+               http.Error(w, fmt.Sprintf("Error retrieving month data: %v", err), http.StatusInternalServerError)
+               return
        }
 
-       for i := 0; i < 3; i++ {
-               endIndex := strings.Index(text[index+1:], " ")
-               if endIndex == -1 {
-                       return len(text)
-               }
-               index = index + 1 + endIndex
+       // Get encryption key
+       encKey, err := utils.GetEncryptionKey(userID, derivedKey)
+       if err != nil {
+               http.Error(w, fmt.Sprintf("Error getting encryption key: %v", err), http.StatusInternalServerError)
+               return
        }
 
-       return index
-}
+       // Check if days exist
+       days, ok := content["days"].([]any)
+       if !ok {
+               utils.JSONResponse(w, http.StatusOK, []any{})
+               return
+       }
 
-func getContext(text, searchString string, exact bool) string {
-       // Replace whitespace with non-breaking space
-       re := regexp.MustCompile(`\s+`)
-       text = re.ReplaceAllString(text, " ")
+       // Process days
+       result := []any{}
+       for _, dayInterface := range days {
+               day, ok := dayInterface.(map[string]any)
+               if !ok {
+                       continue
+               }
 
-       var pos int
-       if exact {
-               pos = strings.Index(text, searchString)
-       } else {
-               pos = strings.Index(strings.ToLower(text), strings.ToLower(searchString))
-       }
+               dayNum, ok := day["day"].(float64)
+               if !ok {
+                       continue
+               }
+
+               // Create result day
+               resultDay := map[string]any{
+                       "day": int(dayNum),
+               }
+
+               // Decrypt text and date_written
+               if text, ok := day["text"].(string); ok && text != "" {
+                       decryptedText, err := utils.DecryptText(text, encKey)
+                       if err != nil {
+                               http.Error(w, fmt.Sprintf("Error decrypting text: %v", err), http.StatusInternalServerError)
+                               return
+                       }
+                       resultDay["text"] = decryptedText
+
+                       if dateWritten, ok := day["date_written"].(string); ok && dateWritten != "" {
+                               decryptedDate, err := utils.DecryptText(dateWritten, encKey)
+                               if err != nil {
+                                       http.Error(w, fmt.Sprintf("Error decrypting date_written: %v", err), http.StatusInternalServerError)
+                                       return
+                               }
+                               resultDay["date_written"] = decryptedDate
+                       }
+               }
+
+               // Get tags
+               if tags, ok := day["tags"].([]any); ok && len(tags) > 0 {
+                       resultDay["tags"] = tags
+               }
 
-       if pos == -1 {
-               return "<em>Dailytxt: Error formatting...</em>"
+               // Decrypt filenames if files exist
+               if filesList, ok := day["files"].([]any); ok && len(filesList) > 0 {
+                       files := []any{}
+                       for _, fileInterface := range filesList {
+                               file, ok := fileInterface.(map[string]any)
+                               if !ok {
+                                       continue
+                               }
+
+                               if encFilename, ok := file["enc_filename"].(string); ok {
+                                       decryptedFilename, err := utils.DecryptText(encFilename, encKey)
+                                       if err != nil {
+                                               http.Error(w, fmt.Sprintf("Error decrypting filename: %v", err), http.StatusInternalServerError)
+                                               return
+                                       }
+                                       fileCopy := make(map[string]any)
+                                       for k, v := range file {
+                                               fileCopy[k] = v
+                                       }
+                                       fileCopy["filename"] = decryptedFilename
+                                       files = append(files, fileCopy)
+                               }
+                       }
+                       resultDay["files"] = files
+               }
+
+               // Add day to result if it has content
+               if _, hasText := resultDay["text"]; hasText {
+                       result = append(result, resultDay)
+               } else if _, hasFiles := resultDay["files"]; hasFiles {
+                       result = append(result, resultDay)
+               } else if _, hasTags := resultDay["tags"]; hasTags {
+                       result = append(result, resultDay)
+               }
        }
 
-       start := getStartIndex(text, pos)
-       end := getEndIndex(text, pos+len(searchString)-1)
-       return text[start:pos] + "<b>" + text[pos:pos+len(searchString)] + "</b>" + text[pos+len(searchString):end]
+       // Return result
+       utils.JSONResponse(w, http.StatusOK, result)
 }
 
-// Search handles searching logs for text
-func Search(w http.ResponseWriter, r *http.Request) {
+// GetHistory handles retrieving log history
+func GetHistory(w http.ResponseWriter, r *http.Request) {
        // Get user ID and derived key from context
        userID, ok := r.Context().Value(utils.UserIDKey).(int)
        if !ok {
@@ -896,10 +796,37 @@ func Search(w http.ResponseWriter, r *http.Request) {
                return
        }
 
-       // Get query parameter
-       searchString := r.URL.Query().Get("searchString")
-       if searchString == "" {
-               http.Error(w, "Missing search parameter", http.StatusBadRequest)
+       // Get parameters
+       dayStr := r.URL.Query().Get("day")
+       if dayStr == "" {
+               http.Error(w, "Missing day parameter", http.StatusBadRequest)
+               return
+       }
+       day, err := strconv.Atoi(dayStr)
+       if err != nil {
+               http.Error(w, "Invalid day parameter", http.StatusBadRequest)
+               return
+       }
+
+       monthStr := r.URL.Query().Get("month")
+       if monthStr == "" {
+               http.Error(w, "Missing month parameter", http.StatusBadRequest)
+               return
+       }
+       month, err := strconv.Atoi(monthStr)
+       if err != nil {
+               http.Error(w, "Invalid month parameter", http.StatusBadRequest)
+               return
+       }
+
+       yearStr := r.URL.Query().Get("year")
+       if yearStr == "" {
+               http.Error(w, "Missing year parameter", http.StatusBadRequest)
+               return
+       }
+       year, err := strconv.Atoi(yearStr)
+       if err != nil {
+               http.Error(w, "Invalid year parameter", http.StatusBadRequest)
                return
        }
 
@@ -910,196 +837,81 @@ func Search(w http.ResponseWriter, r *http.Request) {
                return
        }
 
-       // Get user directory
-       userDir := filepath.Join(utils.Settings.DataPath, strconv.Itoa(userID))
-       results := []any{}
-
-       // Traverse all years and months
-       yearEntries, err := os.ReadDir(userDir)
+       // Get month data
+       content, err := utils.GetMonth(userID, year, month)
        if err != nil {
-               http.Error(w, fmt.Sprintf("Error reading user directory: %v", err), http.StatusInternalServerError)
+               http.Error(w, fmt.Sprintf("Error retrieving month data: %v", err), http.StatusInternalServerError)
                return
        }
 
-       // Regex to match year directories (4 digits)
-       yearRegex := regexp.MustCompile(`^\d{4}$`)
+       // Check if days exist
+       days, ok := content["days"].([]any)
+       if !ok {
+               utils.JSONResponse(w, http.StatusOK, []any{})
+               return
+       }
 
-       for _, yearEntry := range yearEntries {
-               if !yearEntry.IsDir() || !yearRegex.MatchString(yearEntry.Name()) {
+       // Find day
+       for _, dayInterface := range days {
+               dayObj, ok := dayInterface.(map[string]any)
+               if !ok {
                        continue
                }
-               year := yearEntry.Name()
 
-               // Read month files in year directory
-               yearDir := filepath.Join(userDir, year)
-               monthEntries, err := os.ReadDir(yearDir)
-               if err != nil {
+               dayNum, ok := dayObj["day"].(float64)
+               if !ok || int(dayNum) != day {
                        continue
                }
 
-               // Regex to match month files (2 digits + .json)
-               monthRegex := regexp.MustCompile(`^(\d{2})\.json$`)
-
-               for _, monthEntry := range monthEntries {
-                       if monthEntry.IsDir() {
-                               continue
-                       }
+               // Check for history
+               history, ok := dayObj["history"].([]any)
+               if !ok || len(history) == 0 {
+                       utils.JSONResponse(w, http.StatusOK, []any{})
+                       return
+               }
 
-                       matches := monthRegex.FindStringSubmatch(monthEntry.Name())
-                       if len(matches) != 2 {
+               // Decrypt history entries
+               result := []any{}
+               for _, historyInterface := range history {
+                       historyEntry, ok := historyInterface.(map[string]any)
+                       if !ok {
                                continue
                        }
-                       month := matches[1]
 
-                       // Get month content
-                       monthInt, _ := strconv.Atoi(month)
-                       yearInt, _ := strconv.Atoi(year)
-                       content, err := utils.GetMonth(userID, yearInt, monthInt)
-                       if err != nil {
+                       text, ok := historyEntry["text"].(string)
+                       if !ok {
                                continue
                        }
 
-                       days, ok := content["days"].([]any)
+                       dateWritten, ok := historyEntry["date_written"].(string)
                        if !ok {
                                continue
                        }
 
-                       // Process each day
-                       for _, dayInterface := range days {
-                               dayLog, ok := dayInterface.(map[string]any)
-                               if !ok {
-                                       continue
-                               }
-
-                               dayNum, ok := dayLog["day"].(float64)
-                               if !ok {
-                                       continue
-                               }
-                               day := int(dayNum)
-
-                               // Check text
-                               if text, ok := dayLog["text"].(string); ok {
-                                       decryptedText, err := utils.DecryptText(text, encKey)
-                                       if err != nil {
-                                               continue
-                                       }
-
-                                       // Apply search logic
-                                       if strings.HasPrefix(searchString, "\"") && strings.HasSuffix(searchString, "\"") {
-                                               // Exact match
-                                               searchTerm := searchString[1 : len(searchString)-1]
-                                               if strings.Contains(decryptedText, searchTerm) {
-                                                       context := getContext(decryptedText, searchTerm, true)
-                                                       results = append(results, map[string]any{
-                                                               "year":  year,
-                                                               "month": month,
-                                                               "day":   day,
-                                                               "text":  context,
-                                                       })
-                                               }
-                                       } else if strings.Contains(searchString, "|") {
-                                               // OR search
-                                               words := strings.Split(searchString, "|")
-                                               for _, word := range words {
-                                                       wordTrimmed := strings.TrimSpace(word)
-                                                       if strings.Contains(strings.ToLower(decryptedText), strings.ToLower(wordTrimmed)) {
-                                                               context := getContext(decryptedText, wordTrimmed, false)
-                                                               results = append(results, map[string]any{
-                                                                       "year":  year,
-                                                                       "month": month,
-                                                                       "day":   day,
-                                                                       "text":  context,
-                                                               })
-                                                               break
-                                                       }
-                                               }
-                                       } else if strings.Contains(searchString, " ") {
-                                               // AND search
-                                               words := strings.Split(searchString, " ")
-                                               allWordsMatch := true
-                                               for _, word := range words {
-                                                       wordTrimmed := strings.TrimSpace(word)
-                                                       if !strings.Contains(strings.ToLower(decryptedText), strings.ToLower(wordTrimmed)) {
-                                                               allWordsMatch = false
-                                                               break
-                                                       }
-                                               }
-                                               if allWordsMatch {
-                                                       context := getContext(decryptedText, strings.TrimSpace(words[0]), false)
-                                                       results = append(results, map[string]any{
-                                                               "year":  year,
-                                                               "month": month,
-                                                               "day":   day,
-                                                               "text":  context,
-                                                       })
-                                               }
-                                       } else {
-                                               // Simple search
-                                               if strings.Contains(strings.ToLower(decryptedText), strings.ToLower(searchString)) {
-                                                       context := getContext(decryptedText, searchString, false)
-                                                       results = append(results, map[string]any{
-                                                               "year":  year,
-                                                               "month": month,
-                                                               "day":   day,
-                                                               "text":  context,
-                                                       })
-                                               }
-                                       }
-                               }
-
-                               // Check filenames
-                               if files, ok := dayLog["files"].([]any); ok {
-                                       for _, fileInterface := range files {
-                                               file, ok := fileInterface.(map[string]any)
-                                               if !ok {
-                                                       continue
-                                               }
-
-                                               if encFilename, ok := file["enc_filename"].(string); ok {
-                                                       decryptedFilename, err := utils.DecryptText(encFilename, encKey)
-                                                       if err != nil {
-                                                               continue
-                                                       }
-
-                                                       if strings.Contains(strings.ToLower(decryptedFilename), strings.ToLower(searchString)) {
-                                                               context := "📎 " + decryptedFilename
-                                                               results = append(results, map[string]any{
-                                                                       "year":  year,
-                                                                       "month": month,
-                                                                       "day":   day,
-                                                                       "text":  context,
-                                                               })
-                                                               break
-                                                       }
-                                               }
-                                       }
-                               }
+                       // Decrypt text and date
+                       decryptedText, err := utils.DecryptText(text, encKey)
+                       if err != nil {
+                               http.Error(w, fmt.Sprintf("Error decrypting history text: %v", err), http.StatusInternalServerError)
+                               return
                        }
-               }
-       }
 
-       // Sort results by date
-       sort.Slice(results, func(i, j int) bool {
-               ri := results[i].(map[string]any)
-               rj := results[j].(map[string]any)
-
-               yearI, _ := strconv.Atoi(ri["year"].(string))
-               yearJ, _ := strconv.Atoi(rj["year"].(string))
-               if yearI != yearJ {
-                       return yearI < yearJ
-               }
+                       decryptedDate, err := utils.DecryptText(dateWritten, encKey)
+                       if err != nil {
+                               http.Error(w, fmt.Sprintf("Error decrypting history date: %v", err), http.StatusInternalServerError)
+                               return
+                       }
 
-               monthI, _ := strconv.Atoi(ri["month"].(string))
-               monthJ, _ := strconv.Atoi(rj["month"].(string))
-               if monthI != monthJ {
-                       return monthI < monthJ
+                       result = append(result, map[string]any{
+                               "text":         decryptedText,
+                               "date_written": decryptedDate,
+                       })
                }
 
-               dayI := ri["day"].(int)
-               dayJ := rj["day"].(int)
-               return dayI < dayJ
-       })
+               // Return history
+               utils.JSONResponse(w, http.StatusOK, result)
+               return
+       }
 
-       // Return results
-       utils.JSONResponse(w, http.StatusOK, results)
+       // Day not found
+       utils.JSONResponse(w, http.StatusOK, []any{})
 }
index d09239bd52392f036a6d5742b0ed54c3bc51b4f6..6cf551cd3b29ce8a61a2e254fd05f3db90d070ac 100644 (file)
@@ -47,6 +47,7 @@ func Login(w http.ResponseWriter, r *http.Request) {
        var hashedPassword string
        var salt string
        found := false
+       var username string
 
        for _, u := range usersList {
                user, ok := u.(map[string]any)
@@ -54,7 +55,7 @@ func Login(w http.ResponseWriter, r *http.Request) {
                        continue
                }
 
-               if username, ok := user["username"].(string); ok && username == req.Username {
+               if username, ok = user["username"].(string); ok && strings.EqualFold(username, req.Username) {
                        found = true
                        if id, ok := user["user_id"].(float64); ok {
                                userID = int(id)
@@ -78,7 +79,7 @@ func Login(w http.ResponseWriter, r *http.Request) {
                        return
                }
 
-               oldUsersList, ok := oldUsers["users"].([]interface{})
+               oldUsersList, ok := oldUsers["users"].([]any)
                if !ok || len(oldUsersList) == 0 {
                        utils.Logger.Printf("Login failed. User '%s' not found in new or old data", req.Username)
                        http.Error(w, "User/Password combination not found", http.StatusNotFound)
@@ -93,7 +94,7 @@ func Login(w http.ResponseWriter, r *http.Request) {
                                continue
                        }
 
-                       if username, ok := user["username"].(string); ok && username == req.Username {
+                       if username, ok = user["username"].(string); ok && strings.EqualFold(username, req.Username) {
                                oldUser = user
                                break
                        }
@@ -125,7 +126,7 @@ func Login(w http.ResponseWriter, r *http.Request) {
 
                // Check if there is already a migration in progress for this user
                activeMigrationsMutex.RLock()
-               isActive := activeMigrations[req.Username]
+               isActive := activeMigrations[username]
                activeMigrationsMutex.RUnlock()
 
                if isActive {
@@ -138,7 +139,7 @@ func Login(w http.ResponseWriter, r *http.Request) {
 
                // Mark this user as having an active migration
                activeMigrationsMutex.Lock()
-               activeMigrations[req.Username] = true
+               activeMigrations[username] = true
                activeMigrationsMutex.Unlock()
 
                // Create a channel to report progress
@@ -153,7 +154,7 @@ func Login(w http.ResponseWriter, r *http.Request) {
                                for progress := range progressChan {
                                        migrationProgressMutex.Lock()
                                        // Convert from utils.MigrationProgress to handlers.MigrationProgress
-                                       migrationProgress[req.Username] = MigrationProgress{
+                                       migrationProgress[username] = MigrationProgress{
                                                Phase:          progress.Phase,
                                                ProcessedItems: progress.ProcessedItems,
                                                TotalItems:     progress.TotalItems,
@@ -163,26 +164,28 @@ func Login(w http.ResponseWriter, r *http.Request) {
                                }
                        }()
 
-                       err := utils.MigrateUserData(req.Username, req.Password, Register, progressChan)
+                       utils.Logger.Printf("Starting migration for user '%s'", username)
+
+                       err := utils.MigrateUserData(username, req.Password, Register, progressChan)
                        if err != nil {
-                               utils.Logger.Printf("Migration failed for user '%s': %v", req.Username, err)
+                               utils.Logger.Printf("Migration failed for user '%s': %v", username, err)
                                // Mark migration as completed even on error
                                activeMigrationsMutex.Lock()
-                               activeMigrations[req.Username] = false
+                               activeMigrations[username] = false
                                activeMigrationsMutex.Unlock()
                                return
                        }
 
                        // Mark migration as completed
                        activeMigrationsMutex.Lock()
-                       activeMigrations[req.Username] = false
+                       activeMigrations[username] = false
                        activeMigrationsMutex.Unlock()
                }()
 
                // Return migration status to client
                utils.JSONResponse(w, http.StatusAccepted, map[string]any{
                        "migration_started": true,
-                       "username":          req.Username,
+                       "username":          username,
                })
                return
        }
@@ -222,7 +225,7 @@ func Login(w http.ResponseWriter, r *http.Request) {
        // Return success
        utils.JSONResponse(w, http.StatusOK, map[string]any{
                "migration_started": false,
-               "username":          req.Username,
+               "username":          username,
        })
 }
 
index 6ab09a3eaf0355519fd6e1d16893e662c8e4555f..238032edff76c5b1e37f2d75fd113e5da8d0211a 100644 (file)
@@ -32,7 +32,6 @@ func main() {
 
        // Register routes
        mux.HandleFunc("POST /users/login", handlers.Login)
-       mux.HandleFunc("GET /users/migrationWebSocket", handlers.HandleMigrationWebSocket)
        mux.HandleFunc("GET /users/migrationProgress", handlers.GetMigrationProgress)
        mux.HandleFunc("GET /users/isRegistrationAllowed", handlers.IsRegistrationAllowed)
        mux.HandleFunc("POST /users/register", handlers.RegisterHandler)
index 51093ad3d0121a32ce47611716b7c873d2db9422..685ff02379db1fe5983ca94d523640cbea23b05e 100644 (file)
@@ -346,15 +346,11 @@ func MigrateUserData(username, password string, registerFunc RegisterUserFunc, p
                return handleError("Error getting encryption key", err)
        }
 
-       time.Sleep(800 * time.Millisecond) // Simulate some delay for migration
-
        // Migrate templates
        if err := migrateTemplates(oldDataDir, newDataDir, oldEncKey, encKey, &currentProgress, progressChan); err != nil {
                return handleError("Error migrating templates", err)
        }
 
-       time.Sleep(800 * time.Millisecond) // Simulate some delay for migration
-
        // Migrate logs (years/months)
        if err := migrateLogs(oldDataDir, newDataDir, oldEncKey, encKey, &currentProgress, progressChan); err != nil {
                return handleError("Error migrating logs", err)
@@ -438,13 +434,91 @@ func migrateTemplates(oldDir, newDir string, oldKey string, newKey string, progr
                return fmt.Errorf("error reading old templates: %v", err)
        }
 
-       // Templates need to be parsed and re-written with proper indentation
+       // Templates need to be parsed
        var templatesData map[string]any
        if err := json.Unmarshal(oldTemplatesBytes, &templatesData); err != nil {
                return fmt.Errorf("error parsing old templates: %v", err)
        }
 
-       // Create templates.json file with proper indentation
+       // Decrypt and encrypt templates
+       oldKeyBytes, err := base64.URLEncoding.DecodeString(oldKey)
+       if err != nil {
+               Logger.Printf("Error decoding oldKey %v", err)
+               progress.ErrorCount++
+               return fmt.Errorf("error decoding oldKey: %v", err)
+       }
+
+       // Get the templates array
+       templatesArray, ok := templatesData["templates"].([]any)
+       if !ok {
+               return fmt.Errorf("invalid templates format - 'templates' is not an array")
+       }
+
+       // Create a new templates array for the migrated templates
+       newTemplatesArray := make([]map[string]any, 0)
+
+       // Process each template in the array
+       for _, templateItem := range templatesArray {
+               templateMap, ok := templateItem.(map[string]any)
+               if !ok {
+                       Logger.Printf("Warning: template item is not a map: %v", templateItem)
+                       progress.ErrorCount++
+                       continue
+               }
+
+               // Create a new template without the 'number' field
+               newTemplate := make(map[string]any)
+
+               // Process encrypted name
+               if encName, ok := templateMap["name"].(string); ok && encName != "" {
+                       // Decrypt with old key
+                       decryptedName, err := FernetDecrypt(encName, oldKeyBytes)
+                       if err != nil {
+                               Logger.Printf("Error decrypting template name: %v", err)
+                               progress.ErrorCount++
+                               continue
+                       }
+
+                       // Encrypt with new key
+                       newEncName, err := EncryptText(decryptedName, newKey)
+                       if err != nil {
+                               Logger.Printf("Error encrypting template name: %v", err)
+                               progress.ErrorCount++
+                               continue
+                       }
+
+                       newTemplate["name"] = newEncName
+               }
+
+               // Process encrypted text
+               if encText, ok := templateMap["text"].(string); ok && encText != "" {
+                       // Decrypt with old key
+                       decryptedText, err := FernetDecrypt(encText, oldKeyBytes)
+                       if err != nil {
+                               Logger.Printf("Error decrypting template text: %v", err)
+                               progress.ErrorCount++
+                               continue
+                       }
+
+                       // Encrypt with new key
+                       newEncText, err := EncryptText(decryptedText, newKey)
+                       if err != nil {
+                               Logger.Printf("Error encrypting template text: %v", err)
+                               progress.ErrorCount++
+                               continue
+                       }
+
+                       newTemplate["text"] = newEncText
+               }
+
+               // Add the new template to the array
+               newTemplatesArray = append(newTemplatesArray, newTemplate)
+       }
+
+       // Replace the old templates array with the new one
+       templatesData["templates"] = newTemplatesArray
+
+       // Create templates.json file
        newTemplatesPath := filepath.Join(newDir, "templates.json")
        templatesMutex.Lock()
 
@@ -740,7 +814,6 @@ func migrateLogs(oldDir, newDir string, oldKey string, newKey string, progress *
                logsMutex.Unlock()
 
                processedMonths++
-               time.Sleep(20 * time.Millisecond) // Simulate some delay for migration
        }
 
        // Final progress update
@@ -1004,7 +1077,6 @@ func migrateFiles(oldFilesDir, newDir string, oldKey string, newKey string, prog
                                progressChan <- *progress
                        }
                }
-               time.Sleep(100 * time.Millisecond) // Simulate some delay for migration
        }
 
        // Third pass: update all month files with new file IDs
index f2eb91e5038aa68e74d65b740506c687c5fa3863..bb8590bcc932a06bac2fe48ac390e1a7b405d7a0 100644 (file)
@@ -25,4 +25,6 @@ export { formatBytes, sameDate };
 export let alwaysShowSidenav = writable(true);
 
 // check if offcanvas/sidenav is open
-export let offcanvasIsOpen = writable(false);
\ No newline at end of file
+export let offcanvasIsOpen = writable(false);
+
+export let currentUser = writable("")
\ No newline at end of file
index f42dfd2671953941721a57880fb1f54429f61a2e..beb7d3e0d519954c9fb6640e68ece161a753f29d 100644 (file)
@@ -20,7 +20,7 @@
        import trianglify from 'trianglify';
        import { tags } from '$lib/tagStore.js';
        import TagModal from '$lib/TagModal.svelte';
-       import { alwaysShowSidenav } from '$lib/helpers.js';
+       import { alwaysShowSidenav, currentUser } from '$lib/helpers.js';
        import { templates } from '$lib/templateStore';
        import {
                faRightFromBracket,
                }
        });
 
+       $effect(() => {
+               if ($currentUser !== null) {
+                       getUserSettings();
+                       getTemplates();
+               } else {
+                       $settings = {};
+                       $templates = [];
+               }
+       });
+
        function logout() {
                axios
                        .get(API_URL + '/users/logout')
                        .then((response) => {
                                localStorage.removeItem('user');
+                               $currentUser = null;
                                goto('/login');
                        })
                        .catch((error) => {
        }
 
        function updateSelectedTemplate() {
-               if (selectedTemplate === '-1' || selectedTemplate === null) {
+               if (selectedTemplate === '-1' || selectedTemplate === null || $templates.length === 0) {
                        // new template
                        templateName = '';
                        templateText = '';
                                                                                                {#if deleteTagId === tag.id}
                                                                                                        <div class="alert alert-danger align-items-center" role="alert">
                                                                                                                <div>
-                                                                                                                       <Fa icon={faTriangleExclamation} fw /> <b>Tag dauerhaft löschen?</b> Dies
-                                                                                                                       kann einen Moment dauern, da jeder Eintrag nach potenziellen Verlinkungen
+                                                                                                                       <Fa icon={faTriangleExclamation} fw /> <b>Tag dauerhaft löschen?</b>
+                                                                                                                       Dies kann einen Moment dauern, da jeder Eintrag nach potenziellen Verlinkungen
                                                                                                                        durchsucht werden muss. Änderungen werden zudem u. U. erst nach einem Neuladen
                                                                                                                        im Browser angezeigt.
                                                                                                                </div>
                                                                                {#if confirmDeleteTemplate}
                                                                                        <div transition:slide class="d-flex flex-row align-items-center mb-2">
                                                                                                <span
-                                                                                                       >Vorlage <b>{$templates[selectedTemplate].name}</b> wirklich löschen?</span
+                                                                                                       >Vorlage <b>{$templates[selectedTemplate]?.name}</b> wirklich löschen?</span
                                                                                                >
                                                                                                <button
                                                                                                        type="button"
index 1f0b8d9b4dd7aa6814e959262ff79809ea14d90a..74dd068a14cbc19bcb34c497ac4f20b8c3a74085 100644 (file)
@@ -5,6 +5,7 @@
        import axios from 'axios';
        import { goto } from '$app/navigation';
        import { API_URL } from '$lib/APIurl.js';
+       import { currentUser } from '$lib/helpers';
 
        let show_login_failed = $state(false);
        let show_login_warning_empty_fields = $state(false);
                }
 
                is_logging_in = true;
-               console.log(API_URL);
 
                axios
                        .post(API_URL + '/users/login', { username, password })
                        .then((response) => {
+                               $currentUser = response.data.username;
+
                                if (response.data.migration_started) {
                                        is_migrating = true;
 
                                                        {#if is_migrating || migration_phase == 'completed'}
                                                                <div class="alert alert-info" role="alert">
                                                                        Daten-Migration wurde gestartet. Dies kann einige Momente dauern.<br />
-                                                                       <div class="text-bg-danger p-2 my-2 rounded">
-                                                                               Währenddessen die Seite nicht neu laden und nicht neu einloggen!
-                                                                       </div>
+                                                                       {#if migration_phase !== 'completed'}
+                                                                               <div class="text-bg-danger p-2 my-2 rounded">
+                                                                                       Währenddessen die Seite nicht neu laden und nicht neu einloggen!
+                                                                               </div>
+                                                                       {/if}
+
                                                                        Fortschritt:
                                                                        <div class="progress-item {active_phase >= 0 ? 'active' : ''}">
                                                                                <div class="d-flex">
                                                                                                        ✅
                                                                                                {/if}
                                                                                        </div>
-                                                                                       Logs migrieren
+                                                                                       Einträge migrieren
                                                                                </div>
 
                                                                                {#if active_phase === 2}
                                                                                        <div class="text-bg-success p-2 my-2 rounded">
                                                                                                Migration wurde ohne erkannte Fehler abgeschlossen! Bitte Login erneut
                                                                                                starten. <br />
-                                                                                               Prüfen Sie anschließend, ob alle Daten korrekt migriert wurden.
+                                                                                               Prüfe anschließend, ob alle Daten korrekt migriert wurden.
                                                                                        </div>
                                                                                {:else}
                                                                                        <div class="text-bg-warning p-2 my-2 rounded">
                                                                                                Migration wurde mit {migration_error_count} erkannten Fehlern abgeschlossen!
-                                                                                               Prüfen Sie die Server-Logs für Details!<br />
+                                                                                               Prüfe die Server-Logs für Details!<br />
                                                                                                Falls der Login nicht funktioniert, oder die Daten fehlerhaft sind, so müssen
                                                                                                die migrierten Daten händisch entfernt werden.
                                                                                        </div>
git clone https://git.99rst.org/PROJECT