added feature to delete day
authorPhiTux <redacted>
Wed, 13 Aug 2025 17:25:47 +0000 (19:25 +0200)
committerPhiTux <redacted>
Wed, 13 Aug 2025 17:25:47 +0000 (19:25 +0200)
backend/handlers/logs.go
backend/main.go
frontend/src/routes/(authed)/write/+page.svelte

index a611c044f4a4f95b82a426acfb6e794303b00600..63ed42a310eaa2752330bea55f9f3e2629566ab7 100644 (file)
@@ -915,3 +915,92 @@ func GetHistory(w http.ResponseWriter, r *http.Request) {
        // Day not found
        utils.JSONResponse(w, http.StatusOK, []any{})
 }
+
+// DeleteDay deletes all data of the specified day
+// Also delete files, that might be uploaded
+func DeleteDay(w http.ResponseWriter, r *http.Request) {
+       utils.UsersFileMutex.Lock()
+       defer utils.UsersFileMutex.Unlock()
+
+       // Get user ID from context
+       userID, ok := r.Context().Value(utils.UserIDKey).(int)
+       if !ok {
+               http.Error(w, "Unauthorized", http.StatusUnauthorized)
+               return
+       }
+
+       // Get parameters from URL
+       year, err := strconv.Atoi(r.URL.Query().Get("year"))
+       if err != nil {
+               http.Error(w, "Invalid year parameter", http.StatusBadRequest)
+               return
+       }
+       month, err := strconv.Atoi(r.URL.Query().Get("month"))
+       if err != nil {
+               http.Error(w, "Invalid month parameter", http.StatusBadRequest)
+               return
+       }
+       dayValue, err := strconv.Atoi(r.URL.Query().Get("day"))
+       if err != nil {
+               http.Error(w, "Invalid day 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
+       }
+
+       days, ok := content["days"].([]any)
+       if !ok {
+               utils.JSONResponse(w, http.StatusOK, map[string]bool{"success": true})
+               return
+       }
+
+       for i, dayInterface := range days {
+               day, ok := dayInterface.(map[string]any)
+               if !ok {
+                       continue
+               }
+
+               dayNum, ok := day["day"].(float64)
+               if !ok || int(dayNum) != dayValue {
+                       continue
+               }
+
+               // Delete associated files before removing the day entry
+               if filesList, ok := day["files"].([]any); ok && len(filesList) > 0 {
+                       for _, fileInterface := range filesList {
+                               file, ok := fileInterface.(map[string]any)
+                               if !ok {
+                                       continue
+                               }
+
+                               if fileID, ok := file["uuid_filename"].(string); ok {
+                                       // Delete the actual file from the filesystem
+                                       err := utils.RemoveFile(userID, fileID)
+                                       if err != nil {
+                                               utils.Logger.Printf("Warning: Failed to delete file %s for user %d: %v", fileID, userID, err)
+                                               // Continue with deletion even if file removal fails
+                                       }
+                               }
+                       }
+               }
+
+               // Remove the day from the days array
+               days = append(days[:i], days[i+1:]...)
+               content["days"] = days
+
+               if err := utils.WriteMonth(userID, year, 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})
+               return
+       }
+
+       utils.JSONResponse(w, http.StatusOK, map[string]bool{"success": true})
+}
index a230083c02d79454497f91d3d415376562c0ac9b..0f65dd8564a9d94e617475a7b0ed044367ce78b6 100644 (file)
@@ -63,6 +63,7 @@ func main() {
        mux.HandleFunc("GET /logs/deleteFile", middleware.RequireAuth(handlers.DeleteFile))
        mux.HandleFunc("GET /logs/getHistory", middleware.RequireAuth(handlers.GetHistory))
        mux.HandleFunc("GET /logs/bookmarkDay", middleware.RequireAuth(handlers.BookmarkDay))
+       mux.HandleFunc("GET /logs/deleteDay", middleware.RequireAuth(handlers.DeleteDay))
 
        // Create a handler chain with Logger and CORS middleware
        // Logger middleware will be executed first, then CORS
index a3dcce0acc177acb86ae1d41b97b73bc0d58d89e..32302658b6927ea7e70991eb960b804de17a2891 100644 (file)
@@ -18,7 +18,8 @@
                faQuestionCircle,
                faClockRotateLeft,
                faArrowLeft,
-               faArrowRight
+               faArrowRight,
+               faTrash
        } from '@fortawesome/free-solid-svg-icons';
        import Fa from 'svelte-fa';
        import { v4 as uuidv4 } from 'uuid';
                tinyMDE.setContent(currentLog);
                tinyMDE.setSelection({ row: 0, col: 0 });
        }
+
+       function showDeleteDayModal() {
+               const modal = new bootstrap.Modal(document.getElementById('modalDeleteDay'));
+               modal.show();
+       }
+
+       function deleteDay() {
+               axios
+                       .get(API_URL + '/logs/deleteDay', {
+                               params: {
+                                       day: $selectedDate.day,
+                                       month: $selectedDate.month,
+                                       year: $selectedDate.year
+                               }
+                       })
+                       .then((response) => {
+                               if (response.data.success) {
+                                       currentLog = '';
+                                       tinyMDE.setContent(currentLog);
+                                       savedLog = '';
+                                       logDateWritten = '';
+
+                                       selectedTags = [];
+                                       history = [];
+                                       filesOfDay = [];
+                                       images = [];
+                                       $cal.daysBookmarked = $cal.daysBookmarked.filter((day) => day !== $selectedDate.day);
+                                       $cal.daysWithFiles = $cal.daysWithFiles.filter((day) => day !== $selectedDate.day);
+                                       $cal.daysWithLogs = $cal.daysWithLogs.filter((day) => day !== $selectedDate.day);
+                               } else {
+                                       const toast = new bootstrap.Toast(document.getElementById('toastErrorDeletingDay'));
+                                       toast.show();
+                               }
+                       })
+                       .catch((error) => {
+                               console.error(error);
+                               const toast = new bootstrap.Toast(document.getElementById('toastErrorDeletingDay'));
+                               toast.show();
+                       });
+       }
 </script>
 
 <DatepickerLogic />
                                                </button>
                                        </div>
                                {/if}
-                               <div class="textAreaDelete">delete</div>
+                               <div class="textAreaDelete d-flex flex-column justify-content-center">
+                                       <button class="btn px-0 btn-hover" onclick={() => showDeleteDayModal()}>
+                                               <Fa icon={faTrash} class="" size="1.5x" fw />
+                                       </button>
+                               </div>
                        </div>
                        <div id="log" class="focus-ring">
                                <div id="toolbar"></div>
                                <div class="toast-body">Fehler beim Download einer Datei!</div>
                        </div>
                </div>
+
+               <div
+                       id="toastErrorDeletingDay"
+                       class="toast align-items-center text-bg-danger"
+                       role="alert"
+                       aria-live="assertive"
+                       aria-atomic="true"
+               >
+                       <div class="d-flex">
+                               <div class="toast-body">Fehler beim Löschen des Tages!</div>
+                       </div>
+               </div>
        </div>
 
        <div class="modal fade" id="modalConfirmDeleteFile" tabindex="-1">
                </div>
        </div>
 
+       <div class="modal fade" id="modalDeleteDay" tabindex="-1">
+               <div class="modal-dialog modal-dialog-centered">
+                       <div class="modal-content">
+                               <div class="modal-header">
+                                       <h5 class="modal-title">Tag vollständig löschen?</h5>
+                                       <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"
+                                       ></button>
+                               </div>
+                               <div class="modal-body">
+                                       Du löschst <b><u>sämtliche Daten</u></b> vom
+                                       <b><u>{`${$selectedDate.day}.${$selectedDate.month}.${$selectedDate.year}`}</u></b>!<br
+                                       /><br />
+                                       Dies beinhaltet:
+                                       <ul>
+                                               {#snippet deleteDayBool(available, description)}
+                                                       <li class={available ? 'text-decoration-underline' : 'text-muted fst-italic'}>
+                                                               {#if available}✔️{:else}❌{/if}
+                                                               {description}
+                                                       </li>
+                                               {/snippet}
+
+                                               {#snippet deleteDayCount(item, description)}
+                                                       <li class={item.length > 0 ? 'text-decoration-underline' : 'text-muted fst-italic'}>
+                                                               {item.length}
+                                                               {description}
+                                                       </li>
+                                               {/snippet}
+
+                                               {@render deleteDayBool(logDateWritten !== '', 'Tagebucheintrag')}
+                                               {@render deleteDayBool(historyAvailable, 'Verlauf')}
+
+                                               {@render deleteDayCount(filesOfDay, 'Dateien')}
+                                               {@render deleteDayCount(selectedTags, 'Tags')}
+                                               {@render deleteDayBool($cal.daysBookmarked.includes($selectedDate.day), 'Lesezeichen')}
+                                       </ul>
+                               </div>
+                               <div class="modal-footer">
+                                       <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Schließen</button>
+                                       <button
+                                               onclick={() => deleteDay()}
+                                               type="button"
+                                               class="btn btn-primary"
+                                               data-bs-dismiss="modal">Löschen</button
+                                       >
+                               </div>
+                       </div>
+               </div>
+       </div>
+
        <TagModal
                bind:this={tagModal}
                bind:editTag={newTag}
                padding: 0.25em;
        }
 
+       .textAreaDelete {
+               padding: 0.25em;
+       }
+
        #log div:focus:not(.notSaved) {
                border-color: #90ee90;
                box-shadow: 0 0 0 0.25rem #90ee9070;
git clone https://git.99rst.org/PROJECT