[#151] remove files on cascade deletion
This commit is contained in:
		
							parent
							
								
									04e0cec32c
								
							
						
					
					
						commit
						8ef3d4e966
					
				| 
						 | 
					@ -1,8 +1,6 @@
 | 
				
			||||||
package apis
 | 
					package apis
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"errors"
 | 
					 | 
				
			||||||
	"log"
 | 
					 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/labstack/echo/v5"
 | 
						"github.com/labstack/echo/v5"
 | 
				
			||||||
| 
						 | 
					@ -160,13 +158,6 @@ func (api *collectionApi) delete(c echo.Context) error {
 | 
				
			||||||
			return rest.NewBadRequestError("Failed to delete collection. Make sure that the collection is not referenced by other collections.", err)
 | 
								return rest.NewBadRequestError("Failed to delete collection. Make sure that the collection is not referenced by other collections.", err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// try to delete the collection files
 | 
					 | 
				
			||||||
		if err := api.deleteCollectionFiles(e.Collection); err != nil && api.app.IsDebug() {
 | 
					 | 
				
			||||||
			// non critical error - only log for debug
 | 
					 | 
				
			||||||
			// (usually could happen because of S3 api limits)
 | 
					 | 
				
			||||||
			log.Println(err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return e.HttpContext.NoContent(http.StatusNoContent)
 | 
							return e.HttpContext.NoContent(http.StatusNoContent)
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -176,18 +167,3 @@ func (api *collectionApi) delete(c echo.Context) error {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return handlerErr
 | 
						return handlerErr
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
func (api *collectionApi) deleteCollectionFiles(collection *models.Collection) error {
 | 
					 | 
				
			||||||
	fs, err := api.app.NewFilesystem()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer fs.Close()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	failed := fs.DeletePrefix(collection.BaseFilesPath())
 | 
					 | 
				
			||||||
	if len(failed) > 0 {
 | 
					 | 
				
			||||||
		return errors.New("Failed to delete all record files.")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -356,13 +356,6 @@ func (api *recordApi) delete(c echo.Context) error {
 | 
				
			||||||
			return rest.NewBadRequestError("Failed to delete record. Make sure that the record is not part of a required relation reference.", err)
 | 
								return rest.NewBadRequestError("Failed to delete record. Make sure that the record is not part of a required relation reference.", err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// try to delete the record files
 | 
					 | 
				
			||||||
		if err := api.deleteRecordFiles(e.Record); err != nil && api.app.IsDebug() {
 | 
					 | 
				
			||||||
			// non critical error - only log for debug
 | 
					 | 
				
			||||||
			// (usually could happen due to S3 api limits)
 | 
					 | 
				
			||||||
			log.Println(err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return e.HttpContext.NoContent(http.StatusNoContent)
 | 
							return e.HttpContext.NoContent(http.StatusNoContent)
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -373,21 +366,6 @@ func (api *recordApi) delete(c echo.Context) error {
 | 
				
			||||||
	return handlerErr
 | 
						return handlerErr
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (api *recordApi) deleteRecordFiles(record *models.Record) error {
 | 
					 | 
				
			||||||
	fs, err := api.app.NewFilesystem()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer fs.Close()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	failed := fs.DeletePrefix(record.BaseFilesPath())
 | 
					 | 
				
			||||||
	if len(failed) > 0 {
 | 
					 | 
				
			||||||
		return fmt.Errorf("Failed to delete %d record files.", len(failed))
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (api *recordApi) exportRequestData(c echo.Context) map[string]any {
 | 
					func (api *recordApi) exportRequestData(c echo.Context) map[string]any {
 | 
				
			||||||
	result := map[string]any{}
 | 
						result := map[string]any{}
 | 
				
			||||||
	queryParams := map[string]any{}
 | 
						queryParams := map[string]any{}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										37
									
								
								core/base.go
								
								
								
								
							
							
						
						
									
										37
									
								
								core/base.go
								
								
								
								
							| 
						 | 
					@ -5,6 +5,7 @@ import (
 | 
				
			||||||
	"database/sql"
 | 
						"database/sql"
 | 
				
			||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
	"errors"
 | 
						"errors"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"path/filepath"
 | 
						"path/filepath"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
| 
						 | 
					@ -123,7 +124,7 @@ type BaseApp struct {
 | 
				
			||||||
//
 | 
					//
 | 
				
			||||||
// To initialize the app, you need to call `app.Bootsrap()`.
 | 
					// To initialize the app, you need to call `app.Bootsrap()`.
 | 
				
			||||||
func NewBaseApp(dataDir string, encryptionEnv string, isDebug bool) *BaseApp {
 | 
					func NewBaseApp(dataDir string, encryptionEnv string, isDebug bool) *BaseApp {
 | 
				
			||||||
	return &BaseApp{
 | 
						app := &BaseApp{
 | 
				
			||||||
		dataDir:             dataDir,
 | 
							dataDir:             dataDir,
 | 
				
			||||||
		isDebug:             isDebug,
 | 
							isDebug:             isDebug,
 | 
				
			||||||
		encryptionEnv:       encryptionEnv,
 | 
							encryptionEnv:       encryptionEnv,
 | 
				
			||||||
| 
						 | 
					@ -209,6 +210,10 @@ func NewBaseApp(dataDir string, encryptionEnv string, isDebug bool) *BaseApp {
 | 
				
			||||||
		onCollectionBeforeDeleteRequest: &hook.Hook[*CollectionDeleteEvent]{},
 | 
							onCollectionBeforeDeleteRequest: &hook.Hook[*CollectionDeleteEvent]{},
 | 
				
			||||||
		onCollectionAfterDeleteRequest:  &hook.Hook[*CollectionDeleteEvent]{},
 | 
							onCollectionAfterDeleteRequest:  &hook.Hook[*CollectionDeleteEvent]{},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						app.registerDefaultHooks()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return app
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Bootstrap initializes the application
 | 
					// Bootstrap initializes the application
 | 
				
			||||||
| 
						 | 
					@ -750,3 +755,33 @@ func (app *BaseApp) createDao(db dbx.Builder) *daos.Dao {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return dao
 | 
						return dao
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (app *BaseApp) registerDefaultHooks() {
 | 
				
			||||||
 | 
						deletePrefix := func(prefix string) error {
 | 
				
			||||||
 | 
							fs, err := app.NewFilesystem()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							defer fs.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							failed := fs.DeletePrefix(prefix)
 | 
				
			||||||
 | 
							if len(failed) > 0 {
 | 
				
			||||||
 | 
								return errors.New("Failed to delete the files at " + prefix)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// delete storage files from deleted Collection, Records, etc.
 | 
				
			||||||
 | 
						app.OnModelAfterDelete().Add(func(e *ModelEvent) error {
 | 
				
			||||||
 | 
							if m, ok := e.Model.(models.FilesManager); ok && m.BaseFilesPath() != "" {
 | 
				
			||||||
 | 
								if err := deletePrefix(m.BaseFilesPath()); err != nil && app.IsDebug() {
 | 
				
			||||||
 | 
									// non critical error - only log for debug
 | 
				
			||||||
 | 
									// (usually could happen because of S3 api limits)
 | 
				
			||||||
 | 
									log.Println(err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,6 +15,12 @@ type ColumnValueMapper interface {
 | 
				
			||||||
	ColumnValueMap() map[string]any
 | 
						ColumnValueMap() map[string]any
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FilesManager defines an interface with common methods that files manager models should implement.
 | 
				
			||||||
 | 
					type FilesManager interface {
 | 
				
			||||||
 | 
						// BaseFilesPath returns the storage dir path used by the interface instance.
 | 
				
			||||||
 | 
						BaseFilesPath() string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Model defines an interface with common methods that all db models should have.
 | 
					// Model defines an interface with common methods that all db models should have.
 | 
				
			||||||
type Model interface {
 | 
					type Model interface {
 | 
				
			||||||
	TableName() string
 | 
						TableName() string
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,6 +3,7 @@ package models
 | 
				
			||||||
import "github.com/pocketbase/pocketbase/models/schema"
 | 
					import "github.com/pocketbase/pocketbase/models/schema"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var _ Model = (*Collection)(nil)
 | 
					var _ Model = (*Collection)(nil)
 | 
				
			||||||
 | 
					var _ FilesManager = (*Collection)(nil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Collection struct {
 | 
					type Collection struct {
 | 
				
			||||||
	BaseModel
 | 
						BaseModel
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,6 +16,7 @@ import (
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var _ Model = (*Record)(nil)
 | 
					var _ Model = (*Record)(nil)
 | 
				
			||||||
var _ ColumnValueMapper = (*Record)(nil)
 | 
					var _ ColumnValueMapper = (*Record)(nil)
 | 
				
			||||||
 | 
					var _ FilesManager = (*Record)(nil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Record struct {
 | 
					type Record struct {
 | 
				
			||||||
	BaseModel
 | 
						BaseModel
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue