package files import ( "fmt" "io" "os" "path/filepath" "strings" "time" ) type Service struct { root string } type StoredFile struct { OriginalName string RelativePath string AbsolutePath string } func NewService(root string) *Service { return &Service{root: root} } func (s *Service) Save(projectID uint, originalName string, content io.Reader) (StoredFile, error) { cleanName := filepath.Base(strings.ReplaceAll(strings.TrimSpace(originalName), "\\", "/")) if cleanName == "." || cleanName == "" { cleanName = "upload.bin" } relative := filepath.ToSlash(filepath.Join("projects", fmt.Sprint(projectID), fmt.Sprintf("%d-%s", time.Now().UnixNano(), cleanName))) absolute := filepath.Join(s.root, filepath.FromSlash(relative)) if err := os.MkdirAll(filepath.Dir(absolute), 0o755); err != nil { return StoredFile{}, err } file, err := os.Create(absolute) if err != nil { return StoredFile{}, err } defer file.Close() if _, err := io.Copy(file, content); err != nil { return StoredFile{}, err } return StoredFile{OriginalName: cleanName, RelativePath: relative, AbsolutePath: absolute}, nil }