• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

vocdoni / saas-backend / 17557469823

08 Sep 2025 04:25PM UTC coverage: 58.777% (-0.06%) from 58.841%
17557469823

Pull #213

github

altergui
fix
Pull Request #213: api: standardize parameters ProcessID, CensusID, GroupID, JobID, UserID, BundleID

254 of 345 new or added lines in 22 files covered. (73.62%)

19 existing lines in 7 files now uncovered.

5652 of 9616 relevant lines covered (58.78%)

32.01 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.54
/objectstorage/objectstorage.go
1
package objectstorage
2

3
import (
4
        "crypto/md5"
5
        "fmt"
6
        "io"
7
        "net/http"
8
        "strings"
9

10
        lru "github.com/hashicorp/golang-lru/v2"
11
        "github.com/vocdoni/saas-backend/db"
12
        "github.com/vocdoni/saas-backend/internal"
13
)
14

15
var (
16
        // ErrorObjectNotFound is returned when the requested object is not found in storage.
17
        ErrorObjectNotFound = fmt.Errorf("object not found")
18
        // ErrorInvalidObjectID is returned when the provided object ID is invalid or empty.
19
        ErrorInvalidObjectID = fmt.Errorf("invalid object ID")
20
        // ErrorFileTypeNotSupported is returned when the file type is not in the supported types list.
21
        ErrorFileTypeNotSupported = fmt.Errorf("file type not supported")
22
)
23

24
// ObjectFileType represents the MIME type of a stored object file.
25
type ObjectFileType string
26

27
const (
28
        // FileTypeJPEG represents the JPEG image MIME type.
29
        FileTypeJPEG ObjectFileType = "image/jpeg"
30
        // FileTypePNG represents the PNG image MIME type.
31
        FileTypePNG ObjectFileType = "image/png"
32
        // FileTypeJPG represents the JPG image MIME type.
33
        FileTypeJPG ObjectFileType = "image/jpg"
34
)
35

36
// DefaultSupportedFileTypes is a map of file types that are supported by default.
37
var DefaultSupportedFileTypes = map[ObjectFileType]bool{
38
        FileTypeJPEG: true,
39
        FileTypePNG:  true,
40
        FileTypeJPG:  true,
41
}
42

43
// Config holds the configuration for the object storage client.
44
// It includes the MongoDB storage, supported file types, and server URL.
45
type Config struct {
46
        DB             *db.MongoStorage
47
        SupportedTypes []ObjectFileType
48
        ServerURL      string
49
}
50

51
// Client provides functionality for storing and retrieving objects.
52
// It uses MongoDB for storage and includes an LRU cache for improved performance.
53
type Client struct {
54
        db             *db.MongoStorage
55
        supportedTypes map[ObjectFileType]bool
56
        cache          *lru.Cache[string, db.Object]
57
        ServerURL      string
58
}
59

60
// New initializes a new ObjectStorageClient with the provided API credentials and configuration.
61
// It sets up a MinIO client and verifies the existence of the specified bucket.
62
func New(conf *Config) (*Client, error) {
4✔
63
        if conf == nil {
5✔
64
                return nil, fmt.Errorf("invalid object storage configuration")
1✔
65
        }
1✔
66
        supportedTypes := DefaultSupportedFileTypes
3✔
67
        for _, t := range conf.SupportedTypes {
5✔
68
                supportedTypes[t] = true
2✔
69
        }
2✔
70
        cache, err := lru.New[string, db.Object](256)
3✔
71
        if err != nil {
3✔
72
                return nil, fmt.Errorf("cannot create cache: %w", err)
×
73
        }
×
74
        return &Client{
3✔
75
                db:             conf.DB,
3✔
76
                supportedTypes: supportedTypes,
3✔
77
                cache:          cache,
3✔
78
                ServerURL:      conf.ServerURL,
3✔
79
        }, nil
3✔
80
}
81

82
// Get retrieves an object from storage by its ID. It first checks the cache,
83
// and if not found, retrieves it from the database. The objectID is a string
84
// that can have a directory-like notation (e.g., "folder-path/hello-world.txt").
85
// Returns the object or an error if not found or invalid.
86
func (osc *Client) Get(objectID internal.ObjectID) (*db.Object, error) {
6✔
87
        if objectID.IsZero() {
7✔
88
                return nil, ErrorInvalidObjectID
1✔
89
        }
1✔
90

91
        // check if the object is in the cache
92
        if object, ok := osc.cache.Get(objectID.String()); ok {
5✔
93
                return &object, nil
×
94
        }
×
95

96
        object, err := osc.db.Object(objectID)
5✔
97
        if err != nil {
6✔
98
                if err == db.ErrNotFound {
2✔
99
                        return nil, ErrorObjectNotFound
1✔
100
                }
1✔
101
                return nil, fmt.Errorf("error retrieving object: %w", err)
×
102
        }
103

104
        // store the object in the cache
105
        osc.cache.Add(objectID.String(), *object)
4✔
106

4✔
107
        return object, nil
4✔
108
}
109

110
// Put uploads a object image associated to a user (free-form string)
111
//
112
//        It calculates the objectID from the data and uses that as filename. It returns
113
//
114
// the URL of the uploaded object image. It stores the object in the database.
115
// If an error occurs, it returns an empty string and the error.
116
func (osc *Client) Put(data io.Reader, size int64, user string) (string, error) {
6✔
117
        // Create a buffer of the appropriate size
6✔
118
        buff := make([]byte, size)
6✔
119
        _, err := data.Read(buff)
6✔
120
        if err != nil {
8✔
121
                return "", fmt.Errorf("cannot read file %s", err.Error())
2✔
122
        }
2✔
123
        // checking the content type
124
        // so we don't allow files other than images
125
        filetype := http.DetectContentType(buff)
4✔
126
        // extract type/extesion from the filetype
4✔
127
        fileExtension := strings.Split(filetype, "/")[1]
4✔
128

4✔
129
        if !osc.supportedTypes[ObjectFileType(filetype)] {
5✔
130
                return "ObjectFileType", ErrorFileTypeNotSupported
1✔
131
        }
1✔
132

133
        objectID, err := calculateObjectID(buff)
3✔
134
        if err != nil {
3✔
135
                return "", fmt.Errorf("error calculating objectID: %w", err)
×
136
        }
×
137

138
        // store the object in the database
139
        if err := osc.db.SetObject(objectID, user, filetype, buff); err != nil {
3✔
140
                return "", fmt.Errorf("cannot set object: %w", err)
×
141
        }
×
142
        // return objectURL(osc.serverURL, objectID, fileExtension), nil
143
        return fmt.Sprintf("%s.%s", objectID, fileExtension), nil
3✔
144
}
145

146
// calculateObjectID calculates the objectID from the given data. The objectID
147
// is the first 12 bytes of the md5 hash of the data. If an error occurs, it
148
// returns an empty string and the error.
149
func calculateObjectID(data []byte) (internal.ObjectID, error) {
10✔
150
        md5hash := md5.New()
10✔
151
        if _, err := md5hash.Write(data); err != nil {
10✔
NEW
152
                return internal.NilObjectID, fmt.Errorf("cannot calculate hash: %w", err)
×
153
        }
×
154
        bhash := md5hash.Sum(nil)[:12]
10✔
155
        return internal.ObjectID(bhash), nil
10✔
156
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc