/
reviews_service.go
167 lines (145 loc) · 5.75 KB
/
reviews_service.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
package reviews
import (
"fmt"
"github.com/gazebo-web/gz-go/v7"
"reflect"
"strconv"
"strings"
res "github.com/gazebo-web/fuel-server/bundles/common_resources"
"github.com/gazebo-web/fuel-server/bundles/users"
"github.com/gazebo-web/fuel-server/globals"
"github.com/gazebo-web/fuel-server/permissions"
"github.com/jinzhu/gorm"
)
const noFullTextSearch = ":noft:"
// Service is the main struct exported by this Reviews Service.
// It was meant as a way to structure code and help future extensions.
type Service struct {
ResourceType reflect.Type
}
// GetResourceInstance returns an instance of the type contained in ResourceType.
func (s *Service) GetResourceInstance() interface{} {
return reflect.New(s.ResourceType).Elem().Interface()
}
// GetResourceSlice returns a slice of the type contained in ResourceType.
func (s *Service) GetResourceSlice(len int, cap int) interface{} {
resourceSlice := reflect.MakeSlice(reflect.SliceOf(s.ResourceType), len, cap)
rs := reflect.New(resourceSlice.Type())
rs.Elem().Set(resourceSlice)
return rs.Interface()
}
// ReviewList returns a paginated list of reviews.
// This function returns a list of Reviews that can then be mashalled into json or protobuf.
func (s *Service) ReviewList(p *gz.PaginationRequest, tx *gorm.DB, owner *string,
order, search string, modelID *uint, user *users.User) (interface{}, *gz.PaginationResult, *gz.ErrMsg) {
resourceInstance := s.GetResourceInstance()
reviewList := s.GetResourceSlice(0, 0)
// Create query
q := tx.Model(&resourceInstance)
// Override default Order BY, unless the user explicitly requested ASC order
if !(order != "" && strings.ToLower(order) == "asc") {
// Important: you need to reassign 'q' to keep the updated query
q = q.Order("created_at desc, id", true)
}
// filter resources based on modelID, if exist
if modelID != nil {
q = QueryForModelReviews(q, *modelID)
}
// filter resources based on privacy setting
// We need filter resource based on review privacy setting
q = res.QueryForResourceVisibility(tx, q, owner, user)
// todo(anyone) check if search works
// If a search criteria was defined, then also apply a fulltext search on "review's description"
if search != "" {
// Trim leading and trailing whitespaces
searchStr := strings.TrimSpace(search)
if len(searchStr) > 0 {
// Check if the user wants a full-text search or a simple one. The simple
// search allows searching for "partial words" (eg. UI filtering while the
// user types in).
if strings.HasPrefix(searchStr, noFullTextSearch) {
searchStr = strings.TrimPrefix(searchStr, noFullTextSearch)
expanded := fmt.Sprintf("%%%s%%", searchStr)
q = q.Where("title LIKE ?", expanded)
} else {
// Note: this is a fulltext search IN NATURAL LANGUAGE MODE.
// See https://dev.mysql.com/doc/refman/5.7/en/fulltext-search.html for other
// modes, eg BOOLEAN and WITH QUERY EXPANSION modes.
q = q.Where("MATCH (title, description) AGAINST (?)", searchStr)
}
}
}
// Use pagination
paginationResult, err := gz.PaginateQuery(q, reviewList, *p)
if err != nil {
em := gz.NewErrorMessageWithBase(gz.ErrorInvalidPaginationRequest, err)
return nil, nil, em
}
if !paginationResult.PageFound {
em := gz.NewErrorMessage(gz.ErrorPaginationPageNotFound)
return nil, nil, em
}
reviewListValue := reflect.ValueOf(reviewList)
reviewListValueLen := reflect.Indirect(reviewListValue).Len()
reviewsProto := make([]interface{}, reviewListValueLen)
for i := 0; i < reviewListValueLen; i++ {
// Get the item from the slice
review := reflect.Indirect(reviewListValue).Index(i).Addr()
// Attempt to cast it
protoReview, ok := review.Interface().(Protobuffer)
// If the review cannot be cast to the interface, just fail
if !ok {
em := gz.NewErrorMessage(gz.ErrorMarshalProto)
return nil, nil, em
}
// Store the element's protobuf representation
reviewsProto[i] = protoReview.ToProto()
}
return reviewsProto, paginationResult, nil
}
// Protobuffer should be implemented by resources that have a protobuf
// representation. It provides methods to convert to a protobuf representation.
type Protobuffer interface {
// This method returns a protobuf representation of the object
// Note: consider using proto.Message interface instead of just an empty
// interface as ToProto return data type.
// https://godoc.org/github.com/golang/protobuf/proto#Message
ToProto() interface{}
}
// CreateModelReview creates a new model review
func (s *Service) CreateModelReview(cmr CreateModelReview, tx *gorm.DB, creator *users.User) (*ModelReview, *gz.ErrMsg) {
// set the owner
owner := cmr.CreateReview.Owner
if owner == "" {
owner = *creator.Username
} else {
ok, em := users.VerifyOwner(tx, owner, *creator.Username, permissions.Read)
if !ok {
return nil, em
}
}
// create the ModelReview struct
modelReview, err := NewModelReview(&cmr.CreateReview.Title, &cmr.CreateReview.Description,
&owner, cmr.CreateReview.Branch, cmr.CreateReview.Status,
cmr.CreateReview.Reviewers, cmr.CreateReview.Approvals, cmr.ModelID)
modelReview.Creator = creator.Username
if err != nil {
return nil, gz.NewErrorMessageWithBase(gz.ErrorCreatingDir, err)
}
// create model review in the DB
if err := tx.Create(&modelReview).Error; err != nil {
return nil, gz.NewErrorMessageWithBase(gz.ErrorDbSave, err)
}
// read and write permissions
// convert ID to string
modelIDStr := strconv.FormatUint(uint64(*modelReview.ModelID), 10)
_, err = globals.Permissions.AddPermission(owner, modelIDStr, permissions.Read)
if err != nil {
return nil, gz.NewErrorMessageWithBase(gz.ErrorUnexpected, err)
}
_, err = globals.Permissions.AddPermission(owner, modelIDStr, permissions.Write)
if err != nil {
return nil, gz.NewErrorMessageWithBase(gz.ErrorUnexpected, err)
}
return &modelReview, nil
}