mirror of
https://github.com/pocketbase/pocketbase.git
synced 2025-04-22 15:57:52 +02:00
[#1187] move file upload and delete out of the record save transaction
This commit is contained in:
parent
808f5054d0
commit
355f7053fd
@ -317,14 +317,14 @@ func (dao *Dao) SuggestUniqueAuthRecordUsername(
|
|||||||
func (dao *Dao) SaveRecord(record *models.Record) error {
|
func (dao *Dao) SaveRecord(record *models.Record) error {
|
||||||
if record.Collection().IsAuth() {
|
if record.Collection().IsAuth() {
|
||||||
if record.Username() == "" {
|
if record.Username() == "" {
|
||||||
return errors.New("Unable to save auth record without username.")
|
return errors.New("unable to save auth record without username")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cross-check that the auth record id is unique for all auth collections.
|
// Cross-check that the auth record id is unique for all auth collections.
|
||||||
// This is to make sure that the filter `@request.auth.id` always returns a unique id.
|
// This is to make sure that the filter `@request.auth.id` always returns a unique id.
|
||||||
authCollections, err := dao.FindCollectionsByType(models.CollectionTypeAuth)
|
authCollections, err := dao.FindCollectionsByType(models.CollectionTypeAuth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Unable to fetch the auth collections for cross-id unique check: %w", err)
|
return fmt.Errorf("unable to fetch the auth collections for cross-id unique check: %w", err)
|
||||||
}
|
}
|
||||||
for _, collection := range authCollections {
|
for _, collection := range authCollections {
|
||||||
if record.Collection().Id == collection.Id {
|
if record.Collection().Id == collection.Id {
|
||||||
@ -332,7 +332,7 @@ func (dao *Dao) SaveRecord(record *models.Record) error {
|
|||||||
}
|
}
|
||||||
isUnique := dao.IsRecordValueUnique(collection.Id, schema.FieldNameId, record.Id)
|
isUnique := dao.IsRecordValueUnique(collection.Id, schema.FieldNameId, record.Id)
|
||||||
if !isUnique {
|
if !isUnique {
|
||||||
return errors.New("The auth record ID must be unique across all auth collections.")
|
return errors.New("the auth record ID must be unique across all auth collections")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,8 +34,8 @@ type RecordUpsert struct {
|
|||||||
manageAccess bool
|
manageAccess bool
|
||||||
record *models.Record
|
record *models.Record
|
||||||
|
|
||||||
filesToDelete []string // names list
|
|
||||||
filesToUpload map[string][]*rest.UploadedFile
|
filesToUpload map[string][]*rest.UploadedFile
|
||||||
|
filesToDelete []string // names list
|
||||||
|
|
||||||
// base model fields
|
// base model fields
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
@ -648,35 +648,57 @@ func (form *RecordUpsert) Submit(interceptors ...InterceptorFunc) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return runInterceptors(func() error {
|
return runInterceptors(func() error {
|
||||||
return form.dao.RunInTransaction(func(txDao *daos.Dao) error {
|
if !form.record.HasId() {
|
||||||
// persist record model
|
form.record.RefreshId()
|
||||||
if err := txDao.SaveRecord(form.record); err != nil {
|
form.record.MarkAsNew()
|
||||||
return fmt.Errorf("Failed to save the record: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// upload new files (if any)
|
// upload new files (if any)
|
||||||
if err := form.processFilesToUpload(); err != nil {
|
if err := form.processFilesToUpload(); err != nil {
|
||||||
return fmt.Errorf("Failed to process the upload files: %w", err)
|
return fmt.Errorf("failed to process the uploaded files: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// persist the record model
|
||||||
|
if saveErr := form.dao.SaveRecord(form.record); saveErr != nil {
|
||||||
|
// try to cleanup the successfully uploaded files
|
||||||
|
if _, err := form.deleteFilesByNamesList(form.getFilesToUploadNames()); err != nil && form.app.IsDebug() {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("failed to save the record: %w", saveErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// delete old files (if any)
|
// delete old files (if any)
|
||||||
if err := form.processFilesToDelete(); err != nil { //nolint:staticcheck
|
//
|
||||||
// for now fail silently to avoid reupload when `form.Submit()`
|
// for now fail silently to avoid reupload when `form.Submit()`
|
||||||
// is called manually (aka. not from an api request)...
|
// is called manually (aka. not from an api request)...
|
||||||
|
if err := form.processFilesToDelete(); err != nil && form.app.IsDebug() {
|
||||||
|
log.Println(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
|
||||||
}, interceptors...)
|
}, interceptors...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (form *RecordUpsert) getFilesToUploadNames() []string {
|
||||||
|
names := []string{}
|
||||||
|
|
||||||
|
for fieldKey := range form.filesToUpload {
|
||||||
|
for _, file := range form.filesToUpload[fieldKey] {
|
||||||
|
names = append(names, file.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return names
|
||||||
|
}
|
||||||
|
|
||||||
func (form *RecordUpsert) processFilesToUpload() error {
|
func (form *RecordUpsert) processFilesToUpload() error {
|
||||||
if len(form.filesToUpload) == 0 {
|
if len(form.filesToUpload) == 0 {
|
||||||
return nil // no parsed file fields
|
return nil // no parsed file fields
|
||||||
}
|
}
|
||||||
|
|
||||||
if !form.record.HasId() {
|
if !form.record.HasId() {
|
||||||
return errors.New("The record is not persisted yet.")
|
return errors.New("the record is not persisted yet")
|
||||||
}
|
}
|
||||||
|
|
||||||
fs, err := form.app.NewFilesystem()
|
fs, err := form.app.NewFilesystem()
|
||||||
@ -685,65 +707,75 @@ func (form *RecordUpsert) processFilesToUpload() error {
|
|||||||
}
|
}
|
||||||
defer fs.Close()
|
defer fs.Close()
|
||||||
|
|
||||||
var uploadErrors []error
|
var uploadErrors []error // list of upload errors
|
||||||
|
var uploaded []string // list of uploaded file paths
|
||||||
|
|
||||||
for fieldKey := range form.filesToUpload {
|
for fieldKey := range form.filesToUpload {
|
||||||
for i := len(form.filesToUpload[fieldKey]) - 1; i >= 0; i-- {
|
for i, file := range form.filesToUpload[fieldKey] {
|
||||||
file := form.filesToUpload[fieldKey][i]
|
|
||||||
path := form.record.BaseFilesPath() + "/" + file.Name()
|
path := form.record.BaseFilesPath() + "/" + file.Name()
|
||||||
|
|
||||||
if err := fs.UploadMultipart(file.Header(), path); err == nil {
|
if err := fs.UploadMultipart(file.Header(), path); err == nil {
|
||||||
// remove the uploaded file from the list
|
// keep track of the already uploaded file
|
||||||
form.filesToUpload[fieldKey] = append(form.filesToUpload[fieldKey][:i], form.filesToUpload[fieldKey][i+1:]...)
|
uploaded = append(uploaded, path)
|
||||||
} else {
|
} else {
|
||||||
// store the upload error
|
// store the upload error
|
||||||
uploadErrors = append(uploadErrors, fmt.Errorf("File %d: %v", i, err))
|
uploadErrors = append(uploadErrors, fmt.Errorf("file %d: %v", i, err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(uploadErrors) > 0 {
|
if len(uploadErrors) > 0 {
|
||||||
return fmt.Errorf("Failed to upload all files: %v", uploadErrors)
|
// cleanup - try to delete the successfully uploaded files (if any)
|
||||||
|
form.deleteFilesByNamesList(uploaded)
|
||||||
|
|
||||||
|
return fmt.Errorf("failed to upload all files: %v", uploadErrors)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (form *RecordUpsert) processFilesToDelete() error {
|
func (form *RecordUpsert) processFilesToDelete() (err error) {
|
||||||
if len(form.filesToDelete) == 0 {
|
form.filesToDelete, err = form.deleteFilesByNamesList(form.filesToDelete)
|
||||||
return nil // nothing to delete
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteFiles deletes a list of record files by their names.
|
||||||
|
// Returns the failed/remaining files.
|
||||||
|
func (form *RecordUpsert) deleteFilesByNamesList(filenames []string) ([]string, error) {
|
||||||
|
if len(filenames) == 0 {
|
||||||
|
return filenames, nil // nothing to delete
|
||||||
}
|
}
|
||||||
|
|
||||||
if !form.record.HasId() {
|
if !form.record.HasId() {
|
||||||
return errors.New("The record is not persisted yet.")
|
return filenames, errors.New("the record doesn't have a unique ID")
|
||||||
}
|
}
|
||||||
|
|
||||||
fs, err := form.app.NewFilesystem()
|
fs, err := form.app.NewFilesystem()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return filenames, err
|
||||||
}
|
}
|
||||||
defer fs.Close()
|
defer fs.Close()
|
||||||
|
|
||||||
var deleteErrors []error
|
var deleteErrors []error
|
||||||
for i := len(form.filesToDelete) - 1; i >= 0; i-- {
|
|
||||||
filename := form.filesToDelete[i]
|
for i := len(filenames) - 1; i >= 0; i-- {
|
||||||
|
filename := filenames[i]
|
||||||
path := form.record.BaseFilesPath() + "/" + filename
|
path := form.record.BaseFilesPath() + "/" + filename
|
||||||
|
|
||||||
if err := fs.Delete(path); err == nil {
|
if err := fs.Delete(path); err == nil {
|
||||||
// remove the deleted file from the list
|
// remove the deleted file from the list
|
||||||
form.filesToDelete = append(form.filesToDelete[:i], form.filesToDelete[i+1:]...)
|
filenames = append(filenames[:i], filenames[i+1:]...)
|
||||||
} else {
|
|
||||||
// store the delete error
|
|
||||||
deleteErrors = append(deleteErrors, fmt.Errorf("File %d: %v", i, err))
|
|
||||||
}
|
|
||||||
|
|
||||||
// try to delete the related file thumbs (if any)
|
// try to delete the related file thumbs (if any)
|
||||||
fs.DeletePrefix(form.record.BaseFilesPath() + "/thumbs_" + filename + "/")
|
fs.DeletePrefix(form.record.BaseFilesPath() + "/thumbs_" + filename + "/")
|
||||||
|
} else {
|
||||||
|
// store the delete error
|
||||||
|
deleteErrors = append(deleteErrors, fmt.Errorf("file %d: %v", i, err))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(deleteErrors) > 0 {
|
if len(deleteErrors) > 0 {
|
||||||
return fmt.Errorf("Failed to delete all files: %v", deleteErrors)
|
return filenames, fmt.Errorf("failed to delete all files: %v", deleteErrors)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return filenames, nil
|
||||||
}
|
}
|
||||||
|
@ -176,13 +176,11 @@ func (s *System) DeletePrefix(prefix string) []error {
|
|||||||
dirsMap := map[string]struct{}{}
|
dirsMap := map[string]struct{}{}
|
||||||
dirsMap[prefix] = struct{}{}
|
dirsMap[prefix] = struct{}{}
|
||||||
|
|
||||||
opts := blob.ListOptions{
|
|
||||||
Prefix: prefix,
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete all files with the prefix
|
// delete all files with the prefix
|
||||||
// ---
|
// ---
|
||||||
iter := s.bucket.List(&opts)
|
iter := s.bucket.List(&blob.ListOptions{
|
||||||
|
Prefix: prefix,
|
||||||
|
})
|
||||||
for {
|
for {
|
||||||
obj, err := iter.Next(s.ctx)
|
obj, err := iter.Next(s.ctx)
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user