package recycle import ( "encoding/json" "math" "os" "path/filepath" "sort" "strings" "time" "write-server/internal/config" ) var RECYCLE_POSTS_DIR string var RECYCLE_META_DIR string type RecycleItem struct { Slug string `json:"slug"` Title string `json:"title"` DeletedAt string `json:"deletedAt"` OriginalPath string `json:"originalPath"` ExpiresAt string `json:"expiresAt"` DaysRemaining int `json:"daysRemaining"` } func InitRecycleBin() { base := config.RECYCLE_DIR RECYCLE_POSTS_DIR = filepath.Join(base, "posts") RECYCLE_META_DIR = filepath.Join(base, "meta") if err := os.MkdirAll(RECYCLE_POSTS_DIR, 0755); err != nil { panic(err) } if err := os.MkdirAll(RECYCLE_META_DIR, 0755); err != nil { panic(err) } } func MoveToRecycle(slug, sourceDir, title string) error { InitRecycleBin() now := time.Now() expires := now.AddDate(0, 0, config.RECYCLE_DAYS) meta := map[string]interface{}{ "slug": slug, "title": title, "deletedAt": now.Format(time.RFC3339), "originalPath": sourceDir, "expiresAt": expires.Format(time.RFC3339), } targetDir := filepath.Join(RECYCLE_POSTS_DIR, slug) os.RemoveAll(targetDir) if err := os.Rename(sourceDir, targetDir); err != nil { return err } metaPath := filepath.Join(RECYCLE_META_DIR, slug+".json") data, _ := json.MarshalIndent(meta, "", " ") return os.WriteFile(metaPath, data, 0644) } func ListRecycle() []RecycleItem { if _, err := os.Stat(RECYCLE_META_DIR); os.IsNotExist(err) { return nil } entries, err := os.ReadDir(RECYCLE_META_DIR) if err != nil { return nil } var items []RecycleItem now := time.Now() for _, entry := range entries { if !strings.HasSuffix(entry.Name(), ".json") { continue } data, err := os.ReadFile(filepath.Join(RECYCLE_META_DIR, entry.Name())) if err != nil { continue } var meta map[string]interface{} if err := json.Unmarshal(data, &meta); err != nil { continue } expiresAt, _ := time.Parse(time.RFC3339, getString(meta, "expiresAt")) daysRemaining := int(math.Ceil(expiresAt.Sub(now).Hours() / 24)) if daysRemaining < 0 { daysRemaining = 0 } items = append(items, RecycleItem{ Slug: getString(meta, "slug"), Title: getString(meta, "title"), DeletedAt: getString(meta, "deletedAt"), OriginalPath: getString(meta, "originalPath"), ExpiresAt: getString(meta, "expiresAt"), DaysRemaining: daysRemaining, }) } sort.Slice(items, func(i, j int) bool { ti, _ := time.Parse(time.RFC3339, items[i].DeletedAt) tj, _ := time.Parse(time.RFC3339, items[j].DeletedAt) return ti.After(tj) }) return items } func RestoreFromRecycle(slug string) bool { metaPath := filepath.Join(RECYCLE_META_DIR, slug+".json") data, err := os.ReadFile(metaPath) if err != nil { return false } var meta map[string]interface{} json.Unmarshal(data, &meta) recyclePostPath := filepath.Join(RECYCLE_POSTS_DIR, slug) if _, err := os.Stat(recyclePostPath); os.IsNotExist(err) { return false } originalPath := getString(meta, "originalPath") os.MkdirAll(filepath.Dir(originalPath), 0755) os.RemoveAll(originalPath) if err := os.Rename(recyclePostPath, originalPath); err != nil { return false } os.Remove(metaPath) return true } func PermanentDelete(slug string) bool { deleted := false recyclePostPath := filepath.Join(RECYCLE_POSTS_DIR, slug) if _, err := os.Stat(recyclePostPath); err == nil { os.RemoveAll(recyclePostPath) deleted = true } metaPath := filepath.Join(RECYCLE_META_DIR, slug+".json") if _, err := os.Stat(metaPath); err == nil { os.Remove(metaPath) deleted = true } return deleted } func CleanExpired() int { items := ListRecycle() count := 0 for _, item := range items { if item.DaysRemaining <= 0 { PermanentDelete(item.Slug) count++ } } return count } func getString(m map[string]interface{}, key string) string { if v, ok := m[key]; ok { if s, ok := v.(string); ok { return s } } return "" }