package services import ( "document-flow-server/internal/apperrors" "document-flow-server/internal/constants" "document-flow-server/internal/models" "document-flow-server/internal/repositories" "document-flow-server/internal/storage" "document-flow-server/internal/utils" "encoding/json" "fmt" "mime/multipart" "os" "path/filepath" ) type FileInfo struct { OldFilePath string OldDirPath string NewDirPath string NewFileName string OldJSONName string NewFilePathInOldDir string NewFilePath string OldJSONInNewPath string NewJSONFileName string } // GetTemplates получает список шаблонов: либо всех, либо с пагинацией func GetTemplates(page, limit *int) (models.TemplatesResponse, error) { if page == nil || limit == nil { return repositories.GetAllTemplates() } return repositories.GetTemplatesPaginated(*page, *limit) } // CreateTemplate создаёт новый шаблон и возвращает его с предзагруженными данными func CreateTemplate(fieldsJSON string, file *multipart.File, fileHeader *multipart.FileHeader, user *models.User) (*models.Template, error) { if !user.HasRole("admin") { return nil, apperrors.ErrForbidden } var template models.Template err := json.Unmarshal([]byte(fieldsJSON), &template) if err != nil { return nil, fmt.Errorf("недопустимый формат JSON в полях: %w", err) } template.Creator = nil // проверка на существование шаблона с таким же именем exists, err := repositories.TemplateExistsByName(template.Name) if err != nil { return nil, fmt.Errorf("ошибка при проверке существования шаблона: %w", err) } if exists { return nil, apperrors.ErrTemplateAlreadyExists } savedName := utils.ToSnakeCase(template.Name) + filepath.Ext(fileHeader.Filename) savedPath, err := storage.SaveFile(*file, savedName, filepath.Join(constants.TEMPLATES_FOLDER, utils.ToSnakeCase(template.Name))) if err != nil { return nil, err } template.FileName = savedName template.Path = savedPath createdTemplate, err := repositories.CreateTemplate(&template) if err != nil { return nil, fmt.Errorf("ошибка при сохранении шаблона в базу данных: %w", err) } fileNameJson := utils.ToSnakeCase(template.Name) + ".json" savePath := filepath.Join(constants.TEMPLATES_FOLDER, utils.ToSnakeCase(template.Name)) templateJson, err := json.MarshalIndent(template, "", " ") if err != nil { return nil, fmt.Errorf("не удалось сериализовать шаблон в JSON: %v", err) } _, err = storage.SaveFileFromBytes(templateJson, fileNameJson, savePath) if err != nil { return nil, fmt.Errorf("не удалось сохранить файл JSON: %v", err) } return createdTemplate, nil } // GetTemplateById получает шаблон по его идентификатору func GetTemplateById(id uint64) (*models.Template, error) { return repositories.GetTemplateById(id) } // UpdateTemplateById обновляет шаблон по его идентификатору func UpdateTemplateById(id uint64, fieldsJSON string, file *multipart.File, fileHeader *multipart.FileHeader, user *models.User) (*models.Template, error) { if !user.HasRole("admin") { return nil, apperrors.ErrForbidden } currentTemplate, err := repositories.GetTemplateById(id) if err != nil { return nil, err } updates := make(map[string]interface{}) // парсим JSON только если он не пустой if fieldsJSON != "" { if err := json.Unmarshal([]byte(fieldsJSON), &updates); err != nil { return nil, fmt.Errorf("неверный формат JSON: %v", err) } if markers, ok := updates["markers"].([]interface{}); ok { var strArr models.StringArray for _, m := range markers { if s, ok := m.(string); ok { strArr = append(strArr, s) } } updates["markers"] = strArr } } newName, nameUpdated := updates["name"].(string) if nameUpdated && newName != currentTemplate.Name { exists, err := repositories.TemplateExistsByName(newName) if err != nil { return nil, fmt.Errorf("ошибка проверки имени: %v", err) } if exists { return nil, apperrors.ErrTemplateAlreadyExists } } else { newName = currentTemplate.Name } var newDir, newFileName, newFilePath string if nameUpdated || file != nil { if nameUpdated { newDir = utils.ToSnakeCase(newName) } else { newDir = utils.ToSnakeCase(currentTemplate.Name) } if file != nil { newFileName = utils.ToSnakeCase(newName) + filepath.Ext(fileHeader.Filename) } else { newFileName = utils.ToSnakeCase(newName) + filepath.Ext(currentTemplate.FileName) } newFilePath = filepath.Join(constants.TEMPLATES_FOLDER, newDir, newFileName) if nameUpdated { os.MkdirAll(filepath.Join(constants.TEMPLATES_FOLDER, newDir), os.ModePerm) oldDir := filepath.Dir(currentTemplate.Path) if file != nil { savedPath, err := storage.SaveFile(*file, newFileName, filepath.Join(constants.TEMPLATES_FOLDER, newDir)) if err != nil { return nil, err } newFilePath = savedPath } else { os.Rename(currentTemplate.Path, newFilePath) oldJsonPath := filepath.Join(oldDir, utils.ToSnakeCase(currentTemplate.Name)+".json") newJsonPath := filepath.Join(constants.TEMPLATES_FOLDER, newDir, utils.ToSnakeCase(newName)+".json") os.Rename(oldJsonPath, newJsonPath) os.Remove(oldDir) } } else if file != nil { savedPath, err := storage.SaveFile(*file, newFileName, filepath.Join(constants.TEMPLATES_FOLDER, newDir)) if err != nil { return nil, err } newFilePath = savedPath } updates["path"] = newFilePath updates["file_name"] = newFileName } allowedFields := map[string]bool{"name": true, "description": true, "color": true, "markers": true, "path": true, "file_name": true} filteredUpdates := make(map[string]interface{}) for key, val := range updates { if allowedFields[key] { filteredUpdates[key] = val } } // если есть что обновлять в БД var updatedTemplate *models.Template if len(filteredUpdates) > 0 || nameUpdated || file != nil { updatedTemplate, err = repositories.UpdateTemplateById(id, filteredUpdates) if err != nil { return nil, err } } else { return currentTemplate, nil } jsonData, _ := json.MarshalIndent(updatedTemplate, "", " ") jsonFileName := utils.ToSnakeCase(updatedTemplate.Name) + ".json" jsonDir := filepath.Join(constants.TEMPLATES_FOLDER, utils.ToSnakeCase(updatedTemplate.Name)) storage.SaveFileFromBytes(jsonData, jsonFileName, jsonDir) return updatedTemplate, nil } // DeleteTemplateById удаляет шаблон по его идентификатору func DeleteTemplateById(id uint64, user *models.User) error { if !user.HasRole("admin") { return apperrors.ErrForbidden } // получение данных шаблона перед удалением (чтобы удалить файлы) //template, err := repositories.GetTemplateById(id) //if err != nil { // fmt.Printf("Шаблон с ID %d не найден", id) // return err //} if err := repositories.DeleteTemplateById(id); err != nil { return fmt.Errorf("ошибка при удалении шаблона из базы данных: %w", err) } // удаление директории шаблона с сервера // пока временно убрано, чтобы можно было редактировать шаблоны, основанные на удаленных шаблонах //dirPath := filepath.Join(constants.TEMPLATES_FOLDER, utils.ToSnakeCase(template.Name)) //err = storage.DeleteDir(dirPath, 0) //if err != nil { // fmt.Printf("Ошибка при удалении директории шаблона %s\n", err.Error()) //} return nil } // SearchTemplates универсально ищет по разным полям func SearchTemplates(query string, page, limit *int) (models.TemplatesResponse, error) { if page != nil && *page < 1 || limit != nil && *limit < 1 { return models.TemplatesResponse{}, apperrors.ErrInvalidParams.WithCustomMessage("неверные параметры пагинации. page и limit должны быть больше 0") } return repositories.SearchTemplates(query, *page, *limit) }