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  }