return
}
- log.Printf("%v", r.Body)
-
// Default duration 5 minutes; optionally allow custom seconds (max 15 min)
duration := 5 * 60 // seconds
if req.Seconds > 0 && req.Seconds <= 15*60 {
--- /dev/null
+package handlers
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "strconv"
+ "strings"
+
+ "github.com/phitux/dailytxt/backend/utils"
+)
+
+// UploadFile handles uploading a file
+func UploadFile(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 form
+ if err := r.ParseMultipartForm(10 << 20); err != nil { // 10 MB max
+ http.Error(w, fmt.Sprintf("Error parsing form: %v", err), http.StatusBadRequest)
+ return
+ }
+
+ // Get form values
+ dayStr := r.FormValue("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.FormValue("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.FormValue("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
+ }
+
+ uuid := r.FormValue("uuid")
+ if uuid == "" {
+ http.Error(w, "Missing uuid parameter", http.StatusBadRequest)
+ return
+ }
+
+ // Get file
+ file, header, err := r.FormFile("file")
+ if err != nil {
+ http.Error(w, fmt.Sprintf("Error getting file: %v", err), http.StatusBadRequest)
+ return
+ }
+ defer file.Close()
+
+ // Get encryption key first (before reading large file)
+ encKey, err := utils.GetEncryptionKey(userID, derivedKey)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("Error getting encryption key: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ // Read file into a buffer (more memory efficient)
+ fileBytes, err := io.ReadAll(file)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("Error reading file: %v", err), http.StatusInternalServerError)
+ return
+ }
+ // Ensure fileBytes is cleared when function exits
+ defer func() { fileBytes = nil }()
+
+ // Encrypt file
+ encryptedFile, err := utils.EncryptFile(fileBytes, encKey)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("Error encrypting file: %v", err), http.StatusInternalServerError)
+ return
+ }
+ // Ensure encryptedFile is cleared when function exits
+ defer func() { encryptedFile = nil }()
+
+ // Clear original file data from memory immediately after encryption
+ fileBytes = nil
+
+ // Write file
+ if err := utils.WriteFile(encryptedFile, userID, uuid); err != nil {
+ http.Error(w, fmt.Sprintf("Error writing file: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ // Clear encrypted data from memory immediately after writing
+ encryptedFile = nil
+
+ // 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
+ }
+
+ // Encrypt filename
+ encFilename, err := utils.EncryptText(header.Filename, encKey)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("Error encrypting filename: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ // Create new file entry
+ newFile := map[string]any{
+ "enc_filename": encFilename,
+ "uuid_filename": uuid,
+ "size": header.Size,
+ }
+
+ // Add file to day
+ days, ok := content["days"].([]any)
+ if !ok {
+ days = []any{}
+ }
+
+ dayFound := false
+ for i, dayInterface := range days {
+ dayObj, ok := dayInterface.(map[string]any)
+ if !ok {
+ continue
+ }
+
+ dayNum, ok := dayObj["day"].(float64)
+ if !ok || int(dayNum) != day {
+ continue
+ }
+
+ // Add file to existing day
+ dayFound = true
+ files, ok := dayObj["files"].([]any)
+ if !ok {
+ files = []any{}
+ }
+ files = append(files, newFile)
+ dayObj["files"] = files
+ days[i] = dayObj
+ break
+ }
+
+ if !dayFound {
+ // Create new day with file
+ days = append(days, map[string]any{
+ "day": day,
+ "files": []any{newFile},
+ })
+ }
+
+ // Update days array
+ content["days"] = days
+
+ // Write month data
+ if err := utils.WriteMonth(userID, year, month, content); err != nil {
+ // Cleanup on error
+ utils.RemoveFile(userID, uuid)
+ http.Error(w, fmt.Sprintf("Error writing month data: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ // Return success
+ utils.JSONResponse(w, http.StatusOK, map[string]bool{
+ "success": true,
+ })
+}
+
+// DownloadFile handles downloading a file
+func DownloadFile(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 uuid parameter
+ uuid := r.URL.Query().Get("uuid")
+ if uuid == "" {
+ http.Error(w, "Missing uuid 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
+ }
+
+ // Read file
+ encryptedFile, err := utils.ReadFile(userID, uuid)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("Error reading file: %v", err), http.StatusInternalServerError)
+ return
+ }
+ // Ensure encryptedFile is cleared when function exits
+ defer func() { encryptedFile = nil }()
+
+ // Decrypt file
+ decryptedFile, err := utils.DecryptFile(encryptedFile, encKey)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("Error decrypting file: %v", err), http.StatusInternalServerError)
+ return
+ }
+ // Ensure decryptedFile is cleared when function exits
+ defer func() { decryptedFile = nil }()
+
+ // Clear encrypted data from memory immediately after decryption
+ encryptedFile = nil
+
+ // Set response headers for streaming
+ w.Header().Set("Content-Type", "application/octet-stream")
+ w.Header().Set("Content-Disposition", "attachment")
+
+ // Write file to response
+ if _, err := w.Write(decryptedFile); err != nil {
+ http.Error(w, fmt.Sprintf("Error writing response: %v", err), http.StatusInternalServerError)
+ return
+ }
+}
+
+// DeleteFile handles deleting a file
+func DeleteFile(w http.ResponseWriter, r *http.Request) {
+ // Get user ID from context
+ userID, ok := r.Context().Value(utils.UserIDKey).(int)
+ if !ok {
+ http.Error(w, "Unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ // Get parameters
+ uuid := r.URL.Query().Get("uuid")
+ if uuid == "" {
+ http.Error(w, "Missing uuid parameter", http.StatusBadRequest)
+ return
+ }
+
+ 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 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 {
+ http.Error(w, "Day not found - json error", http.StatusInternalServerError)
+ return
+ }
+
+ // Find day and file
+ fileFound := false
+ for i, 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 files
+ files, ok := dayObj["files"].([]any)
+ if !ok {
+ continue
+ }
+
+ // Find file
+ for j, fileInterface := range files {
+ file, ok := fileInterface.(map[string]any)
+ if !ok {
+ continue
+ }
+
+ uuidFilename, ok := file["uuid_filename"].(string)
+ if !ok || uuidFilename != uuid {
+ continue
+ }
+
+ // Remove file from array
+ if err := utils.RemoveFile(userID, uuid); err != nil {
+ http.Error(w, fmt.Sprintf("Failed to delete file: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ files = append(files[:j], files[j+1:]...)
+ dayObj["files"] = files
+ days[i] = dayObj
+ fileFound = true
+ break
+ }
+
+ if fileFound {
+ break
+ }
+ }
+
+ if !fileFound {
+ http.Error(w, "Failed to delete file - not found in log", http.StatusInternalServerError)
+ return
+ }
+
+ // Update days array
+ content["days"] = days
+
+ // Write month data
+ if err := utils.WriteMonth(userID, year, month, content); err != nil {
+ http.Error(w, fmt.Sprintf("Failed to write changes of deleted file: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ // Return success
+ utils.JSONResponse(w, http.StatusOK, map[string]bool{
+ "success": true,
+ })
+}
+
+// RenameFileRequest represents the rename file request body
+type RenameFileRequest struct {
+ UUID string `json:"uuid"`
+ NewFilename string `json:"new_filename"`
+ Day int `json:"day"`
+ Month int `json:"month"`
+ Year int `json:"year"`
+}
+
+// RenameFile handles renaming a file
+func RenameFile(w http.ResponseWriter, r *http.Request) {
+ // Get user ID 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 RenameFileRequest
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ http.Error(w, "Invalid request body", http.StatusBadRequest)
+ return
+ }
+
+ // Validate input
+ req.NewFilename = strings.TrimSpace(req.NewFilename)
+ if req.NewFilename == "" {
+ utils.JSONResponse(w, http.StatusBadRequest, map[string]any{
+ "success": false,
+ "message": "New filename cannot be empty",
+ })
+ return
+ }
+
+ if req.UUID == "" {
+ utils.JSONResponse(w, http.StatusBadRequest, map[string]any{
+ "success": false,
+ "message": "File UUID is required",
+ })
+ return
+ }
+
+ // Get month data
+ content, err := utils.GetMonth(userID, req.Year, req.Month)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("Error retrieving month data: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ encKey, err := utils.GetEncryptionKey(userID, derivedKey)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("Error getting encryption key: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ enc_filename, err := utils.EncryptText(req.NewFilename, encKey)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("Error encrypting text: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ // Find and update the file
+ days, ok := content["days"].([]any)
+ if !ok {
+ utils.JSONResponse(w, http.StatusNotFound, map[string]any{
+ "success": false,
+ "message": "No days found",
+ })
+ return
+ }
+
+ found := false
+ for _, d := range days {
+ day, ok := d.(map[string]any)
+ if !ok {
+ continue
+ }
+
+ dayNum, ok := day["day"].(float64)
+ if !ok || int(dayNum) != req.Day {
+ continue
+ }
+
+ files, ok := day["files"].([]any)
+ if !ok {
+ continue
+ }
+
+ // Find and rename the specific file
+ for _, f := range files {
+ file, ok := f.(map[string]any)
+ if !ok {
+ continue
+ }
+
+ if uuid, ok := file["uuid_filename"].(string); ok && uuid == req.UUID {
+ file["enc_filename"] = enc_filename
+ found = true
+ break
+ }
+ }
+
+ if found {
+ break
+ }
+ }
+
+ if !found {
+ utils.JSONResponse(w, http.StatusNotFound, map[string]any{
+ "success": false,
+ "message": "File not found",
+ })
+ return
+ }
+
+ // Save the updated month data
+ if err := utils.WriteMonth(userID, req.Year, req.Month, content); err != nil {
+ http.Error(w, fmt.Sprintf("Error writing month data: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ utils.Logger.Printf("File renamed successfully for user %d: %s -> %s", userID, req.UUID, req.NewFilename)
+ utils.JSONResponse(w, http.StatusOK, map[string]bool{"success": true})
+}
+
+// ReorderFilesRequest represents the reorder files request body
+type ReorderFilesRequest struct {
+ Day int `json:"day"`
+ Month int `json:"month"`
+ Year int `json:"year"`
+ FileOrder map[string]int `json:"file_order"` // UUID -> order index
+}
+
+// ReorderFiles handles reordering files within a day
+func ReorderFiles(w http.ResponseWriter, r *http.Request) {
+ // Get user ID from context
+ userID, ok := r.Context().Value(utils.UserIDKey).(int)
+ if !ok {
+ http.Error(w, "Unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ // Parse request body
+ var req ReorderFilesRequest
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ http.Error(w, "Invalid request body", http.StatusBadRequest)
+ return
+ }
+
+ if len(req.FileOrder) == 0 {
+ utils.JSONResponse(w, http.StatusBadRequest, map[string]any{
+ "success": false,
+ "message": "File order mapping is required",
+ })
+ return
+ }
+
+ // Get month data
+ content, err := utils.GetMonth(userID, req.Year, req.Month)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("Error retrieving month data: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ // Find and reorder files for the specific day
+ days, ok := content["days"].([]any)
+ if !ok {
+ utils.JSONResponse(w, http.StatusNotFound, map[string]any{
+ "success": false,
+ "message": "No days found",
+ })
+ return
+ }
+
+ found := false
+ for _, d := range days {
+ day, ok := d.(map[string]any)
+ if !ok {
+ continue
+ }
+
+ dayNum, ok := day["day"].(float64)
+ if !ok || int(dayNum) != req.Day {
+ continue
+ }
+
+ files, ok := day["files"].([]any)
+ if !ok {
+ continue
+ }
+
+ // Create a slice to hold files with their new order
+ type fileWithOrder struct {
+ file map[string]any
+ order int
+ }
+
+ var orderedFiles []fileWithOrder
+
+ // Assign order to each file
+ for _, f := range files {
+ file, ok := f.(map[string]any)
+ if !ok {
+ continue
+ }
+
+ uuid, ok := file["uuid_filename"].(string)
+ if !ok {
+ continue
+ }
+
+ if order, exists := req.FileOrder[uuid]; exists {
+ orderedFiles = append(orderedFiles, fileWithOrder{file: file, order: order})
+ } else {
+ // Files not in the reorder map get appended at the end
+ orderedFiles = append(orderedFiles, fileWithOrder{file: file, order: len(req.FileOrder)})
+ }
+ }
+
+ // Sort files by their order
+ for i := 0; i < len(orderedFiles)-1; i++ {
+ for j := i + 1; j < len(orderedFiles); j++ {
+ if orderedFiles[i].order > orderedFiles[j].order {
+ orderedFiles[i], orderedFiles[j] = orderedFiles[j], orderedFiles[i]
+ }
+ }
+ }
+
+ // Update the files array with the new order
+ newFiles := make([]any, len(orderedFiles))
+ for i, fileWithOrder := range orderedFiles {
+ newFiles[i] = fileWithOrder.file
+ }
+ day["files"] = newFiles
+
+ found = true
+ break
+ }
+
+ if !found {
+ utils.JSONResponse(w, http.StatusNotFound, map[string]any{
+ "success": false,
+ "message": "Day not found",
+ })
+ return
+ }
+
+ // Save the updated month data
+ if err := utils.WriteMonth(userID, req.Year, req.Month, content); err != nil {
+ http.Error(w, fmt.Sprintf("Error writing month data: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ utils.JSONResponse(w, http.StatusOK, map[string]bool{"success": true})
+}
})
}
-// TemplatesRequest represents a templates request
-type TemplatesRequest struct {
- Templates []struct {
- Name string `json:"name"`
- Text string `json:"text"`
- } `json:"templates"`
-}
-
-// GetTemplates handles retrieving a user's templates
-func GetTemplates(w http.ResponseWriter, r *http.Request) {
- // Get user ID and derived key from context
+// BookmarkDay handles bookmarking a day
+func BookmarkDay(w http.ResponseWriter, r *http.Request) {
+ // Get user ID 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)
+
+ // Get parameters
+ dayStr := r.URL.Query().Get("day")
+ if dayStr == "" {
+ http.Error(w, "Missing day parameter", http.StatusBadRequest)
return
}
-
- // Get templates
- content, err := utils.GetTemplates(userID)
+ day, err := strconv.Atoi(dayStr)
if err != nil {
- http.Error(w, fmt.Sprintf("Error retrieving templates: %v", err), http.StatusInternalServerError)
+ http.Error(w, "Invalid day parameter", http.StatusBadRequest)
return
}
- // If no templates, return empty array
- if templates, ok := content["templates"].([]any); !ok || len(templates) == 0 {
- utils.JSONResponse(w, http.StatusOK, []any{})
+ monthStr := r.URL.Query().Get("month")
+ if monthStr == "" {
+ http.Error(w, "Missing month parameter", http.StatusBadRequest)
return
}
-
- // Get encryption key
- encKey, err := utils.GetEncryptionKey(userID, derivedKey)
+ month, err := strconv.Atoi(monthStr)
if err != nil {
- http.Error(w, fmt.Sprintf("Error getting encryption key: %v", err), http.StatusInternalServerError)
+ http.Error(w, "Invalid month parameter", http.StatusBadRequest)
return
}
- // Decrypt template data
- templates := content["templates"].([]any)
- result := []any{}
-
- for _, templateInterface := range templates {
- template, ok := templateInterface.(map[string]any)
- if !ok {
- continue
- }
-
- // Decrypt name and text
- if encName, ok := template["name"].(string); ok {
- decryptedName, err := utils.DecryptText(encName, encKey)
- if err != nil {
- http.Error(w, fmt.Sprintf("Error decrypting template name: %v", err), http.StatusInternalServerError)
- return
- }
- template["name"] = decryptedName
- }
-
- if encText, ok := template["text"].(string); ok {
- decryptedText, err := utils.DecryptText(encText, encKey)
- if err != nil {
- http.Error(w, fmt.Sprintf("Error decrypting template text: %v", err), http.StatusInternalServerError)
- return
- }
- template["text"] = decryptedText
- }
-
- result = append(result, template)
- }
-
- // Return templates
- utils.JSONResponse(w, http.StatusOK, result)
-}
-
-// SaveTemplates handles saving templates
-func SaveTemplates(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)
+ yearStr := r.URL.Query().Get("year")
+ if yearStr == "" {
+ http.Error(w, "Missing year parameter", http.StatusBadRequest)
return
}
-
- // Parse request body
- var req TemplatesRequest
- if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
- http.Error(w, "Invalid request body", http.StatusBadRequest)
+ 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)
+ // Get month data
+ content, err := utils.GetMonth(userID, year, month)
if err != nil {
- http.Error(w, fmt.Sprintf("Error getting encryption key: %v", err), http.StatusInternalServerError)
+ http.Error(w, fmt.Sprintf("Error retrieving month data: %v", err), http.StatusInternalServerError)
return
}
- // Create new templates content
- content := map[string]any{
- "templates": []any{},
+ // Get or create days array
+ days, ok := content["days"].([]any)
+ if !ok {
+ days = []any{}
}
- // Encrypt template data
- templates := []any{}
- for _, template := range req.Templates {
- encName, err := utils.EncryptText(template.Name, encKey)
- if err != nil {
- http.Error(w, fmt.Sprintf("Error encrypting template name: %v", err), http.StatusInternalServerError)
- return
+ // Find day
+ dayFound := false
+ bookmarked := true
+ for i, dayInterface := range days {
+ dayObj, ok := dayInterface.(map[string]any)
+ if !ok {
+ continue
}
- encText, err := utils.EncryptText(template.Text, encKey)
- if err != nil {
- http.Error(w, fmt.Sprintf("Error encrypting template text: %v", err), http.StatusInternalServerError)
- return
+ dayNum, ok := dayObj["day"].(float64)
+ if !ok || int(dayNum) != day {
+ continue
+ }
+
+ // Day found, toggle bookmark
+ dayFound = true
+ if bookmark, ok := dayObj["isBookmarked"].(bool); ok && bookmark {
+ dayObj["isBookmarked"] = false
+ bookmarked = false
+ } else {
+ dayObj["isBookmarked"] = true
}
+ days[i] = dayObj
+ break
+ }
- templates = append(templates, map[string]any{
- "name": encName,
- "text": encText,
+ if !dayFound {
+ // Create new day with bookmark
+ days = append(days, map[string]any{
+ "day": day,
+ "isBookmarked": true,
})
}
- content["templates"] = templates
+ // Update days array
+ content["days"] = days
- // Write templates
- if err := utils.WriteTemplates(userID, content); err != nil {
- http.Error(w, fmt.Sprintf("Error writing templates: %v", err), http.StatusInternalServerError)
+ // Write month data
+ if err := utils.WriteMonth(userID, year, month, content); err != nil {
+ http.Error(w, fmt.Sprintf("Failed to bookmark day - error writing log: %v", err), http.StatusInternalServerError)
return
}
// Return success
- utils.JSONResponse(w, http.StatusOK, map[string]bool{
- "success": true,
+ utils.JSONResponse(w, http.StatusOK, map[string]any{
+ "success": true,
+ "bookmarked": bookmarked,
})
}
utils.JSONResponse(w, http.StatusOK, map[string]bool{"success": true})
}
-
-// RenameFileRequest represents the rename file request body
-type RenameFileRequest struct {
- UUID string `json:"uuid"`
- NewFilename string `json:"new_filename"`
- Day int `json:"day"`
- Month int `json:"month"`
- Year int `json:"year"`
-}
-
-// RenameFile handles renaming a file
-func RenameFile(w http.ResponseWriter, r *http.Request) {
- // Get user ID 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 RenameFileRequest
- if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
- http.Error(w, "Invalid request body", http.StatusBadRequest)
- return
- }
-
- // Validate input
- req.NewFilename = strings.TrimSpace(req.NewFilename)
- if req.NewFilename == "" {
- utils.JSONResponse(w, http.StatusBadRequest, map[string]any{
- "success": false,
- "message": "New filename cannot be empty",
- })
- return
- }
-
- if req.UUID == "" {
- utils.JSONResponse(w, http.StatusBadRequest, map[string]any{
- "success": false,
- "message": "File UUID is required",
- })
- return
- }
-
- // Get month data
- content, err := utils.GetMonth(userID, req.Year, req.Month)
- if err != nil {
- http.Error(w, fmt.Sprintf("Error retrieving month data: %v", err), http.StatusInternalServerError)
- return
- }
-
- encKey, err := utils.GetEncryptionKey(userID, derivedKey)
- if err != nil {
- http.Error(w, fmt.Sprintf("Error getting encryption key: %v", err), http.StatusInternalServerError)
- return
- }
-
- enc_filename, err := utils.EncryptText(req.NewFilename, encKey)
- if err != nil {
- http.Error(w, fmt.Sprintf("Error encrypting text: %v", err), http.StatusInternalServerError)
- return
- }
-
- // Find and update the file
- days, ok := content["days"].([]any)
- if !ok {
- utils.JSONResponse(w, http.StatusNotFound, map[string]any{
- "success": false,
- "message": "No days found",
- })
- return
- }
-
- found := false
- for _, d := range days {
- day, ok := d.(map[string]any)
- if !ok {
- continue
- }
-
- dayNum, ok := day["day"].(float64)
- if !ok || int(dayNum) != req.Day {
- continue
- }
-
- files, ok := day["files"].([]any)
- if !ok {
- continue
- }
-
- // Find and rename the specific file
- for _, f := range files {
- file, ok := f.(map[string]any)
- if !ok {
- continue
- }
-
- if uuid, ok := file["uuid_filename"].(string); ok && uuid == req.UUID {
- file["enc_filename"] = enc_filename
- found = true
- break
- }
- }
-
- if found {
- break
- }
- }
-
- if !found {
- utils.JSONResponse(w, http.StatusNotFound, map[string]any{
- "success": false,
- "message": "File not found",
- })
- return
- }
-
- // Save the updated month data
- if err := utils.WriteMonth(userID, req.Year, req.Month, content); err != nil {
- http.Error(w, fmt.Sprintf("Error writing month data: %v", err), http.StatusInternalServerError)
- return
- }
-
- utils.Logger.Printf("File renamed successfully for user %d: %s -> %s", userID, req.UUID, req.NewFilename)
- utils.JSONResponse(w, http.StatusOK, map[string]bool{"success": true})
-}
-
-// ReorderFilesRequest represents the reorder files request body
-type ReorderFilesRequest struct {
- Day int `json:"day"`
- Month int `json:"month"`
- Year int `json:"year"`
- FileOrder map[string]int `json:"file_order"` // UUID -> order index
-}
-
-// ReorderFiles handles reordering files within a day
-func ReorderFiles(w http.ResponseWriter, r *http.Request) {
- // Get user ID from context
- userID, ok := r.Context().Value(utils.UserIDKey).(int)
- if !ok {
- http.Error(w, "Unauthorized", http.StatusUnauthorized)
- return
- }
-
- // Parse request body
- var req ReorderFilesRequest
- if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
- http.Error(w, "Invalid request body", http.StatusBadRequest)
- return
- }
-
- if len(req.FileOrder) == 0 {
- utils.JSONResponse(w, http.StatusBadRequest, map[string]any{
- "success": false,
- "message": "File order mapping is required",
- })
- return
- }
-
- // Get month data
- content, err := utils.GetMonth(userID, req.Year, req.Month)
- if err != nil {
- http.Error(w, fmt.Sprintf("Error retrieving month data: %v", err), http.StatusInternalServerError)
- return
- }
-
- // Find and reorder files for the specific day
- days, ok := content["days"].([]any)
- if !ok {
- utils.JSONResponse(w, http.StatusNotFound, map[string]any{
- "success": false,
- "message": "No days found",
- })
- return
- }
-
- found := false
- for _, d := range days {
- day, ok := d.(map[string]any)
- if !ok {
- continue
- }
-
- dayNum, ok := day["day"].(float64)
- if !ok || int(dayNum) != req.Day {
- continue
- }
-
- files, ok := day["files"].([]any)
- if !ok {
- continue
- }
-
- // Create a slice to hold files with their new order
- type fileWithOrder struct {
- file map[string]any
- order int
- }
-
- var orderedFiles []fileWithOrder
-
- // Assign order to each file
- for _, f := range files {
- file, ok := f.(map[string]any)
- if !ok {
- continue
- }
-
- uuid, ok := file["uuid_filename"].(string)
- if !ok {
- continue
- }
-
- if order, exists := req.FileOrder[uuid]; exists {
- orderedFiles = append(orderedFiles, fileWithOrder{file: file, order: order})
- } else {
- // Files not in the reorder map get appended at the end
- orderedFiles = append(orderedFiles, fileWithOrder{file: file, order: len(req.FileOrder)})
- }
- }
-
- // Sort files by their order
- for i := 0; i < len(orderedFiles)-1; i++ {
- for j := i + 1; j < len(orderedFiles); j++ {
- if orderedFiles[i].order > orderedFiles[j].order {
- orderedFiles[i], orderedFiles[j] = orderedFiles[j], orderedFiles[i]
- }
- }
- }
-
- // Update the files array with the new order
- newFiles := make([]any, len(orderedFiles))
- for i, fileWithOrder := range orderedFiles {
- newFiles[i] = fileWithOrder.file
- }
- day["files"] = newFiles
-
- found = true
- break
- }
-
- if !found {
- utils.JSONResponse(w, http.StatusNotFound, map[string]any{
- "success": false,
- "message": "Day not found",
- })
- return
- }
-
- // Save the updated month data
- if err := utils.WriteMonth(userID, req.Year, req.Month, content); err != nil {
- http.Error(w, fmt.Sprintf("Error writing month data: %v", err), http.StatusInternalServerError)
- return
- }
-
- utils.JSONResponse(w, http.StatusOK, map[string]bool{"success": true})
-}
"github.com/phitux/dailytxt/backend/utils"
)
-// EditTagRequest represents the edit tag request
-type EditTagRequest struct {
- ID int `json:"id"`
- Icon string `json:"icon"`
- Name string `json:"name"`
- Color string `json:"color"`
-}
-
-// EditTag handles editing a tag
-func EditTag(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 EditTagRequest
- 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
- }
-
- // Check if tags exist
- tags, ok := content["tags"].([]any)
- if !ok {
- http.Error(w, "Tag not found - json error", 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
- }
-
- // Find and update tag
- found := false
- for i, tagInterface := range tags {
- tag, ok := tagInterface.(map[string]any)
- if !ok {
- continue
- }
-
- if id, ok := tag["id"].(float64); ok && int(id) == req.ID {
- // 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
- }
-
- // Update tag
- tag["icon"] = encIcon
- tag["name"] = encName
- tag["color"] = encColor
- tags[i] = tag
- found = true
- break
- }
- }
-
- if !found {
- http.Error(w, "Tag not found - not in tags", http.StatusInternalServerError)
- return
- }
-
- // Write tags
- if err := utils.WriteTags(userID, content); err != nil {
- http.Error(w, fmt.Sprintf("Failed to write tag - error writing tags: %v", err), http.StatusInternalServerError)
- return
- }
-
- // Return success
- utils.JSONResponse(w, http.StatusOK, map[string]bool{
- "success": true,
- })
-}
-
-// DeleteTag handles deleting a tag
-func DeleteTag(w http.ResponseWriter, r *http.Request) {
- // Get user ID from context
- userID, ok := r.Context().Value(utils.UserIDKey).(int)
- if !ok {
- http.Error(w, "Unauthorized", http.StatusUnauthorized)
- return
- }
-
- // Get tag ID
- idStr := r.URL.Query().Get("id")
- if idStr == "" {
- http.Error(w, "Missing id parameter", http.StatusBadRequest)
- return
- }
- id, err := strconv.Atoi(idStr)
- if err != nil {
- http.Error(w, "Invalid id parameter", http.StatusBadRequest)
- return
- }
-
- // Get all years and months
- years, err := utils.GetYears(userID)
- if err != nil {
- http.Error(w, fmt.Sprintf("Error retrieving years: %v", err), http.StatusInternalServerError)
- return
- }
-
- // Remove tag from all logs
- for _, year := range years {
- yearInt, _ := strconv.Atoi(year)
- months, err := utils.GetMonths(userID, year)
- if err != nil {
- continue
- }
-
- for _, month := range months {
- monthInt, _ := strconv.Atoi(month)
- content, err := utils.GetMonth(userID, yearInt, monthInt)
- if err != nil {
- continue
- }
-
- days, ok := content["days"].([]any)
- if !ok {
- continue
- }
-
- // Check each day for the tag
- modified := false
- for i, dayInterface := range days {
- day, ok := dayInterface.(map[string]any)
- if !ok {
- continue
- }
-
- tags, ok := day["tags"].([]any)
- if !ok {
- continue
- }
-
- // Find and remove the tag
- for j, tagID := range tags {
- if tagIDFloat, ok := tagID.(float64); ok && int(tagIDFloat) == id {
- // Remove tag
- tags = append(tags[:j], tags[j+1:]...)
- day["tags"] = tags
- days[i] = day
- modified = true
- break
- }
- }
- }
-
- // Write updated month if modified
- if modified {
- content["days"] = days
- if err := utils.WriteMonth(userID, yearInt, monthInt, content); err != nil {
- http.Error(w, fmt.Sprintf("Failed to delete tag - error writing log: %v", err), http.StatusInternalServerError)
- return
- }
- }
- }
- }
-
- // Get tags
- content, err := utils.GetTags(userID)
- if err != nil {
- http.Error(w, fmt.Sprintf("Error retrieving tags: %v", err), http.StatusInternalServerError)
- return
- }
-
- // Check if tags exist
- tags, ok := content["tags"].([]any)
- if !ok {
- http.Error(w, "Tag not found - json error", http.StatusInternalServerError)
- return
- }
-
- // Find and remove tag
- found := false
- for i, tagInterface := range tags {
- tag, ok := tagInterface.(map[string]any)
- if !ok {
- continue
- }
-
- if tagID, ok := tag["id"].(float64); ok && int(tagID) == id {
- // Remove tag
- tags = append(tags[:i], tags[i+1:]...)
- content["tags"] = tags
- found = true
- break
- }
- }
-
- if !found {
- http.Error(w, "Tag not found - not in tags", http.StatusInternalServerError)
- return
- }
-
- // Write tags
- if err := utils.WriteTags(userID, content); err != nil {
- http.Error(w, fmt.Sprintf("Failed to delete tag - error writing tags: %v", err), http.StatusInternalServerError)
- return
- }
-
- // Return success
- utils.JSONResponse(w, http.StatusOK, map[string]bool{
- "success": true,
- })
-}
-
-// TagLogRequest represents the tag log request
-type TagLogRequest struct {
- Day int `json:"day"`
- Month int `json:"month"`
- Year int `json:"year"`
- TagID int `json:"tag_id"`
-}
-
-// AddTagToLog handles adding a tag to a log
-func AddTagToLog(w http.ResponseWriter, r *http.Request) {
- // Get user ID from context
- userID, ok := r.Context().Value(utils.UserIDKey).(int)
- if !ok {
- http.Error(w, "Unauthorized", http.StatusUnauthorized)
- return
- }
-
- // Parse request body
- var req TagLogRequest
- if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
- http.Error(w, "Invalid request body", http.StatusBadRequest)
- return
- }
-
- // Get month data
- content, err := utils.GetMonth(userID, req.Year, req.Month)
- if err != nil {
- http.Error(w, fmt.Sprintf("Error retrieving month data: %v", err), http.StatusInternalServerError)
- return
- }
-
- // Get or create days array
- days, ok := content["days"].([]any)
- if !ok {
- days = []any{}
- }
-
- // Find day
- dayFound := false
- for i, dayInterface := range days {
- day, ok := dayInterface.(map[string]any)
- if !ok {
- continue
- }
-
- dayNum, ok := day["day"].(float64)
- if !ok || int(dayNum) != req.Day {
- continue
- }
-
- // Day found, add tag
- dayFound = true
- tags, ok := day["tags"].([]any)
- if !ok {
- tags = []any{}
- }
-
- // Check if tag already exists
- tagExists := false
- for _, tagID := range tags {
- if tagIDFloat, ok := tagID.(float64); ok && int(tagIDFloat) == req.TagID {
- tagExists = true
- break
- }
- }
-
- if !tagExists {
- tags = append(tags, float64(req.TagID))
- day["tags"] = tags
- days[i] = day
- }
- break
- }
-
- if !dayFound {
- // Create new day with tag
- days = append(days, map[string]any{
- "day": req.Day,
- "tags": []any{float64(req.TagID)},
- })
- }
-
- // Update days array
- content["days"] = days
-
- // Write month data
- if err := utils.WriteMonth(userID, req.Year, req.Month, content); err != nil {
- http.Error(w, fmt.Sprintf("Failed to write tag - error writing log: %v", err), http.StatusInternalServerError)
- return
- }
-
- // Return success
- utils.JSONResponse(w, http.StatusOK, map[string]bool{
- "success": true,
- })
-}
-
-// RemoveTagFromLog handles removing a tag from a log
-func RemoveTagFromLog(w http.ResponseWriter, r *http.Request) {
- // Get user ID from context
- userID, ok := r.Context().Value(utils.UserIDKey).(int)
- if !ok {
- http.Error(w, "Unauthorized", http.StatusUnauthorized)
- return
- }
-
- // Parse request body
- var req TagLogRequest
- if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
- http.Error(w, "Invalid request body", http.StatusBadRequest)
- return
- }
-
- // Get month data
- content, err := utils.GetMonth(userID, req.Year, req.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 {
- http.Error(w, "Day not found - json error", http.StatusInternalServerError)
- return
- }
-
- // Find day
- found := false
- for i, dayInterface := range days {
- day, ok := dayInterface.(map[string]any)
- if !ok {
- continue
- }
-
- dayNum, ok := day["day"].(float64)
- if !ok || int(dayNum) != req.Day {
- continue
- }
-
- // Day found, check for tags
- tags, ok := day["tags"].([]any)
- if !ok {
- http.Error(w, "Failed to remove tag - not found in log", http.StatusInternalServerError)
- return
- }
-
- // Find and remove tag
- for j, tagID := range tags {
- if tagIDFloat, ok := tagID.(float64); ok && int(tagIDFloat) == req.TagID {
- // Remove tag
- tags = append(tags[:j], tags[j+1:]...)
- day["tags"] = tags
- days[i] = day
- found = true
- break
- }
- }
-
- if !found {
- http.Error(w, "Failed to remove tag - not found in log", http.StatusInternalServerError)
- return
- }
- break
- }
-
- if !found {
- http.Error(w, "Failed to remove tag - not found in log", http.StatusInternalServerError)
- return
- }
-
- // Update days array
- content["days"] = days
-
- // Write month data
- if err := utils.WriteMonth(userID, req.Year, req.Month, content); err != nil {
- http.Error(w, fmt.Sprintf("Failed to remove tag - error writing log: %v", err), http.StatusInternalServerError)
- return
- }
-
- // Return success
- utils.JSONResponse(w, http.StatusOK, map[string]bool{
- "success": true,
- })
-}
-
-// UploadFile handles uploading a file
-func UploadFile(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 form
- if err := r.ParseMultipartForm(10 << 20); err != nil { // 10 MB max
- http.Error(w, fmt.Sprintf("Error parsing form: %v", err), http.StatusBadRequest)
- return
- }
-
- // Get form values
- dayStr := r.FormValue("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.FormValue("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.FormValue("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
- }
-
- uuid := r.FormValue("uuid")
- if uuid == "" {
- http.Error(w, "Missing uuid parameter", http.StatusBadRequest)
- return
- }
-
- // Get file
- file, header, err := r.FormFile("file")
- if err != nil {
- http.Error(w, fmt.Sprintf("Error getting file: %v", err), http.StatusBadRequest)
- return
- }
- defer file.Close()
-
- // Get encryption key first (before reading large file)
- encKey, err := utils.GetEncryptionKey(userID, derivedKey)
- if err != nil {
- http.Error(w, fmt.Sprintf("Error getting encryption key: %v", err), http.StatusInternalServerError)
- return
- }
-
- // Read file into a buffer (more memory efficient)
- fileBytes, err := io.ReadAll(file)
- if err != nil {
- http.Error(w, fmt.Sprintf("Error reading file: %v", err), http.StatusInternalServerError)
- return
- }
- // Ensure fileBytes is cleared when function exits
- defer func() { fileBytes = nil }()
-
- // Encrypt file
- encryptedFile, err := utils.EncryptFile(fileBytes, encKey)
- if err != nil {
- http.Error(w, fmt.Sprintf("Error encrypting file: %v", err), http.StatusInternalServerError)
- return
- }
- // Ensure encryptedFile is cleared when function exits
- defer func() { encryptedFile = nil }()
-
- // Clear original file data from memory immediately after encryption
- fileBytes = nil
-
- // Write file
- if err := utils.WriteFile(encryptedFile, userID, uuid); err != nil {
- http.Error(w, fmt.Sprintf("Error writing file: %v", err), http.StatusInternalServerError)
- return
- }
-
- // Clear encrypted data from memory immediately after writing
- encryptedFile = nil
-
- // 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
- }
-
- // Encrypt filename
- encFilename, err := utils.EncryptText(header.Filename, encKey)
- if err != nil {
- http.Error(w, fmt.Sprintf("Error encrypting filename: %v", err), http.StatusInternalServerError)
- return
- }
-
- // Create new file entry
- newFile := map[string]any{
- "enc_filename": encFilename,
- "uuid_filename": uuid,
- "size": header.Size,
- }
-
- // Add file to day
- days, ok := content["days"].([]any)
- if !ok {
- days = []any{}
- }
-
- dayFound := false
- for i, dayInterface := range days {
- dayObj, ok := dayInterface.(map[string]any)
- if !ok {
- continue
- }
-
- dayNum, ok := dayObj["day"].(float64)
- if !ok || int(dayNum) != day {
- continue
- }
-
- // Add file to existing day
- dayFound = true
- files, ok := dayObj["files"].([]any)
- if !ok {
- files = []any{}
- }
- files = append(files, newFile)
- dayObj["files"] = files
- days[i] = dayObj
- break
- }
-
- if !dayFound {
- // Create new day with file
- days = append(days, map[string]any{
- "day": day,
- "files": []any{newFile},
- })
- }
-
- // Update days array
- content["days"] = days
-
- // Write month data
- if err := utils.WriteMonth(userID, year, month, content); err != nil {
- // Cleanup on error
- utils.RemoveFile(userID, uuid)
- http.Error(w, fmt.Sprintf("Error writing month data: %v", err), http.StatusInternalServerError)
- return
- }
-
- // Return success
- utils.JSONResponse(w, http.StatusOK, map[string]bool{
- "success": true,
- })
-}
-
-// DownloadFile handles downloading a file
-func DownloadFile(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 uuid parameter
- uuid := r.URL.Query().Get("uuid")
- if uuid == "" {
- http.Error(w, "Missing uuid 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
- }
-
- // Read file
- encryptedFile, err := utils.ReadFile(userID, uuid)
- if err != nil {
- http.Error(w, fmt.Sprintf("Error reading file: %v", err), http.StatusInternalServerError)
- return
- }
- // Ensure encryptedFile is cleared when function exits
- defer func() { encryptedFile = nil }()
-
- // Decrypt file
- decryptedFile, err := utils.DecryptFile(encryptedFile, encKey)
- if err != nil {
- http.Error(w, fmt.Sprintf("Error decrypting file: %v", err), http.StatusInternalServerError)
- return
- }
- // Ensure decryptedFile is cleared when function exits
- defer func() { decryptedFile = nil }()
-
- // Clear encrypted data from memory immediately after decryption
- encryptedFile = nil
-
- // Set response headers for streaming
- w.Header().Set("Content-Type", "application/octet-stream")
- w.Header().Set("Content-Disposition", "attachment")
-
- // Write file to response
- if _, err := w.Write(decryptedFile); err != nil {
- http.Error(w, fmt.Sprintf("Error writing response: %v", err), http.StatusInternalServerError)
- return
- }
-}
-
-// DeleteFile handles deleting a file
-func DeleteFile(w http.ResponseWriter, r *http.Request) {
- // Get user ID from context
- userID, ok := r.Context().Value(utils.UserIDKey).(int)
- if !ok {
- http.Error(w, "Unauthorized", http.StatusUnauthorized)
- return
- }
-
- // Get parameters
- uuid := r.URL.Query().Get("uuid")
- if uuid == "" {
- http.Error(w, "Missing uuid parameter", http.StatusBadRequest)
- return
- }
-
- 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 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 {
- http.Error(w, "Day not found - json error", http.StatusInternalServerError)
- return
- }
-
- // Find day and file
- fileFound := false
- for i, 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 files
- files, ok := dayObj["files"].([]any)
- if !ok {
- continue
- }
-
- // Find file
- for j, fileInterface := range files {
- file, ok := fileInterface.(map[string]any)
- if !ok {
- continue
- }
-
- uuidFilename, ok := file["uuid_filename"].(string)
- if !ok || uuidFilename != uuid {
- continue
- }
-
- // Remove file from array
- if err := utils.RemoveFile(userID, uuid); err != nil {
- http.Error(w, fmt.Sprintf("Failed to delete file: %v", err), http.StatusInternalServerError)
- return
- }
-
- files = append(files[:j], files[j+1:]...)
- dayObj["files"] = files
- days[i] = dayObj
- fileFound = true
- break
- }
-
- if fileFound {
- break
- }
- }
-
- if !fileFound {
- http.Error(w, "Failed to delete file - not found in log", http.StatusInternalServerError)
- return
- }
-
- // Update days array
- content["days"] = days
-
- // Write month data
- if err := utils.WriteMonth(userID, year, month, content); err != nil {
- http.Error(w, fmt.Sprintf("Failed to write changes of deleted file: %v", err), http.StatusInternalServerError)
- return
- }
-
- // Return success
- utils.JSONResponse(w, http.StatusOK, map[string]bool{
- "success": true,
- })
-}
-
-// BookmarkDay handles bookmarking a day
-func BookmarkDay(w http.ResponseWriter, r *http.Request) {
- // Get user ID from context
- userID, ok := r.Context().Value(utils.UserIDKey).(int)
- 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 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 or create days array
- days, ok := content["days"].([]any)
- if !ok {
- days = []any{}
- }
-
- // Find day
- dayFound := false
- bookmarked := true
- for i, dayInterface := range days {
- dayObj, ok := dayInterface.(map[string]any)
- if !ok {
- continue
- }
-
- dayNum, ok := dayObj["day"].(float64)
- if !ok || int(dayNum) != day {
- continue
- }
-
- // Day found, toggle bookmark
- dayFound = true
- if bookmark, ok := dayObj["isBookmarked"].(bool); ok && bookmark {
- dayObj["isBookmarked"] = false
- bookmarked = false
- } else {
- dayObj["isBookmarked"] = true
- }
- days[i] = dayObj
- break
- }
-
- if !dayFound {
- // Create new day with bookmark
- days = append(days, map[string]any{
- "day": day,
- "isBookmarked": true,
- })
- }
-
- // Update days array
- content["days"] = days
-
- // Write month data
- if err := utils.WriteMonth(userID, year, month, content); err != nil {
- http.Error(w, fmt.Sprintf("Failed to bookmark day - error writing log: %v", err), http.StatusInternalServerError)
- return
- }
-
- // Return success
- utils.JSONResponse(w, http.StatusOK, map[string]any{
- "success": true,
- "bookmarked": bookmarked,
- })
-}
-
// SearchTag handles searching logs by tag
func SearchTag(w http.ResponseWriter, r *http.Request) {
// Get user ID and derived key from context
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
- }
-
- // Check for duplicate tag names
- tags, ok := content["tags"].([]any)
- if ok {
- for _, tagInterface := range tags {
- tag, ok := tagInterface.(map[string]any)
- if !ok {
- continue
- }
-
- 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
- }
- if decryptedName == req.Name {
- http.Error(w, "Tag name already exists", http.StatusBadRequest)
- 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 {
--- /dev/null
+package handlers
+
+import (
+ "encoding/json"
+ "fmt"
+ "maps"
+ "net/http"
+
+ "github.com/phitux/dailytxt/backend/utils"
+)
+
+func GetDefaultSettings() map[string]any {
+ // Default settings
+ return map[string]any{
+ "autoloadImagesByDefault": false,
+ "setAutoloadImagesPerDevice": true,
+ "useALookBack": true,
+ "aLookBackYears": []int{1, 5, 10},
+ "useBrowserTimezone": true,
+ "timezone": "UTC",
+ "useBrowserLanguage": true,
+ "language": "en",
+ "darkModeAutoDetect": true,
+ "useDarkMode": false,
+ "background": "gradient",
+ "monochromeBackgroundColor": "#ececec",
+ "checkForUpdates": true,
+ "includeTestVersions": false,
+ "requirePasswordOnPageLoad": false,
+ }
+}
+
+// GetUserSettings retrieves user settings
+func GetUserSettings(w http.ResponseWriter, r *http.Request) {
+ // Get user ID from context
+ userID, ok := r.Context().Value(utils.UserIDKey).(int)
+ if !ok {
+ http.Error(w, "Unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ // Get derived key from context
+ derivedKey, ok := r.Context().Value(utils.DerivedKeyKey).(string)
+ if !ok {
+ http.Error(w, "Unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ // Get user settings
+ encryptedSettings, err := utils.GetUserSettings(userID)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("Error retrieving user settings: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ // Default settings
+ defaultSettings := GetDefaultSettings()
+
+ // If no settings found, return defaults
+ if len(encryptedSettings) == 0 {
+ utils.JSONResponse(w, http.StatusOK, defaultSettings)
+ return
+ }
+
+ // Decrypt settings
+ encKey, err := utils.GetEncryptionKey(userID, derivedKey)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("Error getting encryption key: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ decryptedSettings, err := utils.DecryptText(encryptedSettings, encKey)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("Error decrypting settings: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ // Parse JSON
+ var settings map[string]any
+ if err := json.Unmarshal([]byte(decryptedSettings), &settings); err != nil {
+ http.Error(w, fmt.Sprintf("Error parsing settings: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ // Apply defaults for missing keys
+ for key, value := range defaultSettings {
+ if _, exists := settings[key]; !exists {
+ settings[key] = value
+ }
+ }
+
+ // Return settings
+ utils.JSONResponse(w, http.StatusOK, settings)
+}
+
+// SaveUserSettings saves user settings
+func SaveUserSettings(w http.ResponseWriter, r *http.Request) {
+ // Get user ID from context
+ userID, ok := r.Context().Value(utils.UserIDKey).(int)
+ if !ok {
+ http.Error(w, "Unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ // Get derived key from context
+ derivedKey, ok := r.Context().Value(utils.DerivedKeyKey).(string)
+ if !ok {
+ http.Error(w, "Unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ // Parse request body
+ var newSettings map[string]any
+ if err := json.NewDecoder(r.Body).Decode(&newSettings); err != nil {
+ http.Error(w, "Invalid request body", http.StatusBadRequest)
+ return
+ }
+
+ // Get existing settings
+ encryptedSettings, err := utils.GetUserSettings(userID)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("Error retrieving user settings: %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
+ }
+
+ // Current settings
+ var currentSettings map[string]any
+
+ // If settings exist, decrypt them
+ if len(encryptedSettings) > 0 {
+ decryptedSettings, err := utils.DecryptText(encryptedSettings, encKey)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("Error decrypting settings: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ // Parse JSON
+ if err := json.Unmarshal([]byte(decryptedSettings), ¤tSettings); err != nil {
+ http.Error(w, fmt.Sprintf("Error parsing settings: %v", err), http.StatusInternalServerError)
+ return
+ }
+ }
+
+ // If no settings or empty, use defaults
+ if len(currentSettings) == 0 {
+ currentSettings = GetDefaultSettings()
+ }
+
+ // Update settings
+ maps.Copy(currentSettings, newSettings)
+
+ // Encrypt settings
+ settingsJSON, err := json.Marshal(currentSettings)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("Error encoding settings: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ encryptedNewSettings, err := utils.EncryptText(string(settingsJSON), encKey)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("Error encrypting settings: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ // Write settings
+ if err := utils.WriteUserSettings(userID, encryptedNewSettings); err != nil {
+ http.Error(w, fmt.Sprintf("Error writing settings: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ // Return success
+ utils.JSONResponse(w, http.StatusOK, map[string]bool{
+ "success": true,
+ })
+}
--- /dev/null
+package handlers
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "strconv"
+
+ "github.com/phitux/dailytxt/backend/utils"
+)
+
+// EditTagRequest represents the edit tag request
+type EditTagRequest struct {
+ ID int `json:"id"`
+ Icon string `json:"icon"`
+ Name string `json:"name"`
+ Color string `json:"color"`
+}
+
+// EditTag handles editing a tag
+func EditTag(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 EditTagRequest
+ 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
+ }
+
+ // Check if tags exist
+ tags, ok := content["tags"].([]any)
+ if !ok {
+ http.Error(w, "Tag not found - json error", 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
+ }
+
+ // Find and update tag
+ found := false
+ for i, tagInterface := range tags {
+ tag, ok := tagInterface.(map[string]any)
+ if !ok {
+ continue
+ }
+
+ if id, ok := tag["id"].(float64); ok && int(id) == req.ID {
+ // 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
+ }
+
+ // Update tag
+ tag["icon"] = encIcon
+ tag["name"] = encName
+ tag["color"] = encColor
+ tags[i] = tag
+ found = true
+ break
+ }
+ }
+
+ if !found {
+ http.Error(w, "Tag not found - not in tags", http.StatusInternalServerError)
+ return
+ }
+
+ // Write tags
+ if err := utils.WriteTags(userID, content); err != nil {
+ http.Error(w, fmt.Sprintf("Failed to write tag - error writing tags: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ // Return success
+ utils.JSONResponse(w, http.StatusOK, map[string]bool{
+ "success": true,
+ })
+}
+
+// DeleteTag handles deleting a tag
+func DeleteTag(w http.ResponseWriter, r *http.Request) {
+ // Get user ID from context
+ userID, ok := r.Context().Value(utils.UserIDKey).(int)
+ if !ok {
+ http.Error(w, "Unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ // Get tag ID
+ idStr := r.URL.Query().Get("id")
+ if idStr == "" {
+ http.Error(w, "Missing id parameter", http.StatusBadRequest)
+ return
+ }
+ id, err := strconv.Atoi(idStr)
+ if err != nil {
+ http.Error(w, "Invalid id parameter", http.StatusBadRequest)
+ return
+ }
+
+ // Get all years and months
+ years, err := utils.GetYears(userID)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("Error retrieving years: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ // Remove tag from all logs
+ for _, year := range years {
+ yearInt, _ := strconv.Atoi(year)
+ months, err := utils.GetMonths(userID, year)
+ if err != nil {
+ continue
+ }
+
+ for _, month := range months {
+ monthInt, _ := strconv.Atoi(month)
+ content, err := utils.GetMonth(userID, yearInt, monthInt)
+ if err != nil {
+ continue
+ }
+
+ days, ok := content["days"].([]any)
+ if !ok {
+ continue
+ }
+
+ // Check each day for the tag
+ modified := false
+ for i, dayInterface := range days {
+ day, ok := dayInterface.(map[string]any)
+ if !ok {
+ continue
+ }
+
+ tags, ok := day["tags"].([]any)
+ if !ok {
+ continue
+ }
+
+ // Find and remove the tag
+ for j, tagID := range tags {
+ if tagIDFloat, ok := tagID.(float64); ok && int(tagIDFloat) == id {
+ // Remove tag
+ tags = append(tags[:j], tags[j+1:]...)
+ day["tags"] = tags
+ days[i] = day
+ modified = true
+ break
+ }
+ }
+ }
+
+ // Write updated month if modified
+ if modified {
+ content["days"] = days
+ if err := utils.WriteMonth(userID, yearInt, monthInt, content); err != nil {
+ http.Error(w, fmt.Sprintf("Failed to delete tag - error writing log: %v", err), http.StatusInternalServerError)
+ return
+ }
+ }
+ }
+ }
+
+ // Get tags
+ content, err := utils.GetTags(userID)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("Error retrieving tags: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ // Check if tags exist
+ tags, ok := content["tags"].([]any)
+ if !ok {
+ http.Error(w, "Tag not found - json error", http.StatusInternalServerError)
+ return
+ }
+
+ // Find and remove tag
+ found := false
+ for i, tagInterface := range tags {
+ tag, ok := tagInterface.(map[string]any)
+ if !ok {
+ continue
+ }
+
+ if tagID, ok := tag["id"].(float64); ok && int(tagID) == id {
+ // Remove tag
+ tags = append(tags[:i], tags[i+1:]...)
+ content["tags"] = tags
+ found = true
+ break
+ }
+ }
+
+ if !found {
+ http.Error(w, "Tag not found - not in tags", http.StatusInternalServerError)
+ return
+ }
+
+ // Write tags
+ if err := utils.WriteTags(userID, content); err != nil {
+ http.Error(w, fmt.Sprintf("Failed to delete tag - error writing tags: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ // Return success
+ utils.JSONResponse(w, http.StatusOK, map[string]bool{
+ "success": true,
+ })
+}
+
+// TagLogRequest represents the tag log request
+type TagLogRequest struct {
+ Day int `json:"day"`
+ Month int `json:"month"`
+ Year int `json:"year"`
+ TagID int `json:"tag_id"`
+}
+
+// AddTagToLog handles adding a tag to a log
+func AddTagToLog(w http.ResponseWriter, r *http.Request) {
+ // Get user ID from context
+ userID, ok := r.Context().Value(utils.UserIDKey).(int)
+ if !ok {
+ http.Error(w, "Unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ // Parse request body
+ var req TagLogRequest
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ http.Error(w, "Invalid request body", http.StatusBadRequest)
+ return
+ }
+
+ // Get month data
+ content, err := utils.GetMonth(userID, req.Year, req.Month)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("Error retrieving month data: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ // Get or create days array
+ days, ok := content["days"].([]any)
+ if !ok {
+ days = []any{}
+ }
+
+ // Find day
+ dayFound := false
+ for i, dayInterface := range days {
+ day, ok := dayInterface.(map[string]any)
+ if !ok {
+ continue
+ }
+
+ dayNum, ok := day["day"].(float64)
+ if !ok || int(dayNum) != req.Day {
+ continue
+ }
+
+ // Day found, add tag
+ dayFound = true
+ tags, ok := day["tags"].([]any)
+ if !ok {
+ tags = []any{}
+ }
+
+ // Check if tag already exists
+ tagExists := false
+ for _, tagID := range tags {
+ if tagIDFloat, ok := tagID.(float64); ok && int(tagIDFloat) == req.TagID {
+ tagExists = true
+ break
+ }
+ }
+
+ if !tagExists {
+ tags = append(tags, float64(req.TagID))
+ day["tags"] = tags
+ days[i] = day
+ }
+ break
+ }
+
+ if !dayFound {
+ // Create new day with tag
+ days = append(days, map[string]any{
+ "day": req.Day,
+ "tags": []any{float64(req.TagID)},
+ })
+ }
+
+ // Update days array
+ content["days"] = days
+
+ // Write month data
+ if err := utils.WriteMonth(userID, req.Year, req.Month, content); err != nil {
+ http.Error(w, fmt.Sprintf("Failed to write tag - error writing log: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ // Return success
+ utils.JSONResponse(w, http.StatusOK, map[string]bool{
+ "success": true,
+ })
+}
+
+// RemoveTagFromLog handles removing a tag from a log
+func RemoveTagFromLog(w http.ResponseWriter, r *http.Request) {
+ // Get user ID from context
+ userID, ok := r.Context().Value(utils.UserIDKey).(int)
+ if !ok {
+ http.Error(w, "Unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ // Parse request body
+ var req TagLogRequest
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ http.Error(w, "Invalid request body", http.StatusBadRequest)
+ return
+ }
+
+ // Get month data
+ content, err := utils.GetMonth(userID, req.Year, req.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 {
+ http.Error(w, "Day not found - json error", http.StatusInternalServerError)
+ return
+ }
+
+ // Find day
+ found := false
+ for i, dayInterface := range days {
+ day, ok := dayInterface.(map[string]any)
+ if !ok {
+ continue
+ }
+
+ dayNum, ok := day["day"].(float64)
+ if !ok || int(dayNum) != req.Day {
+ continue
+ }
+
+ // Day found, check for tags
+ tags, ok := day["tags"].([]any)
+ if !ok {
+ http.Error(w, "Failed to remove tag - not found in log", http.StatusInternalServerError)
+ return
+ }
+
+ // Find and remove tag
+ for j, tagID := range tags {
+ if tagIDFloat, ok := tagID.(float64); ok && int(tagIDFloat) == req.TagID {
+ // Remove tag
+ tags = append(tags[:j], tags[j+1:]...)
+ day["tags"] = tags
+ days[i] = day
+ found = true
+ break
+ }
+ }
+
+ if !found {
+ http.Error(w, "Failed to remove tag - not found in log", http.StatusInternalServerError)
+ return
+ }
+ break
+ }
+
+ if !found {
+ http.Error(w, "Failed to remove tag - not found in log", http.StatusInternalServerError)
+ return
+ }
+
+ // Update days array
+ content["days"] = days
+
+ // Write month data
+ if err := utils.WriteMonth(userID, req.Year, req.Month, content); err != nil {
+ http.Error(w, fmt.Sprintf("Failed to remove tag - error writing log: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ // Return success
+ utils.JSONResponse(w, http.StatusOK, map[string]bool{
+ "success": true,
+ })
+}
+
+// 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
+ }
+
+ // Check for duplicate tag names
+ tags, ok := content["tags"].([]any)
+ if ok {
+ for _, tagInterface := range tags {
+ tag, ok := tagInterface.(map[string]any)
+ if !ok {
+ continue
+ }
+
+ 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
+ }
+ if decryptedName == req.Name {
+ http.Error(w, "Tag name already exists", http.StatusBadRequest)
+ 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,
+ })
+}
--- /dev/null
+package handlers
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+
+ "github.com/phitux/dailytxt/backend/utils"
+)
+
+// TemplatesRequest represents a templates request
+type TemplatesRequest struct {
+ Templates []struct {
+ Name string `json:"name"`
+ Text string `json:"text"`
+ } `json:"templates"`
+}
+
+// GetTemplates handles retrieving a user's templates
+func GetTemplates(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 templates
+ content, err := utils.GetTemplates(userID)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("Error retrieving templates: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ // If no templates, return empty array
+ if templates, ok := content["templates"].([]any); !ok || len(templates) == 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 template data
+ templates := content["templates"].([]any)
+ result := []any{}
+
+ for _, templateInterface := range templates {
+ template, ok := templateInterface.(map[string]any)
+ if !ok {
+ continue
+ }
+
+ // Decrypt name and text
+ if encName, ok := template["name"].(string); ok {
+ decryptedName, err := utils.DecryptText(encName, encKey)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("Error decrypting template name: %v", err), http.StatusInternalServerError)
+ return
+ }
+ template["name"] = decryptedName
+ }
+
+ if encText, ok := template["text"].(string); ok {
+ decryptedText, err := utils.DecryptText(encText, encKey)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("Error decrypting template text: %v", err), http.StatusInternalServerError)
+ return
+ }
+ template["text"] = decryptedText
+ }
+
+ result = append(result, template)
+ }
+
+ // Return templates
+ utils.JSONResponse(w, http.StatusOK, result)
+}
+
+// SaveTemplates handles saving templates
+func SaveTemplates(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 TemplatesRequest
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ http.Error(w, "Invalid request body", 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
+ }
+
+ // Create new templates content
+ content := map[string]any{
+ "templates": []any{},
+ }
+
+ // Encrypt template data
+ templates := []any{}
+ for _, template := range req.Templates {
+ encName, err := utils.EncryptText(template.Name, encKey)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("Error encrypting template name: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ encText, err := utils.EncryptText(template.Text, encKey)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("Error encrypting template text: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ templates = append(templates, map[string]any{
+ "name": encName,
+ "text": encText,
+ })
+ }
+
+ content["templates"] = templates
+
+ // Write templates
+ if err := utils.WriteTemplates(userID, content); err != nil {
+ http.Error(w, fmt.Sprintf("Error writing templates: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ // Return success
+ utils.JSONResponse(w, http.StatusOK, map[string]bool{
+ "success": true,
+ })
+}
"encoding/base64"
"encoding/json"
"fmt"
- "maps"
"net/http"
"strings"
"sync"
})
}
-func GetDefaultSettings() map[string]any {
- // Default settings
- return map[string]any{
- "autoloadImagesByDefault": false,
- "setAutoloadImagesPerDevice": true,
- "useALookBack": true,
- "aLookBackYears": []int{1, 5, 10},
- "useBrowserTimezone": true,
- "timezone": "UTC",
- "useBrowserLanguage": true,
- "language": "en",
- "darkModeAutoDetect": true,
- "useDarkMode": false,
- "background": "gradient",
- "monochromeBackgroundColor": "#ececec",
- "checkForUpdates": true,
- "includeTestVersions": false,
- "requirePasswordOnPageLoad": false,
- }
-}
-
-// GetUserSettings retrieves user settings
-func GetUserSettings(w http.ResponseWriter, r *http.Request) {
- // Get user ID from context
- userID, ok := r.Context().Value(utils.UserIDKey).(int)
- if !ok {
- http.Error(w, "Unauthorized", http.StatusUnauthorized)
- return
- }
-
- // Get derived key from context
- derivedKey, ok := r.Context().Value(utils.DerivedKeyKey).(string)
- if !ok {
- http.Error(w, "Unauthorized", http.StatusUnauthorized)
- return
- }
-
- // Get user settings
- encryptedSettings, err := utils.GetUserSettings(userID)
- if err != nil {
- http.Error(w, fmt.Sprintf("Error retrieving user settings: %v", err), http.StatusInternalServerError)
- return
- }
-
- // Default settings
- defaultSettings := GetDefaultSettings()
-
- // If no settings found, return defaults
- if len(encryptedSettings) == 0 {
- utils.JSONResponse(w, http.StatusOK, defaultSettings)
- return
- }
-
- // Decrypt settings
- encKey, err := utils.GetEncryptionKey(userID, derivedKey)
- if err != nil {
- http.Error(w, fmt.Sprintf("Error getting encryption key: %v", err), http.StatusInternalServerError)
- return
- }
-
- decryptedSettings, err := utils.DecryptText(encryptedSettings, encKey)
- if err != nil {
- http.Error(w, fmt.Sprintf("Error decrypting settings: %v", err), http.StatusInternalServerError)
- return
- }
-
- // Parse JSON
- var settings map[string]any
- if err := json.Unmarshal([]byte(decryptedSettings), &settings); err != nil {
- http.Error(w, fmt.Sprintf("Error parsing settings: %v", err), http.StatusInternalServerError)
- return
- }
-
- // Apply defaults for missing keys
- for key, value := range defaultSettings {
- if _, exists := settings[key]; !exists {
- settings[key] = value
- }
- }
-
- // Return settings
- utils.JSONResponse(w, http.StatusOK, settings)
-}
-
-// SaveUserSettings saves user settings
-func SaveUserSettings(w http.ResponseWriter, r *http.Request) {
- // Get user ID from context
- userID, ok := r.Context().Value(utils.UserIDKey).(int)
- if !ok {
- http.Error(w, "Unauthorized", http.StatusUnauthorized)
- return
- }
-
- // Get derived key from context
- derivedKey, ok := r.Context().Value(utils.DerivedKeyKey).(string)
- if !ok {
- http.Error(w, "Unauthorized", http.StatusUnauthorized)
- return
- }
-
- // Parse request body
- var newSettings map[string]any
- if err := json.NewDecoder(r.Body).Decode(&newSettings); err != nil {
- http.Error(w, "Invalid request body", http.StatusBadRequest)
- return
- }
-
- // Get existing settings
- encryptedSettings, err := utils.GetUserSettings(userID)
- if err != nil {
- http.Error(w, fmt.Sprintf("Error retrieving user settings: %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
- }
-
- // Current settings
- var currentSettings map[string]any
-
- // If settings exist, decrypt them
- if len(encryptedSettings) > 0 {
- decryptedSettings, err := utils.DecryptText(encryptedSettings, encKey)
- if err != nil {
- http.Error(w, fmt.Sprintf("Error decrypting settings: %v", err), http.StatusInternalServerError)
- return
- }
-
- // Parse JSON
- if err := json.Unmarshal([]byte(decryptedSettings), ¤tSettings); err != nil {
- http.Error(w, fmt.Sprintf("Error parsing settings: %v", err), http.StatusInternalServerError)
- return
- }
- }
-
- // If no settings or empty, use defaults
- if len(currentSettings) == 0 {
- currentSettings = GetDefaultSettings()
- }
-
- // Update settings
- maps.Copy(currentSettings, newSettings)
-
- // Encrypt settings
- settingsJSON, err := json.Marshal(currentSettings)
- if err != nil {
- http.Error(w, fmt.Sprintf("Error encoding settings: %v", err), http.StatusInternalServerError)
- return
- }
-
- encryptedNewSettings, err := utils.EncryptText(string(settingsJSON), encKey)
- if err != nil {
- http.Error(w, fmt.Sprintf("Error encrypting settings: %v", err), http.StatusInternalServerError)
- return
- }
-
- // Write settings
- if err := utils.WriteUserSettings(userID, encryptedNewSettings); err != nil {
- http.Error(w, fmt.Sprintf("Error writing settings: %v", err), http.StatusInternalServerError)
- return
- }
-
- // Return success
- utils.JSONResponse(w, http.StatusOK, map[string]bool{
- "success": true,
- })
-}
-
// MigrationProgress stores the progress of user data migration
type MigrationProgress struct {
Phase string `json:"phase"` // Current migration phase