mirror of
https://github.com/the-second-city/faceclaimer.git
synced 2025-10-29 03:56:02 -07:00
Simplify character deletion
This commit is contained in:
18
README.md
18
README.md
@ -60,7 +60,7 @@ Downloads an image from a URL, converts it to WebP, and stores it.
|
||||
|
||||
### Delete Single Image
|
||||
|
||||
**DELETE** `/image/{guild}/{user}/{charid}/{imageid}.webp`
|
||||
**DELETE** `/image/{charid}/{imageid}.webp`
|
||||
|
||||
Deletes a specific image file. Automatically cleans up empty parent directories.
|
||||
|
||||
@ -81,13 +81,13 @@ curl -X DELETE http://localhost:8080/image/68f5a69c1cd9d39b5e9d7ba1/68f5ce16713c
|
||||
|
||||
### Delete Character Images
|
||||
|
||||
**DELETE** `/character/{guild}/{user}/{charid}`
|
||||
**DELETE** `/character/{charid}`
|
||||
|
||||
Deletes all images for a specific character. Automatically cleans up empty parent directories.
|
||||
|
||||
**Example:**
|
||||
```bash
|
||||
curl -X DELETE http://localhost:8080/character/12345/67890/68f5a69c1cd9d39b5e9d7ba1
|
||||
curl -X DELETE http://localhost:8080/character/68f5a69c1cd9d39b5e9d7ba1
|
||||
```
|
||||
|
||||
**Response:**
|
||||
@ -106,16 +106,12 @@ Images are organized in a hierarchical directory structure:
|
||||
|
||||
```
|
||||
images/
|
||||
└── {guild}/
|
||||
└── {user}/
|
||||
└── {charid}/
|
||||
├── {imageid1}.webp
|
||||
├── {imageid2}.webp
|
||||
└── {imageid3}.webp
|
||||
└── {charid}/
|
||||
├── {imageid1}.webp
|
||||
├── {imageid2}.webp
|
||||
└── {imageid3}.webp
|
||||
```
|
||||
|
||||
- `guild`: Discord guild (server) ID (integer)
|
||||
- `user`: Discord user ID (integer)
|
||||
- `charid`: Character ID (MongoDB ObjectID - 24 hex characters)
|
||||
- `imageid`: Image ID (MongoDB ObjectID - 24 hex characters)
|
||||
|
||||
|
||||
@ -40,7 +40,7 @@ func setupRouter(cfg *Config) *gin.Engine {
|
||||
r.DELETE("/image/*imagePath", func(c *gin.Context) {
|
||||
handleSingleDelete(c, cfg)
|
||||
})
|
||||
r.DELETE("/character/:guild/:user/:charID", func(c *gin.Context) {
|
||||
r.DELETE("/character/:charID", func(c *gin.Context) {
|
||||
handleCharacterDelete(c, cfg)
|
||||
})
|
||||
|
||||
@ -173,8 +173,6 @@ func handleSingleDelete(c *gin.Context, cfg *Config) {
|
||||
|
||||
// handleCharacterDelete deletes all of a character's images.
|
||||
func handleCharacterDelete(c *gin.Context, cfg *Config) {
|
||||
guild := c.Param("guild")
|
||||
user := c.Param("user")
|
||||
charID := c.Param("charID")
|
||||
|
||||
if !checks.IsValidObjectId(charID) {
|
||||
@ -182,8 +180,7 @@ func handleCharacterDelete(c *gin.Context, cfg *Config) {
|
||||
return
|
||||
}
|
||||
|
||||
charPath := filepath.Join(guild, user, charID)
|
||||
charPath, err := checks.AbsPath(cfg.ImagesDir, charPath)
|
||||
charPath, err := checks.AbsPath(cfg.ImagesDir, charID)
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
|
||||
@ -451,7 +451,7 @@ func TestHandleCharacterDelete(t *testing.T) {
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
// Create character directory with multiple images
|
||||
charPath := filepath.Join(tmpDir, "123", "456", "507f1f77bcf86cd799439011")
|
||||
charPath := filepath.Join(tmpDir, "507f1f77bcf86cd799439011")
|
||||
if err := os.MkdirAll(charPath, 0755); err != nil {
|
||||
t.Fatalf("Failed to create char dir: %v", err)
|
||||
}
|
||||
@ -463,7 +463,7 @@ func TestHandleCharacterDelete(t *testing.T) {
|
||||
router := setupRouter(cfg)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("DELETE", "/character/123/456/507f1f77bcf86cd799439011", nil)
|
||||
req, _ := http.NewRequest("DELETE", "/character/507f1f77bcf86cd799439011", nil)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
@ -485,7 +485,7 @@ func TestHandleCharacterDelete(t *testing.T) {
|
||||
router := setupRouter(cfg)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("DELETE", "/character/123/456/not-a-valid-objectid", nil)
|
||||
req, _ := http.NewRequest("DELETE", "/character/not-a-valid-objectid", nil)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
@ -507,7 +507,7 @@ func TestHandleCharacterDelete(t *testing.T) {
|
||||
router := setupRouter(cfg)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("DELETE", "/character/123/456/507f1f77bcf86cd799439011", nil)
|
||||
req, _ := http.NewRequest("DELETE", "/character/507f1f77bcf86cd799439011", nil)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
@ -528,10 +528,10 @@ func TestHandleCharacterDelete(t *testing.T) {
|
||||
cfg := &Config{ImagesDir: tmpDir, BaseURL: "https://example.com", Quality: 90}
|
||||
router := setupRouter(cfg)
|
||||
|
||||
// Try to use path traversal via params (e.g. guild="../..", user="etc", charID=valid ObjectID)
|
||||
// Try to use path traversal via charID param (e.g. charID="../../../etc/passwd")
|
||||
// This should be blocked by checks.AbsPath
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("DELETE", "/character/../.././usr/507f1f77bcf86cd799439011", nil)
|
||||
req, _ := http.NewRequest("DELETE", "/character/../../../etc/passwd", nil)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
// Should either be 400 (blocked by path validation) or 404 (route not matched)
|
||||
@ -548,7 +548,7 @@ func TestHandleCharacterDelete(t *testing.T) {
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
// Create character directory
|
||||
charPath := filepath.Join(tmpDir, "123", "456", "507f1f77bcf86cd799439011")
|
||||
charPath := filepath.Join(tmpDir, "507f1f77bcf86cd799439011")
|
||||
if err := os.MkdirAll(charPath, 0755); err != nil {
|
||||
t.Fatalf("Failed to create char dir: %v", err)
|
||||
}
|
||||
@ -558,17 +558,16 @@ func TestHandleCharacterDelete(t *testing.T) {
|
||||
router := setupRouter(cfg)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("DELETE", "/character/123/456/507f1f77bcf86cd799439011", nil)
|
||||
req, _ := http.NewRequest("DELETE", "/character/507f1f77bcf86cd799439011", nil)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("Expected status 200, got %d", w.Code)
|
||||
}
|
||||
|
||||
// Verify empty parent directories were cleaned up
|
||||
guildDir := filepath.Join(tmpDir, "123")
|
||||
if checks.PathExists(guildDir) {
|
||||
t.Error("Empty parent directories should have been cleaned up")
|
||||
// Verify character directory was deleted
|
||||
if checks.PathExists(charPath) {
|
||||
t.Error("Character directory should have been deleted")
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -814,13 +813,13 @@ func TestSetupRouter(t *testing.T) {
|
||||
t.Error("DELETE /image/* route not registered")
|
||||
}
|
||||
|
||||
// Test DELETE /character/:guild/:user/:charID exists
|
||||
// Test DELETE /character/:charID exists
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("DELETE", "/character/123/456/507f1f77bcf86cd799439011", nil)
|
||||
req, _ = http.NewRequest("DELETE", "/character/507f1f77bcf86cd799439011", nil)
|
||||
router.ServeHTTP(w, req)
|
||||
// Should not be 404 (might be 400 due to non-existent dir, but route exists)
|
||||
if w.Code == http.StatusNotFound {
|
||||
t.Error("DELETE /character/:guild/:user/:charID route not registered")
|
||||
t.Error("DELETE /character/:charID route not registered")
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Create test directory structure with multiple images for a character
|
||||
mkdir -p ../images/12345/67890/68f5a69c1cd9d39b5e9d7ba2
|
||||
touch ../images/12345/67890/68f5a69c1cd9d39b5e9d7ba2/test-image-001.webp
|
||||
touch ../images/12345/67890/68f5a69c1cd9d39b5e9d7ba2/test-image-002.webp
|
||||
touch ../images/12345/67890/68f5a69c1cd9d39b5e9d7ba2/test-image-003.webp
|
||||
mkdir -p ../images/68f5a69c1cd9d39b5e9d7ba2
|
||||
touch ../images/68f5a69c1cd9d39b5e9d7ba2/test-image-001.webp
|
||||
touch ../images/68f5a69c1cd9d39b5e9d7ba2/test-image-002.webp
|
||||
touch ../images/68f5a69c1cd9d39b5e9d7ba2/test-image-003.webp
|
||||
|
||||
echo "Created test character with 3 images:"
|
||||
echo " images/12345/67890/68f5a69c1cd9d39b5e9d7ba2/test-image-001.webp"
|
||||
echo " images/12345/67890/68f5a69c1cd9d39b5e9d7ba2/test-image-002.webp"
|
||||
echo " images/12345/67890/68f5a69c1cd9d39b5e9d7ba2/test-image-003.webp"
|
||||
echo " images/68f5a69c1cd9d39b5e9d7ba2/test-image-001.webp"
|
||||
echo " images/68f5a69c1cd9d39b5e9d7ba2/test-image-002.webp"
|
||||
echo " images/68f5a69c1cd9d39b5e9d7ba2/test-image-003.webp"
|
||||
|
||||
# Delete all images for the character
|
||||
curl -X DELETE \
|
||||
http://127.0.0.1:8080/character/12345/67890/68f5a69c1cd9d39b5e9d7ba2
|
||||
http://127.0.0.1:8080/character/68f5a69c1cd9d39b5e9d7ba2
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Create test directory structure and image
|
||||
mkdir -p ../images/12345/67890/test-char-abc123
|
||||
touch ../images/12345/67890/test-char-abc123/test-image-xyz789.webp
|
||||
mkdir -p ../images/68f5a69c1cd9d39b5e9d7ba1
|
||||
touch ../images/68f5a69c1cd9d39b5e9d7ba1/test-image-xyz789.webp
|
||||
|
||||
echo "Created test image: images/12345/67890/test-char-abc123/test-image-xyz789.webp"
|
||||
echo "Created test image: images/68f5a69c1cd9d39b5e9d7ba1/test-image-xyz789.webp"
|
||||
|
||||
# Delete the single image
|
||||
curl -X DELETE \
|
||||
http://127.0.0.1:8080/image/12345/67890/test-char-abc123/test-image-xyz789.webp
|
||||
http://127.0.0.1:8080/image/68f5a69c1cd9d39b5e9d7ba1/test-image-xyz789.webp
|
||||
|
||||
Reference in New Issue
Block a user