169 lines
3.9 KiB
Go
169 lines
3.9 KiB
Go
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 ""
|
|
}
|
|
|