github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/pkg/couchdb/changes.go (about) 1 package couchdb 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "io" 8 "net/http" 9 10 "github.com/cozy/cozy-stack/pkg/prefixer" 11 "github.com/google/go-querystring/query" 12 ) 13 14 // ChangesFeedMode is a value for the feed parameter of a ChangesRequest 15 type ChangesFeedMode string 16 17 // ChangesFeedStyle is a value for the style parameter of a ChangesRequest 18 type ChangesFeedStyle string 19 20 const ( 21 // ChangesModeNormal is the only mode supported by cozy-stack 22 ChangesModeNormal ChangesFeedMode = "normal" 23 // ChangesStyleAllDocs pass all revisions including conflicts 24 ChangesStyleAllDocs ChangesFeedStyle = "all_docs" 25 // ChangesStyleMainOnly only pass the winning revision 26 ChangesStyleMainOnly ChangesFeedStyle = "main_only" 27 ) 28 29 // ValidChangesMode convert any string into a ChangesFeedMode or gives an error 30 // if the string is invalid. 31 func ValidChangesMode(feed string) (ChangesFeedMode, error) { 32 if feed == "" || feed == string(ChangesModeNormal) { 33 return ChangesModeNormal, nil 34 } 35 36 err := fmt.Errorf("Unsuported feed value '%s'", feed) 37 return ChangesModeNormal, err 38 } 39 40 // ValidChangesStyle convert any string into a ChangesFeedStyle or gives an 41 // error if the string is invalid. 42 func ValidChangesStyle(style string) (ChangesFeedStyle, error) { 43 if style == "" || style == string(ChangesStyleMainOnly) { 44 return ChangesStyleMainOnly, nil 45 } 46 if style == string(ChangesStyleAllDocs) { 47 return ChangesStyleAllDocs, nil 48 } 49 err := fmt.Errorf("Unsuported style value '%s'", style) 50 return ChangesStyleMainOnly, err 51 } 52 53 // StaticChangesFilter checks that the filter is static (ie doesn't depend of a 54 // design doc) 55 func StaticChangesFilter(filter string) (string, error) { 56 switch filter { 57 case "", "_doc_ids", "_selector", "_design": 58 return filter, nil 59 default: 60 return "", fmt.Errorf("Unsuported filter value '%s'", filter) 61 } 62 } 63 64 // A ChangesRequest are all parameters than can be passed to a changes feed 65 type ChangesRequest struct { 66 DocType string `url:"-"` 67 // see Changes Feeds. Default is normal. 68 Feed ChangesFeedMode `url:"feed,omitempty"` 69 // Maximum period in milliseconds to wait for a change before the response 70 // is sent, even if there are no results. Only applicable for longpoll or 71 // continuous feeds. Default value is specified by httpd/changes_timeout 72 // configuration option. Note that 60000 value is also the default maximum 73 // timeout to prevent undetected dead connections. 74 Timeout int `url:"timeout,omitempty"` 75 // Period in milliseconds after which an empty line is sent in the results. 76 // Only applicable for longpoll, continuous, and eventsource feeds. Overrides 77 // any timeout to keep the feed alive indefinitely. Default is 60000. May be 78 // true to use default value. 79 Heartbeat int `url:"heartbeat,omitempty"` 80 // Includes conflicts information in response. Ignored if include_docs isn’t 81 // true. Default is false. 82 Conflicts bool `url:"conflicts,omitempty"` 83 // Return the change results in descending sequence order (most recent change 84 // first). Default is false. 85 Descending bool `url:"descending,omitempty"` 86 // Include the associated document with each result. If there are conflicts, 87 // only the winning revision is returned. Default is false. 88 IncludeDocs bool `url:"include_docs,omitempty"` 89 // Include the Base64-encoded content of attachments in the documents that 90 // are included if include_docs is true. Ignored if include_docs isn’t true. 91 // Default is false. 92 Attachments bool `url:"attachments,omitempty"` 93 // Include encoding information in attachment stubs if include_docs is true 94 // and the particular attachment is compressed. Ignored if include_docs isn’t 95 // true. Default is false. 96 AttEncodingInfo bool `url:"att_encoding_info,omitempty"` 97 // Alias of Last-Event-ID header. 98 LastEventID int `url:"last,omitempty"` 99 // Limit number of result rows to the specified value (note that using 0 here 100 // has the same effect as 1). 101 Limit int `url:"limit,omitempty"` 102 // Start the results from the change immediately after the given update 103 // sequence. Can be valid update sequence or now value. Default is 0. 104 Since string `url:"since,omitempty"` 105 // Specifies how many revisions are returned in the changes array. The 106 // default, main_only, will only return the current “winning” revision; 107 // all_docs will return all leaf revisions (including conflicts and deleted 108 // former conflicts). 109 Style ChangesFeedStyle `url:"style,omitempty"` 110 // Reference to a filter function from a design document that will filter 111 // whole stream emitting only filtered events. See the section Change 112 // Notifications in the book CouchDB The Definitive Guide for more 113 // information. 114 Filter string `url:"filter,omitempty"` 115 // Allows to use view functions as filters. Documents counted as “passed” for 116 // view filter in case if map function emits at least one record for them. 117 // See _view for more info. 118 View string `url:"view,omitempty"` 119 // SeqInterval tells CouchDB to only calculate the update seq with every 120 // Nth result returned. It is used by PouchDB replication, and helps to 121 // lower the load on a CouchDB cluster. 122 SeqInterval int `url:"seq_interval,omitempty"` 123 } 124 125 // A ChangesResponse is the response provided by a GetChanges call 126 type ChangesResponse struct { 127 LastSeq string `json:"last_seq"` // Last change update sequence 128 Pending int `json:"pending"` // Count of remaining items in the feed 129 Results []Change `json:"results"` // Changes made to a database 130 } 131 132 // A Change is an atomic change in couchdb 133 type Change struct { 134 DocID string `json:"id"` 135 Seq string `json:"seq"` 136 Doc JSONDoc `json:"doc,omitempty"` 137 Deleted bool `json:"deleted,omitempty"` 138 Changes []struct { 139 Rev string `json:"rev"` 140 } `json:"changes"` 141 } 142 143 // GetChanges returns a list of changes in couchdb 144 func GetChanges(db prefixer.Prefixer, req *ChangesRequest) (*ChangesResponse, error) { 145 if req.DocType == "" { 146 return nil, errors.New("Empty doctype in GetChanges") 147 } 148 149 v, err := query.Values(req) 150 if err != nil { 151 return nil, err 152 } 153 154 var response ChangesResponse 155 url := "_changes?" + v.Encode() 156 err = makeRequest(db, req.DocType, http.MethodGet, url, nil, &response) 157 158 if err != nil { 159 return nil, err 160 } 161 return &response, nil 162 } 163 164 // PostChanges returns a list of changes in couchdb 165 func PostChanges(db prefixer.Prefixer, req *ChangesRequest, body io.ReadCloser) (*ChangesResponse, error) { 166 var payload json.RawMessage 167 if err := json.NewDecoder(body).Decode(&payload); err != nil { 168 return nil, err 169 } 170 if err := body.Close(); err != nil { 171 return nil, err 172 } 173 174 if req.DocType == "" { 175 return nil, errors.New("Empty doctype in GetChanges") 176 } 177 178 v, err := query.Values(req) 179 if err != nil { 180 return nil, err 181 } 182 183 var response ChangesResponse 184 url := "_changes?" + v.Encode() 185 err = makeRequest(db, req.DocType, http.MethodPost, url, payload, &response) 186 187 if err != nil { 188 return nil, err 189 } 190 return &response, nil 191 }