feat: Initial
This commit is contained in:
commit
ed9af2b459
10 files changed
+376
No files matched your search
Generated
+8
@@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
Generated
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/universe_badger.iml" filepath="$PROJECT_DIR$/.idea/universe_badger.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+9
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="Go" enabled="true" />
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
Generated
+4
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings" defaultProject="true" />
|
||||
</project>
|
||||
@@ -0,0 +1,31 @@
|
||||
module go.mrlokop.dev/universe/data/badger
|
||||
|
||||
go 1.24
|
||||
|
||||
require (
|
||||
github.com/dgraph-io/badger/v4 v4.7.0
|
||||
go.uber.org/fx v1.23.0
|
||||
go.uber.org/zap v1.27.0
|
||||
universe.mrlokop.dev/core v0.0.0-20250425091205-db1d0545d9c6
|
||||
universe.mrlokop.dev/key_value v0.0.0-20250425093329-a79c041fe7e1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/dgraph-io/ristretto/v2 v2.2.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/google/flatbuffers v25.2.10+incompatible // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/otel v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.35.0 // indirect
|
||||
go.uber.org/dig v1.18.1 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/net v0.39.0 // indirect
|
||||
golang.org/x/sys v0.32.0 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
)
|
||||
@@ -0,0 +1,61 @@
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgraph-io/badger/v4 v4.7.0 h1:Q+J8HApYAY7UMpL8d9owqiB+odzEc0zn/aqOD9jhc6Y=
|
||||
github.com/dgraph-io/badger/v4 v4.7.0/go.mod h1:He7TzG3YBy3j4f5baj5B7Zl2XyfNe5bl4Udl0aPemVA=
|
||||
github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM=
|
||||
github.com/dgraph-io/ristretto/v2 v2.2.0/go.mod h1:RZrm63UmcBAaYWC1DotLYBmTvgkrs0+XhBd7Npn7/zI=
|
||||
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38=
|
||||
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q=
|
||||
github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
|
||||
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
|
||||
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
|
||||
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
|
||||
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
|
||||
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
|
||||
go.uber.org/dig v1.18.1 h1:rLww6NuajVjeQn+49u5NcezUJEGwd5uXmyoCKW2g5Es=
|
||||
go.uber.org/dig v1.18.1/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE=
|
||||
go.uber.org/fx v1.23.0 h1:lIr/gYWQGfTwGcSXWXu4vP5Ws6iqnNEIY+F/aFzCKTg=
|
||||
go.uber.org/fx v1.23.0/go.mod h1:o/D9n+2mLP6v1EG+qsdT1O8wKopYAsqZasju97SDFCU=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
||||
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
universe.mrlokop.dev/core v0.0.0-20250425091205-db1d0545d9c6 h1:aM0o2QFSizLY9KOXuoOmsp/o345yJEptgq8egA85VnM=
|
||||
universe.mrlokop.dev/core v0.0.0-20250425091205-db1d0545d9c6/go.mod h1:1xEErU5FKve38at9zd2RZDDbojuebd9rGpWzeUD4zu4=
|
||||
universe.mrlokop.dev/key_value v0.0.0-20250425091341-1d08501b51d7 h1:jJmVvb+UVioVyJpYe5jF3W0iCxNF+RR4uR4h695aYVI=
|
||||
universe.mrlokop.dev/key_value v0.0.0-20250425091341-1d08501b51d7/go.mod h1:ajRUrvqBdKaLg9vINxxCEYme+DXfzotA+HIfje+mmZM=
|
||||
universe.mrlokop.dev/key_value v0.0.0-20250425093329-a79c041fe7e1 h1:dpEjqVgueSxw+G73g9g80B0yhJKIu4zWwO38ovbRoiE=
|
||||
universe.mrlokop.dev/key_value v0.0.0-20250425093329-a79c041fe7e1/go.mod h1:WNqaFQruy5ich5wzhivNQf6PljJ9QxiBJ8gmvJMemG4=
|
||||
@@ -0,0 +1,32 @@
|
||||
package universe_badger
|
||||
|
||||
import (
|
||||
"github.com/dgraph-io/badger/v4"
|
||||
kv "universe.mrlokop.dev/key_value"
|
||||
)
|
||||
|
||||
type keyValueProvider struct {
|
||||
db *badger.DB
|
||||
}
|
||||
|
||||
func (k *keyValueProvider) HasCapability(capability kv.Capability) bool {
|
||||
switch capability {
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (k *keyValueProvider) ProvideBinary(prefix string) (kv.KeyValueStoreBinary, error) {
|
||||
return &keyValueStorage{
|
||||
db: k.db,
|
||||
prefix: prefix,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (k *keyValueProvider) ProvideString(prefix string) (kv.KeyValueStoreString, error) {
|
||||
bin, err := k.ProvideBinary(prefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return kv.WrapStringToBinary(bin), err
|
||||
}
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
package universe_badger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/dgraph-io/badger/v4"
|
||||
"time"
|
||||
"universe.mrlokop.dev/core/errors"
|
||||
kv "universe.mrlokop.dev/key_value"
|
||||
)
|
||||
|
||||
func wrapErr(err error) error {
|
||||
if errors.Is(err, badger.ErrKeyNotFound) {
|
||||
return kv.ErrNoSuchKey.CreateWithCause(err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
type keyValueStorage struct {
|
||||
db *badger.DB
|
||||
prefix string
|
||||
}
|
||||
|
||||
func (k *keyValueStorage) Get(ctx context.Context, key string) ([]byte, error) {
|
||||
var result []byte
|
||||
err := k.db.View(func(txn *badger.Txn) error {
|
||||
keyBytes := []byte(k.prefix + key)
|
||||
item, err := txn.Get(keyBytes)
|
||||
if err != nil {
|
||||
return wrapErr(err)
|
||||
}
|
||||
result = make([]byte, item.ValueSize())
|
||||
_, err = item.ValueCopy(result)
|
||||
return wrapErr(err)
|
||||
})
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (k *keyValueStorage) Has(ctx context.Context, key string) (bool, error) {
|
||||
var exists bool
|
||||
err := k.db.View(func(txn *badger.Txn) error {
|
||||
keyBytes := []byte(k.prefix + key)
|
||||
_, err := txn.Get(keyBytes)
|
||||
if err != nil {
|
||||
if errors.Is(err, badger.ErrKeyNotFound) {
|
||||
exists = false
|
||||
return nil
|
||||
}
|
||||
return wrapErr(err)
|
||||
}
|
||||
exists = true
|
||||
return nil
|
||||
})
|
||||
return exists, err
|
||||
}
|
||||
|
||||
func (k *keyValueStorage) Set(ctx context.Context, key string, value []byte) error {
|
||||
return k.db.Update(func(txn *badger.Txn) error {
|
||||
keyBytes := []byte(k.prefix + key)
|
||||
return wrapErr(txn.Set(keyBytes, value))
|
||||
})
|
||||
}
|
||||
|
||||
func (k *keyValueStorage) SetWithExpiration(ctx context.Context, key string, value []byte, expiresAt time.Time) error {
|
||||
return k.db.Update(func(txn *badger.Txn) error {
|
||||
keyBytes := []byte(k.prefix + key)
|
||||
entry := badger.NewEntry(keyBytes, value)
|
||||
entry.ExpiresAt = uint64(expiresAt.Unix())
|
||||
return wrapErr(txn.SetEntry(entry))
|
||||
})
|
||||
}
|
||||
|
||||
func (k *keyValueStorage) SetIfNotExist(ctx context.Context, key string, value []byte) (bool, error) {
|
||||
var wasSet bool
|
||||
err := k.db.Update(func(txn *badger.Txn) error {
|
||||
keyBytes := []byte(k.prefix + key)
|
||||
_, err := txn.Get(keyBytes)
|
||||
if err != nil {
|
||||
if errors.Is(err, badger.ErrKeyNotFound) {
|
||||
wasSet = true
|
||||
return wrapErr(txn.Set(keyBytes, value))
|
||||
}
|
||||
}
|
||||
return wrapErr(err)
|
||||
})
|
||||
return wasSet, wrapErr(err)
|
||||
}
|
||||
|
||||
func (k *keyValueStorage) SetIfNotExistWithExpiration(ctx context.Context, key string, value []byte, expiration time.Time) (bool, error) {
|
||||
var wasSet bool
|
||||
err := k.db.Update(func(txn *badger.Txn) error {
|
||||
keyBytes := []byte(k.prefix + key)
|
||||
_, err := txn.Get(keyBytes)
|
||||
if err != nil {
|
||||
if errors.Is(err, badger.ErrKeyNotFound) {
|
||||
wasSet = true
|
||||
entry := badger.NewEntry(keyBytes, value)
|
||||
entry.ExpiresAt = uint64(expiration.Unix())
|
||||
return wrapErr(txn.SetEntry(entry))
|
||||
}
|
||||
}
|
||||
return wrapErr(err)
|
||||
})
|
||||
return wasSet, err
|
||||
}
|
||||
|
||||
func (k *keyValueStorage) ExpireAt(ctx context.Context, key string, expiration time.Time) error {
|
||||
return k.db.Update(func(txn *badger.Txn) error {
|
||||
keyBytes := []byte(k.prefix + key)
|
||||
item, err := txn.Get(keyBytes)
|
||||
if err != nil {
|
||||
return wrapErr(err)
|
||||
}
|
||||
|
||||
copyOfBytes := make([]byte, item.ValueSize())
|
||||
_, err = item.ValueCopy(copyOfBytes)
|
||||
if err != nil {
|
||||
return wrapErr(err)
|
||||
}
|
||||
|
||||
entry := badger.NewEntry(keyBytes, copyOfBytes)
|
||||
entry.ExpiresAt = uint64(expiration.Unix())
|
||||
return wrapErr(txn.SetEntry(entry))
|
||||
})
|
||||
}
|
||||
|
||||
func (k *keyValueStorage) Remove(ctx context.Context, key string) error {
|
||||
return k.db.Update(func(txn *badger.Txn) error {
|
||||
keyBytes := []byte(k.prefix + key)
|
||||
return wrapErr(txn.Delete(keyBytes))
|
||||
})
|
||||
}
|
||||
|
||||
func (k *keyValueStorage) HasCapability(capability kv.Capability) bool {
|
||||
switch capability {
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package universe_badger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go.uber.org/zap"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type zapRedirectingLogger struct {
|
||||
target *zap.Logger
|
||||
}
|
||||
|
||||
func (z *zapRedirectingLogger) Errorf(s string, i ...interface{}) {
|
||||
s, _ = strings.CutSuffix(s, "\n")
|
||||
z.target.Error(fmt.Sprintf(s, i...))
|
||||
}
|
||||
|
||||
func (z *zapRedirectingLogger) Warningf(s string, i ...interface{}) {
|
||||
s, _ = strings.CutSuffix(s, "\n")
|
||||
z.target.Warn(fmt.Sprintf(s, i...))
|
||||
}
|
||||
|
||||
func (z *zapRedirectingLogger) Infof(s string, i ...interface{}) {
|
||||
s, _ = strings.CutSuffix(s, "\n")
|
||||
z.target.Info(fmt.Sprintf(s, i...))
|
||||
}
|
||||
|
||||
func (z *zapRedirectingLogger) Debugf(s string, i ...interface{}) {
|
||||
s, _ = strings.CutSuffix(s, "\n")
|
||||
z.target.Debug(fmt.Sprintf(s, i...))
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package universe_badger
|
||||
|
||||
import (
|
||||
"github.com/dgraph-io/badger/v4"
|
||||
"go.uber.org/fx"
|
||||
"go.uber.org/zap"
|
||||
um "universe.mrlokop.dev/core/modules"
|
||||
kv "universe.mrlokop.dev/key_value"
|
||||
)
|
||||
|
||||
type moduleConfig struct {
|
||||
Path string
|
||||
}
|
||||
|
||||
var (
|
||||
Module = um.SimpleModule("universe:badger", func(ctx um.ModuleFactoryContext) error {
|
||||
cfg := &moduleConfig{}
|
||||
err := ctx.ExtractConfig(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.Provide(fx.Annotate(func(
|
||||
logger *zap.Logger,
|
||||
) (*badger.DB, error) {
|
||||
var (
|
||||
ll = ctx.ConfigureLogger(logger)
|
||||
opts = badger.DefaultOptions(cfg.Path)
|
||||
)
|
||||
|
||||
if cfg.Path == "" {
|
||||
opts.WithInMemory(true)
|
||||
}
|
||||
|
||||
opts.WithLogger(&zapRedirectingLogger{target: ll})
|
||||
|
||||
db, err := badger.Open(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}, fx.ResultTags(ctx.NameTag())))
|
||||
|
||||
ctx.Provide(fx.Annotate(func(
|
||||
badger *badger.DB,
|
||||
logger *zap.Logger,
|
||||
) kv.KeyValueProvider {
|
||||
return &keyValueProvider{}
|
||||
}, fx.ParamTags(ctx.NameTag()), fx.ResultTags(ctx.NameTag())))
|
||||
|
||||
return nil
|
||||
})
|
||||
)
|
||||
Reference in new issue
Block a user