blob: 6693000832bc8dc6045a7c78358d0014258c26cc [file] [log] [blame]
package db
import (
"context"
"database/sql"
"fmt"
"github.com/Masterminds/semver/v3"
pb "gomodules.avm99963.com/twpt-server/api_proto"
)
func isValidSemVersion(v string) bool {
_, err := semver.NewVersion(v)
return err == nil
}
func fillRawKillSwitch(db *sql.DB, ctx context.Context, k *pb.KillSwitch, featureId int32) error {
f, err := GetFeatureById(db, ctx, featureId)
if err != nil {
return fmt.Errorf("fillRawKillSwitch: %v", err)
}
if f == nil {
return fmt.Errorf("fillRawKillSwitch: linked feature doesn't exist.")
}
k.Feature = f
rows, err := db.QueryContext(ctx, "SELECT browser FROM KillSwitch2Browser WHERE kswitch_id = ?", k.Id)
if err != nil {
return fmt.Errorf("fillRawKillSwitch: %v", err)
}
defer rows.Close()
var browsers []pb.Environment_Browser
for rows.Next() {
var b pb.Environment_Browser
if err := rows.Scan(&b); err != nil {
return fmt.Errorf("fillRawKillSwitch: %v", err)
}
browsers = append(browsers, b)
}
k.Browsers = browsers
return nil
}
func GetKillSwitchById(db *sql.DB, ctx context.Context, id int32) (*pb.KillSwitch, error) {
query := db.QueryRowContext(ctx, "SELECT kswitch_id, feat_id, min_version, max_version, active FROM KillSwitch WHERE kswitch_id = ?", id)
var featureId int32
var k pb.KillSwitch
if err := query.Scan(&k.Id, &featureId, &k.MinVersion, &k.MaxVersion, &k.Active); err != nil {
if err == sql.ErrNoRows {
return nil, nil
}
return nil, fmt.Errorf("GetKillSwitchById: %v.", err)
}
err := fillRawKillSwitch(db, ctx, &k, featureId)
if err != nil {
return nil, fmt.Errorf("GetKillSwitchById: $v.", err)
}
return &k, nil
}
func ListKillSwitches(db *sql.DB, ctx context.Context, withNonactiveKillSwitches bool) ([]*pb.KillSwitch, error) {
var rows *sql.Rows
var err error
if withNonactiveKillSwitches {
rows, err = db.QueryContext(ctx, "SELECT kswitch_id, feat_id, min_version, max_version, active FROM KillSwitch")
} else {
rows, err = db.QueryContext(ctx, "SELECT kswitch_id, feat_id, min_version, max_version, active FROM KillSwitch WHERE active <> 0")
}
if err != nil {
return nil, fmt.Errorf("ListKillSwitches: ", err)
}
defer rows.Close()
var killSwitches []*pb.KillSwitch
for rows.Next() {
var featureId int32
var k pb.KillSwitch
if err := rows.Scan(&k.Id, &featureId, &k.MinVersion, &k.MaxVersion, &k.Active); err != nil {
return nil, fmt.Errorf("ListKillSwitches: %v.", err)
}
err := fillRawKillSwitch(db, ctx, &k, featureId)
if err != nil {
return nil, fmt.Errorf("ListKillSwitches: $v.", err)
}
killSwitches = append(killSwitches, &k)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("ListKillSwitches: ", err)
}
return killSwitches, nil
}
func EnableKillSwitch(db *sql.DB, ctx context.Context, k *pb.KillSwitch, currentUser *pb.KillSwitchAuthorizedUser) error {
if k.MinVersion != "" && !isValidSemVersion(k.MinVersion) {
return fmt.Errorf("min_version is not a valid semantic version.")
}
if k.MaxVersion != "" && !isValidSemVersion(k.MaxVersion) {
return fmt.Errorf("max_version is not a valid semantic version.")
}
if k.MinVersion != "" && k.MaxVersion != "" {
minVersion, _ := semver.NewVersion(k.MinVersion)
maxVersion, _ := semver.NewVersion(k.MaxVersion)
if minVersion.GreaterThan(maxVersion) {
return fmt.Errorf("min_version must be less than max_version.")
}
}
for _, b := range k.GetBrowsers() {
if b == pb.Environment_BROWSER_UNKNOWN {
return fmt.Errorf("browsers cannot contain BROWSER_UNKNOWN.")
}
}
k.Active = true
f, err := GetFeatureById(db, ctx, k.Feature.Id)
if err != nil {
return fmt.Errorf("EnableKillSwitch: %v", err)
}
if f == nil {
return fmt.Errorf("EnableKillSwitch: this feature doesn't exist.")
}
k.Feature = f
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return err
}
result, err := tx.ExecContext(ctx, "INSERT INTO KillSwitch (feat_id, min_version, max_version, active) VALUES (?, ?, ?, ?)", k.Feature.Id, k.MinVersion, k.MaxVersion, k.Active)
if err != nil {
tx.Rollback()
return err
}
id, err := result.LastInsertId()
if err != nil {
tx.Rollback()
return err
}
k.Id = int32(id)
for _, b := range k.GetBrowsers() {
if _, err := tx.ExecContext(ctx, "INSERT INTO KillSwitch2Browser (kswitch_id, browser) VALUES (?, ?)", k.Id, b); err != nil {
tx.Rollback()
return err
}
}
logEntry := &pb.KillSwitchAuditLogEntry{
User: currentUser,
Description: &pb.KillSwitchAuditLogEntry_KillSwitchEnabled_{
&pb.KillSwitchAuditLogEntry_KillSwitchEnabled{
KillSwitch: k,
},
},
}
if err := AddKillSwitchAuditLogEntry(tx, ctx, logEntry); err != nil {
tx.Rollback()
return err
}
return tx.Commit()
}
func DisableKillSwitch(db *sql.DB, ctx context.Context, id int32, currentUser *pb.KillSwitchAuthorizedUser) error {
oldKillSwitch, err := GetKillSwitchById(db, ctx, id)
if err != nil {
return err
}
if oldKillSwitch == nil {
return fmt.Errorf("Such kill switch doesn't exist")
}
if oldKillSwitch.GetActive() != true {
return fmt.Errorf("The kill switch is not active")
}
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return err
}
newKillSwitch := *oldKillSwitch
newKillSwitch.Active = false
if _, err := tx.ExecContext(ctx, "UPDATE KillSwitch SET active = ? WHERE kswitch_id = ?", newKillSwitch.Active, id); err != nil {
tx.Rollback()
return err
}
logEntry := &pb.KillSwitchAuditLogEntry{
User: currentUser,
Description: &pb.KillSwitchAuditLogEntry_KillSwitchDisabled_{
&pb.KillSwitchAuditLogEntry_KillSwitchDisabled{
Transformation: &pb.KillSwitchTransformation{
Old: oldKillSwitch,
New: &newKillSwitch,
},
},
},
}
if err := AddKillSwitchAuditLogEntry(tx, ctx, logEntry); err != nil {
tx.Rollback()
return err
}
return tx.Commit()
}