2024-07-07 22:28:19 +02:00
|
|
|
package api
|
2024-07-08 09:55:13 +02:00
|
|
|
|
|
|
|
import (
|
2024-07-10 11:11:26 +02:00
|
|
|
"encoding/json"
|
2024-07-08 13:15:04 +02:00
|
|
|
"errors"
|
2024-07-08 20:56:59 +02:00
|
|
|
"fmt"
|
2024-10-26 14:56:17 +02:00
|
|
|
"github.com/semaphoreui/semaphore/api/helpers"
|
|
|
|
"github.com/semaphoreui/semaphore/db"
|
|
|
|
"github.com/semaphoreui/semaphore/util"
|
2024-07-08 13:15:04 +02:00
|
|
|
"github.com/gorilla/context"
|
2024-07-08 09:55:13 +02:00
|
|
|
"net/http"
|
2024-07-08 20:56:59 +02:00
|
|
|
"reflect"
|
2024-07-22 13:51:29 +02:00
|
|
|
"sort"
|
2024-07-08 20:56:59 +02:00
|
|
|
"strings"
|
2024-07-08 09:55:13 +02:00
|
|
|
)
|
|
|
|
|
2024-07-08 20:56:59 +02:00
|
|
|
func structToFlatMap(obj interface{}) map[string]interface{} {
|
|
|
|
result := make(map[string]interface{})
|
|
|
|
val := reflect.ValueOf(obj)
|
|
|
|
typ := reflect.TypeOf(obj)
|
|
|
|
|
|
|
|
if typ.Kind() == reflect.Ptr {
|
|
|
|
val = val.Elem()
|
|
|
|
typ = typ.Elem()
|
|
|
|
}
|
|
|
|
|
|
|
|
if typ.Kind() != reflect.Struct {
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
// Iterate over the struct fields
|
|
|
|
for i := 0; i < val.NumField(); i++ {
|
|
|
|
field := val.Field(i)
|
|
|
|
fieldType := typ.Field(i)
|
|
|
|
jsonTag := fieldType.Tag.Get("json")
|
|
|
|
|
|
|
|
// Use the json tag if it is set, otherwise use the field name
|
|
|
|
fieldName := jsonTag
|
|
|
|
if fieldName == "" || fieldName == "-" {
|
|
|
|
fieldName = fieldType.Name
|
|
|
|
} else {
|
|
|
|
// Handle the case where the json tag might have options like `json:"name,omitempty"`
|
|
|
|
fieldName = strings.Split(fieldName, ",")[0]
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if the field is a struct itself
|
|
|
|
if field.Kind() == reflect.Struct {
|
|
|
|
// Convert nested struct to map
|
|
|
|
nestedMap := structToFlatMap(field.Interface())
|
|
|
|
// Add nested map to result with a prefixed key
|
|
|
|
for k, v := range nestedMap {
|
|
|
|
result[fieldName+"."+k] = v
|
|
|
|
}
|
2024-07-22 14:04:35 +02:00
|
|
|
} else if (field.Kind() == reflect.Ptr ||
|
|
|
|
field.Kind() == reflect.Array ||
|
|
|
|
field.Kind() == reflect.Slice ||
|
|
|
|
field.Kind() == reflect.Map) && field.IsNil() {
|
|
|
|
result[fieldName] = nil
|
2024-07-08 20:56:59 +02:00
|
|
|
} else {
|
|
|
|
result[fieldName] = field.Interface()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2024-07-08 13:15:04 +02:00
|
|
|
func validateAppID(str string) error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func appMiddleware(next http.Handler) http.Handler {
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
appID, err := helpers.GetStrParam("app_id", w, r)
|
|
|
|
if err != nil {
|
|
|
|
helpers.WriteErrorStatus(w, err.Error(), http.StatusBadRequest)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := validateAppID(appID); err != nil {
|
|
|
|
helpers.WriteErrorStatus(w, err.Error(), http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
context.Set(r, "app_id", appID)
|
|
|
|
next.ServeHTTP(w, r)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-07-08 09:55:13 +02:00
|
|
|
func getApps(w http.ResponseWriter, r *http.Request) {
|
2024-07-22 13:51:29 +02:00
|
|
|
|
2024-07-08 09:55:13 +02:00
|
|
|
type app struct {
|
2024-07-22 13:51:29 +02:00
|
|
|
util.App
|
|
|
|
ID string `json:"id"`
|
2024-07-08 09:55:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
apps := make([]app, 0)
|
|
|
|
|
2024-07-10 14:35:21 +02:00
|
|
|
for k, a := range util.Config.Apps {
|
2024-07-08 09:55:13 +02:00
|
|
|
|
|
|
|
apps = append(apps, app{
|
2024-07-22 13:51:29 +02:00
|
|
|
App: a,
|
|
|
|
ID: k,
|
2024-07-08 09:55:13 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-07-22 13:51:29 +02:00
|
|
|
sort.Slice(apps, func(i, j int) bool {
|
|
|
|
return apps[i].Priority > apps[j].Priority
|
|
|
|
})
|
|
|
|
|
2024-07-08 09:55:13 +02:00
|
|
|
helpers.WriteJSON(w, http.StatusOK, apps)
|
|
|
|
}
|
2024-07-08 11:45:18 +02:00
|
|
|
|
|
|
|
func getApp(w http.ResponseWriter, r *http.Request) {
|
2024-07-08 13:15:04 +02:00
|
|
|
appID := context.Get(r, "app_id").(string)
|
|
|
|
|
|
|
|
app, ok := util.Config.Apps[appID]
|
|
|
|
if !ok {
|
|
|
|
helpers.WriteErrorStatus(w, "app not found", http.StatusNotFound)
|
|
|
|
return
|
|
|
|
}
|
2024-07-08 11:45:18 +02:00
|
|
|
|
2024-07-08 13:15:04 +02:00
|
|
|
helpers.WriteJSON(w, http.StatusOK, app)
|
|
|
|
}
|
|
|
|
|
|
|
|
func deleteApp(w http.ResponseWriter, r *http.Request) {
|
|
|
|
appID := context.Get(r, "app_id").(string)
|
|
|
|
|
|
|
|
store := helpers.Store(r)
|
|
|
|
|
|
|
|
err := store.DeleteOptions("apps." + appID)
|
|
|
|
if err != nil && !errors.Is(err, db.ErrNotFound) {
|
|
|
|
helpers.WriteErrorStatus(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-07-09 12:37:47 +02:00
|
|
|
delete(util.Config.Apps, appID)
|
|
|
|
|
2024-07-08 13:15:04 +02:00
|
|
|
w.WriteHeader(http.StatusNoContent)
|
2024-07-08 11:45:18 +02:00
|
|
|
}
|
|
|
|
|
2024-07-09 10:57:33 +02:00
|
|
|
func setAppOption(store db.Store, appID string, field string, val interface{}) error {
|
|
|
|
key := "apps." + appID + "." + field
|
|
|
|
|
2024-07-22 14:04:35 +02:00
|
|
|
if val == nil {
|
|
|
|
return store.DeleteOptions(key)
|
|
|
|
}
|
|
|
|
|
2024-07-09 10:57:33 +02:00
|
|
|
v := fmt.Sprintf("%v", val)
|
|
|
|
|
|
|
|
if err := store.SetOption(key, v); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
opts := make(map[string]string)
|
|
|
|
opts[key] = v
|
|
|
|
|
|
|
|
options := db.ConvertFlatToNested(opts)
|
|
|
|
|
|
|
|
_ = db.AssignMapToStruct(options, util.Config)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-07-08 11:45:18 +02:00
|
|
|
func setApp(w http.ResponseWriter, r *http.Request) {
|
2024-07-08 20:56:59 +02:00
|
|
|
appID := context.Get(r, "app_id").(string)
|
|
|
|
|
|
|
|
store := helpers.Store(r)
|
|
|
|
|
|
|
|
var app util.App
|
|
|
|
|
|
|
|
if !helpers.Bind(w, r, &app) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
options := structToFlatMap(app)
|
|
|
|
|
|
|
|
for k, v := range options {
|
2024-07-10 11:11:26 +02:00
|
|
|
t := reflect.TypeOf(v)
|
2024-07-22 14:17:45 +02:00
|
|
|
|
|
|
|
if v != nil {
|
|
|
|
switch t.Kind() {
|
|
|
|
case reflect.String:
|
|
|
|
if v == "" {
|
|
|
|
v = nil
|
|
|
|
}
|
|
|
|
case reflect.Slice, reflect.Array:
|
|
|
|
newV, err := json.Marshal(v)
|
|
|
|
if err != nil {
|
|
|
|
helpers.WriteErrorStatus(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
v = string(newV)
|
|
|
|
if v == "[]" {
|
|
|
|
v = nil
|
|
|
|
}
|
|
|
|
default:
|
2024-07-10 11:11:26 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-09 10:57:33 +02:00
|
|
|
if err := setAppOption(store, appID, k, v); err != nil {
|
2024-07-08 20:56:59 +02:00
|
|
|
helpers.WriteErrorStatus(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
2024-07-08 13:15:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func setAppActive(w http.ResponseWriter, r *http.Request) {
|
2024-07-08 20:56:59 +02:00
|
|
|
appID := context.Get(r, "app_id").(string)
|
2024-07-08 11:45:18 +02:00
|
|
|
|
2024-07-08 20:56:59 +02:00
|
|
|
store := helpers.Store(r)
|
|
|
|
|
|
|
|
var body struct {
|
|
|
|
Active bool `json:"active"`
|
|
|
|
}
|
|
|
|
|
|
|
|
if !helpers.Bind(w, r, &body) {
|
2024-09-26 12:54:03 +02:00
|
|
|
helpers.WriteErrorStatus(w, "Invalid request body", http.StatusBadRequest)
|
2024-07-08 20:56:59 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-07-09 10:57:33 +02:00
|
|
|
if err := setAppOption(store, appID, "active", body.Active); err != nil {
|
2024-07-08 20:56:59 +02:00
|
|
|
helpers.WriteErrorStatus(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
2024-07-08 11:45:18 +02:00
|
|
|
}
|