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

goto / guardian / 17695496595

13 Sep 2025 10:46AM UTC coverage: 70.095%. First build
17695496595

push

github

rahmatrhd
feat: introduce create resource API

0 of 65 new or added lines in 5 files covered. (0.0%)

11319 of 16148 relevant lines covered (70.1%)

4.63 hits per line

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

82.71
/internal/store/postgres/resource_repository.go
1
package postgres
2

3
import (
4
        "context"
5
        "fmt"
6
        "strings"
7

8
        "github.com/google/uuid"
9
        "github.com/goto/guardian/core/resource"
10
        "github.com/goto/guardian/domain"
11
        "github.com/goto/guardian/internal/store/postgres/model"
12
        "github.com/goto/guardian/utils"
13
        "gorm.io/gorm"
14
)
15

16
var (
17
        resourcesDefaultSort = []string{
18
                "created_at:desc",
19
        }
20
)
21

22
// ResourceRepository talks to the store/database to read/insert data
23
type ResourceRepository struct {
24
        db *gorm.DB
25
}
26

27
// NewResourceRepository returns *Repository
28
func NewResourceRepository(db *gorm.DB) *ResourceRepository {
7✔
29
        return &ResourceRepository{db}
7✔
30
}
7✔
31

32
// Find records based on filters
33
func (r *ResourceRepository) Find(ctx context.Context, filter domain.ListResourcesFilter) ([]*domain.Resource, error) {
20✔
34
        if err := utils.ValidateStruct(filter); err != nil {
21✔
35
                return nil, err
1✔
36
        }
1✔
37

38
        db := r.db.WithContext(ctx)
19✔
39
        var err error
19✔
40
        db, err = applyResourceFilter(db, filter)
19✔
41
        if err != nil {
20✔
42
                return nil, err
1✔
43
        }
1✔
44
        var models []*model.Resource
18✔
45
        if err := db.Find(&models).Error; err != nil {
19✔
46
                return nil, err
1✔
47
        }
1✔
48

49
        records := []*domain.Resource{}
17✔
50
        for _, m := range models {
50✔
51
                r, err := m.ToDomain()
33✔
52
                if err != nil {
33✔
53
                        return nil, err
×
54
                }
×
55

56
                records = append(records, r)
33✔
57
        }
58

59
        return records, nil
17✔
60
}
61

62
func (r *ResourceRepository) GetResourcesTotalCount(ctx context.Context, filter domain.ListResourcesFilter) (int64, error) {
2✔
63
        db := r.db.WithContext(ctx)
2✔
64

2✔
65
        f := filter
2✔
66
        f.Size = 0
2✔
67
        f.Offset = 0
2✔
68
        var err error
2✔
69
        db, err = applyResourceFilter(db, f)
2✔
70
        if err != nil {
3✔
71
                return 0, err
1✔
72
        }
1✔
73
        var count int64
1✔
74
        err = db.Model(&model.Resource{}).Count(&count).Error
1✔
75

1✔
76
        return count, err
1✔
77
}
78

79
func applyResourceFilter(db *gorm.DB, filter domain.ListResourcesFilter) (*gorm.DB, error) {
21✔
80
        if filter.Q != "" {
25✔
81
                // NOTE: avoid adding conditions before this grouped where clause.
4✔
82
                // Otherwise, it will be wrapped in parentheses and the query will be invalid.
4✔
83
                db = db.Where(db.
4✔
84
                        Where(`"urn" ILIKE ?`, fmt.Sprintf("%%%s%%", filter.Q)).
4✔
85
                        Or(`"name" ILIKE ?`, fmt.Sprintf("%%%s%%", filter.Q)).
4✔
86
                        Or(`"global_urn" ILIKE ?`, fmt.Sprintf("%%%s%%", filter.Q)),
4✔
87
                )
4✔
88
        }
4✔
89
        if filter.IDs != nil {
23✔
90
                db = db.Where(filter.IDs)
2✔
91
        }
2✔
92
        if !filter.IsDeleted {
42✔
93
                db = db.Where(`"is_deleted" = ?`, filter.IsDeleted)
21✔
94
        }
21✔
95
        if filter.ResourceType != "" {
22✔
96
                db = db.Where(`"type" = ?`, filter.ResourceType)
1✔
97
        }
1✔
98
        if filter.Name != "" {
22✔
99
                db = db.Where(`"name" = ?`, filter.Name)
1✔
100
        }
1✔
101
        if filter.ProviderType != "" {
22✔
102
                db = db.Where(`"provider_type" = ?`, filter.ProviderType)
1✔
103
        }
1✔
104
        if filter.ProviderURN != "" {
22✔
105
                db = db.Where(`"provider_urn" = ?`, filter.ProviderURN)
1✔
106
        }
1✔
107
        if filter.ResourceURN != "" {
22✔
108
                db = db.Where(`"urn" = ?`, filter.ResourceURN)
1✔
109
        }
1✔
110
        if filter.ResourceURNs != nil {
22✔
111
                db = db.Where(`"urn" IN ?`, filter.ResourceURNs)
1✔
112
        }
1✔
113
        if filter.ResourceTypes != nil {
22✔
114
                db = db.Where(`"type" IN ?`, filter.ResourceTypes)
1✔
115
        }
1✔
116

117
        if filter.Size > 0 {
24✔
118
                db = db.Limit(int(filter.Size))
3✔
119
        }
3✔
120

121
        if filter.Offset > 0 {
22✔
122
                db = db.Offset(int(filter.Offset))
1✔
123
        }
1✔
124

125
        var sortOrder []string
21✔
126

21✔
127
        if filter.Offset >= 0 {
42✔
128
                sortOrder = resourcesDefaultSort
21✔
129
        }
21✔
130

131
        if filter.OrderBy != nil {
25✔
132
                sortOrder = filter.OrderBy
4✔
133
        }
4✔
134

135
        if len(sortOrder) != 0 {
42✔
136
                var err error
21✔
137
                db, err = addOrderByClause(db, sortOrder, addOrderByClauseOptions{
21✔
138
                        statusColumnName: "",
21✔
139
                        statusesOrder:    []string{},
21✔
140
                        searchQuery:      filter.Q,
21✔
141
                },
21✔
142
                        []string{"updated_at", "created_at", "name", "urn", "global_urn"})
21✔
143

21✔
144
                if err != nil {
23✔
145
                        return nil, err
2✔
146
                }
2✔
147
        }
148

149
        for path, v := range filter.Details {
20✔
150
                pathArr := "{" + strings.Join(strings.Split(path, "."), ",") + "}"
1✔
151
                db = db.Where(`"details" #>> ? = ?`, pathArr, v)
1✔
152
        }
1✔
153
        return db, nil
19✔
154
}
155

156
// GetOne record by ID
157
func (r *ResourceRepository) GetOne(ctx context.Context, id string) (*domain.Resource, error) {
4✔
158
        if id == "" {
5✔
159
                return nil, resource.ErrEmptyIDParam
1✔
160
        }
1✔
161

162
        whereClause := `"id" = ?`
3✔
163
        if _, err := uuid.Parse(id); err != nil {
3✔
164
                whereClause = `"global_urn" = ?`
×
165
        }
×
166

167
        var m model.Resource
3✔
168
        if err := r.db.WithContext(ctx).Where(whereClause, id).Take(&m).Error; err != nil {
4✔
169
                if err == gorm.ErrRecordNotFound {
2✔
170
                        return nil, resource.ErrRecordNotFound
1✔
171
                }
1✔
172
                return nil, err
×
173
        }
174

175
        res, err := m.ToDomain()
2✔
176
        if err != nil {
2✔
177
                return nil, err
×
178
        }
×
179

180
        return res, nil
2✔
181
}
182

