github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/web/instances/feature.go (about) 1 package instances 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 "sort" 8 9 "github.com/cozy/cozy-stack/model/feature" 10 "github.com/cozy/cozy-stack/model/instance" 11 "github.com/cozy/cozy-stack/model/instance/lifecycle" 12 "github.com/cozy/cozy-stack/pkg/config/config" 13 "github.com/cozy/cozy-stack/pkg/consts" 14 "github.com/cozy/cozy-stack/pkg/couchdb" 15 "github.com/cozy/cozy-stack/pkg/prefixer" 16 "github.com/labstack/echo/v4" 17 ) 18 19 func getFeatureFlags(c echo.Context) error { 20 inst, err := lifecycle.GetInstance(c.Param("domain")) 21 if err != nil { 22 return wrapError(err) 23 } 24 return c.JSON(http.StatusOK, inst.FeatureFlags) 25 } 26 27 func patchFeatureFlags(c echo.Context) error { 28 inst, err := lifecycle.GetInstance(c.Param("domain")) 29 if err != nil { 30 return wrapError(err) 31 } 32 var patch map[string]interface{} 33 if err := json.NewDecoder(c.Request().Body).Decode(&patch); err != nil { 34 return wrapError(err) 35 } 36 if inst.FeatureFlags == nil { 37 inst.FeatureFlags = make(map[string]interface{}) 38 } 39 for k, v := range patch { 40 if v == nil { 41 delete(inst.FeatureFlags, k) 42 } else { 43 inst.FeatureFlags[k] = v 44 } 45 } 46 if err := instance.Update(inst); err != nil { 47 return wrapError(err) 48 } 49 return c.JSON(http.StatusOK, inst.FeatureFlags) 50 } 51 52 func getFeatureSets(c echo.Context) error { 53 inst, err := lifecycle.GetInstance(c.Param("domain")) 54 if err != nil { 55 return wrapError(err) 56 } 57 return c.JSON(http.StatusOK, inst.FeatureSets) 58 } 59 60 func putFeatureSets(c echo.Context) error { 61 inst, err := lifecycle.GetInstance(c.Param("domain")) 62 if err != nil { 63 return wrapError(err) 64 } 65 var list []string 66 if err := json.NewDecoder(c.Request().Body).Decode(&list); err != nil { 67 return wrapError(err) 68 } 69 sort.Strings(list) 70 inst.FeatureSets = list 71 if err := instance.Update(inst); err != nil { 72 return wrapError(err) 73 } 74 return c.JSON(http.StatusOK, inst.FeatureSets) 75 } 76 77 func getContextFromConfig(context string) (interface{}, error) { 78 contexts := config.GetConfig().Contexts 79 80 if context != "" { 81 ctx, ok := contexts[context] 82 if ok { 83 return ctx, nil 84 } 85 } 86 87 ctx, ok := contexts[config.DefaultInstanceContext] 88 if ok && ctx != nil { 89 return ctx, nil 90 } 91 92 return nil, fmt.Errorf("Unable to get context %q from config", context) 93 } 94 95 func getFeatureConfig(c echo.Context) error { 96 context, err := getContextFromConfig(c.Param("context")) 97 if err != nil { 98 return wrapError(err) 99 } 100 ctx := context.(map[string]interface{}) 101 102 normalized := make(map[string]interface{}) 103 if m, ok := ctx["features"].(map[string]interface{}); ok { 104 for k, v := range m { 105 normalized[k] = v 106 } 107 } else if items, ok := ctx["features"].([]interface{}); ok { 108 for _, item := range items { 109 if m, ok := item.(map[string]interface{}); ok && len(m) == 1 { 110 for k, v := range m { 111 normalized[k] = v 112 } 113 } else { 114 normalized[fmt.Sprintf("%v", item)] = true 115 } 116 } 117 } 118 119 return c.JSON(http.StatusOK, normalized) 120 } 121 122 func getFeatureContext(c echo.Context) error { 123 id := fmt.Sprintf("%s.%s", consts.ContextFlagsSettingsID, c.Param("context")) 124 var flags feature.Flags 125 err := couchdb.GetDoc(prefixer.GlobalPrefixer, consts.Settings, id, &flags) 126 if err != nil && !couchdb.IsNotFoundError(err) { 127 return wrapError(err) 128 } 129 return c.JSON(http.StatusOK, flags.M) 130 } 131 132 type contextParameters struct { 133 Ratio float64 `json:"ratio"` 134 Value interface{} `json:"value"` 135 } 136 137 func patchFeatureContext(c echo.Context) error { 138 id := fmt.Sprintf("%s.%s", consts.ContextFlagsSettingsID, c.Param("context")) 139 var flags couchdb.JSONDoc 140 err := couchdb.GetDoc(prefixer.GlobalPrefixer, consts.Settings, id, &flags) 141 if err != nil && !couchdb.IsNotFoundError(err) { 142 return wrapError(err) 143 } 144 145 var patch map[string][]contextParameters 146 if err := json.NewDecoder(c.Request().Body).Decode(&patch); err != nil { 147 return wrapError(err) 148 } 149 if flags.M == nil { 150 flags.M = make(map[string]interface{}) 151 } 152 flags.Type = consts.Settings 153 flags.SetID(id) 154 for k, v := range patch { 155 if len(v) == 0 { 156 delete(flags.M, k) 157 } else { 158 flags.M[k] = v 159 } 160 } 161 if err := couchdb.Upsert(prefixer.GlobalPrefixer, &flags); err != nil { 162 return wrapError(err) 163 } 164 165 delete(flags.M, "_id") 166 delete(flags.M, "_rev") 167 return c.JSON(http.StatusOK, flags.M) 168 } 169 170 func getFeatureDefaults(c echo.Context) error { 171 var defaults feature.Flags 172 err := couchdb.GetDoc(prefixer.GlobalPrefixer, consts.Settings, consts.DefaultFlagsSettingsID, &defaults) 173 if err != nil && !couchdb.IsNotFoundError(err) { 174 return wrapError(err) 175 } 176 return c.JSON(http.StatusOK, defaults.M) 177 } 178 179 func patchFeatureDefaults(c echo.Context) error { 180 var defaults couchdb.JSONDoc 181 err := couchdb.GetDoc(prefixer.GlobalPrefixer, consts.Settings, consts.DefaultFlagsSettingsID, &defaults) 182 if err != nil && !couchdb.IsNotFoundError(err) { 183 return wrapError(err) 184 } 185 186 var patch map[string]interface{} 187 if err := json.NewDecoder(c.Request().Body).Decode(&patch); err != nil { 188 return wrapError(err) 189 } 190 if defaults.M == nil { 191 defaults.M = make(map[string]interface{}) 192 } 193 defaults.Type = consts.Settings 194 defaults.SetID(consts.DefaultFlagsSettingsID) 195 for k, v := range patch { 196 if v == nil { 197 delete(defaults.M, k) 198 } else { 199 defaults.M[k] = v 200 } 201 } 202 if err := couchdb.Upsert(prefixer.GlobalPrefixer, &defaults); err != nil { 203 return wrapError(err) 204 } 205 206 delete(defaults.M, "_id") 207 delete(defaults.M, "_rev") 208 return c.JSON(http.StatusOK, defaults.M) 209 }