lot of new functionalities
This commit is contained in:
74
core/utils/cache/cache.go
vendored
Normal file
74
core/utils/cache/cache.go
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/storage"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type CacheService struct {
|
||||
cache storage.CacheHandler
|
||||
}
|
||||
|
||||
func NewCacheService(cache storage.CacheHandler) *CacheService {
|
||||
return &CacheService{cache: cache}
|
||||
}
|
||||
|
||||
type GetCacheResult struct {
|
||||
Data []byte
|
||||
}
|
||||
|
||||
func (s *CacheService) GetCacheData(cacheID string, limitsMin, limitsMax *int) (*GetCacheResult, error) {
|
||||
d, err := s.cache.Get(cacheID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var data []any
|
||||
if val, ok := d.([]any); ok {
|
||||
data = val
|
||||
} else {
|
||||
data = []any{d}
|
||||
}
|
||||
|
||||
result := data
|
||||
if limitsMin != nil {
|
||||
min := *limitsMin
|
||||
if limitsMax != nil {
|
||||
max := *limitsMax
|
||||
if max > len(data) {
|
||||
result = data[min:]
|
||||
} else {
|
||||
result = data[min:max]
|
||||
}
|
||||
} else {
|
||||
result = data[min:]
|
||||
}
|
||||
}
|
||||
|
||||
j, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &GetCacheResult{
|
||||
Data: j,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ParseLimits(limitsMinStr, limitsMaxStr string) (limitsMin, limitsMax *int) {
|
||||
if limitsMinStr != "" {
|
||||
if min, err := strconv.Atoi(limitsMinStr); err == nil {
|
||||
limitsMin = &min
|
||||
}
|
||||
}
|
||||
if limitsMaxStr != "" {
|
||||
if max, err := strconv.Atoi(limitsMaxStr); err == nil {
|
||||
limitsMax = &max
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
14
core/utils/form-validators/form-validators.go
Executable file
14
core/utils/form-validators/form-validators.go
Executable file
@@ -0,0 +1,14 @@
|
||||
package formvalidators
|
||||
|
||||
import "github.com/go-playground/validator/v10"
|
||||
|
||||
type FormValidator struct {
|
||||
*validator.Validate
|
||||
}
|
||||
|
||||
func New() (f FormValidator) {
|
||||
validate := validator.New()
|
||||
validate.RegisterValidation("phoneNumber", PhoneNumber)
|
||||
f.Validate = validate
|
||||
return
|
||||
}
|
||||
15
core/utils/form-validators/phone-numbers.go
Executable file
15
core/utils/form-validators/phone-numbers.go
Executable file
@@ -0,0 +1,15 @@
|
||||
package formvalidators
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
const phoneNumberRegexStreing = "^((\\+)33|0)[1-9](\\d{2}){4}$"
|
||||
|
||||
var phoneNumberRegex = regexp.MustCompile(phoneNumberRegexStreing)
|
||||
|
||||
func PhoneNumber(fl validator.FieldLevel) bool {
|
||||
return phoneNumberRegex.MatchString(fl.Field().String())
|
||||
}
|
||||
17
core/utils/gender/gender.go
Normal file
17
core/utils/gender/gender.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package gender
|
||||
|
||||
// ISO5218ToString converts ISO 5218 gender codes to French text labels
|
||||
func ISO5218ToString(value string) string {
|
||||
switch value {
|
||||
case "0":
|
||||
return "Inconnu"
|
||||
case "1":
|
||||
return "Masculin"
|
||||
case "2":
|
||||
return "Féminin"
|
||||
case "9":
|
||||
return "Sans objet"
|
||||
default:
|
||||
return value
|
||||
}
|
||||
}
|
||||
50
core/utils/geo/geo.go
Normal file
50
core/utils/geo/geo.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package geo
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type GeoService struct {
|
||||
peliasURL string
|
||||
}
|
||||
|
||||
func NewGeoService(peliasURL string) *GeoService {
|
||||
return &GeoService{peliasURL: peliasURL}
|
||||
}
|
||||
|
||||
type AutocompleteResult struct {
|
||||
Features []any
|
||||
}
|
||||
|
||||
func (s *GeoService) Autocomplete(text string) (*AutocompleteResult, error) {
|
||||
resp, err := http.Get(fmt.Sprintf("%s/autocomplete?text=%s", s.peliasURL, text))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to read response body")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var response map[string]any
|
||||
if err := json.Unmarshal(body, &response); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
features, ok := response["features"].([]any)
|
||||
if !ok {
|
||||
features = []any{}
|
||||
}
|
||||
|
||||
return &AutocompleteResult{
|
||||
Features: features,
|
||||
}, nil
|
||||
}
|
||||
26
core/utils/icons/svg-icons.go
Executable file
26
core/utils/icons/svg-icons.go
Executable file
@@ -0,0 +1,26 @@
|
||||
package icons
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
)
|
||||
|
||||
type IconSet struct {
|
||||
Icons map[string]string
|
||||
}
|
||||
|
||||
func NewIconSet(set map[string]string) IconSet {
|
||||
return IconSet{
|
||||
Icons: set,
|
||||
}
|
||||
}
|
||||
|
||||
func (i IconSet) Icon(name string, classes string) template.HTML {
|
||||
icon, ok := i.Icons[name]
|
||||
|
||||
if !ok {
|
||||
return template.HTML("")
|
||||
}
|
||||
|
||||
return template.HTML(fmt.Sprintf(icon, classes))
|
||||
}
|
||||
75
core/utils/identification/groups.go
Executable file
75
core/utils/identification/groups.go
Executable file
@@ -0,0 +1,75 @@
|
||||
package identification
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
groupsmanagement "git.coopgo.io/coopgo-platform/groups-management/grpcapi"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const GroupKey ContextKey = "group"
|
||||
const RolesKey ContextKey = "roles"
|
||||
|
||||
func (p *IdentificationProvider) GroupsMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
claims := r.Context().Value(ClaimsKey).(map[string]any)
|
||||
|
||||
session, _ := p.SessionsStore.Get(r, "parcoursmob_session")
|
||||
|
||||
o, ok := session.Values["organization"]
|
||||
if !ok || o == nil {
|
||||
http.Redirect(w, r, "/auth/groups/", http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
org := o.(string)
|
||||
|
||||
claimgroups, ok := claims["groups"].([]any)
|
||||
|
||||
if !ok {
|
||||
log.Error().Msg("cast issue")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
for _, group := range claimgroups {
|
||||
if group == org {
|
||||
|
||||
request := &groupsmanagement.GetGroupRequest{
|
||||
Id: group.(string),
|
||||
}
|
||||
|
||||
resp, err := p.Services.GRPC.GroupsManagement.GetGroup(context.TODO(), request)
|
||||
if err != nil {
|
||||
delete(session.Values, "organization")
|
||||
session.Save(r, w)
|
||||
http.Redirect(w, r, "/auth/groups/", http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), GroupKey, resp.Group.ToStorageType())
|
||||
|
||||
roles := map[string]bool{}
|
||||
|
||||
for _, role := range claimgroups {
|
||||
//TODO handle flexible roles / roles discovery
|
||||
if role == fmt.Sprintf("%s:admin", org) {
|
||||
roles[role.(string)] = true
|
||||
}
|
||||
}
|
||||
|
||||
ctx = context.WithValue(ctx, RolesKey, roles)
|
||||
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Session organization is not in the available groups
|
||||
delete(session.Values, "organization")
|
||||
session.Save(r, w)
|
||||
http.Redirect(w, r, "/auth/groups/", http.StatusFound)
|
||||
})
|
||||
}
|
||||
141
core/utils/identification/oidc.go
Executable file
141
core/utils/identification/oidc.go
Executable file
@@ -0,0 +1,141 @@
|
||||
package identification
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/services"
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/storage"
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"github.com/gorilla/sessions"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/viper"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type ContextKey string
|
||||
|
||||
const IdtokenKey ContextKey = "idtoken"
|
||||
const ClaimsKey ContextKey = "claims"
|
||||
|
||||
type IdentificationProvider struct {
|
||||
SessionsStore sessions.Store
|
||||
Provider *oidc.Provider
|
||||
OAuth2Config oauth2.Config
|
||||
TokenVerifier *oidc.IDTokenVerifier
|
||||
Services *services.ServicesHandler
|
||||
}
|
||||
|
||||
func NewIdentificationProvider(cfg *viper.Viper, services *services.ServicesHandler, kv storage.KVHandler) (*IdentificationProvider, error) {
|
||||
var (
|
||||
providerURL = cfg.GetString("identification.oidc.provider")
|
||||
clientID = cfg.GetString("identification.oidc.client_id")
|
||||
clientSecret = cfg.GetString("identification.oidc.client_secret")
|
||||
redirectURL = cfg.GetString("identification.oidc.redirect_url")
|
||||
sessionsSecret = cfg.GetString("identification.sessions.secret")
|
||||
)
|
||||
|
||||
provider, err := oidc.NewProvider(context.Background(), providerURL)
|
||||
if err != nil {
|
||||
var (
|
||||
issuerUrl = cfg.GetString("identification.oidc.provider_config.issuer_url")
|
||||
authUrl = cfg.GetString("identification.oidc.provider_config.auth_url")
|
||||
tokenUrl = cfg.GetString("identification.oidc.provider_config.token_url")
|
||||
userInfoUrl = cfg.GetString("identification.oidc.provider_config.user_info_url")
|
||||
jwksUrl = cfg.GetString("identification.oidc.provider_config.jwks_url")
|
||||
algorithms = []string{"RS256"}
|
||||
)
|
||||
if issuerUrl == "" || authUrl == "" || tokenUrl == "" || jwksUrl == "" {
|
||||
return nil, err
|
||||
}
|
||||
providerConfig := oidc.ProviderConfig{
|
||||
IssuerURL: issuerUrl,
|
||||
AuthURL: authUrl,
|
||||
TokenURL: tokenUrl,
|
||||
UserInfoURL: userInfoUrl,
|
||||
JWKSURL: jwksUrl,
|
||||
Algorithms: algorithms,
|
||||
}
|
||||
|
||||
provider = providerConfig.NewProvider(context.Background())
|
||||
}
|
||||
|
||||
oauth2Config := oauth2.Config{
|
||||
ClientID: clientID,
|
||||
ClientSecret: clientSecret,
|
||||
RedirectURL: redirectURL,
|
||||
|
||||
// Discovery returns the OAuth2 endpoints.
|
||||
Endpoint: provider.Endpoint(),
|
||||
|
||||
// "openid" is a required scope for OpenID Connect flows.
|
||||
Scopes: []string{oidc.ScopeOpenID, "groups", "first_name", "last_name", "display_name", "email"},
|
||||
}
|
||||
|
||||
store := storage.NewSessionStore(kv, []byte(sessionsSecret))
|
||||
verifier := provider.Verifier(&oidc.Config{ClientID: oauth2Config.ClientID})
|
||||
|
||||
return &IdentificationProvider{
|
||||
SessionsStore: store,
|
||||
Provider: provider,
|
||||
OAuth2Config: oauth2Config,
|
||||
TokenVerifier: verifier,
|
||||
Services: services,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *IdentificationProvider) Middleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
session, err := p.SessionsStore.Get(r, "parcoursmob_session")
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("")
|
||||
}
|
||||
|
||||
if session.Values["idtoken"] == nil || session.Values["idtoken"] == "" {
|
||||
state, err := newState()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
session.Values["state"] = state
|
||||
session.Save(r, w)
|
||||
url := p.OAuth2Config.AuthCodeURL(state)
|
||||
http.Redirect(w, r, url, http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
idtoken, err := p.TokenVerifier.Verify(context.Background(), session.Values["idtoken"].(string))
|
||||
if err != nil {
|
||||
state, err := newState()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
session.Values["state"] = state
|
||||
delete(session.Values, "idtoken")
|
||||
http.Redirect(w, r, p.OAuth2Config.AuthCodeURL(state), http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
var claims map[string]any
|
||||
|
||||
err = idtoken.Claims(&claims)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("")
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), IdtokenKey, idtoken)
|
||||
ctx = context.WithValue(ctx, ClaimsKey, claims)
|
||||
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
func newState() (string, error) {
|
||||
b := make([]byte, 16)
|
||||
if _, err := io.ReadFull(rand.Reader, b); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.RawURLEncoding.EncodeToString(b), nil
|
||||
}
|
||||
37
core/utils/profile-pictures/profile-pictures.go
Executable file
37
core/utils/profile-pictures/profile-pictures.go
Executable file
@@ -0,0 +1,37 @@
|
||||
package profilepictures
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
|
||||
"github.com/fogleman/gg"
|
||||
"github.com/rs/zerolog/log"
|
||||
"golang.org/x/image/font"
|
||||
"golang.org/x/image/math/fixed"
|
||||
)
|
||||
|
||||
func DefaultProfilePicture(initials string) *image.RGBA {
|
||||
img := image.NewRGBA(image.Rect(0, 0, 300, 300))
|
||||
col := color.RGBA{36, 56, 135, 255}
|
||||
white := color.RGBA{255, 255, 255, 255}
|
||||
point := fixed.Point26_6{fixed.I(40), fixed.I(200)}
|
||||
|
||||
draw.Draw(img, img.Bounds(), &image.Uniform{col}, image.Point{X: 0, Y: 0}, draw.Src)
|
||||
|
||||
ff, err := gg.LoadFontFace("themes/default/web/fonts/bitter.ttf", 150.0)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("")
|
||||
return img
|
||||
}
|
||||
|
||||
d := &font.Drawer{
|
||||
Dst: img,
|
||||
Src: image.NewUniform(white),
|
||||
Face: ff,
|
||||
Dot: point,
|
||||
}
|
||||
d.DrawString(initials)
|
||||
|
||||
return img
|
||||
}
|
||||
13
core/utils/sorting/beneficiaries.go
Executable file
13
core/utils/sorting/beneficiaries.go
Executable file
@@ -0,0 +1,13 @@
|
||||
package sorting
|
||||
|
||||
import (
|
||||
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
||||
)
|
||||
|
||||
type BeneficiariesByName []mobilityaccountsstorage.Account
|
||||
|
||||
func (e BeneficiariesByName) Len() int { return len(e) }
|
||||
func (e BeneficiariesByName) Less(i, j int) bool {
|
||||
return e[i].Data["first_name"].(string) < e[j].Data["first_name"].(string)
|
||||
}
|
||||
func (e BeneficiariesByName) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
|
||||
11
core/utils/sorting/events.go
Executable file
11
core/utils/sorting/events.go
Executable file
@@ -0,0 +1,11 @@
|
||||
package sorting
|
||||
|
||||
import (
|
||||
agendastorage "git.coopgo.io/coopgo-platform/agenda/storage"
|
||||
)
|
||||
|
||||
type EventsByStartdate []agendastorage.Event
|
||||
|
||||
func (e EventsByStartdate) Len() int { return len(e) }
|
||||
func (e EventsByStartdate) Less(i, j int) bool { return e[i].Startdate.Before(e[j].Startdate) }
|
||||
func (e EventsByStartdate) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
|
||||
73
core/utils/sorting/fleets.go
Executable file
73
core/utils/sorting/fleets.go
Executable file
@@ -0,0 +1,73 @@
|
||||
package sorting
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"git.coopgo.io/coopgo-platform/fleets/storage"
|
||||
fleetsstorage "git.coopgo.io/coopgo-platform/fleets/storage"
|
||||
"github.com/paulmach/orb/geo"
|
||||
"github.com/paulmach/orb/geojson"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type VehiclesByLicencePlate []fleetsstorage.Vehicle
|
||||
|
||||
func (a VehiclesByLicencePlate) Len() int { return len(a) }
|
||||
func (a VehiclesByLicencePlate) Less(i, j int) bool {
|
||||
return strings.Compare(a[i].Data["licence_plate"].(string), a[j].Data["licence_plate"].(string)) < 0
|
||||
}
|
||||
func (a VehiclesByLicencePlate) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
||||
type BookingsByStartdate []fleetsstorage.Booking
|
||||
|
||||
func (a BookingsByStartdate) Len() int { return len(a) }
|
||||
func (a BookingsByStartdate) Less(i, j int) bool {
|
||||
return a[i].Startdate.Before(a[j].Startdate)
|
||||
}
|
||||
func (a BookingsByStartdate) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
||||
// Functions
|
||||
|
||||
func VehiclesByDistanceFrom(from geojson.Feature) func(vehicle1, vehicle2 storage.Vehicle) int {
|
||||
return func(vehicle1, vehicle2 storage.Vehicle) int {
|
||||
vehicle1Address, ok := vehicle1.Data["address"]
|
||||
if !ok {
|
||||
return 1
|
||||
}
|
||||
vehicle1Json, err := json.Marshal(vehicle1Address)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed marshalling vehicle 1 json")
|
||||
return 1
|
||||
}
|
||||
|
||||
vehicle1Geojson, err := geojson.UnmarshalFeature(vehicle1Json)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed unmarshalling vehicle 1 geojson")
|
||||
return 1
|
||||
}
|
||||
|
||||
vehicle2Address, ok := vehicle2.Data["address"]
|
||||
if !ok {
|
||||
log.Debug().Msg("Vehicle 2 does not have an address")
|
||||
return -1
|
||||
}
|
||||
vehicle2Json, err := json.Marshal(vehicle2Address)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed marshalling vehicle 2 json")
|
||||
return -1
|
||||
}
|
||||
|
||||
vehicle2Geojson, err := geojson.UnmarshalFeature(vehicle2Json)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed unmarshalling vehicle 2 geojson")
|
||||
return -1
|
||||
}
|
||||
|
||||
distance1 := geo.Distance(from.Point(), vehicle1Geojson.Point())
|
||||
distance2 := geo.Distance(from.Point(), vehicle2Geojson.Point())
|
||||
|
||||
return cmp.Compare(distance1, distance2)
|
||||
}
|
||||
}
|
||||
15
core/utils/sorting/groups.go
Executable file
15
core/utils/sorting/groups.go
Executable file
@@ -0,0 +1,15 @@
|
||||
package sorting
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
groupstorage "git.coopgo.io/coopgo-platform/groups-management/storage"
|
||||
)
|
||||
|
||||
type GroupsByName []groupstorage.Group
|
||||
|
||||
func (a GroupsByName) Len() int { return len(a) }
|
||||
func (a GroupsByName) Less(i, j int) bool {
|
||||
return strings.Compare(a[i].Data["name"].(string), a[j].Data["name"].(string)) < 0
|
||||
}
|
||||
func (a GroupsByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
32
core/utils/sorting/solidarity-transport.go
Normal file
32
core/utils/sorting/solidarity-transport.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package sorting
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
||||
"git.coopgo.io/coopgo-platform/solidarity-transport/servers/grpc/proto/gen"
|
||||
)
|
||||
|
||||
type SolidarityDriversByName []mobilityaccountsstorage.Account
|
||||
|
||||
func (e SolidarityDriversByName) Len() int { return len(e) }
|
||||
func (e SolidarityDriversByName) Less(i, j int) bool {
|
||||
return e[i].Data["first_name"].(string) < e[j].Data["first_name"].(string)
|
||||
}
|
||||
func (e SolidarityDriversByName) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
|
||||
|
||||
type SolidarityAvailabilitiesByDay []*gen.DriverRegularAvailability
|
||||
|
||||
func (e SolidarityAvailabilitiesByDay) Len() int { return len(e) }
|
||||
func (e SolidarityAvailabilitiesByDay) Less(i, j int) bool {
|
||||
if e[i].Day == e[j].Day {
|
||||
return strings.Compare(e[i].StartTime, e[j].StartTime) < 0
|
||||
}
|
||||
|
||||
if e[i].Day == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return e[i].Day < e[j].Day
|
||||
}
|
||||
func (e SolidarityAvailabilitiesByDay) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
|
||||
1
core/utils/sorting/sorting.go
Executable file
1
core/utils/sorting/sorting.go
Executable file
@@ -0,0 +1 @@
|
||||
package sorting
|
||||
1
core/utils/storage/badger.go
Normal file
1
core/utils/storage/badger.go
Normal file
@@ -0,0 +1 @@
|
||||
package storage
|
||||
11
core/utils/storage/cache.go
Executable file
11
core/utils/storage/cache.go
Executable file
@@ -0,0 +1,11 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type CacheHandler KVHandler
|
||||
|
||||
func NewCacheHandler(cfg *viper.Viper) (CacheHandler, error) {
|
||||
return NewKVHandler(cfg)
|
||||
}
|
||||
164
core/utils/storage/etcd.go
Executable file
164
core/utils/storage/etcd.go
Executable file
@@ -0,0 +1,164 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/gob"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/viper"
|
||||
clientv3 "go.etcd.io/etcd/client/v3"
|
||||
"go.etcd.io/etcd/client/v3/namespace"
|
||||
)
|
||||
|
||||
type EtcdSerializer interface {
|
||||
Deserialize(d []byte, m *any) error
|
||||
Serialize(m any) ([]byte, error)
|
||||
}
|
||||
|
||||
type JSONEtcdSerializer struct{}
|
||||
|
||||
// Serialize to JSON. Will err if there are unmarshalable key values
|
||||
func (s JSONEtcdSerializer) Serialize(m any) ([]byte, error) {
|
||||
return json.Marshal(m)
|
||||
}
|
||||
|
||||
// Deserialize back to map[string]interface{}
|
||||
func (s JSONEtcdSerializer) Deserialize(d []byte, m *any) (err error) {
|
||||
err = json.Unmarshal(d, &m)
|
||||
if err != nil {
|
||||
fmt.Printf("JSONSerializer.deserialize() Error: %v", err)
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GobEtcdSerializer uses gob package to encode the session map
|
||||
type GobEtcdSerializer struct{}
|
||||
|
||||
// Serialize using gob
|
||||
func (s GobEtcdSerializer) Serialize(m any) ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
enc := gob.NewEncoder(buf)
|
||||
err := enc.Encode(m)
|
||||
if err == nil {
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Deserialize back to map[interface{}]interface{}
|
||||
func (s GobEtcdSerializer) Deserialize(d []byte, m any) error {
|
||||
dec := gob.NewDecoder(bytes.NewBuffer(d))
|
||||
return dec.Decode(&m)
|
||||
}
|
||||
|
||||
type EtcdHandler struct {
|
||||
*clientv3.Client
|
||||
serializer EtcdSerializer
|
||||
}
|
||||
|
||||
func NewEtcdHandler(cfg *viper.Viper) (*EtcdHandler, error) {
|
||||
var (
|
||||
endpoints = cfg.GetStringSlice("storage.kv.etcd.endpoints")
|
||||
prefix = cfg.GetString("storage.kv.etcd.prefix")
|
||||
username = cfg.GetString("storage.kv.etcd.username")
|
||||
password = cfg.GetString("storage.kv.etcd.password")
|
||||
)
|
||||
|
||||
cli, err := clientv3.New(clientv3.Config{
|
||||
Endpoints: endpoints,
|
||||
Username: username,
|
||||
Password: password,
|
||||
DialTimeout: 5 * time.Second,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cli.KV = namespace.NewKV(cli.KV, prefix)
|
||||
cli.Watcher = namespace.NewWatcher(cli.Watcher, prefix)
|
||||
cli.Lease = namespace.NewLease(cli.Lease, prefix)
|
||||
|
||||
return &EtcdHandler{
|
||||
Client: cli,
|
||||
serializer: JSONEtcdSerializer{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *EtcdHandler) Put(k string, v any) error {
|
||||
data, err := s.serializer.Serialize(v)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("")
|
||||
return err
|
||||
}
|
||||
// _, err = s.Client.KV.Put(context.TODO(), k, data.String())
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
_, err = s.Client.KV.Put(ctx, k, string(data))
|
||||
cancel()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *EtcdHandler) PutWithTTL(k string, v any, duration time.Duration) error {
|
||||
lease, err := s.Client.Lease.Grant(context.TODO(), int64(duration.Seconds()))
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("")
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := s.serializer.Serialize(v)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("")
|
||||
return err
|
||||
}
|
||||
// _, err = s.Client.KV.Put(context.TODO(), k, data.String(), clientv3.WithLease(lease.ID))
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
_, err = s.Client.KV.Put(ctx, k, string(data), clientv3.WithLease(lease.ID))
|
||||
cancel()
|
||||
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *EtcdHandler) Get(k string) (any, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
resp, err := s.Client.KV.Get(ctx, k)
|
||||
cancel()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("")
|
||||
return nil, err
|
||||
}
|
||||
for _, v := range resp.Kvs {
|
||||
var data any
|
||||
err := s.serializer.Deserialize([]byte(v.Value), &data)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("")
|
||||
return nil, err
|
||||
}
|
||||
// We return directly as we want to last revision of value
|
||||
return data, nil
|
||||
}
|
||||
return nil, fmt.Errorf("no value %v", k)
|
||||
}
|
||||
|
||||
func (s *EtcdHandler) Delete(k string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
_, err := s.Client.KV.Delete(ctx, k)
|
||||
cancel()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
36
core/utils/storage/files.go
Executable file
36
core/utils/storage/files.go
Executable file
@@ -0,0 +1,36 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
PREFIX_BENEFICIARIES = "beneficiaries"
|
||||
PREFIX_SOLIDARITY_TRANSPORT_DRIVERS = "solidarity_transport/drivers"
|
||||
PREFIX_ORGANIZED_CARPOOL_DRIVERS = "organized_carpool/drivers"
|
||||
PREFIX_BOOKINGS = "fleets_bookings"
|
||||
PREFIX_AGENDA = "event_files"
|
||||
)
|
||||
|
||||
type FileInfo struct {
|
||||
Key string
|
||||
FileName string
|
||||
LastModified time.Time
|
||||
ContentType string
|
||||
Metadata map[string]string
|
||||
}
|
||||
|
||||
type FileStorage interface {
|
||||
Put(reader io.Reader, prefix string, filename string, size int64, metadata map[string]string) error
|
||||
List(prefix string) []FileInfo
|
||||
Get(prefix string, file string) (io.Reader, *FileInfo, error)
|
||||
Copy(src string, dest string) error
|
||||
Delete(prefix string, file string) error
|
||||
}
|
||||
|
||||
func NewFileStorage(cfg *viper.Viper) (FileStorage, error) {
|
||||
return NewMinioStorageHandler(cfg)
|
||||
}
|
||||
20
core/utils/storage/kv.go
Executable file
20
core/utils/storage/kv.go
Executable file
@@ -0,0 +1,20 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type KVHandler interface {
|
||||
Put(k string, v any) error
|
||||
PutWithTTL(k string, v any, duration time.Duration) error
|
||||
Get(k string) (any, error)
|
||||
Delete(k string) error
|
||||
}
|
||||
|
||||
func NewKVHandler(cfg *viper.Viper) (KVHandler, error) {
|
||||
return NewEtcdHandler(cfg)
|
||||
return nil, nil
|
||||
|
||||
}
|
||||
133
core/utils/storage/minio.go
Executable file
133
core/utils/storage/minio.go
Executable file
@@ -0,0 +1,133 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type MinioStorageHandler struct {
|
||||
*minio.Client
|
||||
|
||||
BucketName string
|
||||
}
|
||||
|
||||
func NewMinioStorageHandler(cfg *viper.Viper) (*MinioStorageHandler, error) {
|
||||
minioClient, err := minio.New(cfg.GetString("storage.files.minio.endpoint"), &minio.Options{
|
||||
Creds: credentials.NewStaticV4(cfg.GetString("storage.files.minio.access_key"), cfg.GetString("storage.files.minio.secret_key"), ""),
|
||||
Secure: cfg.GetBool("storage.files.minio.use_ssl"),
|
||||
})
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &MinioStorageHandler{
|
||||
Client: minioClient,
|
||||
BucketName: cfg.GetString("storage.files.minio.bucket_name"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *MinioStorageHandler) Put(reader io.Reader, prefix string, filename string, size int64, metadata map[string]string) error {
|
||||
s.Client.PutObject(context.TODO(), s.BucketName, prefix+"/"+filename, reader, size, minio.PutObjectOptions{
|
||||
|
||||
UserMetadata: metadata,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *MinioStorageHandler) List(prefix string) []FileInfo {
|
||||
res := []FileInfo{}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
objectCh := s.Client.ListObjects(ctx, s.BucketName, minio.ListObjectsOptions{
|
||||
Prefix: prefix,
|
||||
Recursive: true,
|
||||
})
|
||||
|
||||
for object := range objectCh {
|
||||
if object.Err != nil {
|
||||
log.Error().Str("prefix", prefix).Err(object.Err).Msg("Error listing files for prefix")
|
||||
continue
|
||||
}
|
||||
|
||||
objinfo, err := s.Client.StatObject(context.Background(), s.BucketName, object.Key, minio.StatObjectOptions{})
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("")
|
||||
continue
|
||||
}
|
||||
|
||||
path := strings.Split(object.Key, "/")
|
||||
|
||||
fileinfo := FileInfo{
|
||||
Key: object.Key,
|
||||
FileName: path[len(path)-1],
|
||||
LastModified: object.LastModified,
|
||||
ContentType: object.ContentType,
|
||||
Metadata: objinfo.UserMetadata,
|
||||
}
|
||||
|
||||
res = append(res, fileinfo)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func (s *MinioStorageHandler) Get(prefix string, file string) (io.Reader, *FileInfo, error) {
|
||||
object, err := s.Client.GetObject(context.Background(), s.BucketName, prefix+"/"+file, minio.GetObjectOptions{})
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("")
|
||||
return nil, nil, err
|
||||
}
|
||||
objinfo, err := s.Client.StatObject(context.Background(), s.BucketName, prefix+"/"+file, minio.StatObjectOptions{})
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("")
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
path := strings.Split(objinfo.Key, "/")
|
||||
fileinfo := &FileInfo{
|
||||
Key: objinfo.Key,
|
||||
FileName: path[len(path)-1],
|
||||
LastModified: objinfo.LastModified,
|
||||
ContentType: objinfo.ContentType,
|
||||
Metadata: objinfo.UserMetadata,
|
||||
}
|
||||
|
||||
return object, fileinfo, nil
|
||||
}
|
||||
|
||||
func (s *MinioStorageHandler) Copy(src string, dst string) error {
|
||||
srcOpts := minio.CopySrcOptions{
|
||||
Bucket: s.BucketName,
|
||||
Object: src,
|
||||
}
|
||||
|
||||
// Destination object
|
||||
dstOpts := minio.CopyDestOptions{
|
||||
Bucket: s.BucketName,
|
||||
Object: dst,
|
||||
}
|
||||
|
||||
_, err := s.Client.CopyObject(context.Background(), dstOpts, srcOpts)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *MinioStorageHandler) Delete(prefix string, file string) error {
|
||||
err := s.Client.RemoveObject(context.Background(), s.BucketName, prefix+"/"+file, minio.RemoveObjectOptions{})
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Error deleting file from storage")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
152
core/utils/storage/sessions.go
Executable file
152
core/utils/storage/sessions.go
Executable file
@@ -0,0 +1,152 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base32"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/securecookie"
|
||||
"github.com/gorilla/sessions"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// Amount of time for cookies/kv keys to expire.
|
||||
var sessionExpire = 86400 * 30
|
||||
|
||||
type SessionStore struct {
|
||||
KV KVHandler
|
||||
Codecs []securecookie.Codec
|
||||
options *sessions.Options // default configuration
|
||||
DefaultMaxAge int // default TiKV TTL for a MaxAge == 0 session
|
||||
//maxLength int
|
||||
keyPrefix string
|
||||
//serializer SessionSerializer
|
||||
}
|
||||
|
||||
func NewSessionStore(client KVHandler, keyPairs ...[]byte) *SessionStore {
|
||||
es := &SessionStore{
|
||||
KV: client,
|
||||
Codecs: securecookie.CodecsFromPairs(keyPairs...),
|
||||
options: &sessions.Options{
|
||||
Path: "/",
|
||||
MaxAge: sessionExpire,
|
||||
},
|
||||
DefaultMaxAge: sessionExpire, // 20 minutes seems like a reasonable default
|
||||
//maxLength: 4096,
|
||||
keyPrefix: "session/",
|
||||
}
|
||||
|
||||
return es
|
||||
}
|
||||
|
||||
func (s *SessionStore) Get(r *http.Request, name string) (*sessions.Session, error) {
|
||||
// session := sessions.NewSession(s, name)
|
||||
// ok, err := s.load(r.Context(), session)
|
||||
// if !(err == nil && ok) {
|
||||
// if err == nil {
|
||||
// err = errors.New("key does not exist")
|
||||
// }
|
||||
// }
|
||||
// return session, err
|
||||
return sessions.GetRegistry(r).Get(s, name)
|
||||
}
|
||||
|
||||
func (s *SessionStore) New(r *http.Request, name string) (*sessions.Session, error) {
|
||||
session := sessions.NewSession(s, name)
|
||||
options := *s.options
|
||||
session.Options = &options
|
||||
session.IsNew = true
|
||||
if c, errCookie := r.Cookie(name); errCookie == nil {
|
||||
err := securecookie.DecodeMulti(name, c.Value, &session.ID, s.Codecs...)
|
||||
if err != nil {
|
||||
return session, err
|
||||
}
|
||||
ok, err := s.load(r.Context(), session)
|
||||
session.IsNew = !(err == nil && ok) // not new if no error and data available
|
||||
}
|
||||
return session, nil
|
||||
}
|
||||
|
||||
// Save adds a single session to the response.
|
||||
func (s *SessionStore) Save(r *http.Request, w http.ResponseWriter, session *sessions.Session) error {
|
||||
|
||||
// Marked for deletion.
|
||||
if session.Options.MaxAge <= 0 {
|
||||
if err := s.delete(r.Context(), session); err != nil {
|
||||
log.Error().Err(err).Msg("")
|
||||
return err
|
||||
}
|
||||
http.SetCookie(w, sessions.NewCookie(session.Name(), "", session.Options))
|
||||
} else {
|
||||
// Build an alphanumeric key for the kv store.
|
||||
if session.ID == "" {
|
||||
session.ID = strings.TrimRight(base32.StdEncoding.EncodeToString(securecookie.GenerateRandomKey(32)), "=")
|
||||
}
|
||||
|
||||
if err := s.save(r.Context(), session); err != nil {
|
||||
log.Error().Err(err).Msg("")
|
||||
return err
|
||||
}
|
||||
|
||||
encoded, err := securecookie.EncodeMulti(session.Name(), session.ID, s.Codecs...)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("")
|
||||
return err
|
||||
}
|
||||
http.SetCookie(w, sessions.NewCookie(session.Name(), encoded, session.Options))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// save stores the session in kv.
|
||||
func (s *SessionStore) save(ctx context.Context, session *sessions.Session) error {
|
||||
m := make(map[string]interface{}, len(session.Values))
|
||||
for k, v := range session.Values {
|
||||
ks, ok := k.(string)
|
||||
if !ok {
|
||||
err := fmt.Errorf("non-string key value, cannot serialize session: %v", k)
|
||||
log.Error().Err(err).Msg("")
|
||||
return err
|
||||
}
|
||||
m[ks] = v
|
||||
}
|
||||
|
||||
age := session.Options.MaxAge
|
||||
if age == 0 {
|
||||
age = s.DefaultMaxAge
|
||||
}
|
||||
|
||||
return s.KV.PutWithTTL(s.keyPrefix+session.ID, m, time.Duration(age)*time.Second)
|
||||
}
|
||||
|
||||
// load reads the session from kv store.
|
||||
// returns true if there is a sessoin data in DB
|
||||
func (s *SessionStore) load(ctx context.Context, session *sessions.Session) (bool, error) {
|
||||
|
||||
data, err := s.KV.Get(s.keyPrefix + session.ID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if data == nil && err == nil {
|
||||
return false, errors.New("key does not exist")
|
||||
}
|
||||
|
||||
for k, v := range data.(map[string]any) {
|
||||
session.Values[k] = v
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// delete removes keys from tikv if MaxAge<0
|
||||
func (s *SessionStore) delete(ctx context.Context, session *sessions.Session) error {
|
||||
if err := s.KV.Delete(s.keyPrefix + session.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
102
core/utils/validated-profile/validated-profile.go
Normal file
102
core/utils/validated-profile/validated-profile.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package validatedprofile
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/storage"
|
||||
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/objx"
|
||||
)
|
||||
|
||||
type Comparison struct {
|
||||
Field string
|
||||
Type string
|
||||
Value any
|
||||
}
|
||||
|
||||
func ValidateProfile(cfg *viper.Viper) func(mobilityaccountsstorage.Account, []storage.FileInfo) bool {
|
||||
enabled := cfg.GetBool("enabled")
|
||||
requiredDocuments := cfg.GetStringSlice("required.documents")
|
||||
requiredFields := cfg.GetStringSlice("required.fields")
|
||||
comp := cfg.Get("assert.compare")
|
||||
var comparisons []Comparison
|
||||
err := mapstructure.Decode(comp, &comparisons)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("reading comparisons issue")
|
||||
}
|
||||
return func(account mobilityaccountsstorage.Account, docs []storage.FileInfo) bool {
|
||||
if !enabled {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, d := range requiredDocuments {
|
||||
if !slices.ContainsFunc(docs, func(f storage.FileInfo) bool {
|
||||
log.Debug().Str("required", d).Str("checked", f.Metadata["Type"]).Msg("file check")
|
||||
return f.Metadata["Type"] == d
|
||||
}) {
|
||||
log.Debug().Msg("file missing")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
obj := objx.Map(account.Data)
|
||||
|
||||
for _, f := range requiredFields {
|
||||
if obj.Get(f) == nil {
|
||||
log.Debug().Msg("field missing")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for _, c := range comparisons {
|
||||
val := obj.Get(c.Field)
|
||||
if val == nil {
|
||||
return false
|
||||
}
|
||||
value := ""
|
||||
if v, ok := c.Value.(string); ok {
|
||||
value = v
|
||||
} else if v, ok := c.Value.(time.Time); ok {
|
||||
value = v.Format("2006-01-02")
|
||||
} else {
|
||||
log.Error().Msg("could not get type")
|
||||
return false
|
||||
}
|
||||
result := cmp.Compare(val.String(), value)
|
||||
|
||||
if c.Type == "gte" {
|
||||
if result < 0 {
|
||||
log.Debug().Int("comparison result", result).Str("operand", c.Type).Msg("comparison issue")
|
||||
return false
|
||||
}
|
||||
} else if c.Type == "gt" {
|
||||
if result <= 0 {
|
||||
log.Debug().Int("comparison result", result).Str("operand", c.Type).Msg("comparison issue")
|
||||
return false
|
||||
}
|
||||
} else if c.Type == "lt" {
|
||||
if result >= 0 {
|
||||
log.Debug().Int("comparison result", result).Str("operand", c.Type).Msg("comparison issue")
|
||||
return false
|
||||
}
|
||||
} else if c.Type == "lte" {
|
||||
if result < 0 {
|
||||
log.Debug().Int("comparison result", result).Str("operand", c.Type).Msg("comparison issue")
|
||||
return false
|
||||
}
|
||||
} else if c.Type == "eq" {
|
||||
if result != 0 {
|
||||
log.Debug().Int("comparison result", result).Str("operand", c.Type).Msg("comparison issue")
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user