NEW
183
func (r *ResourceRepository) Create(ctx context.Context, resource *domain.Resource) error {
×
NEW
184
        m := new(model.Resource)
×
NEW
185
        if err := m.FromDomain(resource); err != nil {
×
NEW
186
                return fmt.Errorf("failed to convert to db model: %w", err)
×
NEW
187
        }
×
188

NEW
189
        return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
×
NEW
190
                if result := tx.Create(m); result.Error != nil {
×
NEW
191
                        return result.Error
×
NEW
192
                }
×
193

NEW
194
                newResource, err := m.ToDomain()
×
NEW
195
                if err != nil {
×
NEW
196
                        return err
×
NEW
197
                }
×
198

NEW
199
                *resource = *newResource
×
NEW
200

×
NEW
201
                return nil
×
202
        })
203
}
204

205
// BulkUpsert inserts records if the records are not exist, or updates the records if they are already exist
206
func (r *ResourceRepository) BulkUpsert(ctx context.Context, resources []*domain.Resource) error {
16✔
207
        var models []*model.Resource
16✔
208
        for _, r := range resources {
42✔
209
                m := new(model.Resource)
26✔
210
                if err := m.FromDomain(r); err != nil {
27✔
211
                        return err
1✔
212
                }
1✔
213

214
                models = append(models, m)
25✔
215
        }
216

217
        if len(models) > 0 {
29✔
218
                return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
28✔
219
                        // upsert clause is moved to model.Resource.BeforeCreate() (gorm's hook) to apply the same for associations (model.Resource.Children)
14✔
220
                        if err := r.db.
14✔
221
                                Session(&gorm.Session{CreateBatchSize: 1000}).
14✔
222
                                Create(models).Error; err != nil {
14✔
223
                                return err
×
224
                        }
×
225

226
                        for i, m := range models {
39✔
227
                                r, err := m.ToDomain()
25✔
228
                                if err != nil {
25✔
229
                                        return err
×
230
                                }
×
231
                                *resources[i] = *r
25✔
232
                        }
233

234
                        return nil
14✔
235
                })
236
        }
237

238
        return nil
1✔
239
}
240

241
// Update record by ID
242
func (r *ResourceRepository) Update(ctx context.Context, res *domain.Resource) error {
3✔
243
        if res.ID == "" {
4✔
244
                return resource.ErrEmptyIDParam
1✔
245
        }
1✔
246

247
        m := new(model.Resource)
2✔
248
        if err := m.FromDomain(res); err != nil {
3✔
249
                return err
1✔
250
        }
1✔
251

252
        return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
2✔
253
                if err := tx.Model(m).Where(`"id" = ?`, res.ID).Updates(*m).Error; err != nil {
1✔
254
                        return err
×
255
                }
×
256

257
                newRecord, err := m.ToDomain()
1✔
258
                if err != nil {
1✔
259
                        return err
×
260
                }
×
261

262
                *res = *newRecord
1✔
263

1✔
264
                return nil
1✔
265
        })
266
}
267

268
func (r *ResourceRepository) Delete(ctx context.Context, id string) error {
3✔
269
        if id == "" {
4✔
270
                return resource.ErrEmptyIDParam
1✔
271
        }
1✔
272
        whereClause := `"id" = ?`
2✔
273
        if _, err := uuid.Parse(id); err != nil {
2✔
274
                whereClause = `"global_urn" = ?`
×
275
        }
×
276

277
        result := r.db.WithContext(ctx).Where(whereClause, id).Delete(&model.Resource{})
2✔
278
        if result.Error != nil {
2✔
279
                return result.Error
×
280
        }
×
281
        if result.RowsAffected == 0 {
3✔
282
                return resource.ErrRecordNotFound
1✔
283
        }
1✔
284

285
        return nil
1✔
286
}
287

288
func (r *ResourceRepository) BatchDelete(ctx context.Context, ids []string) error {
3✔
289
        if ids == nil {
4✔
290
                return resource.ErrEmptyIDParam
1✔
291
        }
1✔
292

293
        result := r.db.WithContext(ctx).Delete(&model.Resource{}, ids)
2✔
294
        if result.Error != nil {
2✔
295
                return result.Error
×
296
        }
×
297
        if result.RowsAffected == 0 {
3✔
298
                return resource.ErrRecordNotFound
1✔
299
        }
1✔
300

301
        return nil
1✔
302
}
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