[server] Add admin endpoint for updating feature flag

This commit is contained in:
Neeraj Gupta 2024-04-22 17:43:14 +05:30
parent 648806baa7
commit 5afc2de7bc
4 changed files with 72 additions and 10 deletions

View file

@ -194,7 +194,7 @@ func main() {
commonBillController := commonbilling.NewController(storagBonusRepo, userRepo, usageRepo)
appStoreController := controller.NewAppStoreController(defaultPlan,
billingRepo, fileRepo, userRepo, commonBillController)
remoteStoreController := &remoteStoreCtrl.Controller{Repo: remoteStoreRepository}
playStoreController := controller.NewPlayStoreController(defaultPlan,
billingRepo, fileRepo, userRepo, storagBonusRepo, commonBillController)
stripeController := controller.NewStripeController(plans, stripeClients,
@ -610,6 +610,7 @@ func main() {
UserAuthRepo: userAuthRepo,
UserController: userController,
FamilyController: familyController,
RemoteStoreController: remoteStoreController,
FileRepo: fileRepo,
StorageBonusRepo: storagBonusRepo,
BillingRepo: billingRepo,
@ -631,6 +632,7 @@ func main() {
adminAPI.PUT("/user/change-email", adminHandler.ChangeEmail)
adminAPI.DELETE("/user/delete", adminHandler.DeleteUser)
adminAPI.POST("/user/recover", adminHandler.RecoverAccount)
adminAPI.POST("/user/update-flag", adminHandler.UpdateFeatureFlag)
adminAPI.GET("/email-hash", adminHandler.GetEmailHash)
adminAPI.POST("/emails-from-hashes", adminHandler.GetEmailsFromHashes)
adminAPI.PUT("/user/subscription", adminHandler.UpdateSubscription)
@ -658,7 +660,6 @@ func main() {
privateAPI.DELETE("/authenticator/entity", authenticatorHandler.DeleteEntity)
privateAPI.GET("/authenticator/entity/diff", authenticatorHandler.GetDiff)
remoteStoreController := &remoteStoreCtrl.Controller{Repo: remoteStoreRepository}
dataCleanupController := &dataCleanupCtrl.DeleteUserCleanupController{
Repo: dataCleanupRepository,
UserRepo: userRepo,

View file

@ -14,6 +14,12 @@ type UpdateKeyValueRequest struct {
Value string `json:"value" binding:"required"`
}
type AdminUpdateKeyValueRequest struct {
UserID int64 `json:"userID" binding:"required"`
Key string `json:"key" binding:"required"`
Value string `json:"value" binding:"required"`
}
type FeatureFlagResponse struct {
EnableStripe bool `json:"enableStripe"`
// If true, the mobile client will stop using CF worker to download files
@ -33,6 +39,8 @@ const (
MapEnabled FlagKey = "mapEnabled"
FaceSearchEnabled FlagKey = "faceSearchEnabled"
PassKeyEnabled FlagKey = "passKeyEnabled"
IsInternalUser FlagKey = "internalUser"
IsBetaUser FlagKey = "betaUser"
)
func (k FlagKey) String() string {
@ -49,9 +57,20 @@ func (k FlagKey) UserEditable() bool {
}
}
func (k FlagKey) IsAdminEditable() bool {
switch k {
case RecoveryKeyVerified, MapEnabled, FaceSearchEnabled:
return false
case IsInternalUser, IsBetaUser, PassKeyEnabled:
return true
default:
return true
}
}
func (k FlagKey) IsBoolType() bool {
switch k {
case RecoveryKeyVerified, MapEnabled, FaceSearchEnabled, PassKeyEnabled:
case RecoveryKeyVerified, MapEnabled, FaceSearchEnabled, PassKeyEnabled, IsInternalUser, IsBetaUser:
return true
default:
return false

View file

@ -3,6 +3,7 @@ package api
import (
"errors"
"fmt"
"github.com/ente-io/museum/pkg/controller/remotestore"
"net/http"
"strconv"
"strings"
@ -43,6 +44,7 @@ type AdminHandler struct {
BillingController *controller.BillingController
UserController *user.UserController
FamilyController *family.Controller
RemoteStoreController *remotestore.Controller
ObjectCleanupController *controller.ObjectCleanupController
MailingListsController *controller.MailingListsController
DiscordController *discord.DiscordController
@ -260,6 +262,32 @@ func (h *AdminHandler) RemovePasskeys(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{})
}
func (h *AdminHandler) UpdateFeatureFlag(c *gin.Context) {
var request ente.AdminUpdateKeyValueRequest
if err := c.ShouldBindJSON(&request); err != nil {
handler.Error(c, stacktrace.Propagate(ente.ErrBadRequest, "Bad request"))
return
}
go h.DiscordController.NotifyAdminAction(
fmt.Sprintf("Admin (%d) updating flag:%s to val:%s for %d", auth.GetUserID(c.Request.Header), request.Key, request.Value, request.UserID))
logger := logrus.WithFields(logrus.Fields{
"user_id": request.UserID,
"admin_id": auth.GetUserID(c.Request.Header),
"req_id": requestid.Get(c),
"req_ctx": "update_feature_flag",
})
logger.Info("Start update")
err := h.RemoteStoreController.AdminInsertOrUpdate(c, request)
if err != nil {
logger.WithError(err).Error("Failed to update flag")
handler.Error(c, stacktrace.Propagate(err, ""))
return
}
logger.Info("successfully updated flag")
c.JSON(http.StatusOK, gin.H{})
}
func (h *AdminHandler) CloseFamily(c *gin.Context) {
var request ente.AdminOpsForUserRequest

View file

@ -19,13 +19,20 @@ type Controller struct {
// InsertOrUpdate the key's value
func (c *Controller) InsertOrUpdate(ctx *gin.Context, request ente.UpdateKeyValueRequest) error {
if err := _validateRequest(request); err != nil {
if err := _validateRequest(request.Key, request.Value, false); err != nil {
return err
}
userID := auth.GetUserID(ctx.Request.Header)
return c.Repo.InsertOrUpdate(ctx, userID, request.Key, request.Value)
}
func (c *Controller) AdminInsertOrUpdate(ctx *gin.Context, request ente.AdminUpdateKeyValueRequest) error {
if err := _validateRequest(request.Key, request.Value, true); err != nil {
return err
}
return c.Repo.InsertOrUpdate(ctx, request.UserID, request.Key, request.Value)
}
func (c *Controller) Get(ctx *gin.Context, req ente.GetValueRequest) (*ente.GetValueResponse, error) {
userID := auth.GetUserID(ctx.Request.Header)
value, err := c.Repo.GetValue(ctx, userID, req.Key)
@ -63,18 +70,25 @@ func (c *Controller) GetFeatureFlags(ctx *gin.Context) (*ente.FeatureFlagRespons
response.FaceSearchEnabled = value == "true"
case ente.PassKeyEnabled:
response.PassKeyEnabled = value == "true"
case ente.IsInternalUser:
response.InternalUser = value == "true"
case ente.IsBetaUser:
response.BetaUser = value == "true"
}
}
return response, nil
}
func _validateRequest(request ente.UpdateKeyValueRequest) error {
flag := ente.FlagKey(request.Key)
if !flag.UserEditable() {
return stacktrace.Propagate(ente.NewBadRequestWithMessage(fmt.Sprintf("key %s is not user editable", request.Key)), "key not user editable")
func _validateRequest(key, value string, byAdmin bool) error {
flag := ente.FlagKey(key)
if !flag.UserEditable() && !byAdmin {
return stacktrace.Propagate(ente.NewBadRequestWithMessage(fmt.Sprintf("key %s is not user editable", key)), "key not user editable")
}
if flag.IsBoolType() && request.Value != "true" && request.Value != "false" {
return stacktrace.Propagate(ente.NewBadRequestWithMessage(fmt.Sprintf("value %s is not allowed", request.Value)), "value not allowed")
if byAdmin && !flag.IsAdminEditable() {
return stacktrace.Propagate(ente.NewBadRequestWithMessage(fmt.Sprintf("key %s is not admin editable", key)), "key not admin editable")
}
if flag.IsBoolType() && value != "true" && value != "false" {
return stacktrace.Propagate(ente.NewBadRequestWithMessage(fmt.Sprintf("value %s is not allowed", value)), "value not allowed")
}
return nil
}