Browse Source
- Split orly-db into orly-db-badger and orly-db-neo4j backends - Split orly-acl into orly-acl-follows, orly-acl-managed, orly-acl-curation - Add shared pkg/database/server/ for gRPC database service - Add shared pkg/acl/server/ for gRPC ACL service - Add pkg/acl/grpc/ client for relay-to-ACL communication - Add gRPC proto definitions for ACL service (pkg/proto/orlyacl/) - Update launcher to compute binary names from backend/mode settings - When ACL disabled, relay runs in open mode (no restrictions) - Add Makefile with all-split, arm64-split, and per-binary targets - Include systemd service file for split IPC deployment Files modified: - Makefile: New build system with split binary targets - app/config/config.go: Add ACL gRPC client config options - cmd/orly-acl/: Legacy monolithic ACL server - cmd/orly-acl-curation/main.go: Curation mode binary - cmd/orly-acl-follows/main.go: Follows mode binary - cmd/orly-acl-managed/main.go: Managed mode binary - cmd/orly-db-badger/main.go: Badger backend binary - cmd/orly-db-neo4j/main.go: Neo4j backend binary - cmd/orly-launcher/config.go: Add DBBackend, compute binary names - cmd/orly-launcher/supervisor.go: Handle no-ACL mode, add ACL health check - main.go: Add gRPC ACL client initialization - orly.service: Systemd service for split IPC mode - pkg/acl/acl.go: Add gRPC ACL interface type - pkg/acl/grpc/client.go: gRPC ACL client implementation - pkg/acl/server/: Shared ACL gRPC server package - pkg/database/server/: Shared database gRPC server package - pkg/proto/orlyacl/: ACL service protobuf definitions - pkg/version/version: v0.54.0 - proto/orlyacl/: ACL proto source files Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>main v0.54.0
28 changed files with 10294 additions and 22 deletions
@ -0,0 +1,194 @@ |
|||||||
|
# ORLY Nostr Relay Build System
|
||||||
|
.PHONY: all orly orly-db orly-acl orly-launcher proto clean test deploy web install help |
||||||
|
.PHONY: orly-db-badger orly-db-neo4j orly-acl-follows orly-acl-managed orly-acl-curation |
||||||
|
.PHONY: all-split arm64-split install-split |
||||||
|
|
||||||
|
# Build flags
|
||||||
|
CGO_ENABLED ?= 0
|
||||||
|
GOOS ?= $(shell go env GOOS)
|
||||||
|
GOARCH ?= $(shell go env GOARCH)
|
||||||
|
BUILD_FLAGS = CGO_ENABLED=$(CGO_ENABLED) GOOS=$(GOOS) GOARCH=$(GOARCH)
|
||||||
|
|
||||||
|
# Binaries
|
||||||
|
BIN_DIR = .
|
||||||
|
ORLY = $(BIN_DIR)/orly
|
||||||
|
ORLY_LAUNCHER = $(BIN_DIR)/orly-launcher
|
||||||
|
|
||||||
|
# Legacy monolithic binaries (for backwards compatibility)
|
||||||
|
ORLY_DB = $(BIN_DIR)/orly-db
|
||||||
|
ORLY_ACL = $(BIN_DIR)/orly-acl
|
||||||
|
|
||||||
|
# Split database backends
|
||||||
|
ORLY_DB_BADGER = $(BIN_DIR)/orly-db-badger
|
||||||
|
ORLY_DB_NEO4J = $(BIN_DIR)/orly-db-neo4j
|
||||||
|
|
||||||
|
# Split ACL modes
|
||||||
|
ORLY_ACL_FOLLOWS = $(BIN_DIR)/orly-acl-follows
|
||||||
|
ORLY_ACL_MANAGED = $(BIN_DIR)/orly-acl-managed
|
||||||
|
ORLY_ACL_CURATION = $(BIN_DIR)/orly-acl-curation
|
||||||
|
|
||||||
|
# === Default Targets (Legacy) ===
|
||||||
|
|
||||||
|
# Default target: build everything (legacy monolithic)
|
||||||
|
all: orly orly-db orly-launcher |
||||||
|
@echo "All binaries built successfully"
|
||||||
|
|
||||||
|
# Build everything including ACL (when proto exists)
|
||||||
|
all-acl: proto orly orly-db orly-acl orly-launcher |
||||||
|
@echo "All binaries (including ACL) built successfully"
|
||||||
|
|
||||||
|
# === Split Binaries (New) ===
|
||||||
|
|
||||||
|
# Build split mode: orly + orly-db-badger + orly-acl-follows + launcher
|
||||||
|
all-split: proto orly orly-db-badger orly-acl-follows orly-launcher |
||||||
|
@echo "Split mode binaries built successfully"
|
||||||
|
|
||||||
|
# Build all split binaries (all backends and all ACL modes)
|
||||||
|
all-backends: proto orly orly-db-badger orly-db-neo4j orly-acl-follows orly-acl-managed orly-acl-curation orly-launcher |
||||||
|
@echo "All backend and ACL mode binaries built successfully"
|
||||||
|
|
||||||
|
# Main relay binary
|
||||||
|
orly: |
||||||
|
$(BUILD_FLAGS) go build -o $(ORLY) .
|
||||||
|
|
||||||
|
# === Database Backends ===
|
||||||
|
|
||||||
|
# Legacy monolithic database server
|
||||||
|
orly-db: |
||||||
|
$(BUILD_FLAGS) go build -o $(ORLY_DB) ./cmd/orly-db
|
||||||
|
|
||||||
|
# Badger database server
|
||||||
|
orly-db-badger: |
||||||
|
$(BUILD_FLAGS) go build -o $(ORLY_DB_BADGER) ./cmd/orly-db-badger
|
||||||
|
|
||||||
|
# Neo4j database server
|
||||||
|
orly-db-neo4j: |
||||||
|
$(BUILD_FLAGS) go build -o $(ORLY_DB_NEO4J) ./cmd/orly-db-neo4j
|
||||||
|
|
||||||
|
# === ACL Modes ===
|
||||||
|
|
||||||
|
# Legacy monolithic ACL server (requires proto generation first)
|
||||||
|
orly-acl: |
||||||
|
$(BUILD_FLAGS) go build -o $(ORLY_ACL) ./cmd/orly-acl
|
||||||
|
|
||||||
|
# Follows ACL server (whitelist based on admin follows)
|
||||||
|
orly-acl-follows: |
||||||
|
$(BUILD_FLAGS) go build -o $(ORLY_ACL_FOLLOWS) ./cmd/orly-acl-follows
|
||||||
|
|
||||||
|
# Managed ACL server (NIP-86 fine-grained control)
|
||||||
|
orly-acl-managed: |
||||||
|
$(BUILD_FLAGS) go build -o $(ORLY_ACL_MANAGED) ./cmd/orly-acl-managed
|
||||||
|
|
||||||
|
# Curation ACL server (rate-limited trust tiers)
|
||||||
|
orly-acl-curation: |
||||||
|
$(BUILD_FLAGS) go build -o $(ORLY_ACL_CURATION) ./cmd/orly-acl-curation
|
||||||
|
|
||||||
|
# Process supervisor/launcher
|
||||||
|
orly-launcher: |
||||||
|
$(BUILD_FLAGS) go build -o $(ORLY_LAUNCHER) ./cmd/orly-launcher
|
||||||
|
|
||||||
|
# Generate protobuf code
|
||||||
|
proto: |
||||||
|
cd proto && buf generate
|
||||||
|
|
||||||
|
# === Cross-Compile for ARM64 ===
|
||||||
|
|
||||||
|
# Build for ARM64 (relay.orly.dev deployment) - legacy
|
||||||
|
arm64: |
||||||
|
$(MAKE) GOOS=linux GOARCH=arm64 all
|
||||||
|
|
||||||
|
# Build everything for ARM64 including ACL - legacy
|
||||||
|
arm64-acl: |
||||||
|
$(MAKE) GOOS=linux GOARCH=arm64 all-acl
|
||||||
|
|
||||||
|
# Build split mode for ARM64 (recommended for relay.orly.dev)
|
||||||
|
arm64-split: |
||||||
|
$(MAKE) GOOS=linux GOARCH=arm64 all-split
|
||||||
|
|
||||||
|
# Build all backends for ARM64
|
||||||
|
arm64-backends: |
||||||
|
$(MAKE) GOOS=linux GOARCH=arm64 all-backends
|
||||||
|
|
||||||
|
# === Other Targets ===
|
||||||
|
|
||||||
|
# Build web UI and embed
|
||||||
|
web: |
||||||
|
./scripts/update-embedded-web.sh
|
||||||
|
|
||||||
|
# Clean build artifacts
|
||||||
|
clean: |
||||||
|
rm -f $(ORLY) $(ORLY_DB) $(ORLY_ACL) $(ORLY_LAUNCHER)
|
||||||
|
rm -f $(ORLY_DB_BADGER) $(ORLY_DB_NEO4J)
|
||||||
|
rm -f $(ORLY_ACL_FOLLOWS) $(ORLY_ACL_MANAGED) $(ORLY_ACL_CURATION)
|
||||||
|
rm -f orly-db-arm64 orly-acl-arm64 orly-launcher-arm64 next.orly.dev
|
||||||
|
|
||||||
|
# Run tests
|
||||||
|
test: |
||||||
|
./scripts/test.sh
|
||||||
|
|
||||||
|
# Deploy to relay.orly.dev (builds on remote) - legacy
|
||||||
|
deploy: |
||||||
|
ssh relay.orly.dev 'cd ~/src/next.orly.dev && git pull origin main && make all && sudo /usr/sbin/setcap cap_net_bind_service=+ep ~/.local/bin/next.orly.dev && sudo systemctl restart orly'
|
||||||
|
|
||||||
|
# Deploy with ACL server - legacy
|
||||||
|
deploy-acl: |
||||||
|
ssh relay.orly.dev 'cd ~/src/next.orly.dev && git pull origin main && make all-acl && sudo /usr/sbin/setcap cap_net_bind_service=+ep ~/.local/bin/next.orly.dev && sudo systemctl restart orly'
|
||||||
|
|
||||||
|
# Deploy split mode (recommended)
|
||||||
|
deploy-split: |
||||||
|
ssh relay.orly.dev 'cd ~/src/next.orly.dev && git pull origin main && make all-split && sudo /usr/sbin/setcap cap_net_bind_service=+ep ~/.local/bin/next.orly.dev && sudo systemctl restart orly'
|
||||||
|
|
||||||
|
# Install all binaries locally - legacy
|
||||||
|
install: all |
||||||
|
mkdir -p ~/.local/bin
|
||||||
|
cp $(ORLY) $(ORLY_DB) $(ORLY_LAUNCHER) ~/.local/bin/
|
||||||
|
|
||||||
|
# Install including ACL - legacy
|
||||||
|
install-acl: all-acl |
||||||
|
mkdir -p ~/.local/bin
|
||||||
|
cp $(ORLY) $(ORLY_DB) $(ORLY_ACL) $(ORLY_LAUNCHER) ~/.local/bin/
|
||||||
|
|
||||||
|
# Install split mode binaries
|
||||||
|
install-split: all-split |
||||||
|
mkdir -p ~/.local/bin
|
||||||
|
cp $(ORLY) $(ORLY_DB_BADGER) $(ORLY_ACL_FOLLOWS) $(ORLY_LAUNCHER) ~/.local/bin/
|
||||||
|
|
||||||
|
# Install all backends and modes
|
||||||
|
install-backends: all-backends |
||||||
|
mkdir -p ~/.local/bin
|
||||||
|
cp $(ORLY) $(ORLY_DB_BADGER) $(ORLY_DB_NEO4J) $(ORLY_ACL_FOLLOWS) $(ORLY_ACL_MANAGED) $(ORLY_ACL_CURATION) $(ORLY_LAUNCHER) ~/.local/bin/
|
||||||
|
|
||||||
|
# Help
|
||||||
|
help: |
||||||
|
@echo "ORLY Build Targets:"
|
||||||
|
@echo ""
|
||||||
|
@echo " Split Mode (Recommended):"
|
||||||
|
@echo " all-split - Build orly + orly-db-badger + orly-acl-follows + launcher"
|
||||||
|
@echo " all-backends - Build all database backends and ACL modes"
|
||||||
|
@echo " arm64-split - Cross-compile split mode for ARM64"
|
||||||
|
@echo " deploy-split - Deploy split mode to relay.orly.dev"
|
||||||
|
@echo " install-split - Install split mode to ~/.local/bin/"
|
||||||
|
@echo ""
|
||||||
|
@echo " Database Backends:"
|
||||||
|
@echo " orly-db-badger - Build Badger database server"
|
||||||
|
@echo " orly-db-neo4j - Build Neo4j database server"
|
||||||
|
@echo ""
|
||||||
|
@echo " ACL Modes:"
|
||||||
|
@echo " orly-acl-follows - Build Follows ACL server (whitelist)"
|
||||||
|
@echo " orly-acl-managed - Build Managed ACL server (NIP-86)"
|
||||||
|
@echo " orly-acl-curation - Build Curation ACL server (rate limits)"
|
||||||
|
@echo ""
|
||||||
|
@echo " Legacy (Monolithic):"
|
||||||
|
@echo " all - Build orly + orly-db + launcher"
|
||||||
|
@echo " all-acl - Build all including orly-acl"
|
||||||
|
@echo " orly-db - Build monolithic database server"
|
||||||
|
@echo " orly-acl - Build monolithic ACL server"
|
||||||
|
@echo ""
|
||||||
|
@echo " Core:"
|
||||||
|
@echo " orly - Build main relay binary"
|
||||||
|
@echo " orly-launcher - Build process supervisor"
|
||||||
|
@echo " proto - Generate protobuf code"
|
||||||
|
@echo " web - Rebuild embedded web UI"
|
||||||
|
@echo " test - Run test suite"
|
||||||
|
@echo " clean - Remove build artifacts"
|
||||||
|
@echo " help - Show this help"
|
||||||
@ -0,0 +1,163 @@ |
|||||||
|
// orly-acl-curation is a standalone gRPC ACL server using the Curating mode.
|
||||||
|
// It provides three-tier classification: trusted, blacklisted, and unclassified (rate-limited).
|
||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"os" |
||||||
|
"path/filepath" |
||||||
|
"strings" |
||||||
|
"time" |
||||||
|
|
||||||
|
"go-simpler.org/env" |
||||||
|
"lol.mleku.dev" |
||||||
|
"lol.mleku.dev/chk" |
||||||
|
"lol.mleku.dev/log" |
||||||
|
|
||||||
|
"next.orly.dev/pkg/acl/server" |
||||||
|
"next.orly.dev/pkg/database" |
||||||
|
databasegrpc "next.orly.dev/pkg/database/grpc" |
||||||
|
) |
||||||
|
|
||||||
|
// Config holds the ACL server configuration.
|
||||||
|
type Config struct { |
||||||
|
// Listen is the gRPC server listen address
|
||||||
|
Listen string `env:"ORLY_ACL_LISTEN" default:"127.0.0.1:50052" usage:"gRPC server listen address"` |
||||||
|
|
||||||
|
// LogLevel is the logging level
|
||||||
|
LogLevel string `env:"ORLY_ACL_LOG_LEVEL" default:"info" usage:"log level (trace, debug, info, warn, error)"` |
||||||
|
|
||||||
|
// Database configuration
|
||||||
|
DBType string `env:"ORLY_ACL_DB_TYPE" default:"grpc" usage:"database type: badger or grpc"` |
||||||
|
GRPCDBServer string `env:"ORLY_ACL_GRPC_DB_SERVER" usage:"gRPC database server address (when DB_TYPE=grpc)"` |
||||||
|
DataDir string `env:"ORLY_DATA_DIR" usage:"database data directory (when DB_TYPE=badger)"` |
||||||
|
|
||||||
|
// Badger configuration (when DB_TYPE=badger)
|
||||||
|
BlockCacheMB int `env:"ORLY_DB_BLOCK_CACHE_MB" default:"256" usage:"block cache size in MB"` |
||||||
|
IndexCacheMB int `env:"ORLY_DB_INDEX_CACHE_MB" default:"128" usage:"index cache size in MB"` |
||||||
|
ZSTDLevel int `env:"ORLY_DB_ZSTD_LEVEL" default:"3" usage:"ZSTD compression level"` |
||||||
|
QueryCacheSizeMB int `env:"ORLY_DB_QUERY_CACHE_SIZE_MB" default:"64" usage:"query cache size in MB"` |
||||||
|
QueryCacheMaxAge time.Duration `env:"ORLY_DB_QUERY_CACHE_MAX_AGE" default:"5m" usage:"query cache max age"` |
||||||
|
QueryCacheDisabled bool `env:"ORLY_DB_QUERY_CACHE_DISABLED" default:"false" usage:"disable query cache"` |
||||||
|
SerialCachePubkeys int `env:"ORLY_SERIAL_CACHE_PUBKEYS" default:"100000" usage:"serial cache pubkeys capacity"` |
||||||
|
SerialCacheEventIds int `env:"ORLY_SERIAL_CACHE_EVENT_IDS" default:"500000" usage:"serial cache event IDs capacity"` |
||||||
|
|
||||||
|
// ACL configuration
|
||||||
|
Owners string `env:"ORLY_OWNERS" usage:"comma-separated list of owner npubs"` |
||||||
|
Admins string `env:"ORLY_ADMINS" usage:"comma-separated list of admin npubs"` |
||||||
|
RelayAddresses string `env:"ORLY_RELAY_ADDRESSES" usage:"comma-separated list of relay addresses (self)"` |
||||||
|
} |
||||||
|
|
||||||
|
func main() { |
||||||
|
cfg := loadConfig() |
||||||
|
|
||||||
|
// Set log level
|
||||||
|
lol.SetLogLevel(cfg.LogLevel) |
||||||
|
log.I.F("orly-acl-curation starting with log level: %s", cfg.LogLevel) |
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background()) |
||||||
|
defer cancel() |
||||||
|
|
||||||
|
// Initialize database (direct Badger or gRPC client)
|
||||||
|
var db database.Database |
||||||
|
var err error |
||||||
|
var ownsDB bool |
||||||
|
|
||||||
|
if cfg.DBType == "grpc" { |
||||||
|
// Use gRPC database client
|
||||||
|
log.I.F("connecting to gRPC database server at %s", cfg.GRPCDBServer) |
||||||
|
db, err = databasegrpc.New(ctx, &databasegrpc.ClientConfig{ |
||||||
|
ServerAddress: cfg.GRPCDBServer, |
||||||
|
ConnectTimeout: 30 * time.Second, |
||||||
|
}) |
||||||
|
if chk.E(err) { |
||||||
|
log.E.F("failed to connect to gRPC database: %v", err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
ownsDB = false // gRPC client doesn't own the database
|
||||||
|
} else { |
||||||
|
// Use direct Badger database
|
||||||
|
dbCfg := &database.DatabaseConfig{ |
||||||
|
DataDir: cfg.DataDir, |
||||||
|
LogLevel: cfg.LogLevel, |
||||||
|
BlockCacheMB: cfg.BlockCacheMB, |
||||||
|
IndexCacheMB: cfg.IndexCacheMB, |
||||||
|
QueryCacheSizeMB: cfg.QueryCacheSizeMB, |
||||||
|
QueryCacheMaxAge: cfg.QueryCacheMaxAge, |
||||||
|
QueryCacheDisabled: cfg.QueryCacheDisabled, |
||||||
|
SerialCachePubkeys: cfg.SerialCachePubkeys, |
||||||
|
SerialCacheEventIds: cfg.SerialCacheEventIds, |
||||||
|
ZSTDLevel: cfg.ZSTDLevel, |
||||||
|
} |
||||||
|
|
||||||
|
log.I.F("initializing Badger database at %s", cfg.DataDir) |
||||||
|
db, err = database.NewWithConfig(ctx, cancel, dbCfg) |
||||||
|
if chk.E(err) { |
||||||
|
log.E.F("failed to initialize database: %v", err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
ownsDB = true |
||||||
|
} |
||||||
|
|
||||||
|
// Wait for database to be ready
|
||||||
|
log.I.F("waiting for database to be ready...") |
||||||
|
<-db.Ready() |
||||||
|
log.I.F("database ready") |
||||||
|
|
||||||
|
// Create server config
|
||||||
|
serverCfg := &server.Config{ |
||||||
|
Listen: cfg.Listen, |
||||||
|
ACLMode: "curating", // Hardcoded for this binary
|
||||||
|
LogLevel: cfg.LogLevel, |
||||||
|
Owners: splitList(cfg.Owners), |
||||||
|
Admins: splitList(cfg.Admins), |
||||||
|
RelayAddresses: splitList(cfg.RelayAddresses), |
||||||
|
} |
||||||
|
|
||||||
|
// Create and configure server
|
||||||
|
srv := server.New(db, serverCfg, ownsDB) |
||||||
|
if err := srv.ConfigureACL(ctx); chk.E(err) { |
||||||
|
log.E.F("failed to configure ACL: %v", err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
// Start server
|
||||||
|
if err := srv.ListenAndServe(ctx, cancel); err != nil { |
||||||
|
log.E.F("gRPC server error: %v", err) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func loadConfig() *Config { |
||||||
|
cfg := &Config{} |
||||||
|
if err := env.Load(cfg, nil); chk.E(err) { |
||||||
|
log.E.F("failed to load config: %v", err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
// Set default data directory if not specified
|
||||||
|
if cfg.DataDir == "" { |
||||||
|
home, err := os.UserHomeDir() |
||||||
|
if chk.E(err) { |
||||||
|
log.E.F("failed to get home directory: %v", err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
cfg.DataDir = filepath.Join(home, ".local", "share", "ORLY") |
||||||
|
} |
||||||
|
|
||||||
|
// Ensure data directory exists (for badger mode)
|
||||||
|
if cfg.DBType == "badger" || cfg.DBType == "" { |
||||||
|
if err := os.MkdirAll(cfg.DataDir, 0700); chk.E(err) { |
||||||
|
log.E.F("failed to create data directory %s: %v", cfg.DataDir, err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return cfg |
||||||
|
} |
||||||
|
|
||||||
|
func splitList(s string) []string { |
||||||
|
if s == "" { |
||||||
|
return nil |
||||||
|
} |
||||||
|
return strings.Split(s, ",") |
||||||
|
} |
||||||
@ -0,0 +1,175 @@ |
|||||||
|
// orly-acl-follows is a standalone gRPC ACL server using the Follows mode.
|
||||||
|
// It whitelists users who are followed by the relay admins.
|
||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"os" |
||||||
|
"path/filepath" |
||||||
|
"strings" |
||||||
|
"time" |
||||||
|
|
||||||
|
"go-simpler.org/env" |
||||||
|
"lol.mleku.dev" |
||||||
|
"lol.mleku.dev/chk" |
||||||
|
"lol.mleku.dev/log" |
||||||
|
|
||||||
|
"next.orly.dev/pkg/acl/server" |
||||||
|
"next.orly.dev/pkg/database" |
||||||
|
databasegrpc "next.orly.dev/pkg/database/grpc" |
||||||
|
) |
||||||
|
|
||||||
|
// Config holds the ACL server configuration.
|
||||||
|
type Config struct { |
||||||
|
// Listen is the gRPC server listen address
|
||||||
|
Listen string `env:"ORLY_ACL_LISTEN" default:"127.0.0.1:50052" usage:"gRPC server listen address"` |
||||||
|
|
||||||
|
// LogLevel is the logging level
|
||||||
|
LogLevel string `env:"ORLY_ACL_LOG_LEVEL" default:"info" usage:"log level (trace, debug, info, warn, error)"` |
||||||
|
|
||||||
|
// Database configuration
|
||||||
|
DBType string `env:"ORLY_ACL_DB_TYPE" default:"grpc" usage:"database type: badger or grpc"` |
||||||
|
GRPCDBServer string `env:"ORLY_ACL_GRPC_DB_SERVER" usage:"gRPC database server address (when DB_TYPE=grpc)"` |
||||||
|
DataDir string `env:"ORLY_DATA_DIR" usage:"database data directory (when DB_TYPE=badger)"` |
||||||
|
|
||||||
|
// Badger configuration (when DB_TYPE=badger)
|
||||||
|
BlockCacheMB int `env:"ORLY_DB_BLOCK_CACHE_MB" default:"256" usage:"block cache size in MB"` |
||||||
|
IndexCacheMB int `env:"ORLY_DB_INDEX_CACHE_MB" default:"128" usage:"index cache size in MB"` |
||||||
|
ZSTDLevel int `env:"ORLY_DB_ZSTD_LEVEL" default:"3" usage:"ZSTD compression level"` |
||||||
|
QueryCacheSizeMB int `env:"ORLY_DB_QUERY_CACHE_SIZE_MB" default:"64" usage:"query cache size in MB"` |
||||||
|
QueryCacheMaxAge time.Duration `env:"ORLY_DB_QUERY_CACHE_MAX_AGE" default:"5m" usage:"query cache max age"` |
||||||
|
QueryCacheDisabled bool `env:"ORLY_DB_QUERY_CACHE_DISABLED" default:"false" usage:"disable query cache"` |
||||||
|
SerialCachePubkeys int `env:"ORLY_SERIAL_CACHE_PUBKEYS" default:"100000" usage:"serial cache pubkeys capacity"` |
||||||
|
SerialCacheEventIds int `env:"ORLY_SERIAL_CACHE_EVENT_IDS" default:"500000" usage:"serial cache event IDs capacity"` |
||||||
|
|
||||||
|
// ACL configuration
|
||||||
|
Owners string `env:"ORLY_OWNERS" usage:"comma-separated list of owner npubs"` |
||||||
|
Admins string `env:"ORLY_ADMINS" usage:"comma-separated list of admin npubs"` |
||||||
|
BootstrapRelays string `env:"ORLY_BOOTSTRAP_RELAYS" usage:"comma-separated list of bootstrap relays"` |
||||||
|
RelayAddresses string `env:"ORLY_RELAY_ADDRESSES" usage:"comma-separated list of relay addresses (self)"` |
||||||
|
|
||||||
|
// Follows ACL configuration
|
||||||
|
FollowListFrequency time.Duration `env:"ORLY_FOLLOW_LIST_FREQUENCY" default:"1h" usage:"follow list sync frequency"` |
||||||
|
FollowsThrottleEnabled bool `env:"ORLY_FOLLOWS_THROTTLE_ENABLED" default:"false" usage:"enable progressive throttle for non-followed users"` |
||||||
|
FollowsThrottlePerEvent time.Duration `env:"ORLY_FOLLOWS_THROTTLE_PER_EVENT" default:"25ms" usage:"throttle delay increment per event"` |
||||||
|
FollowsThrottleMaxDelay time.Duration `env:"ORLY_FOLLOWS_THROTTLE_MAX_DELAY" default:"60s" usage:"maximum throttle delay"` |
||||||
|
} |
||||||
|
|
||||||
|
func main() { |
||||||
|
cfg := loadConfig() |
||||||
|
|
||||||
|
// Set log level
|
||||||
|
lol.SetLogLevel(cfg.LogLevel) |
||||||
|
log.I.F("orly-acl-follows starting with log level: %s", cfg.LogLevel) |
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background()) |
||||||
|
defer cancel() |
||||||
|
|
||||||
|
// Initialize database (direct Badger or gRPC client)
|
||||||
|
var db database.Database |
||||||
|
var err error |
||||||
|
var ownsDB bool |
||||||
|
|
||||||
|
if cfg.DBType == "grpc" { |
||||||
|
// Use gRPC database client
|
||||||
|
log.I.F("connecting to gRPC database server at %s", cfg.GRPCDBServer) |
||||||
|
db, err = databasegrpc.New(ctx, &databasegrpc.ClientConfig{ |
||||||
|
ServerAddress: cfg.GRPCDBServer, |
||||||
|
ConnectTimeout: 30 * time.Second, |
||||||
|
}) |
||||||
|
if chk.E(err) { |
||||||
|
log.E.F("failed to connect to gRPC database: %v", err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
ownsDB = false // gRPC client doesn't own the database
|
||||||
|
} else { |
||||||
|
// Use direct Badger database
|
||||||
|
dbCfg := &database.DatabaseConfig{ |
||||||
|
DataDir: cfg.DataDir, |
||||||
|
LogLevel: cfg.LogLevel, |
||||||
|
BlockCacheMB: cfg.BlockCacheMB, |
||||||
|
IndexCacheMB: cfg.IndexCacheMB, |
||||||
|
QueryCacheSizeMB: cfg.QueryCacheSizeMB, |
||||||
|
QueryCacheMaxAge: cfg.QueryCacheMaxAge, |
||||||
|
QueryCacheDisabled: cfg.QueryCacheDisabled, |
||||||
|
SerialCachePubkeys: cfg.SerialCachePubkeys, |
||||||
|
SerialCacheEventIds: cfg.SerialCacheEventIds, |
||||||
|
ZSTDLevel: cfg.ZSTDLevel, |
||||||
|
} |
||||||
|
|
||||||
|
log.I.F("initializing Badger database at %s", cfg.DataDir) |
||||||
|
db, err = database.NewWithConfig(ctx, cancel, dbCfg) |
||||||
|
if chk.E(err) { |
||||||
|
log.E.F("failed to initialize database: %v", err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
ownsDB = true |
||||||
|
} |
||||||
|
|
||||||
|
// Wait for database to be ready
|
||||||
|
log.I.F("waiting for database to be ready...") |
||||||
|
<-db.Ready() |
||||||
|
log.I.F("database ready") |
||||||
|
|
||||||
|
// Create server config
|
||||||
|
serverCfg := &server.Config{ |
||||||
|
Listen: cfg.Listen, |
||||||
|
ACLMode: "follows", // Hardcoded for this binary
|
||||||
|
LogLevel: cfg.LogLevel, |
||||||
|
Owners: splitList(cfg.Owners), |
||||||
|
Admins: splitList(cfg.Admins), |
||||||
|
BootstrapRelays: splitList(cfg.BootstrapRelays), |
||||||
|
RelayAddresses: splitList(cfg.RelayAddresses), |
||||||
|
FollowListFrequency: cfg.FollowListFrequency, |
||||||
|
FollowsThrottleEnabled: cfg.FollowsThrottleEnabled, |
||||||
|
FollowsThrottlePerEvent: cfg.FollowsThrottlePerEvent, |
||||||
|
FollowsThrottleMaxDelay: cfg.FollowsThrottleMaxDelay, |
||||||
|
} |
||||||
|
|
||||||
|
// Create and configure server
|
||||||
|
srv := server.New(db, serverCfg, ownsDB) |
||||||
|
if err := srv.ConfigureACL(ctx); chk.E(err) { |
||||||
|
log.E.F("failed to configure ACL: %v", err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
// Start server
|
||||||
|
if err := srv.ListenAndServe(ctx, cancel); err != nil { |
||||||
|
log.E.F("gRPC server error: %v", err) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func loadConfig() *Config { |
||||||
|
cfg := &Config{} |
||||||
|
if err := env.Load(cfg, nil); chk.E(err) { |
||||||
|
log.E.F("failed to load config: %v", err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
// Set default data directory if not specified
|
||||||
|
if cfg.DataDir == "" { |
||||||
|
home, err := os.UserHomeDir() |
||||||
|
if chk.E(err) { |
||||||
|
log.E.F("failed to get home directory: %v", err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
cfg.DataDir = filepath.Join(home, ".local", "share", "ORLY") |
||||||
|
} |
||||||
|
|
||||||
|
// Ensure data directory exists (for badger mode)
|
||||||
|
if cfg.DBType == "badger" { |
||||||
|
if err := os.MkdirAll(cfg.DataDir, 0700); chk.E(err) { |
||||||
|
log.E.F("failed to create data directory %s: %v", cfg.DataDir, err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return cfg |
||||||
|
} |
||||||
|
|
||||||
|
func splitList(s string) []string { |
||||||
|
if s == "" { |
||||||
|
return nil |
||||||
|
} |
||||||
|
return strings.Split(s, ",") |
||||||
|
} |
||||||
@ -0,0 +1,138 @@ |
|||||||
|
// orly-acl-managed is a standalone gRPC ACL server using the Managed mode.
|
||||||
|
// It provides fine-grained control via NIP-86 management API.
|
||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"os" |
||||||
|
"path/filepath" |
||||||
|
"strings" |
||||||
|
"time" |
||||||
|
|
||||||
|
"go-simpler.org/env" |
||||||
|
"lol.mleku.dev" |
||||||
|
"lol.mleku.dev/chk" |
||||||
|
"lol.mleku.dev/log" |
||||||
|
|
||||||
|
"next.orly.dev/pkg/acl/server" |
||||||
|
"next.orly.dev/pkg/database" |
||||||
|
) |
||||||
|
|
||||||
|
// Config holds the ACL server configuration.
|
||||||
|
type Config struct { |
||||||
|
// Listen is the gRPC server listen address
|
||||||
|
Listen string `env:"ORLY_ACL_LISTEN" default:"127.0.0.1:50052" usage:"gRPC server listen address"` |
||||||
|
|
||||||
|
// LogLevel is the logging level
|
||||||
|
LogLevel string `env:"ORLY_ACL_LOG_LEVEL" default:"info" usage:"log level (trace, debug, info, warn, error)"` |
||||||
|
|
||||||
|
// Database configuration - Managed mode requires direct Badger access
|
||||||
|
DataDir string `env:"ORLY_DATA_DIR" usage:"database data directory"` |
||||||
|
|
||||||
|
// Badger configuration
|
||||||
|
BlockCacheMB int `env:"ORLY_DB_BLOCK_CACHE_MB" default:"256" usage:"block cache size in MB"` |
||||||
|
IndexCacheMB int `env:"ORLY_DB_INDEX_CACHE_MB" default:"128" usage:"index cache size in MB"` |
||||||
|
ZSTDLevel int `env:"ORLY_DB_ZSTD_LEVEL" default:"3" usage:"ZSTD compression level"` |
||||||
|
QueryCacheSizeMB int `env:"ORLY_DB_QUERY_CACHE_SIZE_MB" default:"64" usage:"query cache size in MB"` |
||||||
|
QueryCacheMaxAge time.Duration `env:"ORLY_DB_QUERY_CACHE_MAX_AGE" default:"5m" usage:"query cache max age"` |
||||||
|
QueryCacheDisabled bool `env:"ORLY_DB_QUERY_CACHE_DISABLED" default:"false" usage:"disable query cache"` |
||||||
|
SerialCachePubkeys int `env:"ORLY_SERIAL_CACHE_PUBKEYS" default:"100000" usage:"serial cache pubkeys capacity"` |
||||||
|
SerialCacheEventIds int `env:"ORLY_SERIAL_CACHE_EVENT_IDS" default:"500000" usage:"serial cache event IDs capacity"` |
||||||
|
|
||||||
|
// ACL configuration
|
||||||
|
Owners string `env:"ORLY_OWNERS" usage:"comma-separated list of owner npubs"` |
||||||
|
Admins string `env:"ORLY_ADMINS" usage:"comma-separated list of admin npubs"` |
||||||
|
RelayAddresses string `env:"ORLY_RELAY_ADDRESSES" usage:"comma-separated list of relay addresses (self)"` |
||||||
|
} |
||||||
|
|
||||||
|
func main() { |
||||||
|
cfg := loadConfig() |
||||||
|
|
||||||
|
// Set log level
|
||||||
|
lol.SetLogLevel(cfg.LogLevel) |
||||||
|
log.I.F("orly-acl-managed starting with log level: %s", cfg.LogLevel) |
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background()) |
||||||
|
defer cancel() |
||||||
|
|
||||||
|
// Managed mode requires direct Badger database access (not gRPC)
|
||||||
|
dbCfg := &database.DatabaseConfig{ |
||||||
|
DataDir: cfg.DataDir, |
||||||
|
LogLevel: cfg.LogLevel, |
||||||
|
BlockCacheMB: cfg.BlockCacheMB, |
||||||
|
IndexCacheMB: cfg.IndexCacheMB, |
||||||
|
QueryCacheSizeMB: cfg.QueryCacheSizeMB, |
||||||
|
QueryCacheMaxAge: cfg.QueryCacheMaxAge, |
||||||
|
QueryCacheDisabled: cfg.QueryCacheDisabled, |
||||||
|
SerialCachePubkeys: cfg.SerialCachePubkeys, |
||||||
|
SerialCacheEventIds: cfg.SerialCacheEventIds, |
||||||
|
ZSTDLevel: cfg.ZSTDLevel, |
||||||
|
} |
||||||
|
|
||||||
|
log.I.F("initializing Badger database at %s", cfg.DataDir) |
||||||
|
db, err := database.NewWithConfig(ctx, cancel, dbCfg) |
||||||
|
if chk.E(err) { |
||||||
|
log.E.F("failed to initialize database: %v", err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
// Wait for database to be ready
|
||||||
|
log.I.F("waiting for database to be ready...") |
||||||
|
<-db.Ready() |
||||||
|
log.I.F("database ready") |
||||||
|
|
||||||
|
// Create server config
|
||||||
|
serverCfg := &server.Config{ |
||||||
|
Listen: cfg.Listen, |
||||||
|
ACLMode: "managed", // Hardcoded for this binary
|
||||||
|
LogLevel: cfg.LogLevel, |
||||||
|
Owners: splitList(cfg.Owners), |
||||||
|
Admins: splitList(cfg.Admins), |
||||||
|
RelayAddresses: splitList(cfg.RelayAddresses), |
||||||
|
} |
||||||
|
|
||||||
|
// Create and configure server
|
||||||
|
srv := server.New(db, serverCfg, true) |
||||||
|
if err := srv.ConfigureACL(ctx); chk.E(err) { |
||||||
|
log.E.F("failed to configure ACL: %v", err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
// Start server
|
||||||
|
if err := srv.ListenAndServe(ctx, cancel); err != nil { |
||||||
|
log.E.F("gRPC server error: %v", err) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func loadConfig() *Config { |
||||||
|
cfg := &Config{} |
||||||
|
if err := env.Load(cfg, nil); chk.E(err) { |
||||||
|
log.E.F("failed to load config: %v", err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
// Set default data directory if not specified
|
||||||
|
if cfg.DataDir == "" { |
||||||
|
home, err := os.UserHomeDir() |
||||||
|
if chk.E(err) { |
||||||
|
log.E.F("failed to get home directory: %v", err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
cfg.DataDir = filepath.Join(home, ".local", "share", "ORLY") |
||||||
|
} |
||||||
|
|
||||||
|
// Ensure data directory exists
|
||||||
|
if err := os.MkdirAll(cfg.DataDir, 0700); chk.E(err) { |
||||||
|
log.E.F("failed to create data directory %s: %v", cfg.DataDir, err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
return cfg |
||||||
|
} |
||||||
|
|
||||||
|
func splitList(s string) []string { |
||||||
|
if s == "" { |
||||||
|
return nil |
||||||
|
} |
||||||
|
return strings.Split(s, ",") |
||||||
|
} |
||||||
@ -0,0 +1,112 @@ |
|||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"os" |
||||||
|
"path/filepath" |
||||||
|
"strings" |
||||||
|
"time" |
||||||
|
|
||||||
|
"go-simpler.org/env" |
||||||
|
"lol.mleku.dev/chk" |
||||||
|
"lol.mleku.dev/log" |
||||||
|
) |
||||||
|
|
||||||
|
// Config holds the ACL server configuration.
|
||||||
|
type Config struct { |
||||||
|
// Listen is the gRPC server listen address
|
||||||
|
Listen string `env:"ORLY_ACL_LISTEN" default:"127.0.0.1:50052" usage:"gRPC server listen address"` |
||||||
|
|
||||||
|
// ACLMode is the active ACL mode (none, follows, managed, curating)
|
||||||
|
ACLMode string `env:"ORLY_ACL_MODE" default:"none" usage:"ACL mode: none, follows, managed, curating"` |
||||||
|
|
||||||
|
// LogLevel is the logging level
|
||||||
|
LogLevel string `env:"ORLY_ACL_LOG_LEVEL" default:"info" usage:"log level (trace, debug, info, warn, error)"` |
||||||
|
|
||||||
|
// Database configuration
|
||||||
|
DBType string `env:"ORLY_ACL_DB_TYPE" default:"badger" usage:"database type: badger or grpc"` |
||||||
|
GRPCDBServer string `env:"ORLY_ACL_GRPC_DB_SERVER" usage:"gRPC database server address (when DB_TYPE=grpc)"` |
||||||
|
DataDir string `env:"ORLY_DATA_DIR" usage:"database data directory (when DB_TYPE=badger)"` |
||||||
|
|
||||||
|
// Badger configuration (when DB_TYPE=badger)
|
||||||
|
BlockCacheMB int `env:"ORLY_DB_BLOCK_CACHE_MB" default:"1024" usage:"block cache size in MB"` |
||||||
|
IndexCacheMB int `env:"ORLY_DB_INDEX_CACHE_MB" default:"512" usage:"index cache size in MB"` |
||||||
|
ZSTDLevel int `env:"ORLY_DB_ZSTD_LEVEL" default:"3" usage:"ZSTD compression level"` |
||||||
|
QueryCacheSizeMB int `env:"ORLY_DB_QUERY_CACHE_SIZE_MB" default:"256" usage:"query cache size in MB"` |
||||||
|
QueryCacheMaxAge time.Duration `env:"ORLY_DB_QUERY_CACHE_MAX_AGE" default:"5m" usage:"query cache max age"` |
||||||
|
QueryCacheDisabled bool `env:"ORLY_DB_QUERY_CACHE_DISABLED" default:"false" usage:"disable query cache"` |
||||||
|
SerialCachePubkeys int `env:"ORLY_SERIAL_CACHE_PUBKEYS" default:"100000" usage:"serial cache pubkeys capacity"` |
||||||
|
SerialCacheEventIds int `env:"ORLY_SERIAL_CACHE_EVENT_IDS" default:"500000" usage:"serial cache event IDs capacity"` |
||||||
|
|
||||||
|
// ACL configuration
|
||||||
|
Owners string `env:"ORLY_OWNERS" usage:"comma-separated list of owner npubs"` |
||||||
|
Admins string `env:"ORLY_ADMINS" usage:"comma-separated list of admin npubs"` |
||||||
|
BootstrapRelays string `env:"ORLY_BOOTSTRAP_RELAYS" usage:"comma-separated list of bootstrap relays"` |
||||||
|
RelayAddresses string `env:"ORLY_RELAY_ADDRESSES" usage:"comma-separated list of relay addresses (self)"` |
||||||
|
|
||||||
|
// Follows ACL configuration
|
||||||
|
FollowListFrequency time.Duration `env:"ORLY_FOLLOW_LIST_FREQUENCY" default:"1h" usage:"follow list sync frequency"` |
||||||
|
FollowsThrottleEnabled bool `env:"ORLY_FOLLOWS_THROTTLE_ENABLED" default:"false" usage:"enable progressive throttle for non-followed users"` |
||||||
|
FollowsThrottlePerEvent time.Duration `env:"ORLY_FOLLOWS_THROTTLE_PER_EVENT" default:"25ms" usage:"throttle delay increment per event"` |
||||||
|
FollowsThrottleMaxDelay time.Duration `env:"ORLY_FOLLOWS_THROTTLE_MAX_DELAY" default:"60s" usage:"maximum throttle delay"` |
||||||
|
} |
||||||
|
|
||||||
|
// loadConfig loads configuration from environment variables.
|
||||||
|
func loadConfig() *Config { |
||||||
|
cfg := &Config{} |
||||||
|
if err := env.Load(cfg, nil); chk.E(err) { |
||||||
|
log.E.F("failed to load config: %v", err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
// Set default data directory if not specified
|
||||||
|
if cfg.DataDir == "" { |
||||||
|
home, err := os.UserHomeDir() |
||||||
|
if chk.E(err) { |
||||||
|
log.E.F("failed to get home directory: %v", err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
cfg.DataDir = filepath.Join(home, ".local", "share", "ORLY") |
||||||
|
} |
||||||
|
|
||||||
|
// Ensure data directory exists (for badger mode)
|
||||||
|
if cfg.DBType == "badger" { |
||||||
|
if err := os.MkdirAll(cfg.DataDir, 0700); chk.E(err) { |
||||||
|
log.E.F("failed to create data directory %s: %v", cfg.DataDir, err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return cfg |
||||||
|
} |
||||||
|
|
||||||
|
// GetOwners returns the list of owner pubkeys
|
||||||
|
func (c *Config) GetOwners() []string { |
||||||
|
if c.Owners == "" { |
||||||
|
return nil |
||||||
|
} |
||||||
|
return strings.Split(c.Owners, ",") |
||||||
|
} |
||||||
|
|
||||||
|
// GetAdmins returns the list of admin pubkeys
|
||||||
|
func (c *Config) GetAdmins() []string { |
||||||
|
if c.Admins == "" { |
||||||
|
return nil |
||||||
|
} |
||||||
|
return strings.Split(c.Admins, ",") |
||||||
|
} |
||||||
|
|
||||||
|
// GetBootstrapRelays returns the list of bootstrap relays
|
||||||
|
func (c *Config) GetBootstrapRelays() []string { |
||||||
|
if c.BootstrapRelays == "" { |
||||||
|
return nil |
||||||
|
} |
||||||
|
return strings.Split(c.BootstrapRelays, ",") |
||||||
|
} |
||||||
|
|
||||||
|
// GetRelayAddresses returns the list of relay addresses (self)
|
||||||
|
func (c *Config) GetRelayAddresses() []string { |
||||||
|
if c.RelayAddresses == "" { |
||||||
|
return nil |
||||||
|
} |
||||||
|
return strings.Split(c.RelayAddresses, ",") |
||||||
|
} |
||||||
@ -0,0 +1,167 @@ |
|||||||
|
// orly-acl is a standalone gRPC ACL server for the ORLY relay.
|
||||||
|
// It wraps the ACL implementations and exposes them via gRPC.
|
||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"net" |
||||||
|
"os" |
||||||
|
"os/signal" |
||||||
|
"syscall" |
||||||
|
"time" |
||||||
|
|
||||||
|
"google.golang.org/grpc" |
||||||
|
"google.golang.org/grpc/reflection" |
||||||
|
"lol.mleku.dev" |
||||||
|
"lol.mleku.dev/chk" |
||||||
|
"lol.mleku.dev/log" |
||||||
|
|
||||||
|
"next.orly.dev/app/config" |
||||||
|
"next.orly.dev/pkg/acl" |
||||||
|
"next.orly.dev/pkg/database" |
||||||
|
databasegrpc "next.orly.dev/pkg/database/grpc" |
||||||
|
orlyaclv1 "next.orly.dev/pkg/proto/orlyacl/v1" |
||||||
|
) |
||||||
|
|
||||||
|
func main() { |
||||||
|
cfg := loadConfig() |
||||||
|
|
||||||
|
// Set log level
|
||||||
|
lol.SetLogLevel(cfg.LogLevel) |
||||||
|
log.I.F("orly-acl starting with log level: %s, mode: %s", cfg.LogLevel, cfg.ACLMode) |
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background()) |
||||||
|
defer cancel() |
||||||
|
|
||||||
|
// Initialize database (direct Badger or gRPC client)
|
||||||
|
var db database.Database |
||||||
|
var err error |
||||||
|
|
||||||
|
if cfg.DBType == "grpc" { |
||||||
|
// Use gRPC database client
|
||||||
|
log.I.F("connecting to gRPC database server at %s", cfg.GRPCDBServer) |
||||||
|
dbClient, err := databasegrpc.New(ctx, &databasegrpc.ClientConfig{ |
||||||
|
ServerAddress: cfg.GRPCDBServer, |
||||||
|
ConnectTimeout: 30 * time.Second, |
||||||
|
}) |
||||||
|
if chk.E(err) { |
||||||
|
log.E.F("failed to connect to gRPC database: %v", err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
db = dbClient |
||||||
|
} else { |
||||||
|
// Use direct Badger database
|
||||||
|
dbCfg := &database.DatabaseConfig{ |
||||||
|
DataDir: cfg.DataDir, |
||||||
|
LogLevel: cfg.LogLevel, |
||||||
|
BlockCacheMB: cfg.BlockCacheMB, |
||||||
|
IndexCacheMB: cfg.IndexCacheMB, |
||||||
|
QueryCacheSizeMB: cfg.QueryCacheSizeMB, |
||||||
|
QueryCacheMaxAge: cfg.QueryCacheMaxAge, |
||||||
|
QueryCacheDisabled: cfg.QueryCacheDisabled, |
||||||
|
SerialCachePubkeys: cfg.SerialCachePubkeys, |
||||||
|
SerialCacheEventIds: cfg.SerialCacheEventIds, |
||||||
|
ZSTDLevel: cfg.ZSTDLevel, |
||||||
|
} |
||||||
|
|
||||||
|
log.I.F("initializing Badger database at %s", cfg.DataDir) |
||||||
|
db, err = database.NewWithConfig(ctx, cancel, dbCfg) |
||||||
|
if chk.E(err) { |
||||||
|
log.E.F("failed to initialize database: %v", err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Wait for database to be ready
|
||||||
|
log.I.F("waiting for database to be ready...") |
||||||
|
<-db.Ready() |
||||||
|
log.I.F("database ready") |
||||||
|
|
||||||
|
// Create app config for ACL configuration
|
||||||
|
appCfg := &config.C{ |
||||||
|
Owners: cfg.GetOwners(), |
||||||
|
Admins: cfg.GetAdmins(), |
||||||
|
BootstrapRelays: cfg.GetBootstrapRelays(), |
||||||
|
RelayAddresses: cfg.GetRelayAddresses(), |
||||||
|
FollowListFrequency: cfg.FollowListFrequency, |
||||||
|
FollowsThrottleEnabled: cfg.FollowsThrottleEnabled, |
||||||
|
FollowsThrottlePerEvent: cfg.FollowsThrottlePerEvent, |
||||||
|
FollowsThrottleMaxDelay: cfg.FollowsThrottleMaxDelay, |
||||||
|
} |
||||||
|
|
||||||
|
// Set ACL mode and configure the registry
|
||||||
|
acl.Registry.SetMode(cfg.ACLMode) |
||||||
|
if err := acl.Registry.Configure(appCfg, db, ctx); chk.E(err) { |
||||||
|
log.E.F("failed to configure ACL: %v", err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
// Start the syncer goroutine for background operations
|
||||||
|
acl.Registry.Syncer() |
||||||
|
log.I.F("ACL syncer started for mode: %s", cfg.ACLMode) |
||||||
|
|
||||||
|
// Create gRPC server
|
||||||
|
grpcServer := grpc.NewServer( |
||||||
|
grpc.MaxRecvMsgSize(16<<20), // 16MB
|
||||||
|
grpc.MaxSendMsgSize(16<<20), // 16MB
|
||||||
|
) |
||||||
|
|
||||||
|
// Register ACL service
|
||||||
|
service := NewACLService(cfg, db) |
||||||
|
orlyaclv1.RegisterACLServiceServer(grpcServer, service) |
||||||
|
|
||||||
|
// Register reflection for debugging with grpcurl
|
||||||
|
reflection.Register(grpcServer) |
||||||
|
|
||||||
|
// Start listening
|
||||||
|
lis, err := net.Listen("tcp", cfg.Listen) |
||||||
|
if chk.E(err) { |
||||||
|
log.E.F("failed to listen on %s: %v", cfg.Listen, err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
log.I.F("gRPC ACL server listening on %s", cfg.Listen) |
||||||
|
|
||||||
|
// Handle graceful shutdown
|
||||||
|
go func() { |
||||||
|
sigs := make(chan os.Signal, 1) |
||||||
|
signal.Notify(sigs, syscall.SIGTERM, syscall.SIGINT) |
||||||
|
sig := <-sigs |
||||||
|
log.I.F("received signal %v, shutting down...", sig) |
||||||
|
|
||||||
|
// Cancel context to stop all operations
|
||||||
|
cancel() |
||||||
|
|
||||||
|
// Gracefully stop gRPC server with timeout
|
||||||
|
stopped := make(chan struct{}) |
||||||
|
go func() { |
||||||
|
grpcServer.GracefulStop() |
||||||
|
close(stopped) |
||||||
|
}() |
||||||
|
|
||||||
|
select { |
||||||
|
case <-stopped: |
||||||
|
log.I.F("gRPC server stopped gracefully") |
||||||
|
case <-time.After(5 * time.Second): |
||||||
|
log.W.F("gRPC graceful stop timed out, forcing stop") |
||||||
|
grpcServer.Stop() |
||||||
|
} |
||||||
|
|
||||||
|
// Sync and close database (only for direct Badger)
|
||||||
|
if cfg.DBType != "grpc" { |
||||||
|
log.I.F("syncing database...") |
||||||
|
if err := db.Sync(); chk.E(err) { |
||||||
|
log.W.F("failed to sync database: %v", err) |
||||||
|
} |
||||||
|
log.I.F("closing database...") |
||||||
|
if err := db.Close(); chk.E(err) { |
||||||
|
log.W.F("failed to close database: %v", err) |
||||||
|
} |
||||||
|
} |
||||||
|
log.I.F("shutdown complete") |
||||||
|
}() |
||||||
|
|
||||||
|
// Serve gRPC
|
||||||
|
if err := grpcServer.Serve(lis); err != nil { |
||||||
|
log.E.F("gRPC server error: %v", err) |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,788 @@ |
|||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"encoding/hex" |
||||||
|
|
||||||
|
"google.golang.org/grpc/codes" |
||||||
|
"google.golang.org/grpc/status" |
||||||
|
"lol.mleku.dev/log" |
||||||
|
|
||||||
|
"next.orly.dev/pkg/acl" |
||||||
|
"next.orly.dev/pkg/database" |
||||||
|
orlyaclv1 "next.orly.dev/pkg/proto/orlyacl/v1" |
||||||
|
orlydbv1 "next.orly.dev/pkg/proto/orlydb/v1" |
||||||
|
) |
||||||
|
|
||||||
|
// ACLService implements the orlyaclv1.ACLServiceServer interface.
|
||||||
|
type ACLService struct { |
||||||
|
orlyaclv1.UnimplementedACLServiceServer |
||||||
|
cfg *Config |
||||||
|
db database.Database |
||||||
|
} |
||||||
|
|
||||||
|
// NewACLService creates a new ACL service.
|
||||||
|
func NewACLService(cfg *Config, db database.Database) *ACLService { |
||||||
|
return &ACLService{ |
||||||
|
cfg: cfg, |
||||||
|
db: db, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// === Core ACL Methods ===
|
||||||
|
|
||||||
|
func (s *ACLService) GetAccessLevel(ctx context.Context, req *orlyaclv1.AccessLevelRequest) (*orlyaclv1.AccessLevelResponse, error) { |
||||||
|
level := acl.Registry.GetAccessLevel(req.Pubkey, req.Address) |
||||||
|
return &orlyaclv1.AccessLevelResponse{Level: level}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) CheckPolicy(ctx context.Context, req *orlyaclv1.PolicyCheckRequest) (*orlyaclv1.PolicyCheckResponse, error) { |
||||||
|
ev := orlydbv1.ProtoToEvent(req.Event) |
||||||
|
allowed, err := acl.Registry.CheckPolicy(ev) |
||||||
|
resp := &orlyaclv1.PolicyCheckResponse{Allowed: allowed} |
||||||
|
if err != nil { |
||||||
|
resp.Error = err.Error() |
||||||
|
} |
||||||
|
return resp, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) GetACLInfo(ctx context.Context, req *orlyaclv1.Empty) (*orlyaclv1.ACLInfoResponse, error) { |
||||||
|
name, description, documentation := acl.Registry.GetACLInfo() |
||||||
|
return &orlyaclv1.ACLInfoResponse{ |
||||||
|
Name: name, |
||||||
|
Description: description, |
||||||
|
Documentation: documentation, |
||||||
|
}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) GetMode(ctx context.Context, req *orlyaclv1.Empty) (*orlyaclv1.ModeResponse, error) { |
||||||
|
return &orlyaclv1.ModeResponse{Mode: acl.Registry.Type()}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) Ready(ctx context.Context, req *orlyaclv1.Empty) (*orlyaclv1.ReadyResponse, error) { |
||||||
|
// Check if database is ready
|
||||||
|
select { |
||||||
|
case <-s.db.Ready(): |
||||||
|
return &orlyaclv1.ReadyResponse{Ready: true}, nil |
||||||
|
default: |
||||||
|
return &orlyaclv1.ReadyResponse{Ready: false}, nil |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// === Follows ACL Methods ===
|
||||||
|
|
||||||
|
func (s *ACLService) GetThrottleDelay(ctx context.Context, req *orlyaclv1.ThrottleDelayRequest) (*orlyaclv1.ThrottleDelayResponse, error) { |
||||||
|
// Get the active ACL and check if it's Follows
|
||||||
|
for _, i := range acl.Registry.ACL { |
||||||
|
if i.Type() == "follows" { |
||||||
|
if follows, ok := i.(*acl.Follows); ok { |
||||||
|
delay := follows.GetThrottleDelay(req.Pubkey, req.Ip) |
||||||
|
return &orlyaclv1.ThrottleDelayResponse{DelayMs: delay.Milliseconds()}, nil |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return &orlyaclv1.ThrottleDelayResponse{DelayMs: 0}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) AddFollow(ctx context.Context, req *orlyaclv1.AddFollowRequest) (*orlyaclv1.Empty, error) { |
||||||
|
acl.Registry.AddFollow(req.Pubkey) |
||||||
|
return &orlyaclv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) GetFollowedPubkeys(ctx context.Context, req *orlyaclv1.Empty) (*orlyaclv1.FollowedPubkeysResponse, error) { |
||||||
|
for _, i := range acl.Registry.ACL { |
||||||
|
if i.Type() == "follows" { |
||||||
|
if follows, ok := i.(*acl.Follows); ok { |
||||||
|
pubkeys := follows.GetFollowedPubkeys() |
||||||
|
return &orlyaclv1.FollowedPubkeysResponse{Pubkeys: pubkeys}, nil |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return &orlyaclv1.FollowedPubkeysResponse{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) GetAdminRelays(ctx context.Context, req *orlyaclv1.Empty) (*orlyaclv1.AdminRelaysResponse, error) { |
||||||
|
for _, i := range acl.Registry.ACL { |
||||||
|
if i.Type() == "follows" { |
||||||
|
if follows, ok := i.(*acl.Follows); ok { |
||||||
|
urls := follows.AdminRelays() |
||||||
|
return &orlyaclv1.AdminRelaysResponse{Urls: urls}, nil |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return &orlyaclv1.AdminRelaysResponse{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
// === Managed ACL Methods ===
|
||||||
|
|
||||||
|
func (s *ACLService) BanPubkey(ctx context.Context, req *orlyaclv1.BanPubkeyRequest) (*orlyaclv1.Empty, error) { |
||||||
|
managed := s.getManagedACL() |
||||||
|
if managed == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL not available") |
||||||
|
} |
||||||
|
managedACL := managed.GetManagedACL() |
||||||
|
if managedACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL database not available") |
||||||
|
} |
||||||
|
if err := managedACL.SaveBannedPubkey(req.Pubkey, req.Reason); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to ban pubkey: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) UnbanPubkey(ctx context.Context, req *orlyaclv1.PubkeyRequest) (*orlyaclv1.Empty, error) { |
||||||
|
managed := s.getManagedACL() |
||||||
|
if managed == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL not available") |
||||||
|
} |
||||||
|
managedACL := managed.GetManagedACL() |
||||||
|
if managedACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL database not available") |
||||||
|
} |
||||||
|
if err := managedACL.RemoveBannedPubkey(req.Pubkey); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to unban pubkey: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) ListBannedPubkeys(ctx context.Context, req *orlyaclv1.Empty) (*orlyaclv1.ListBannedPubkeysResponse, error) { |
||||||
|
managed := s.getManagedACL() |
||||||
|
if managed == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL not available") |
||||||
|
} |
||||||
|
managedACL := managed.GetManagedACL() |
||||||
|
if managedACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL database not available") |
||||||
|
} |
||||||
|
banned, err := managedACL.ListBannedPubkeys() |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to list banned pubkeys: %v", err) |
||||||
|
} |
||||||
|
resp := &orlyaclv1.ListBannedPubkeysResponse{} |
||||||
|
for _, b := range banned { |
||||||
|
resp.Pubkeys = append(resp.Pubkeys, &orlyaclv1.BannedPubkey{ |
||||||
|
Pubkey: b.Pubkey, |
||||||
|
Reason: b.Reason, |
||||||
|
Added: b.Added.Unix(), |
||||||
|
}) |
||||||
|
} |
||||||
|
return resp, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) AllowPubkey(ctx context.Context, req *orlyaclv1.AllowPubkeyRequest) (*orlyaclv1.Empty, error) { |
||||||
|
managed := s.getManagedACL() |
||||||
|
if managed == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL not available") |
||||||
|
} |
||||||
|
managedACL := managed.GetManagedACL() |
||||||
|
if managedACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL database not available") |
||||||
|
} |
||||||
|
if err := managedACL.SaveAllowedPubkey(req.Pubkey, req.Reason); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to allow pubkey: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) DisallowPubkey(ctx context.Context, req *orlyaclv1.PubkeyRequest) (*orlyaclv1.Empty, error) { |
||||||
|
managed := s.getManagedACL() |
||||||
|
if managed == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL not available") |
||||||
|
} |
||||||
|
managedACL := managed.GetManagedACL() |
||||||
|
if managedACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL database not available") |
||||||
|
} |
||||||
|
if err := managedACL.RemoveAllowedPubkey(req.Pubkey); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to disallow pubkey: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) ListAllowedPubkeys(ctx context.Context, req *orlyaclv1.Empty) (*orlyaclv1.ListAllowedPubkeysResponse, error) { |
||||||
|
managed := s.getManagedACL() |
||||||
|
if managed == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL not available") |
||||||
|
} |
||||||
|
managedACL := managed.GetManagedACL() |
||||||
|
if managedACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL database not available") |
||||||
|
} |
||||||
|
allowed, err := managedACL.ListAllowedPubkeys() |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to list allowed pubkeys: %v", err) |
||||||
|
} |
||||||
|
resp := &orlyaclv1.ListAllowedPubkeysResponse{} |
||||||
|
for _, a := range allowed { |
||||||
|
resp.Pubkeys = append(resp.Pubkeys, &orlyaclv1.AllowedPubkey{ |
||||||
|
Pubkey: a.Pubkey, |
||||||
|
Reason: a.Reason, |
||||||
|
Added: a.Added.Unix(), |
||||||
|
}) |
||||||
|
} |
||||||
|
return resp, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) BanEvent(ctx context.Context, req *orlyaclv1.BanEventRequest) (*orlyaclv1.Empty, error) { |
||||||
|
managed := s.getManagedACL() |
||||||
|
if managed == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL not available") |
||||||
|
} |
||||||
|
managedACL := managed.GetManagedACL() |
||||||
|
if managedACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL database not available") |
||||||
|
} |
||||||
|
if err := managedACL.SaveBannedEvent(req.EventId, req.Reason); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to ban event: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) UnbanEvent(ctx context.Context, req *orlyaclv1.EventRequest) (*orlyaclv1.Empty, error) { |
||||||
|
managed := s.getManagedACL() |
||||||
|
if managed == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL not available") |
||||||
|
} |
||||||
|
managedACL := managed.GetManagedACL() |
||||||
|
if managedACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL database not available") |
||||||
|
} |
||||||
|
if err := managedACL.RemoveBannedEvent(req.EventId); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to unban event: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) ListBannedEvents(ctx context.Context, req *orlyaclv1.Empty) (*orlyaclv1.ListBannedEventsResponse, error) { |
||||||
|
managed := s.getManagedACL() |
||||||
|
if managed == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL not available") |
||||||
|
} |
||||||
|
managedACL := managed.GetManagedACL() |
||||||
|
if managedACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL database not available") |
||||||
|
} |
||||||
|
banned, err := managedACL.ListBannedEvents() |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to list banned events: %v", err) |
||||||
|
} |
||||||
|
resp := &orlyaclv1.ListBannedEventsResponse{} |
||||||
|
for _, b := range banned { |
||||||
|
resp.Events = append(resp.Events, &orlyaclv1.BannedEvent{ |
||||||
|
EventId: b.ID, |
||||||
|
Reason: b.Reason, |
||||||
|
Added: b.Added.Unix(), |
||||||
|
}) |
||||||
|
} |
||||||
|
return resp, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) AllowEvent(ctx context.Context, req *orlyaclv1.BanEventRequest) (*orlyaclv1.Empty, error) { |
||||||
|
managed := s.getManagedACL() |
||||||
|
if managed == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL not available") |
||||||
|
} |
||||||
|
managedACL := managed.GetManagedACL() |
||||||
|
if managedACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL database not available") |
||||||
|
} |
||||||
|
if err := managedACL.SaveAllowedEvent(req.EventId, req.Reason); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to allow event: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) DisallowEvent(ctx context.Context, req *orlyaclv1.EventRequest) (*orlyaclv1.Empty, error) { |
||||||
|
managed := s.getManagedACL() |
||||||
|
if managed == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL not available") |
||||||
|
} |
||||||
|
managedACL := managed.GetManagedACL() |
||||||
|
if managedACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL database not available") |
||||||
|
} |
||||||
|
if err := managedACL.RemoveAllowedEvent(req.EventId); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to disallow event: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) ListAllowedEvents(ctx context.Context, req *orlyaclv1.Empty) (*orlyaclv1.ListAllowedEventsResponse, error) { |
||||||
|
managed := s.getManagedACL() |
||||||
|
if managed == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL not available") |
||||||
|
} |
||||||
|
managedACL := managed.GetManagedACL() |
||||||
|
if managedACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL database not available") |
||||||
|
} |
||||||
|
allowed, err := managedACL.ListAllowedEvents() |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to list allowed events: %v", err) |
||||||
|
} |
||||||
|
resp := &orlyaclv1.ListAllowedEventsResponse{} |
||||||
|
for _, a := range allowed { |
||||||
|
resp.Events = append(resp.Events, &orlyaclv1.AllowedEvent{ |
||||||
|
EventId: a.ID, |
||||||
|
Reason: a.Reason, |
||||||
|
Added: a.Added.Unix(), |
||||||
|
}) |
||||||
|
} |
||||||
|
return resp, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) BlockIP(ctx context.Context, req *orlyaclv1.BlockIPRequest) (*orlyaclv1.Empty, error) { |
||||||
|
managed := s.getManagedACL() |
||||||
|
if managed == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL not available") |
||||||
|
} |
||||||
|
managedACL := managed.GetManagedACL() |
||||||
|
if managedACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL database not available") |
||||||
|
} |
||||||
|
if err := managedACL.SaveBlockedIP(req.Ip, req.Reason); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to block IP: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) UnblockIP(ctx context.Context, req *orlyaclv1.IPRequest) (*orlyaclv1.Empty, error) { |
||||||
|
managed := s.getManagedACL() |
||||||
|
if managed == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL not available") |
||||||
|
} |
||||||
|
managedACL := managed.GetManagedACL() |
||||||
|
if managedACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL database not available") |
||||||
|
} |
||||||
|
if err := managedACL.RemoveBlockedIP(req.Ip); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to unblock IP: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) ListBlockedIPs(ctx context.Context, req *orlyaclv1.Empty) (*orlyaclv1.ListBlockedIPsResponse, error) { |
||||||
|
managed := s.getManagedACL() |
||||||
|
if managed == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL not available") |
||||||
|
} |
||||||
|
managedACL := managed.GetManagedACL() |
||||||
|
if managedACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL database not available") |
||||||
|
} |
||||||
|
blocked, err := managedACL.ListBlockedIPs() |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to list blocked IPs: %v", err) |
||||||
|
} |
||||||
|
resp := &orlyaclv1.ListBlockedIPsResponse{} |
||||||
|
for _, b := range blocked { |
||||||
|
resp.Ips = append(resp.Ips, &orlyaclv1.BlockedIP{ |
||||||
|
Ip: b.IP, |
||||||
|
Reason: b.Reason, |
||||||
|
Added: b.Added.Unix(), |
||||||
|
}) |
||||||
|
} |
||||||
|
return resp, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) AllowKind(ctx context.Context, req *orlyaclv1.AllowKindRequest) (*orlyaclv1.Empty, error) { |
||||||
|
managed := s.getManagedACL() |
||||||
|
if managed == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL not available") |
||||||
|
} |
||||||
|
managedACL := managed.GetManagedACL() |
||||||
|
if managedACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL database not available") |
||||||
|
} |
||||||
|
if err := managedACL.SaveAllowedKind(int(req.Kind)); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to allow kind: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) DisallowKind(ctx context.Context, req *orlyaclv1.KindRequest) (*orlyaclv1.Empty, error) { |
||||||
|
managed := s.getManagedACL() |
||||||
|
if managed == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL not available") |
||||||
|
} |
||||||
|
managedACL := managed.GetManagedACL() |
||||||
|
if managedACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL database not available") |
||||||
|
} |
||||||
|
if err := managedACL.RemoveAllowedKind(int(req.Kind)); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to disallow kind: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) ListAllowedKinds(ctx context.Context, req *orlyaclv1.Empty) (*orlyaclv1.ListAllowedKindsResponse, error) { |
||||||
|
managed := s.getManagedACL() |
||||||
|
if managed == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL not available") |
||||||
|
} |
||||||
|
managedACL := managed.GetManagedACL() |
||||||
|
if managedACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL database not available") |
||||||
|
} |
||||||
|
kinds, err := managedACL.ListAllowedKinds() |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to list allowed kinds: %v", err) |
||||||
|
} |
||||||
|
resp := &orlyaclv1.ListAllowedKindsResponse{} |
||||||
|
for _, k := range kinds { |
||||||
|
resp.Kinds = append(resp.Kinds, int32(k)) |
||||||
|
} |
||||||
|
return resp, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) UpdatePeerAdmins(ctx context.Context, req *orlyaclv1.UpdatePeerAdminsRequest) (*orlyaclv1.Empty, error) { |
||||||
|
managed := s.getManagedACL() |
||||||
|
if managed == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL not available") |
||||||
|
} |
||||||
|
managed.UpdatePeerAdmins(req.PeerPubkeys) |
||||||
|
return &orlyaclv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
// === Curating ACL Methods ===
|
||||||
|
|
||||||
|
func (s *ACLService) TrustPubkey(ctx context.Context, req *orlyaclv1.TrustPubkeyRequest) (*orlyaclv1.Empty, error) { |
||||||
|
curating := s.getCuratingACL() |
||||||
|
if curating == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL not available") |
||||||
|
} |
||||||
|
if err := curating.TrustPubkey(req.Pubkey, req.Note); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to trust pubkey: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) UntrustPubkey(ctx context.Context, req *orlyaclv1.PubkeyRequest) (*orlyaclv1.Empty, error) { |
||||||
|
curating := s.getCuratingACL() |
||||||
|
if curating == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL not available") |
||||||
|
} |
||||||
|
if err := curating.UntrustPubkey(req.Pubkey); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to untrust pubkey: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) ListTrustedPubkeys(ctx context.Context, req *orlyaclv1.Empty) (*orlyaclv1.ListTrustedPubkeysResponse, error) { |
||||||
|
curating := s.getCuratingACL() |
||||||
|
if curating == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL not available") |
||||||
|
} |
||||||
|
curatingACL := curating.GetCuratingACL() |
||||||
|
if curatingACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL database not available") |
||||||
|
} |
||||||
|
trusted, err := curatingACL.ListTrustedPubkeys() |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to list trusted pubkeys: %v", err) |
||||||
|
} |
||||||
|
resp := &orlyaclv1.ListTrustedPubkeysResponse{} |
||||||
|
for _, t := range trusted { |
||||||
|
resp.Pubkeys = append(resp.Pubkeys, &orlyaclv1.TrustedPubkey{ |
||||||
|
Pubkey: t.Pubkey, |
||||||
|
Note: t.Note, |
||||||
|
Added: t.Added.Unix(), |
||||||
|
}) |
||||||
|
} |
||||||
|
return resp, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) BlacklistPubkey(ctx context.Context, req *orlyaclv1.BlacklistPubkeyRequest) (*orlyaclv1.Empty, error) { |
||||||
|
curating := s.getCuratingACL() |
||||||
|
if curating == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL not available") |
||||||
|
} |
||||||
|
if err := curating.BlacklistPubkey(req.Pubkey, req.Reason); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to blacklist pubkey: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) UnblacklistPubkey(ctx context.Context, req *orlyaclv1.PubkeyRequest) (*orlyaclv1.Empty, error) { |
||||||
|
curating := s.getCuratingACL() |
||||||
|
if curating == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL not available") |
||||||
|
} |
||||||
|
if err := curating.UnblacklistPubkey(req.Pubkey); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to unblacklist pubkey: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) ListBlacklistedPubkeys(ctx context.Context, req *orlyaclv1.Empty) (*orlyaclv1.ListBlacklistedPubkeysResponse, error) { |
||||||
|
curating := s.getCuratingACL() |
||||||
|
if curating == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL not available") |
||||||
|
} |
||||||
|
curatingACL := curating.GetCuratingACL() |
||||||
|
if curatingACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL database not available") |
||||||
|
} |
||||||
|
blacklisted, err := curatingACL.ListBlacklistedPubkeys() |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to list blacklisted pubkeys: %v", err) |
||||||
|
} |
||||||
|
resp := &orlyaclv1.ListBlacklistedPubkeysResponse{} |
||||||
|
for _, b := range blacklisted { |
||||||
|
resp.Pubkeys = append(resp.Pubkeys, &orlyaclv1.BlacklistedPubkey{ |
||||||
|
Pubkey: b.Pubkey, |
||||||
|
Reason: b.Reason, |
||||||
|
Added: b.Added.Unix(), |
||||||
|
}) |
||||||
|
} |
||||||
|
return resp, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) MarkSpam(ctx context.Context, req *orlyaclv1.MarkSpamRequest) (*orlyaclv1.Empty, error) { |
||||||
|
curating := s.getCuratingACL() |
||||||
|
if curating == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL not available") |
||||||
|
} |
||||||
|
curatingACL := curating.GetCuratingACL() |
||||||
|
if curatingACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL database not available") |
||||||
|
} |
||||||
|
if err := curatingACL.MarkEventAsSpam(req.EventId, req.Pubkey, req.Reason); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to mark spam: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) UnmarkSpam(ctx context.Context, req *orlyaclv1.EventRequest) (*orlyaclv1.Empty, error) { |
||||||
|
curating := s.getCuratingACL() |
||||||
|
if curating == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL not available") |
||||||
|
} |
||||||
|
curatingACL := curating.GetCuratingACL() |
||||||
|
if curatingACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL database not available") |
||||||
|
} |
||||||
|
if err := curatingACL.UnmarkEventAsSpam(req.EventId); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to unmark spam: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) ListSpamEvents(ctx context.Context, req *orlyaclv1.Empty) (*orlyaclv1.ListSpamEventsResponse, error) { |
||||||
|
curating := s.getCuratingACL() |
||||||
|
if curating == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL not available") |
||||||
|
} |
||||||
|
curatingACL := curating.GetCuratingACL() |
||||||
|
if curatingACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL database not available") |
||||||
|
} |
||||||
|
spam, err := curatingACL.ListSpamEvents() |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to list spam events: %v", err) |
||||||
|
} |
||||||
|
resp := &orlyaclv1.ListSpamEventsResponse{} |
||||||
|
for _, se := range spam { |
||||||
|
resp.Events = append(resp.Events, &orlyaclv1.SpamEvent{ |
||||||
|
EventId: se.EventID, |
||||||
|
Pubkey: se.Pubkey, |
||||||
|
Reason: se.Reason, |
||||||
|
Added: se.Added.Unix(), |
||||||
|
}) |
||||||
|
} |
||||||
|
return resp, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) RateLimitCheck(ctx context.Context, req *orlyaclv1.RateLimitCheckRequest) (*orlyaclv1.RateLimitCheckResponse, error) { |
||||||
|
curating := s.getCuratingACL() |
||||||
|
if curating == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL not available") |
||||||
|
} |
||||||
|
allowed, message, err := curating.RateLimitCheck(req.Pubkey, req.Ip) |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to check rate limit: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.RateLimitCheckResponse{ |
||||||
|
Allowed: allowed, |
||||||
|
Message: message, |
||||||
|
}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) ProcessConfigEvent(ctx context.Context, req *orlyaclv1.ConfigEventRequest) (*orlyaclv1.Empty, error) { |
||||||
|
curating := s.getCuratingACL() |
||||||
|
if curating == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL not available") |
||||||
|
} |
||||||
|
ev := orlydbv1.ProtoToEvent(req.Event) |
||||||
|
if err := curating.ProcessConfigEvent(ev); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to process config event: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) GetCuratingConfig(ctx context.Context, req *orlyaclv1.Empty) (*orlyaclv1.CuratingConfig, error) { |
||||||
|
curating := s.getCuratingACL() |
||||||
|
if curating == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL not available") |
||||||
|
} |
||||||
|
config, err := curating.GetConfig() |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to get config: %v", err) |
||||||
|
} |
||||||
|
resp := &orlyaclv1.CuratingConfig{ |
||||||
|
ConfigEventId: config.ConfigEventID, |
||||||
|
ConfigPubkey: config.ConfigPubkey, |
||||||
|
ConfiguredAt: config.ConfiguredAt, |
||||||
|
DailyLimit: int32(config.DailyLimit), |
||||||
|
IpDailyLimit: int32(config.IPDailyLimit), |
||||||
|
FirstBanHours: int32(config.FirstBanHours), |
||||||
|
SecondBanHours: int32(config.SecondBanHours), |
||||||
|
KindCategories: config.KindCategories, |
||||||
|
AllowedRanges: config.AllowedRanges, |
||||||
|
} |
||||||
|
for _, k := range config.AllowedKinds { |
||||||
|
resp.AllowedKinds = append(resp.AllowedKinds, int32(k)) |
||||||
|
} |
||||||
|
return resp, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) IsCuratingConfigured(ctx context.Context, req *orlyaclv1.Empty) (*orlyaclv1.BoolResponse, error) { |
||||||
|
curating := s.getCuratingACL() |
||||||
|
if curating == nil { |
||||||
|
return &orlyaclv1.BoolResponse{Value: false}, nil |
||||||
|
} |
||||||
|
configured, err := curating.IsConfigured() |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to check if configured: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.BoolResponse{Value: configured}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) ListUnclassifiedUsers(ctx context.Context, req *orlyaclv1.PaginationRequest) (*orlyaclv1.ListUnclassifiedUsersResponse, error) { |
||||||
|
curating := s.getCuratingACL() |
||||||
|
if curating == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL not available") |
||||||
|
} |
||||||
|
curatingACL := curating.GetCuratingACL() |
||||||
|
if curatingACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL database not available") |
||||||
|
} |
||||||
|
// The underlying ListUnclassifiedUsers only takes limit, not offset
|
||||||
|
// We'll request limit+offset and skip the first offset items
|
||||||
|
limit := int(req.Limit) |
||||||
|
offset := int(req.Offset) |
||||||
|
if limit == 0 { |
||||||
|
limit = 100 // Default limit
|
||||||
|
} |
||||||
|
users, err := curatingACL.ListUnclassifiedUsers(limit + offset) |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to list unclassified users: %v", err) |
||||||
|
} |
||||||
|
// Apply offset
|
||||||
|
if offset > 0 && len(users) > offset { |
||||||
|
users = users[offset:] |
||||||
|
} else if offset > 0 { |
||||||
|
users = nil |
||||||
|
} |
||||||
|
// Apply limit
|
||||||
|
if limit > 0 && len(users) > limit { |
||||||
|
users = users[:limit] |
||||||
|
} |
||||||
|
resp := &orlyaclv1.ListUnclassifiedUsersResponse{Total: int32(len(users))} |
||||||
|
for _, u := range users { |
||||||
|
resp.Users = append(resp.Users, &orlyaclv1.UnclassifiedUser{ |
||||||
|
Pubkey: u.Pubkey, |
||||||
|
EventCount: int32(u.EventCount), |
||||||
|
FirstSeen: u.LastEvent.Format("2006-01-02T15:04:05Z"), |
||||||
|
}) |
||||||
|
} |
||||||
|
return resp, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) GetEventsForPubkey(ctx context.Context, req *orlyaclv1.GetEventsForPubkeyRequest) (*orlyaclv1.EventsForPubkeyResponse, error) { |
||||||
|
curating := s.getCuratingACL() |
||||||
|
if curating == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL not available") |
||||||
|
} |
||||||
|
curatingACL := curating.GetCuratingACL() |
||||||
|
if curatingACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL database not available") |
||||||
|
} |
||||||
|
events, total, err := curatingACL.GetEventsForPubkey(req.Pubkey, int(req.Limit), int(req.Offset)) |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to get events for pubkey: %v", err) |
||||||
|
} |
||||||
|
resp := &orlyaclv1.EventsForPubkeyResponse{Total: int32(total)} |
||||||
|
for _, ev := range events { |
||||||
|
resp.Events = append(resp.Events, &orlyaclv1.EventSummary{ |
||||||
|
Id: ev.ID, |
||||||
|
Kind: uint32(ev.Kind), |
||||||
|
Content: []byte(ev.Content), |
||||||
|
CreatedAt: ev.CreatedAt, |
||||||
|
}) |
||||||
|
} |
||||||
|
return resp, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) DeleteEventsForPubkey(ctx context.Context, req *orlyaclv1.DeleteEventsForPubkeyRequest) (*orlyaclv1.DeleteCountResponse, error) { |
||||||
|
curating := s.getCuratingACL() |
||||||
|
if curating == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL not available") |
||||||
|
} |
||||||
|
curatingACL := curating.GetCuratingACL() |
||||||
|
if curatingACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL database not available") |
||||||
|
} |
||||||
|
count, err := curatingACL.DeleteEventsForPubkey(req.Pubkey) |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to delete events for pubkey: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.DeleteCountResponse{Count: int32(count)}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) ScanAllPubkeys(ctx context.Context, req *orlyaclv1.Empty) (*orlyaclv1.ScanResultResponse, error) { |
||||||
|
curating := s.getCuratingACL() |
||||||
|
if curating == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL not available") |
||||||
|
} |
||||||
|
curatingACL := curating.GetCuratingACL() |
||||||
|
if curatingACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL database not available") |
||||||
|
} |
||||||
|
result, err := curatingACL.ScanAllPubkeys() |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to scan all pubkeys: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.ScanResultResponse{ |
||||||
|
TotalPubkeys: int32(result.TotalPubkeys), |
||||||
|
TotalEvents: int32(result.TotalEvents), |
||||||
|
}, nil |
||||||
|
} |
||||||
|
|
||||||
|
// === Helper Methods ===
|
||||||
|
|
||||||
|
func (s *ACLService) getManagedACL() *acl.Managed { |
||||||
|
for _, i := range acl.Registry.ACL { |
||||||
|
if i.Type() == "managed" { |
||||||
|
if managed, ok := i.(*acl.Managed); ok { |
||||||
|
return managed |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) getCuratingACL() *acl.Curating { |
||||||
|
for _, i := range acl.Registry.ACL { |
||||||
|
if i.Type() == "curating" { |
||||||
|
if curating, ok := i.(*acl.Curating); ok { |
||||||
|
return curating |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// Unused but may be needed for debugging
|
||||||
|
var _ = log.T |
||||||
|
var _ = hex.EncodeToString |
||||||
@ -0,0 +1,122 @@ |
|||||||
|
// orly-db-badger is a standalone gRPC database server using the Badger backend.
|
||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"os" |
||||||
|
"path/filepath" |
||||||
|
"time" |
||||||
|
|
||||||
|
"go-simpler.org/env" |
||||||
|
"lol.mleku.dev" |
||||||
|
"lol.mleku.dev/chk" |
||||||
|
"lol.mleku.dev/log" |
||||||
|
|
||||||
|
"next.orly.dev/pkg/database" |
||||||
|
"next.orly.dev/pkg/database/server" |
||||||
|
) |
||||||
|
|
||||||
|
// Config holds the database server configuration.
|
||||||
|
type Config struct { |
||||||
|
// Listen is the gRPC server listen address
|
||||||
|
Listen string `env:"ORLY_DB_LISTEN" default:"127.0.0.1:50051" usage:"gRPC server listen address"` |
||||||
|
|
||||||
|
// DataDir is the database data directory
|
||||||
|
DataDir string `env:"ORLY_DATA_DIR" usage:"database data directory"` |
||||||
|
|
||||||
|
// LogLevel is the logging level
|
||||||
|
LogLevel string `env:"ORLY_DB_LOG_LEVEL" default:"info" usage:"log level (trace, debug, info, warn, error)"` |
||||||
|
|
||||||
|
// Badger configuration
|
||||||
|
BlockCacheMB int `env:"ORLY_DB_BLOCK_CACHE_MB" default:"1024" usage:"block cache size in MB"` |
||||||
|
IndexCacheMB int `env:"ORLY_DB_INDEX_CACHE_MB" default:"512" usage:"index cache size in MB"` |
||||||
|
ZSTDLevel int `env:"ORLY_DB_ZSTD_LEVEL" default:"3" usage:"ZSTD compression level (1-19)"` |
||||||
|
|
||||||
|
// Query cache configuration
|
||||||
|
QueryCacheSizeMB int `env:"ORLY_DB_QUERY_CACHE_SIZE_MB" default:"256" usage:"query cache size in MB"` |
||||||
|
QueryCacheMaxAge time.Duration `env:"ORLY_DB_QUERY_CACHE_MAX_AGE" default:"5m" usage:"query cache max age"` |
||||||
|
QueryCacheDisabled bool `env:"ORLY_DB_QUERY_CACHE_DISABLED" default:"false" usage:"disable query cache"` |
||||||
|
|
||||||
|
// Serial cache configuration
|
||||||
|
SerialCachePubkeys int `env:"ORLY_SERIAL_CACHE_PUBKEYS" default:"100000" usage:"serial cache pubkeys capacity"` |
||||||
|
SerialCacheEventIds int `env:"ORLY_SERIAL_CACHE_EVENT_IDS" default:"500000" usage:"serial cache event IDs capacity"` |
||||||
|
|
||||||
|
// gRPC server configuration
|
||||||
|
StreamBatchSize int `env:"ORLY_DB_STREAM_BATCH_SIZE" default:"100" usage:"events per stream batch"` |
||||||
|
} |
||||||
|
|
||||||
|
func main() { |
||||||
|
cfg := loadConfig() |
||||||
|
|
||||||
|
// Set log level
|
||||||
|
lol.SetLogLevel(cfg.LogLevel) |
||||||
|
log.I.F("orly-db-badger starting with log level: %s", cfg.LogLevel) |
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background()) |
||||||
|
defer cancel() |
||||||
|
|
||||||
|
// Create database configuration
|
||||||
|
dbCfg := &database.DatabaseConfig{ |
||||||
|
DataDir: cfg.DataDir, |
||||||
|
LogLevel: cfg.LogLevel, |
||||||
|
BlockCacheMB: cfg.BlockCacheMB, |
||||||
|
IndexCacheMB: cfg.IndexCacheMB, |
||||||
|
QueryCacheSizeMB: cfg.QueryCacheSizeMB, |
||||||
|
QueryCacheMaxAge: cfg.QueryCacheMaxAge, |
||||||
|
QueryCacheDisabled: cfg.QueryCacheDisabled, |
||||||
|
SerialCachePubkeys: cfg.SerialCachePubkeys, |
||||||
|
SerialCacheEventIds: cfg.SerialCacheEventIds, |
||||||
|
ZSTDLevel: cfg.ZSTDLevel, |
||||||
|
} |
||||||
|
|
||||||
|
// Initialize Badger database
|
||||||
|
log.I.F("initializing Badger database at %s", cfg.DataDir) |
||||||
|
db, err := database.NewWithConfig(ctx, cancel, dbCfg) |
||||||
|
if chk.E(err) { |
||||||
|
log.E.F("failed to initialize database: %v", err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
// Wait for database to be ready
|
||||||
|
log.I.F("waiting for database to be ready...") |
||||||
|
<-db.Ready() |
||||||
|
log.I.F("database ready") |
||||||
|
|
||||||
|
// Create and start gRPC server
|
||||||
|
serverCfg := &server.Config{ |
||||||
|
Listen: cfg.Listen, |
||||||
|
LogLevel: cfg.LogLevel, |
||||||
|
StreamBatchSize: cfg.StreamBatchSize, |
||||||
|
} |
||||||
|
|
||||||
|
srv := server.New(db, serverCfg) |
||||||
|
if err := srv.ListenAndServe(ctx, cancel); err != nil { |
||||||
|
log.E.F("gRPC server error: %v", err) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func loadConfig() *Config { |
||||||
|
cfg := &Config{} |
||||||
|
if err := env.Load(cfg, nil); chk.E(err) { |
||||||
|
log.E.F("failed to load config: %v", err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
// Set default data directory if not specified
|
||||||
|
if cfg.DataDir == "" { |
||||||
|
home, err := os.UserHomeDir() |
||||||
|
if chk.E(err) { |
||||||
|
log.E.F("failed to get home directory: %v", err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
cfg.DataDir = filepath.Join(home, ".local", "share", "ORLY") |
||||||
|
} |
||||||
|
|
||||||
|
// Ensure data directory exists
|
||||||
|
if err := os.MkdirAll(cfg.DataDir, 0700); chk.E(err) { |
||||||
|
log.E.F("failed to create data directory %s: %v", cfg.DataDir, err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
return cfg |
||||||
|
} |
||||||
@ -0,0 +1,108 @@ |
|||||||
|
// orly-db-neo4j is a standalone gRPC database server using the Neo4j backend.
|
||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"os" |
||||||
|
"time" |
||||||
|
|
||||||
|
"go-simpler.org/env" |
||||||
|
"lol.mleku.dev" |
||||||
|
"lol.mleku.dev/chk" |
||||||
|
"lol.mleku.dev/log" |
||||||
|
|
||||||
|
"next.orly.dev/pkg/database" |
||||||
|
"next.orly.dev/pkg/database/server" |
||||||
|
|
||||||
|
// Import neo4j to register the factory
|
||||||
|
_ "next.orly.dev/pkg/neo4j" |
||||||
|
) |
||||||
|
|
||||||
|
// Config holds the database server configuration.
|
||||||
|
type Config struct { |
||||||
|
// Listen is the gRPC server listen address
|
||||||
|
Listen string `env:"ORLY_DB_LISTEN" default:"127.0.0.1:50051" usage:"gRPC server listen address"` |
||||||
|
|
||||||
|
// LogLevel is the logging level
|
||||||
|
LogLevel string `env:"ORLY_DB_LOG_LEVEL" default:"info" usage:"log level (trace, debug, info, warn, error)"` |
||||||
|
|
||||||
|
// Neo4j configuration
|
||||||
|
Neo4jURI string `env:"ORLY_NEO4J_URI" default:"bolt://localhost:7687" usage:"Neo4j connection URI"` |
||||||
|
Neo4jUser string `env:"ORLY_NEO4J_USER" default:"neo4j" usage:"Neo4j username"` |
||||||
|
Neo4jPassword string `env:"ORLY_NEO4J_PASSWORD" usage:"Neo4j password"` |
||||||
|
|
||||||
|
// Neo4j driver tuning
|
||||||
|
Neo4jMaxConnPoolSize int `env:"ORLY_NEO4J_MAX_CONN_POOL" default:"25" usage:"max connection pool size"` |
||||||
|
Neo4jFetchSize int `env:"ORLY_NEO4J_FETCH_SIZE" default:"1000" usage:"max records per fetch batch"` |
||||||
|
Neo4jMaxTxRetrySeconds int `env:"ORLY_NEO4J_MAX_TX_RETRY_SEC" default:"30" usage:"max transaction retry time"` |
||||||
|
Neo4jQueryResultLimit int `env:"ORLY_NEO4J_QUERY_RESULT_LIMIT" default:"10000" usage:"max results per query (0=unlimited)"` |
||||||
|
|
||||||
|
// Query cache configuration (for the gRPC server)
|
||||||
|
QueryCacheSizeMB int `env:"ORLY_DB_QUERY_CACHE_SIZE_MB" default:"256" usage:"query cache size in MB"` |
||||||
|
QueryCacheMaxAge time.Duration `env:"ORLY_DB_QUERY_CACHE_MAX_AGE" default:"5m" usage:"query cache max age"` |
||||||
|
QueryCacheDisabled bool `env:"ORLY_DB_QUERY_CACHE_DISABLED" default:"false" usage:"disable query cache"` |
||||||
|
|
||||||
|
// gRPC server configuration
|
||||||
|
StreamBatchSize int `env:"ORLY_DB_STREAM_BATCH_SIZE" default:"100" usage:"events per stream batch"` |
||||||
|
} |
||||||
|
|
||||||
|
func main() { |
||||||
|
cfg := loadConfig() |
||||||
|
|
||||||
|
// Set log level
|
||||||
|
lol.SetLogLevel(cfg.LogLevel) |
||||||
|
log.I.F("orly-db-neo4j starting with log level: %s", cfg.LogLevel) |
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background()) |
||||||
|
defer cancel() |
||||||
|
|
||||||
|
// Create database configuration
|
||||||
|
dbCfg := &database.DatabaseConfig{ |
||||||
|
LogLevel: cfg.LogLevel, |
||||||
|
Neo4jURI: cfg.Neo4jURI, |
||||||
|
Neo4jUser: cfg.Neo4jUser, |
||||||
|
Neo4jPassword: cfg.Neo4jPassword, |
||||||
|
Neo4jMaxConnPoolSize: cfg.Neo4jMaxConnPoolSize, |
||||||
|
Neo4jFetchSize: cfg.Neo4jFetchSize, |
||||||
|
Neo4jMaxTxRetrySeconds: cfg.Neo4jMaxTxRetrySeconds, |
||||||
|
Neo4jQueryResultLimit: cfg.Neo4jQueryResultLimit, |
||||||
|
QueryCacheSizeMB: cfg.QueryCacheSizeMB, |
||||||
|
QueryCacheMaxAge: cfg.QueryCacheMaxAge, |
||||||
|
QueryCacheDisabled: cfg.QueryCacheDisabled, |
||||||
|
} |
||||||
|
|
||||||
|
// Initialize Neo4j database via factory
|
||||||
|
log.I.F("connecting to Neo4j at %s", cfg.Neo4jURI) |
||||||
|
db, err := database.NewDatabaseWithConfig(ctx, cancel, "neo4j", dbCfg) |
||||||
|
if chk.E(err) { |
||||||
|
log.E.F("failed to initialize Neo4j database: %v", err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
// Wait for database to be ready
|
||||||
|
log.I.F("waiting for database to be ready...") |
||||||
|
<-db.Ready() |
||||||
|
log.I.F("database ready") |
||||||
|
|
||||||
|
// Create and start gRPC server
|
||||||
|
serverCfg := &server.Config{ |
||||||
|
Listen: cfg.Listen, |
||||||
|
LogLevel: cfg.LogLevel, |
||||||
|
StreamBatchSize: cfg.StreamBatchSize, |
||||||
|
} |
||||||
|
|
||||||
|
srv := server.New(db, serverCfg) |
||||||
|
if err := srv.ListenAndServe(ctx, cancel); err != nil { |
||||||
|
log.E.F("gRPC server error: %v", err) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func loadConfig() *Config { |
||||||
|
cfg := &Config{} |
||||||
|
if err := env.Load(cfg, nil); chk.E(err) { |
||||||
|
log.E.F("failed to load config: %v", err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
return cfg |
||||||
|
} |
||||||
@ -0,0 +1,66 @@ |
|||||||
|
[Unit] |
||||||
|
Description=ORLY Nostr Relay (Split IPC Mode) |
||||||
|
After=network.target |
||||||
|
|
||||||
|
[Service] |
||||||
|
Type=simple |
||||||
|
User=mleku |
||||||
|
Group=mleku |
||||||
|
WorkingDirectory=/home/mleku/src/next.orly.dev |
||||||
|
|
||||||
|
# Use orly-launcher which manages orly-db-badger, orly-acl-follows, and the relay |
||||||
|
ExecStart=/home/mleku/.local/bin/orly-launcher |
||||||
|
|
||||||
|
# Launcher config - paths to split binaries |
||||||
|
Environment=ORLY_LAUNCHER_DB_BACKEND=badger |
||||||
|
Environment=ORLY_LAUNCHER_DB_BINARY=/home/mleku/.local/bin/orly-db-badger |
||||||
|
Environment=ORLY_LAUNCHER_ACL_BINARY=/home/mleku/.local/bin/orly-acl-follows |
||||||
|
Environment=ORLY_LAUNCHER_RELAY_BINARY=/home/mleku/.local/bin/next.orly.dev |
||||||
|
Environment=ORLY_LAUNCHER_DB_LISTEN=127.0.0.1:50051 |
||||||
|
Environment=ORLY_LAUNCHER_ACL_LISTEN=127.0.0.1:50052 |
||||||
|
Environment=ORLY_LAUNCHER_ACL_ENABLED=true |
||||||
|
Environment=ORLY_ACL_MODE=follows |
||||||
|
|
||||||
|
# gRPC client settings (for relay to connect to db and acl) |
||||||
|
Environment=ORLY_DB_TYPE=grpc |
||||||
|
Environment=ORLY_GRPC_SERVER=127.0.0.1:50051 |
||||||
|
Environment=ORLY_ACL_TYPE=grpc |
||||||
|
Environment=ORLY_GRPC_ACL_SERVER=127.0.0.1:50052 |
||||||
|
|
||||||
|
# Relay settings |
||||||
|
Environment=ORLY_PORT=3334 |
||||||
|
Environment=ORLY_LISTEN=127.0.0.1 |
||||||
|
Environment=ORLY_LOG_LEVEL=info |
||||||
|
Environment=ORLY_ADMINS=npub1fjqqy4a93z5zsjwsfxqhc2764kvykfdyttvldkkkdera8dr78vhsmmleku |
||||||
|
Environment=ORLY_OWNERS=npub1fjqqy4a93z5zsjwsfxqhc2764kvykfdyttvldkkkdera8dr78vhsmmleku |
||||||
|
Environment=ORLY_AUTH_REQUIRED=false |
||||||
|
Environment=ORLY_AUTH_TO_WRITE=false |
||||||
|
Environment=ORLY_NIP46_BYPASS_AUTH=true |
||||||
|
Environment=ORLY_FOLLOWS_THROTTLE=true |
||||||
|
Environment=ORLY_BLOSSOM_RATE_LIMIT=true |
||||||
|
Environment=ORLY_BLOSSOM_DAILY_LIMIT_MB=10 |
||||||
|
|
||||||
|
# Memory settings for database server (orly-db-badger) |
||||||
|
Environment=ORLY_DB_BLOCK_CACHE_MB=256 |
||||||
|
Environment=ORLY_DB_INDEX_CACHE_MB=128 |
||||||
|
Environment=ORLY_QUERY_CACHE_DISABLED=false |
||||||
|
Environment=ORLY_QUERY_CACHE_SIZE_MB=64 |
||||||
|
Environment=ORLY_SERIAL_CACHE_PUBKEYS=100000 |
||||||
|
Environment=ORLY_SERIAL_CACHE_EVENT_IDS=500000 |
||||||
|
Environment=ORLY_GC_ENABLED=false |
||||||
|
|
||||||
|
# Rate limiting for relay process |
||||||
|
Environment=ORLY_RATE_LIMIT_TARGET_MB=2000 |
||||||
|
|
||||||
|
# Connection and query limits |
||||||
|
Environment=ORLY_MAX_CONN_PER_IP=5 |
||||||
|
Environment=ORLY_QUERY_RESULT_LIMIT=256 |
||||||
|
|
||||||
|
# Enable pprof HTTP endpoint for debugging |
||||||
|
Environment=ORLY_PPROF_HTTP=true |
||||||
|
|
||||||
|
Restart=always |
||||||
|
RestartSec=5 |
||||||
|
|
||||||
|
[Install] |
||||||
|
WantedBy=multi-user.target |
||||||
@ -0,0 +1,384 @@ |
|||||||
|
// Package grpc provides a gRPC client that implements the acl.I interface.
|
||||||
|
// This allows the relay to use a remote ACL server via gRPC.
|
||||||
|
package grpc |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"time" |
||||||
|
|
||||||
|
"google.golang.org/grpc" |
||||||
|
"google.golang.org/grpc/credentials/insecure" |
||||||
|
"lol.mleku.dev/chk" |
||||||
|
"lol.mleku.dev/log" |
||||||
|
|
||||||
|
"git.mleku.dev/mleku/nostr/encoders/event" |
||||||
|
acliface "next.orly.dev/pkg/interfaces/acl" |
||||||
|
orlyaclv1 "next.orly.dev/pkg/proto/orlyacl/v1" |
||||||
|
orlydbv1 "next.orly.dev/pkg/proto/orlydb/v1" |
||||||
|
) |
||||||
|
|
||||||
|
// Client implements the acl.I interface via gRPC.
|
||||||
|
type Client struct { |
||||||
|
conn *grpc.ClientConn |
||||||
|
client orlyaclv1.ACLServiceClient |
||||||
|
ready chan struct{} |
||||||
|
mode string |
||||||
|
} |
||||||
|
|
||||||
|
// Verify Client implements acl.I at compile time.
|
||||||
|
var _ acliface.I = (*Client)(nil) |
||||||
|
|
||||||
|
// Verify Client implements acl.PolicyChecker at compile time.
|
||||||
|
var _ acliface.PolicyChecker = (*Client)(nil) |
||||||
|
|
||||||
|
// ClientConfig holds configuration for the gRPC ACL client.
|
||||||
|
type ClientConfig struct { |
||||||
|
ServerAddress string |
||||||
|
ConnectTimeout time.Duration |
||||||
|
} |
||||||
|
|
||||||
|
// New creates a new gRPC ACL client.
|
||||||
|
func New(ctx context.Context, cfg *ClientConfig) (*Client, error) { |
||||||
|
timeout := cfg.ConnectTimeout |
||||||
|
if timeout == 0 { |
||||||
|
timeout = 10 * time.Second |
||||||
|
} |
||||||
|
|
||||||
|
dialCtx, cancel := context.WithTimeout(ctx, timeout) |
||||||
|
defer cancel() |
||||||
|
|
||||||
|
conn, err := grpc.DialContext(dialCtx, cfg.ServerAddress, |
||||||
|
grpc.WithTransportCredentials(insecure.NewCredentials()), |
||||||
|
grpc.WithDefaultCallOptions( |
||||||
|
grpc.MaxCallRecvMsgSize(16<<20), // 16MB
|
||||||
|
grpc.MaxCallSendMsgSize(16<<20), // 16MB
|
||||||
|
), |
||||||
|
) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
c := &Client{ |
||||||
|
conn: conn, |
||||||
|
client: orlyaclv1.NewACLServiceClient(conn), |
||||||
|
ready: make(chan struct{}), |
||||||
|
} |
||||||
|
|
||||||
|
// Check if server is ready and get mode
|
||||||
|
go c.waitForReady(ctx) |
||||||
|
|
||||||
|
return c, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (c *Client) waitForReady(ctx context.Context) { |
||||||
|
for { |
||||||
|
select { |
||||||
|
case <-ctx.Done(): |
||||||
|
return |
||||||
|
default: |
||||||
|
resp, err := c.client.Ready(ctx, &orlyaclv1.Empty{}) |
||||||
|
if err == nil && resp.Ready { |
||||||
|
// Get mode from server
|
||||||
|
modeResp, err := c.client.GetMode(ctx, &orlyaclv1.Empty{}) |
||||||
|
if err == nil { |
||||||
|
c.mode = modeResp.Mode |
||||||
|
} |
||||||
|
close(c.ready) |
||||||
|
log.I.F("gRPC ACL client connected and ready, mode: %s", c.mode) |
||||||
|
return |
||||||
|
} |
||||||
|
time.Sleep(100 * time.Millisecond) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Close closes the gRPC connection.
|
||||||
|
func (c *Client) Close() error { |
||||||
|
if c.conn != nil { |
||||||
|
return c.conn.Close() |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// Ready returns a channel that closes when the client is ready.
|
||||||
|
func (c *Client) Ready() <-chan struct{} { |
||||||
|
return c.ready |
||||||
|
} |
||||||
|
|
||||||
|
// === acl.I Interface Implementation ===
|
||||||
|
|
||||||
|
func (c *Client) Configure(cfg ...any) error { |
||||||
|
// Configuration is done on the server side
|
||||||
|
// The client just passes through to the server
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (c *Client) GetAccessLevel(pub []byte, address string) string { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) |
||||||
|
defer cancel() |
||||||
|
|
||||||
|
resp, err := c.client.GetAccessLevel(ctx, &orlyaclv1.AccessLevelRequest{ |
||||||
|
Pubkey: pub, |
||||||
|
Address: address, |
||||||
|
}) |
||||||
|
if chk.E(err) { |
||||||
|
return "none" |
||||||
|
} |
||||||
|
return resp.Level |
||||||
|
} |
||||||
|
|
||||||
|
func (c *Client) GetACLInfo() (name, description, documentation string) { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) |
||||||
|
defer cancel() |
||||||
|
|
||||||
|
resp, err := c.client.GetACLInfo(ctx, &orlyaclv1.Empty{}) |
||||||
|
if chk.E(err) { |
||||||
|
return "", "", "" |
||||||
|
} |
||||||
|
return resp.Name, resp.Description, resp.Documentation |
||||||
|
} |
||||||
|
|
||||||
|
func (c *Client) Syncer() { |
||||||
|
// The syncer runs on the ACL server, not the client
|
||||||
|
// This is a no-op for the gRPC client
|
||||||
|
} |
||||||
|
|
||||||
|
func (c *Client) Type() string { |
||||||
|
return c.mode |
||||||
|
} |
||||||
|
|
||||||
|
// === acl.PolicyChecker Interface Implementation ===
|
||||||
|
|
||||||
|
func (c *Client) CheckPolicy(ev *event.E) (bool, error) { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) |
||||||
|
defer cancel() |
||||||
|
|
||||||
|
resp, err := c.client.CheckPolicy(ctx, &orlyaclv1.PolicyCheckRequest{ |
||||||
|
Event: orlydbv1.EventToProto(ev), |
||||||
|
}) |
||||||
|
if err != nil { |
||||||
|
return false, err |
||||||
|
} |
||||||
|
if resp.Error != "" { |
||||||
|
return resp.Allowed, &policyError{msg: resp.Error} |
||||||
|
} |
||||||
|
return resp.Allowed, nil |
||||||
|
} |
||||||
|
|
||||||
|
// policyError is a simple error type for policy check failures
|
||||||
|
type policyError struct { |
||||||
|
msg string |
||||||
|
} |
||||||
|
|
||||||
|
func (e *policyError) Error() string { |
||||||
|
return e.msg |
||||||
|
} |
||||||
|
|
||||||
|
// === Follows ACL Methods ===
|
||||||
|
|
||||||
|
// GetThrottleDelay returns the progressive throttle delay for a pubkey.
|
||||||
|
func (c *Client) GetThrottleDelay(pubkey []byte, ip string) time.Duration { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) |
||||||
|
defer cancel() |
||||||
|
|
||||||
|
resp, err := c.client.GetThrottleDelay(ctx, &orlyaclv1.ThrottleDelayRequest{ |
||||||
|
Pubkey: pubkey, |
||||||
|
Ip: ip, |
||||||
|
}) |
||||||
|
if chk.E(err) { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
return time.Duration(resp.DelayMs) * time.Millisecond |
||||||
|
} |
||||||
|
|
||||||
|
// AddFollow adds a pubkey to the followed list.
|
||||||
|
func (c *Client) AddFollow(pubkey []byte) error { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) |
||||||
|
defer cancel() |
||||||
|
|
||||||
|
_, err := c.client.AddFollow(ctx, &orlyaclv1.AddFollowRequest{ |
||||||
|
Pubkey: pubkey, |
||||||
|
}) |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// GetFollowedPubkeys returns all followed pubkeys.
|
||||||
|
func (c *Client) GetFollowedPubkeys() [][]byte { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) |
||||||
|
defer cancel() |
||||||
|
|
||||||
|
resp, err := c.client.GetFollowedPubkeys(ctx, &orlyaclv1.Empty{}) |
||||||
|
if chk.E(err) { |
||||||
|
return nil |
||||||
|
} |
||||||
|
return resp.Pubkeys |
||||||
|
} |
||||||
|
|
||||||
|
// GetAdminRelays returns the admin relay URLs.
|
||||||
|
func (c *Client) GetAdminRelays() []string { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) |
||||||
|
defer cancel() |
||||||
|
|
||||||
|
resp, err := c.client.GetAdminRelays(ctx, &orlyaclv1.Empty{}) |
||||||
|
if chk.E(err) { |
||||||
|
return nil |
||||||
|
} |
||||||
|
return resp.Urls |
||||||
|
} |
||||||
|
|
||||||
|
// === Managed ACL Methods ===
|
||||||
|
|
||||||
|
// BanPubkey adds a pubkey to the ban list.
|
||||||
|
func (c *Client) BanPubkey(pubkey, reason string) error { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) |
||||||
|
defer cancel() |
||||||
|
|
||||||
|
_, err := c.client.BanPubkey(ctx, &orlyaclv1.BanPubkeyRequest{ |
||||||
|
Pubkey: pubkey, |
||||||
|
Reason: reason, |
||||||
|
}) |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// UnbanPubkey removes a pubkey from the ban list.
|
||||||
|
func (c *Client) UnbanPubkey(pubkey string) error { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) |
||||||
|
defer cancel() |
||||||
|
|
||||||
|
_, err := c.client.UnbanPubkey(ctx, &orlyaclv1.PubkeyRequest{ |
||||||
|
Pubkey: pubkey, |
||||||
|
}) |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// AllowPubkey adds a pubkey to the allow list.
|
||||||
|
func (c *Client) AllowPubkey(pubkey, reason string) error { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) |
||||||
|
defer cancel() |
||||||
|
|
||||||
|
_, err := c.client.AllowPubkey(ctx, &orlyaclv1.AllowPubkeyRequest{ |
||||||
|
Pubkey: pubkey, |
||||||
|
Reason: reason, |
||||||
|
}) |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// DisallowPubkey removes a pubkey from the allow list.
|
||||||
|
func (c *Client) DisallowPubkey(pubkey string) error { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) |
||||||
|
defer cancel() |
||||||
|
|
||||||
|
_, err := c.client.DisallowPubkey(ctx, &orlyaclv1.PubkeyRequest{ |
||||||
|
Pubkey: pubkey, |
||||||
|
}) |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// BlockIP adds an IP to the block list.
|
||||||
|
func (c *Client) BlockIP(ip, reason string) error { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) |
||||||
|
defer cancel() |
||||||
|
|
||||||
|
_, err := c.client.BlockIP(ctx, &orlyaclv1.BlockIPRequest{ |
||||||
|
Ip: ip, |
||||||
|
Reason: reason, |
||||||
|
}) |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// UnblockIP removes an IP from the block list.
|
||||||
|
func (c *Client) UnblockIP(ip string) error { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) |
||||||
|
defer cancel() |
||||||
|
|
||||||
|
_, err := c.client.UnblockIP(ctx, &orlyaclv1.IPRequest{ |
||||||
|
Ip: ip, |
||||||
|
}) |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// UpdatePeerAdmins updates the peer relay identity pubkeys.
|
||||||
|
func (c *Client) UpdatePeerAdmins(peerPubkeys [][]byte) error { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) |
||||||
|
defer cancel() |
||||||
|
|
||||||
|
_, err := c.client.UpdatePeerAdmins(ctx, &orlyaclv1.UpdatePeerAdminsRequest{ |
||||||
|
PeerPubkeys: peerPubkeys, |
||||||
|
}) |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// === Curating ACL Methods ===
|
||||||
|
|
||||||
|
// TrustPubkey adds a pubkey to the trusted list.
|
||||||
|
func (c *Client) TrustPubkey(pubkey, note string) error { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) |
||||||
|
defer cancel() |
||||||
|
|
||||||
|
_, err := c.client.TrustPubkey(ctx, &orlyaclv1.TrustPubkeyRequest{ |
||||||
|
Pubkey: pubkey, |
||||||
|
Note: note, |
||||||
|
}) |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// UntrustPubkey removes a pubkey from the trusted list.
|
||||||
|
func (c *Client) UntrustPubkey(pubkey string) error { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) |
||||||
|
defer cancel() |
||||||
|
|
||||||
|
_, err := c.client.UntrustPubkey(ctx, &orlyaclv1.PubkeyRequest{ |
||||||
|
Pubkey: pubkey, |
||||||
|
}) |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// BlacklistPubkey adds a pubkey to the blacklist.
|
||||||
|
func (c *Client) BlacklistPubkey(pubkey, reason string) error { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) |
||||||
|
defer cancel() |
||||||
|
|
||||||
|
_, err := c.client.BlacklistPubkey(ctx, &orlyaclv1.BlacklistPubkeyRequest{ |
||||||
|
Pubkey: pubkey, |
||||||
|
Reason: reason, |
||||||
|
}) |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// UnblacklistPubkey removes a pubkey from the blacklist.
|
||||||
|
func (c *Client) UnblacklistPubkey(pubkey string) error { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) |
||||||
|
defer cancel() |
||||||
|
|
||||||
|
_, err := c.client.UnblacklistPubkey(ctx, &orlyaclv1.PubkeyRequest{ |
||||||
|
Pubkey: pubkey, |
||||||
|
}) |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// RateLimitCheck checks if a pubkey/IP can publish.
|
||||||
|
func (c *Client) RateLimitCheck(pubkey, ip string) (allowed bool, message string, err error) { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) |
||||||
|
defer cancel() |
||||||
|
|
||||||
|
resp, err := c.client.RateLimitCheck(ctx, &orlyaclv1.RateLimitCheckRequest{ |
||||||
|
Pubkey: pubkey, |
||||||
|
Ip: ip, |
||||||
|
}) |
||||||
|
if err != nil { |
||||||
|
return false, "", err |
||||||
|
} |
||||||
|
return resp.Allowed, resp.Message, nil |
||||||
|
} |
||||||
|
|
||||||
|
// IsCuratingConfigured checks if curating mode is configured.
|
||||||
|
func (c *Client) IsCuratingConfigured() (bool, error) { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) |
||||||
|
defer cancel() |
||||||
|
|
||||||
|
resp, err := c.client.IsCuratingConfigured(ctx, &orlyaclv1.Empty{}) |
||||||
|
if err != nil { |
||||||
|
return false, err |
||||||
|
} |
||||||
|
return resp.Value, nil |
||||||
|
} |
||||||
@ -0,0 +1,32 @@ |
|||||||
|
// Package server provides a shared gRPC ACL server implementation.
|
||||||
|
package server |
||||||
|
|
||||||
|
import "time" |
||||||
|
|
||||||
|
// Config holds configuration for the ACL gRPC server.
|
||||||
|
type Config struct { |
||||||
|
// Listen is the gRPC server listen address
|
||||||
|
Listen string |
||||||
|
|
||||||
|
// ACLMode is the active ACL mode (none, follows, managed, curating)
|
||||||
|
ACLMode string |
||||||
|
|
||||||
|
// LogLevel is the logging level
|
||||||
|
LogLevel string |
||||||
|
|
||||||
|
// Owner and admin lists
|
||||||
|
Owners []string |
||||||
|
Admins []string |
||||||
|
|
||||||
|
// Bootstrap relays for follow list syncing
|
||||||
|
BootstrapRelays []string |
||||||
|
|
||||||
|
// Relay addresses (self)
|
||||||
|
RelayAddresses []string |
||||||
|
|
||||||
|
// Follows ACL configuration
|
||||||
|
FollowListFrequency time.Duration |
||||||
|
FollowsThrottleEnabled bool |
||||||
|
FollowsThrottlePerEvent time.Duration |
||||||
|
FollowsThrottleMaxDelay time.Duration |
||||||
|
} |
||||||
@ -0,0 +1,144 @@ |
|||||||
|
package server |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"net" |
||||||
|
"os" |
||||||
|
"os/signal" |
||||||
|
"syscall" |
||||||
|
"time" |
||||||
|
|
||||||
|
"google.golang.org/grpc" |
||||||
|
"google.golang.org/grpc/reflection" |
||||||
|
"lol.mleku.dev/chk" |
||||||
|
"lol.mleku.dev/log" |
||||||
|
|
||||||
|
"next.orly.dev/app/config" |
||||||
|
"next.orly.dev/pkg/acl" |
||||||
|
"next.orly.dev/pkg/database" |
||||||
|
orlyaclv1 "next.orly.dev/pkg/proto/orlyacl/v1" |
||||||
|
) |
||||||
|
|
||||||
|
// Server wraps a gRPC ACL server.
|
||||||
|
type Server struct { |
||||||
|
grpcServer *grpc.Server |
||||||
|
db database.Database |
||||||
|
cfg *Config |
||||||
|
listener net.Listener |
||||||
|
ownsDB bool // Whether we own the database and should close it
|
||||||
|
} |
||||||
|
|
||||||
|
// New creates a new ACL gRPC server.
|
||||||
|
func New(db database.Database, cfg *Config, ownsDB bool) *Server { |
||||||
|
// Create gRPC server
|
||||||
|
grpcServer := grpc.NewServer( |
||||||
|
grpc.MaxRecvMsgSize(16<<20), // 16MB
|
||||||
|
grpc.MaxSendMsgSize(16<<20), // 16MB
|
||||||
|
) |
||||||
|
|
||||||
|
// Register ACL service
|
||||||
|
service := NewACLService(cfg, db) |
||||||
|
orlyaclv1.RegisterACLServiceServer(grpcServer, service) |
||||||
|
|
||||||
|
// Register reflection for debugging with grpcurl
|
||||||
|
reflection.Register(grpcServer) |
||||||
|
|
||||||
|
return &Server{ |
||||||
|
grpcServer: grpcServer, |
||||||
|
db: db, |
||||||
|
cfg: cfg, |
||||||
|
ownsDB: ownsDB, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// ConfigureACL sets up the ACL mode and configures the registry.
|
||||||
|
func (s *Server) ConfigureACL(ctx context.Context) error { |
||||||
|
// Create app config for ACL configuration
|
||||||
|
appCfg := &config.C{ |
||||||
|
Owners: s.cfg.Owners, |
||||||
|
Admins: s.cfg.Admins, |
||||||
|
BootstrapRelays: s.cfg.BootstrapRelays, |
||||||
|
RelayAddresses: s.cfg.RelayAddresses, |
||||||
|
FollowListFrequency: s.cfg.FollowListFrequency, |
||||||
|
FollowsThrottleEnabled: s.cfg.FollowsThrottleEnabled, |
||||||
|
FollowsThrottlePerEvent: s.cfg.FollowsThrottlePerEvent, |
||||||
|
FollowsThrottleMaxDelay: s.cfg.FollowsThrottleMaxDelay, |
||||||
|
} |
||||||
|
|
||||||
|
// Set ACL mode and configure the registry
|
||||||
|
acl.Registry.SetMode(s.cfg.ACLMode) |
||||||
|
if err := acl.Registry.Configure(appCfg, s.db, ctx); chk.E(err) { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// Start the syncer goroutine for background operations
|
||||||
|
acl.Registry.Syncer() |
||||||
|
log.I.F("ACL syncer started for mode: %s", s.cfg.ACLMode) |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// ListenAndServe starts the gRPC server.
|
||||||
|
func (s *Server) ListenAndServe(ctx context.Context, cancel context.CancelFunc) error { |
||||||
|
// Start listening
|
||||||
|
lis, err := net.Listen("tcp", s.cfg.Listen) |
||||||
|
if chk.E(err) { |
||||||
|
return err |
||||||
|
} |
||||||
|
s.listener = lis |
||||||
|
log.I.F("gRPC ACL server listening on %s", s.cfg.Listen) |
||||||
|
|
||||||
|
// Handle graceful shutdown
|
||||||
|
go s.handleShutdown(ctx, cancel) |
||||||
|
|
||||||
|
// Serve gRPC
|
||||||
|
return s.grpcServer.Serve(lis) |
||||||
|
} |
||||||
|
|
||||||
|
func (s *Server) handleShutdown(ctx context.Context, cancel context.CancelFunc) { |
||||||
|
sigs := make(chan os.Signal, 1) |
||||||
|
signal.Notify(sigs, syscall.SIGTERM, syscall.SIGINT) |
||||||
|
|
||||||
|
select { |
||||||
|
case sig := <-sigs: |
||||||
|
log.I.F("received signal %v, shutting down...", sig) |
||||||
|
case <-ctx.Done(): |
||||||
|
log.I.F("context cancelled, shutting down...") |
||||||
|
} |
||||||
|
|
||||||
|
// Cancel context to stop all operations
|
||||||
|
cancel() |
||||||
|
|
||||||
|
// Gracefully stop gRPC server with timeout
|
||||||
|
stopped := make(chan struct{}) |
||||||
|
go func() { |
||||||
|
s.grpcServer.GracefulStop() |
||||||
|
close(stopped) |
||||||
|
}() |
||||||
|
|
||||||
|
select { |
||||||
|
case <-stopped: |
||||||
|
log.I.F("gRPC server stopped gracefully") |
||||||
|
case <-time.After(5 * time.Second): |
||||||
|
log.W.F("gRPC graceful stop timed out, forcing stop") |
||||||
|
s.grpcServer.Stop() |
||||||
|
} |
||||||
|
|
||||||
|
// Sync and close database if we own it
|
||||||
|
if s.ownsDB { |
||||||
|
log.I.F("syncing database...") |
||||||
|
if err := s.db.Sync(); chk.E(err) { |
||||||
|
log.W.F("failed to sync database: %v", err) |
||||||
|
} |
||||||
|
log.I.F("closing database...") |
||||||
|
if err := s.db.Close(); chk.E(err) { |
||||||
|
log.W.F("failed to close database: %v", err) |
||||||
|
} |
||||||
|
} |
||||||
|
log.I.F("shutdown complete") |
||||||
|
} |
||||||
|
|
||||||
|
// Stop stops the server.
|
||||||
|
func (s *Server) Stop() { |
||||||
|
s.grpcServer.Stop() |
||||||
|
} |
||||||
@ -0,0 +1,788 @@ |
|||||||
|
package server |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"encoding/hex" |
||||||
|
|
||||||
|
"google.golang.org/grpc/codes" |
||||||
|
"google.golang.org/grpc/status" |
||||||
|
"lol.mleku.dev/log" |
||||||
|
|
||||||
|
"next.orly.dev/pkg/acl" |
||||||
|
"next.orly.dev/pkg/database" |
||||||
|
orlyaclv1 "next.orly.dev/pkg/proto/orlyacl/v1" |
||||||
|
orlydbv1 "next.orly.dev/pkg/proto/orlydb/v1" |
||||||
|
) |
||||||
|
|
||||||
|
// ACLService implements the orlyaclv1.ACLServiceServer interface.
|
||||||
|
type ACLService struct { |
||||||
|
orlyaclv1.UnimplementedACLServiceServer |
||||||
|
cfg *Config |
||||||
|
db database.Database |
||||||
|
} |
||||||
|
|
||||||
|
// NewACLService creates a new ACL service.
|
||||||
|
func NewACLService(cfg *Config, db database.Database) *ACLService { |
||||||
|
return &ACLService{ |
||||||
|
cfg: cfg, |
||||||
|
db: db, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// === Core ACL Methods ===
|
||||||
|
|
||||||
|
func (s *ACLService) GetAccessLevel(ctx context.Context, req *orlyaclv1.AccessLevelRequest) (*orlyaclv1.AccessLevelResponse, error) { |
||||||
|
level := acl.Registry.GetAccessLevel(req.Pubkey, req.Address) |
||||||
|
return &orlyaclv1.AccessLevelResponse{Level: level}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) CheckPolicy(ctx context.Context, req *orlyaclv1.PolicyCheckRequest) (*orlyaclv1.PolicyCheckResponse, error) { |
||||||
|
ev := orlydbv1.ProtoToEvent(req.Event) |
||||||
|
allowed, err := acl.Registry.CheckPolicy(ev) |
||||||
|
resp := &orlyaclv1.PolicyCheckResponse{Allowed: allowed} |
||||||
|
if err != nil { |
||||||
|
resp.Error = err.Error() |
||||||
|
} |
||||||
|
return resp, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) GetACLInfo(ctx context.Context, req *orlyaclv1.Empty) (*orlyaclv1.ACLInfoResponse, error) { |
||||||
|
name, description, documentation := acl.Registry.GetACLInfo() |
||||||
|
return &orlyaclv1.ACLInfoResponse{ |
||||||
|
Name: name, |
||||||
|
Description: description, |
||||||
|
Documentation: documentation, |
||||||
|
}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) GetMode(ctx context.Context, req *orlyaclv1.Empty) (*orlyaclv1.ModeResponse, error) { |
||||||
|
return &orlyaclv1.ModeResponse{Mode: acl.Registry.Type()}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) Ready(ctx context.Context, req *orlyaclv1.Empty) (*orlyaclv1.ReadyResponse, error) { |
||||||
|
// Check if database is ready
|
||||||
|
select { |
||||||
|
case <-s.db.Ready(): |
||||||
|
return &orlyaclv1.ReadyResponse{Ready: true}, nil |
||||||
|
default: |
||||||
|
return &orlyaclv1.ReadyResponse{Ready: false}, nil |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// === Follows ACL Methods ===
|
||||||
|
|
||||||
|
func (s *ACLService) GetThrottleDelay(ctx context.Context, req *orlyaclv1.ThrottleDelayRequest) (*orlyaclv1.ThrottleDelayResponse, error) { |
||||||
|
// Get the active ACL and check if it's Follows
|
||||||
|
for _, i := range acl.Registry.ACL { |
||||||
|
if i.Type() == "follows" { |
||||||
|
if follows, ok := i.(*acl.Follows); ok { |
||||||
|
delay := follows.GetThrottleDelay(req.Pubkey, req.Ip) |
||||||
|
return &orlyaclv1.ThrottleDelayResponse{DelayMs: delay.Milliseconds()}, nil |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return &orlyaclv1.ThrottleDelayResponse{DelayMs: 0}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) AddFollow(ctx context.Context, req *orlyaclv1.AddFollowRequest) (*orlyaclv1.Empty, error) { |
||||||
|
acl.Registry.AddFollow(req.Pubkey) |
||||||
|
return &orlyaclv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) GetFollowedPubkeys(ctx context.Context, req *orlyaclv1.Empty) (*orlyaclv1.FollowedPubkeysResponse, error) { |
||||||
|
for _, i := range acl.Registry.ACL { |
||||||
|
if i.Type() == "follows" { |
||||||
|
if follows, ok := i.(*acl.Follows); ok { |
||||||
|
pubkeys := follows.GetFollowedPubkeys() |
||||||
|
return &orlyaclv1.FollowedPubkeysResponse{Pubkeys: pubkeys}, nil |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return &orlyaclv1.FollowedPubkeysResponse{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) GetAdminRelays(ctx context.Context, req *orlyaclv1.Empty) (*orlyaclv1.AdminRelaysResponse, error) { |
||||||
|
for _, i := range acl.Registry.ACL { |
||||||
|
if i.Type() == "follows" { |
||||||
|
if follows, ok := i.(*acl.Follows); ok { |
||||||
|
urls := follows.AdminRelays() |
||||||
|
return &orlyaclv1.AdminRelaysResponse{Urls: urls}, nil |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return &orlyaclv1.AdminRelaysResponse{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
// === Managed ACL Methods ===
|
||||||
|
|
||||||
|
func (s *ACLService) BanPubkey(ctx context.Context, req *orlyaclv1.BanPubkeyRequest) (*orlyaclv1.Empty, error) { |
||||||
|
managed := s.getManagedACL() |
||||||
|
if managed == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL not available") |
||||||
|
} |
||||||
|
managedACL := managed.GetManagedACL() |
||||||
|
if managedACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL database not available") |
||||||
|
} |
||||||
|
if err := managedACL.SaveBannedPubkey(req.Pubkey, req.Reason); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to ban pubkey: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) UnbanPubkey(ctx context.Context, req *orlyaclv1.PubkeyRequest) (*orlyaclv1.Empty, error) { |
||||||
|
managed := s.getManagedACL() |
||||||
|
if managed == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL not available") |
||||||
|
} |
||||||
|
managedACL := managed.GetManagedACL() |
||||||
|
if managedACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL database not available") |
||||||
|
} |
||||||
|
if err := managedACL.RemoveBannedPubkey(req.Pubkey); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to unban pubkey: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) ListBannedPubkeys(ctx context.Context, req *orlyaclv1.Empty) (*orlyaclv1.ListBannedPubkeysResponse, error) { |
||||||
|
managed := s.getManagedACL() |
||||||
|
if managed == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL not available") |
||||||
|
} |
||||||
|
managedACL := managed.GetManagedACL() |
||||||
|
if managedACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL database not available") |
||||||
|
} |
||||||
|
banned, err := managedACL.ListBannedPubkeys() |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to list banned pubkeys: %v", err) |
||||||
|
} |
||||||
|
resp := &orlyaclv1.ListBannedPubkeysResponse{} |
||||||
|
for _, b := range banned { |
||||||
|
resp.Pubkeys = append(resp.Pubkeys, &orlyaclv1.BannedPubkey{ |
||||||
|
Pubkey: b.Pubkey, |
||||||
|
Reason: b.Reason, |
||||||
|
Added: b.Added.Unix(), |
||||||
|
}) |
||||||
|
} |
||||||
|
return resp, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) AllowPubkey(ctx context.Context, req *orlyaclv1.AllowPubkeyRequest) (*orlyaclv1.Empty, error) { |
||||||
|
managed := s.getManagedACL() |
||||||
|
if managed == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL not available") |
||||||
|
} |
||||||
|
managedACL := managed.GetManagedACL() |
||||||
|
if managedACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL database not available") |
||||||
|
} |
||||||
|
if err := managedACL.SaveAllowedPubkey(req.Pubkey, req.Reason); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to allow pubkey: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) DisallowPubkey(ctx context.Context, req *orlyaclv1.PubkeyRequest) (*orlyaclv1.Empty, error) { |
||||||
|
managed := s.getManagedACL() |
||||||
|
if managed == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL not available") |
||||||
|
} |
||||||
|
managedACL := managed.GetManagedACL() |
||||||
|
if managedACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL database not available") |
||||||
|
} |
||||||
|
if err := managedACL.RemoveAllowedPubkey(req.Pubkey); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to disallow pubkey: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) ListAllowedPubkeys(ctx context.Context, req *orlyaclv1.Empty) (*orlyaclv1.ListAllowedPubkeysResponse, error) { |
||||||
|
managed := s.getManagedACL() |
||||||
|
if managed == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL not available") |
||||||
|
} |
||||||
|
managedACL := managed.GetManagedACL() |
||||||
|
if managedACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL database not available") |
||||||
|
} |
||||||
|
allowed, err := managedACL.ListAllowedPubkeys() |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to list allowed pubkeys: %v", err) |
||||||
|
} |
||||||
|
resp := &orlyaclv1.ListAllowedPubkeysResponse{} |
||||||
|
for _, a := range allowed { |
||||||
|
resp.Pubkeys = append(resp.Pubkeys, &orlyaclv1.AllowedPubkey{ |
||||||
|
Pubkey: a.Pubkey, |
||||||
|
Reason: a.Reason, |
||||||
|
Added: a.Added.Unix(), |
||||||
|
}) |
||||||
|
} |
||||||
|
return resp, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) BanEvent(ctx context.Context, req *orlyaclv1.BanEventRequest) (*orlyaclv1.Empty, error) { |
||||||
|
managed := s.getManagedACL() |
||||||
|
if managed == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL not available") |
||||||
|
} |
||||||
|
managedACL := managed.GetManagedACL() |
||||||
|
if managedACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL database not available") |
||||||
|
} |
||||||
|
if err := managedACL.SaveBannedEvent(req.EventId, req.Reason); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to ban event: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) UnbanEvent(ctx context.Context, req *orlyaclv1.EventRequest) (*orlyaclv1.Empty, error) { |
||||||
|
managed := s.getManagedACL() |
||||||
|
if managed == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL not available") |
||||||
|
} |
||||||
|
managedACL := managed.GetManagedACL() |
||||||
|
if managedACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL database not available") |
||||||
|
} |
||||||
|
if err := managedACL.RemoveBannedEvent(req.EventId); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to unban event: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) ListBannedEvents(ctx context.Context, req *orlyaclv1.Empty) (*orlyaclv1.ListBannedEventsResponse, error) { |
||||||
|
managed := s.getManagedACL() |
||||||
|
if managed == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL not available") |
||||||
|
} |
||||||
|
managedACL := managed.GetManagedACL() |
||||||
|
if managedACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL database not available") |
||||||
|
} |
||||||
|
banned, err := managedACL.ListBannedEvents() |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to list banned events: %v", err) |
||||||
|
} |
||||||
|
resp := &orlyaclv1.ListBannedEventsResponse{} |
||||||
|
for _, b := range banned { |
||||||
|
resp.Events = append(resp.Events, &orlyaclv1.BannedEvent{ |
||||||
|
EventId: b.ID, |
||||||
|
Reason: b.Reason, |
||||||
|
Added: b.Added.Unix(), |
||||||
|
}) |
||||||
|
} |
||||||
|
return resp, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) AllowEvent(ctx context.Context, req *orlyaclv1.BanEventRequest) (*orlyaclv1.Empty, error) { |
||||||
|
managed := s.getManagedACL() |
||||||
|
if managed == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL not available") |
||||||
|
} |
||||||
|
managedACL := managed.GetManagedACL() |
||||||
|
if managedACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL database not available") |
||||||
|
} |
||||||
|
if err := managedACL.SaveAllowedEvent(req.EventId, req.Reason); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to allow event: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) DisallowEvent(ctx context.Context, req *orlyaclv1.EventRequest) (*orlyaclv1.Empty, error) { |
||||||
|
managed := s.getManagedACL() |
||||||
|
if managed == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL not available") |
||||||
|
} |
||||||
|
managedACL := managed.GetManagedACL() |
||||||
|
if managedACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL database not available") |
||||||
|
} |
||||||
|
if err := managedACL.RemoveAllowedEvent(req.EventId); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to disallow event: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) ListAllowedEvents(ctx context.Context, req *orlyaclv1.Empty) (*orlyaclv1.ListAllowedEventsResponse, error) { |
||||||
|
managed := s.getManagedACL() |
||||||
|
if managed == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL not available") |
||||||
|
} |
||||||
|
managedACL := managed.GetManagedACL() |
||||||
|
if managedACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL database not available") |
||||||
|
} |
||||||
|
allowed, err := managedACL.ListAllowedEvents() |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to list allowed events: %v", err) |
||||||
|
} |
||||||
|
resp := &orlyaclv1.ListAllowedEventsResponse{} |
||||||
|
for _, a := range allowed { |
||||||
|
resp.Events = append(resp.Events, &orlyaclv1.AllowedEvent{ |
||||||
|
EventId: a.ID, |
||||||
|
Reason: a.Reason, |
||||||
|
Added: a.Added.Unix(), |
||||||
|
}) |
||||||
|
} |
||||||
|
return resp, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) BlockIP(ctx context.Context, req *orlyaclv1.BlockIPRequest) (*orlyaclv1.Empty, error) { |
||||||
|
managed := s.getManagedACL() |
||||||
|
if managed == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL not available") |
||||||
|
} |
||||||
|
managedACL := managed.GetManagedACL() |
||||||
|
if managedACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL database not available") |
||||||
|
} |
||||||
|
if err := managedACL.SaveBlockedIP(req.Ip, req.Reason); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to block IP: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) UnblockIP(ctx context.Context, req *orlyaclv1.IPRequest) (*orlyaclv1.Empty, error) { |
||||||
|
managed := s.getManagedACL() |
||||||
|
if managed == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL not available") |
||||||
|
} |
||||||
|
managedACL := managed.GetManagedACL() |
||||||
|
if managedACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL database not available") |
||||||
|
} |
||||||
|
if err := managedACL.RemoveBlockedIP(req.Ip); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to unblock IP: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) ListBlockedIPs(ctx context.Context, req *orlyaclv1.Empty) (*orlyaclv1.ListBlockedIPsResponse, error) { |
||||||
|
managed := s.getManagedACL() |
||||||
|
if managed == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL not available") |
||||||
|
} |
||||||
|
managedACL := managed.GetManagedACL() |
||||||
|
if managedACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL database not available") |
||||||
|
} |
||||||
|
blocked, err := managedACL.ListBlockedIPs() |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to list blocked IPs: %v", err) |
||||||
|
} |
||||||
|
resp := &orlyaclv1.ListBlockedIPsResponse{} |
||||||
|
for _, b := range blocked { |
||||||
|
resp.Ips = append(resp.Ips, &orlyaclv1.BlockedIP{ |
||||||
|
Ip: b.IP, |
||||||
|
Reason: b.Reason, |
||||||
|
Added: b.Added.Unix(), |
||||||
|
}) |
||||||
|
} |
||||||
|
return resp, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) AllowKind(ctx context.Context, req *orlyaclv1.AllowKindRequest) (*orlyaclv1.Empty, error) { |
||||||
|
managed := s.getManagedACL() |
||||||
|
if managed == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL not available") |
||||||
|
} |
||||||
|
managedACL := managed.GetManagedACL() |
||||||
|
if managedACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL database not available") |
||||||
|
} |
||||||
|
if err := managedACL.SaveAllowedKind(int(req.Kind)); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to allow kind: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) DisallowKind(ctx context.Context, req *orlyaclv1.KindRequest) (*orlyaclv1.Empty, error) { |
||||||
|
managed := s.getManagedACL() |
||||||
|
if managed == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL not available") |
||||||
|
} |
||||||
|
managedACL := managed.GetManagedACL() |
||||||
|
if managedACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL database not available") |
||||||
|
} |
||||||
|
if err := managedACL.RemoveAllowedKind(int(req.Kind)); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to disallow kind: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) ListAllowedKinds(ctx context.Context, req *orlyaclv1.Empty) (*orlyaclv1.ListAllowedKindsResponse, error) { |
||||||
|
managed := s.getManagedACL() |
||||||
|
if managed == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL not available") |
||||||
|
} |
||||||
|
managedACL := managed.GetManagedACL() |
||||||
|
if managedACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL database not available") |
||||||
|
} |
||||||
|
kinds, err := managedACL.ListAllowedKinds() |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to list allowed kinds: %v", err) |
||||||
|
} |
||||||
|
resp := &orlyaclv1.ListAllowedKindsResponse{} |
||||||
|
for _, k := range kinds { |
||||||
|
resp.Kinds = append(resp.Kinds, int32(k)) |
||||||
|
} |
||||||
|
return resp, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) UpdatePeerAdmins(ctx context.Context, req *orlyaclv1.UpdatePeerAdminsRequest) (*orlyaclv1.Empty, error) { |
||||||
|
managed := s.getManagedACL() |
||||||
|
if managed == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "managed ACL not available") |
||||||
|
} |
||||||
|
managed.UpdatePeerAdmins(req.PeerPubkeys) |
||||||
|
return &orlyaclv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
// === Curating ACL Methods ===
|
||||||
|
|
||||||
|
func (s *ACLService) TrustPubkey(ctx context.Context, req *orlyaclv1.TrustPubkeyRequest) (*orlyaclv1.Empty, error) { |
||||||
|
curating := s.getCuratingACL() |
||||||
|
if curating == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL not available") |
||||||
|
} |
||||||
|
if err := curating.TrustPubkey(req.Pubkey, req.Note); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to trust pubkey: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) UntrustPubkey(ctx context.Context, req *orlyaclv1.PubkeyRequest) (*orlyaclv1.Empty, error) { |
||||||
|
curating := s.getCuratingACL() |
||||||
|
if curating == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL not available") |
||||||
|
} |
||||||
|
if err := curating.UntrustPubkey(req.Pubkey); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to untrust pubkey: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) ListTrustedPubkeys(ctx context.Context, req *orlyaclv1.Empty) (*orlyaclv1.ListTrustedPubkeysResponse, error) { |
||||||
|
curating := s.getCuratingACL() |
||||||
|
if curating == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL not available") |
||||||
|
} |
||||||
|
curatingACL := curating.GetCuratingACL() |
||||||
|
if curatingACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL database not available") |
||||||
|
} |
||||||
|
trusted, err := curatingACL.ListTrustedPubkeys() |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to list trusted pubkeys: %v", err) |
||||||
|
} |
||||||
|
resp := &orlyaclv1.ListTrustedPubkeysResponse{} |
||||||
|
for _, t := range trusted { |
||||||
|
resp.Pubkeys = append(resp.Pubkeys, &orlyaclv1.TrustedPubkey{ |
||||||
|
Pubkey: t.Pubkey, |
||||||
|
Note: t.Note, |
||||||
|
Added: t.Added.Unix(), |
||||||
|
}) |
||||||
|
} |
||||||
|
return resp, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) BlacklistPubkey(ctx context.Context, req *orlyaclv1.BlacklistPubkeyRequest) (*orlyaclv1.Empty, error) { |
||||||
|
curating := s.getCuratingACL() |
||||||
|
if curating == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL not available") |
||||||
|
} |
||||||
|
if err := curating.BlacklistPubkey(req.Pubkey, req.Reason); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to blacklist pubkey: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) UnblacklistPubkey(ctx context.Context, req *orlyaclv1.PubkeyRequest) (*orlyaclv1.Empty, error) { |
||||||
|
curating := s.getCuratingACL() |
||||||
|
if curating == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL not available") |
||||||
|
} |
||||||
|
if err := curating.UnblacklistPubkey(req.Pubkey); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to unblacklist pubkey: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) ListBlacklistedPubkeys(ctx context.Context, req *orlyaclv1.Empty) (*orlyaclv1.ListBlacklistedPubkeysResponse, error) { |
||||||
|
curating := s.getCuratingACL() |
||||||
|
if curating == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL not available") |
||||||
|
} |
||||||
|
curatingACL := curating.GetCuratingACL() |
||||||
|
if curatingACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL database not available") |
||||||
|
} |
||||||
|
blacklisted, err := curatingACL.ListBlacklistedPubkeys() |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to list blacklisted pubkeys: %v", err) |
||||||
|
} |
||||||
|
resp := &orlyaclv1.ListBlacklistedPubkeysResponse{} |
||||||
|
for _, b := range blacklisted { |
||||||
|
resp.Pubkeys = append(resp.Pubkeys, &orlyaclv1.BlacklistedPubkey{ |
||||||
|
Pubkey: b.Pubkey, |
||||||
|
Reason: b.Reason, |
||||||
|
Added: b.Added.Unix(), |
||||||
|
}) |
||||||
|
} |
||||||
|
return resp, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) MarkSpam(ctx context.Context, req *orlyaclv1.MarkSpamRequest) (*orlyaclv1.Empty, error) { |
||||||
|
curating := s.getCuratingACL() |
||||||
|
if curating == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL not available") |
||||||
|
} |
||||||
|
curatingACL := curating.GetCuratingACL() |
||||||
|
if curatingACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL database not available") |
||||||
|
} |
||||||
|
if err := curatingACL.MarkEventAsSpam(req.EventId, req.Pubkey, req.Reason); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to mark spam: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) UnmarkSpam(ctx context.Context, req *orlyaclv1.EventRequest) (*orlyaclv1.Empty, error) { |
||||||
|
curating := s.getCuratingACL() |
||||||
|
if curating == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL not available") |
||||||
|
} |
||||||
|
curatingACL := curating.GetCuratingACL() |
||||||
|
if curatingACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL database not available") |
||||||
|
} |
||||||
|
if err := curatingACL.UnmarkEventAsSpam(req.EventId); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to unmark spam: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) ListSpamEvents(ctx context.Context, req *orlyaclv1.Empty) (*orlyaclv1.ListSpamEventsResponse, error) { |
||||||
|
curating := s.getCuratingACL() |
||||||
|
if curating == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL not available") |
||||||
|
} |
||||||
|
curatingACL := curating.GetCuratingACL() |
||||||
|
if curatingACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL database not available") |
||||||
|
} |
||||||
|
spam, err := curatingACL.ListSpamEvents() |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to list spam events: %v", err) |
||||||
|
} |
||||||
|
resp := &orlyaclv1.ListSpamEventsResponse{} |
||||||
|
for _, se := range spam { |
||||||
|
resp.Events = append(resp.Events, &orlyaclv1.SpamEvent{ |
||||||
|
EventId: se.EventID, |
||||||
|
Pubkey: se.Pubkey, |
||||||
|
Reason: se.Reason, |
||||||
|
Added: se.Added.Unix(), |
||||||
|
}) |
||||||
|
} |
||||||
|
return resp, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) RateLimitCheck(ctx context.Context, req *orlyaclv1.RateLimitCheckRequest) (*orlyaclv1.RateLimitCheckResponse, error) { |
||||||
|
curating := s.getCuratingACL() |
||||||
|
if curating == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL not available") |
||||||
|
} |
||||||
|
allowed, message, err := curating.RateLimitCheck(req.Pubkey, req.Ip) |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to check rate limit: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.RateLimitCheckResponse{ |
||||||
|
Allowed: allowed, |
||||||
|
Message: message, |
||||||
|
}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) ProcessConfigEvent(ctx context.Context, req *orlyaclv1.ConfigEventRequest) (*orlyaclv1.Empty, error) { |
||||||
|
curating := s.getCuratingACL() |
||||||
|
if curating == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL not available") |
||||||
|
} |
||||||
|
ev := orlydbv1.ProtoToEvent(req.Event) |
||||||
|
if err := curating.ProcessConfigEvent(ev); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to process config event: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) GetCuratingConfig(ctx context.Context, req *orlyaclv1.Empty) (*orlyaclv1.CuratingConfig, error) { |
||||||
|
curating := s.getCuratingACL() |
||||||
|
if curating == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL not available") |
||||||
|
} |
||||||
|
config, err := curating.GetConfig() |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to get config: %v", err) |
||||||
|
} |
||||||
|
resp := &orlyaclv1.CuratingConfig{ |
||||||
|
ConfigEventId: config.ConfigEventID, |
||||||
|
ConfigPubkey: config.ConfigPubkey, |
||||||
|
ConfiguredAt: config.ConfiguredAt, |
||||||
|
DailyLimit: int32(config.DailyLimit), |
||||||
|
IpDailyLimit: int32(config.IPDailyLimit), |
||||||
|
FirstBanHours: int32(config.FirstBanHours), |
||||||
|
SecondBanHours: int32(config.SecondBanHours), |
||||||
|
KindCategories: config.KindCategories, |
||||||
|
AllowedRanges: config.AllowedRanges, |
||||||
|
} |
||||||
|
for _, k := range config.AllowedKinds { |
||||||
|
resp.AllowedKinds = append(resp.AllowedKinds, int32(k)) |
||||||
|
} |
||||||
|
return resp, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) IsCuratingConfigured(ctx context.Context, req *orlyaclv1.Empty) (*orlyaclv1.BoolResponse, error) { |
||||||
|
curating := s.getCuratingACL() |
||||||
|
if curating == nil { |
||||||
|
return &orlyaclv1.BoolResponse{Value: false}, nil |
||||||
|
} |
||||||
|
configured, err := curating.IsConfigured() |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to check if configured: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.BoolResponse{Value: configured}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) ListUnclassifiedUsers(ctx context.Context, req *orlyaclv1.PaginationRequest) (*orlyaclv1.ListUnclassifiedUsersResponse, error) { |
||||||
|
curating := s.getCuratingACL() |
||||||
|
if curating == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL not available") |
||||||
|
} |
||||||
|
curatingACL := curating.GetCuratingACL() |
||||||
|
if curatingACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL database not available") |
||||||
|
} |
||||||
|
// The underlying ListUnclassifiedUsers only takes limit, not offset
|
||||||
|
// We'll request limit+offset and skip the first offset items
|
||||||
|
limit := int(req.Limit) |
||||||
|
offset := int(req.Offset) |
||||||
|
if limit == 0 { |
||||||
|
limit = 100 // Default limit
|
||||||
|
} |
||||||
|
users, err := curatingACL.ListUnclassifiedUsers(limit + offset) |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to list unclassified users: %v", err) |
||||||
|
} |
||||||
|
// Apply offset
|
||||||
|
if offset > 0 && len(users) > offset { |
||||||
|
users = users[offset:] |
||||||
|
} else if offset > 0 { |
||||||
|
users = nil |
||||||
|
} |
||||||
|
// Apply limit
|
||||||
|
if limit > 0 && len(users) > limit { |
||||||
|
users = users[:limit] |
||||||
|
} |
||||||
|
resp := &orlyaclv1.ListUnclassifiedUsersResponse{Total: int32(len(users))} |
||||||
|
for _, u := range users { |
||||||
|
resp.Users = append(resp.Users, &orlyaclv1.UnclassifiedUser{ |
||||||
|
Pubkey: u.Pubkey, |
||||||
|
EventCount: int32(u.EventCount), |
||||||
|
FirstSeen: u.LastEvent.Format("2006-01-02T15:04:05Z"), |
||||||
|
}) |
||||||
|
} |
||||||
|
return resp, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) GetEventsForPubkey(ctx context.Context, req *orlyaclv1.GetEventsForPubkeyRequest) (*orlyaclv1.EventsForPubkeyResponse, error) { |
||||||
|
curating := s.getCuratingACL() |
||||||
|
if curating == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL not available") |
||||||
|
} |
||||||
|
curatingACL := curating.GetCuratingACL() |
||||||
|
if curatingACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL database not available") |
||||||
|
} |
||||||
|
events, total, err := curatingACL.GetEventsForPubkey(req.Pubkey, int(req.Limit), int(req.Offset)) |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to get events for pubkey: %v", err) |
||||||
|
} |
||||||
|
resp := &orlyaclv1.EventsForPubkeyResponse{Total: int32(total)} |
||||||
|
for _, ev := range events { |
||||||
|
resp.Events = append(resp.Events, &orlyaclv1.EventSummary{ |
||||||
|
Id: ev.ID, |
||||||
|
Kind: uint32(ev.Kind), |
||||||
|
Content: []byte(ev.Content), |
||||||
|
CreatedAt: ev.CreatedAt, |
||||||
|
}) |
||||||
|
} |
||||||
|
return resp, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) DeleteEventsForPubkey(ctx context.Context, req *orlyaclv1.DeleteEventsForPubkeyRequest) (*orlyaclv1.DeleteCountResponse, error) { |
||||||
|
curating := s.getCuratingACL() |
||||||
|
if curating == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL not available") |
||||||
|
} |
||||||
|
curatingACL := curating.GetCuratingACL() |
||||||
|
if curatingACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL database not available") |
||||||
|
} |
||||||
|
count, err := curatingACL.DeleteEventsForPubkey(req.Pubkey) |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to delete events for pubkey: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.DeleteCountResponse{Count: int32(count)}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) ScanAllPubkeys(ctx context.Context, req *orlyaclv1.Empty) (*orlyaclv1.ScanResultResponse, error) { |
||||||
|
curating := s.getCuratingACL() |
||||||
|
if curating == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL not available") |
||||||
|
} |
||||||
|
curatingACL := curating.GetCuratingACL() |
||||||
|
if curatingACL == nil { |
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "curating ACL database not available") |
||||||
|
} |
||||||
|
result, err := curatingACL.ScanAllPubkeys() |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "failed to scan all pubkeys: %v", err) |
||||||
|
} |
||||||
|
return &orlyaclv1.ScanResultResponse{ |
||||||
|
TotalPubkeys: int32(result.TotalPubkeys), |
||||||
|
TotalEvents: int32(result.TotalEvents), |
||||||
|
}, nil |
||||||
|
} |
||||||
|
|
||||||
|
// === Helper Methods ===
|
||||||
|
|
||||||
|
func (s *ACLService) getManagedACL() *acl.Managed { |
||||||
|
for _, i := range acl.Registry.ACL { |
||||||
|
if i.Type() == "managed" { |
||||||
|
if managed, ok := i.(*acl.Managed); ok { |
||||||
|
return managed |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ACLService) getCuratingACL() *acl.Curating { |
||||||
|
for _, i := range acl.Registry.ACL { |
||||||
|
if i.Type() == "curating" { |
||||||
|
if curating, ok := i.(*acl.Curating); ok { |
||||||
|
return curating |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// Unused but may be needed for debugging
|
||||||
|
var _ = log.T |
||||||
|
var _ = hex.EncodeToString |
||||||
@ -0,0 +1,33 @@ |
|||||||
|
// Package server provides a shared gRPC database server implementation.
|
||||||
|
package server |
||||||
|
|
||||||
|
import "time" |
||||||
|
|
||||||
|
// Config holds configuration for the database gRPC server.
|
||||||
|
type Config struct { |
||||||
|
// Listen is the gRPC server listen address
|
||||||
|
Listen string |
||||||
|
|
||||||
|
// LogLevel is the logging level
|
||||||
|
LogLevel string |
||||||
|
|
||||||
|
// StreamBatchSize is the number of events per stream batch
|
||||||
|
StreamBatchSize int |
||||||
|
|
||||||
|
// MaxConcurrentQueries is the max concurrent queries
|
||||||
|
MaxConcurrentQueries int |
||||||
|
} |
||||||
|
|
||||||
|
// DatabaseConfig holds Badger-specific configuration.
|
||||||
|
type DatabaseConfig struct { |
||||||
|
DataDir string |
||||||
|
LogLevel string |
||||||
|
BlockCacheMB int |
||||||
|
IndexCacheMB int |
||||||
|
ZSTDLevel int |
||||||
|
QueryCacheSizeMB int |
||||||
|
QueryCacheMaxAge time.Duration |
||||||
|
QueryCacheDisabled bool |
||||||
|
SerialCachePubkeys int |
||||||
|
SerialCacheEventIds int |
||||||
|
} |
||||||
@ -0,0 +1,111 @@ |
|||||||
|
package server |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"net" |
||||||
|
"os" |
||||||
|
"os/signal" |
||||||
|
"syscall" |
||||||
|
"time" |
||||||
|
|
||||||
|
"google.golang.org/grpc" |
||||||
|
"google.golang.org/grpc/reflection" |
||||||
|
"lol.mleku.dev/chk" |
||||||
|
"lol.mleku.dev/log" |
||||||
|
|
||||||
|
"next.orly.dev/pkg/database" |
||||||
|
orlydbv1 "next.orly.dev/pkg/proto/orlydb/v1" |
||||||
|
) |
||||||
|
|
||||||
|
// Server wraps a gRPC database server.
|
||||||
|
type Server struct { |
||||||
|
grpcServer *grpc.Server |
||||||
|
db database.Database |
||||||
|
cfg *Config |
||||||
|
listener net.Listener |
||||||
|
} |
||||||
|
|
||||||
|
// New creates a new database gRPC server.
|
||||||
|
func New(db database.Database, cfg *Config) *Server { |
||||||
|
// Create gRPC server with large message sizes for events
|
||||||
|
grpcServer := grpc.NewServer( |
||||||
|
grpc.MaxRecvMsgSize(64<<20), // 64MB
|
||||||
|
grpc.MaxSendMsgSize(64<<20), // 64MB
|
||||||
|
) |
||||||
|
|
||||||
|
// Register database service
|
||||||
|
service := NewDatabaseService(db, cfg) |
||||||
|
orlydbv1.RegisterDatabaseServiceServer(grpcServer, service) |
||||||
|
|
||||||
|
// Register reflection for debugging with grpcurl
|
||||||
|
reflection.Register(grpcServer) |
||||||
|
|
||||||
|
return &Server{ |
||||||
|
grpcServer: grpcServer, |
||||||
|
db: db, |
||||||
|
cfg: cfg, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// ListenAndServe starts the gRPC server.
|
||||||
|
func (s *Server) ListenAndServe(ctx context.Context, cancel context.CancelFunc) error { |
||||||
|
// Start listening
|
||||||
|
lis, err := net.Listen("tcp", s.cfg.Listen) |
||||||
|
if chk.E(err) { |
||||||
|
return err |
||||||
|
} |
||||||
|
s.listener = lis |
||||||
|
log.I.F("gRPC database server listening on %s", s.cfg.Listen) |
||||||
|
|
||||||
|
// Handle graceful shutdown
|
||||||
|
go s.handleShutdown(ctx, cancel) |
||||||
|
|
||||||
|
// Serve gRPC
|
||||||
|
return s.grpcServer.Serve(lis) |
||||||
|
} |
||||||
|
|
||||||
|
func (s *Server) handleShutdown(ctx context.Context, cancel context.CancelFunc) { |
||||||
|
sigs := make(chan os.Signal, 1) |
||||||
|
signal.Notify(sigs, syscall.SIGTERM, syscall.SIGINT) |
||||||
|
|
||||||
|
select { |
||||||
|
case sig := <-sigs: |
||||||
|
log.I.F("received signal %v, shutting down...", sig) |
||||||
|
case <-ctx.Done(): |
||||||
|
log.I.F("context cancelled, shutting down...") |
||||||
|
} |
||||||
|
|
||||||
|
// Cancel context to stop all operations
|
||||||
|
cancel() |
||||||
|
|
||||||
|
// Gracefully stop gRPC server with timeout
|
||||||
|
stopped := make(chan struct{}) |
||||||
|
go func() { |
||||||
|
s.grpcServer.GracefulStop() |
||||||
|
close(stopped) |
||||||
|
}() |
||||||
|
|
||||||
|
select { |
||||||
|
case <-stopped: |
||||||
|
log.I.F("gRPC server stopped gracefully") |
||||||
|
case <-time.After(5 * time.Second): |
||||||
|
log.W.F("gRPC graceful stop timed out, forcing stop") |
||||||
|
s.grpcServer.Stop() |
||||||
|
} |
||||||
|
|
||||||
|
// Sync and close database
|
||||||
|
log.I.F("syncing database...") |
||||||
|
if err := s.db.Sync(); chk.E(err) { |
||||||
|
log.W.F("failed to sync database: %v", err) |
||||||
|
} |
||||||
|
log.I.F("closing database...") |
||||||
|
if err := s.db.Close(); chk.E(err) { |
||||||
|
log.W.F("failed to close database: %v", err) |
||||||
|
} |
||||||
|
log.I.F("shutdown complete") |
||||||
|
} |
||||||
|
|
||||||
|
// Stop stops the server.
|
||||||
|
func (s *Server) Stop() { |
||||||
|
s.grpcServer.Stop() |
||||||
|
} |
||||||
@ -0,0 +1,731 @@ |
|||||||
|
package server |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"io" |
||||||
|
|
||||||
|
"google.golang.org/grpc/codes" |
||||||
|
"google.golang.org/grpc/status" |
||||||
|
"lol.mleku.dev/chk" |
||||||
|
"lol.mleku.dev/log" |
||||||
|
|
||||||
|
"next.orly.dev/pkg/database" |
||||||
|
orlydbv1 "next.orly.dev/pkg/proto/orlydb/v1" |
||||||
|
) |
||||||
|
|
||||||
|
// DatabaseService implements the orlydbv1.DatabaseServiceServer interface.
|
||||||
|
type DatabaseService struct { |
||||||
|
orlydbv1.UnimplementedDatabaseServiceServer |
||||||
|
db database.Database |
||||||
|
cfg *Config |
||||||
|
} |
||||||
|
|
||||||
|
// NewDatabaseService creates a new database service.
|
||||||
|
func NewDatabaseService(db database.Database, cfg *Config) *DatabaseService { |
||||||
|
return &DatabaseService{ |
||||||
|
db: db, |
||||||
|
cfg: cfg, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// === Lifecycle Methods ===
|
||||||
|
|
||||||
|
func (s *DatabaseService) GetPath(ctx context.Context, req *orlydbv1.Empty) (*orlydbv1.PathResponse, error) { |
||||||
|
return &orlydbv1.PathResponse{Path: s.db.Path()}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) Sync(ctx context.Context, req *orlydbv1.Empty) (*orlydbv1.Empty, error) { |
||||||
|
if err := s.db.Sync(); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "sync failed: %v", err) |
||||||
|
} |
||||||
|
return &orlydbv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) Ready(ctx context.Context, req *orlydbv1.Empty) (*orlydbv1.ReadyResponse, error) { |
||||||
|
// Check if ready channel is closed
|
||||||
|
select { |
||||||
|
case <-s.db.Ready(): |
||||||
|
return &orlydbv1.ReadyResponse{Ready: true}, nil |
||||||
|
default: |
||||||
|
return &orlydbv1.ReadyResponse{Ready: false}, nil |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) SetLogLevel(ctx context.Context, req *orlydbv1.SetLogLevelRequest) (*orlydbv1.Empty, error) { |
||||||
|
s.db.SetLogLevel(req.Level) |
||||||
|
return &orlydbv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
// === Event Storage ===
|
||||||
|
|
||||||
|
func (s *DatabaseService) SaveEvent(ctx context.Context, req *orlydbv1.SaveEventRequest) (*orlydbv1.SaveEventResponse, error) { |
||||||
|
ev := orlydbv1.ProtoToEvent(req.Event) |
||||||
|
exists, err := s.db.SaveEvent(ctx, ev) |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "save event failed: %v", err) |
||||||
|
} |
||||||
|
return &orlydbv1.SaveEventResponse{Exists: exists}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) GetSerialsFromFilter(ctx context.Context, req *orlydbv1.GetSerialsFromFilterRequest) (*orlydbv1.SerialList, error) { |
||||||
|
f := orlydbv1.ProtoToFilter(req.Filter) |
||||||
|
serials, err := s.db.GetSerialsFromFilter(f) |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "get serials failed: %v", err) |
||||||
|
} |
||||||
|
return orlydbv1.Uint40sToProto(serials), nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) WouldReplaceEvent(ctx context.Context, req *orlydbv1.WouldReplaceEventRequest) (*orlydbv1.WouldReplaceEventResponse, error) { |
||||||
|
ev := orlydbv1.ProtoToEvent(req.Event) |
||||||
|
wouldReplace, replacedSerials, err := s.db.WouldReplaceEvent(ev) |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "would replace check failed: %v", err) |
||||||
|
} |
||||||
|
resp := &orlydbv1.WouldReplaceEventResponse{ |
||||||
|
WouldReplace: wouldReplace, |
||||||
|
} |
||||||
|
for _, ser := range replacedSerials { |
||||||
|
resp.ReplacedSerials = append(resp.ReplacedSerials, ser.Get()) |
||||||
|
} |
||||||
|
return resp, nil |
||||||
|
} |
||||||
|
|
||||||
|
// === Event Queries (Streaming) ===
|
||||||
|
|
||||||
|
func (s *DatabaseService) QueryEvents(req *orlydbv1.QueryEventsRequest, stream orlydbv1.DatabaseService_QueryEventsServer) error { |
||||||
|
f := orlydbv1.ProtoToFilter(req.Filter) |
||||||
|
events, err := s.db.QueryEvents(stream.Context(), f) |
||||||
|
if err != nil { |
||||||
|
return status.Errorf(codes.Internal, "query events failed: %v", err) |
||||||
|
} |
||||||
|
return s.streamEvents(orlydbv1.EventsToProto(events), stream) |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) QueryAllVersions(req *orlydbv1.QueryEventsRequest, stream orlydbv1.DatabaseService_QueryAllVersionsServer) error { |
||||||
|
f := orlydbv1.ProtoToFilter(req.Filter) |
||||||
|
events, err := s.db.QueryAllVersions(stream.Context(), f) |
||||||
|
if err != nil { |
||||||
|
return status.Errorf(codes.Internal, "query all versions failed: %v", err) |
||||||
|
} |
||||||
|
return s.streamEvents(orlydbv1.EventsToProto(events), stream) |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) QueryEventsWithOptions(req *orlydbv1.QueryEventsWithOptionsRequest, stream orlydbv1.DatabaseService_QueryEventsWithOptionsServer) error { |
||||||
|
f := orlydbv1.ProtoToFilter(req.Filter) |
||||||
|
events, err := s.db.QueryEventsWithOptions(stream.Context(), f, req.IncludeDeleteEvents, req.ShowAllVersions) |
||||||
|
if err != nil { |
||||||
|
return status.Errorf(codes.Internal, "query events with options failed: %v", err) |
||||||
|
} |
||||||
|
return s.streamEvents(orlydbv1.EventsToProto(events), stream) |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) QueryDeleteEventsByTargetId(req *orlydbv1.QueryDeleteEventsByTargetIdRequest, stream orlydbv1.DatabaseService_QueryDeleteEventsByTargetIdServer) error { |
||||||
|
events, err := s.db.QueryDeleteEventsByTargetId(stream.Context(), req.TargetEventId) |
||||||
|
if err != nil { |
||||||
|
return status.Errorf(codes.Internal, "query delete events failed: %v", err) |
||||||
|
} |
||||||
|
return s.streamEvents(orlydbv1.EventsToProto(events), stream) |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) QueryForSerials(ctx context.Context, req *orlydbv1.QueryEventsRequest) (*orlydbv1.SerialList, error) { |
||||||
|
f := orlydbv1.ProtoToFilter(req.Filter) |
||||||
|
serials, err := s.db.QueryForSerials(ctx, f) |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "query for serials failed: %v", err) |
||||||
|
} |
||||||
|
return orlydbv1.Uint40sToProto(serials), nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) QueryForIds(ctx context.Context, req *orlydbv1.QueryEventsRequest) (*orlydbv1.IdPkTsList, error) { |
||||||
|
f := orlydbv1.ProtoToFilter(req.Filter) |
||||||
|
idPkTs, err := s.db.QueryForIds(ctx, f) |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "query for ids failed: %v", err) |
||||||
|
} |
||||||
|
return orlydbv1.IdPkTsListToProto(idPkTs), nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) CountEvents(ctx context.Context, req *orlydbv1.QueryEventsRequest) (*orlydbv1.CountEventsResponse, error) { |
||||||
|
f := orlydbv1.ProtoToFilter(req.Filter) |
||||||
|
count, approximate, err := s.db.CountEvents(ctx, f) |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "count events failed: %v", err) |
||||||
|
} |
||||||
|
return &orlydbv1.CountEventsResponse{ |
||||||
|
Count: int32(count), |
||||||
|
Approximate: approximate, |
||||||
|
}, nil |
||||||
|
} |
||||||
|
|
||||||
|
// === Event Retrieval by Serial ===
|
||||||
|
|
||||||
|
func (s *DatabaseService) FetchEventBySerial(ctx context.Context, req *orlydbv1.FetchEventBySerialRequest) (*orlydbv1.FetchEventBySerialResponse, error) { |
||||||
|
ser := orlydbv1.ProtoToUint40(&orlydbv1.Uint40{Value: req.Serial}) |
||||||
|
ev, err := s.db.FetchEventBySerial(ser) |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "fetch event by serial failed: %v", err) |
||||||
|
} |
||||||
|
return &orlydbv1.FetchEventBySerialResponse{ |
||||||
|
Event: orlydbv1.EventToProto(ev), |
||||||
|
Found: ev != nil, |
||||||
|
}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) FetchEventsBySerials(ctx context.Context, req *orlydbv1.FetchEventsBySerialRequest) (*orlydbv1.EventMap, error) { |
||||||
|
serials := orlydbv1.ProtoToUint40s(&orlydbv1.SerialList{Serials: req.Serials}) |
||||||
|
events, err := s.db.FetchEventsBySerials(serials) |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "fetch events by serials failed: %v", err) |
||||||
|
} |
||||||
|
return orlydbv1.EventMapToProto(events), nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) GetSerialById(ctx context.Context, req *orlydbv1.GetSerialByIdRequest) (*orlydbv1.GetSerialByIdResponse, error) { |
||||||
|
ser, err := s.db.GetSerialById(req.Id) |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "get serial by id failed: %v", err) |
||||||
|
} |
||||||
|
if ser == nil { |
||||||
|
return &orlydbv1.GetSerialByIdResponse{Found: false}, nil |
||||||
|
} |
||||||
|
return &orlydbv1.GetSerialByIdResponse{ |
||||||
|
Serial: ser.Get(), |
||||||
|
Found: true, |
||||||
|
}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) GetSerialsByIds(ctx context.Context, req *orlydbv1.GetSerialsByIdsRequest) (*orlydbv1.SerialMap, error) { |
||||||
|
// Convert request IDs to tag format
|
||||||
|
ids := orlydbv1.BytesToTag(req.Ids) |
||||||
|
serials, err := s.db.GetSerialsByIds(ids) |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "get serials by ids failed: %v", err) |
||||||
|
} |
||||||
|
result := &orlydbv1.SerialMap{ |
||||||
|
Serials: make(map[string]uint64), |
||||||
|
} |
||||||
|
for k, v := range serials { |
||||||
|
if v != nil { |
||||||
|
result.Serials[k] = v.Get() |
||||||
|
} |
||||||
|
} |
||||||
|
return result, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) GetSerialsByRange(ctx context.Context, req *orlydbv1.GetSerialsByRangeRequest) (*orlydbv1.SerialList, error) { |
||||||
|
r := orlydbv1.ProtoToRange(req.Range) |
||||||
|
serials, err := s.db.GetSerialsByRange(r) |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "get serials by range failed: %v", err) |
||||||
|
} |
||||||
|
return orlydbv1.Uint40sToProto(serials), nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) GetFullIdPubkeyBySerial(ctx context.Context, req *orlydbv1.GetFullIdPubkeyBySerialRequest) (*orlydbv1.IdPkTs, error) { |
||||||
|
ser := orlydbv1.ProtoToUint40(&orlydbv1.Uint40{Value: req.Serial}) |
||||||
|
idPkTs, err := s.db.GetFullIdPubkeyBySerial(ser) |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "get full id pubkey by serial failed: %v", err) |
||||||
|
} |
||||||
|
return orlydbv1.IdPkTsToProto(idPkTs), nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) GetFullIdPubkeyBySerials(ctx context.Context, req *orlydbv1.GetFullIdPubkeyBySerialsRequest) (*orlydbv1.IdPkTsList, error) { |
||||||
|
serials := orlydbv1.ProtoToUint40s(&orlydbv1.SerialList{Serials: req.Serials}) |
||||||
|
idPkTs, err := s.db.GetFullIdPubkeyBySerials(serials) |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "get full id pubkey by serials failed: %v", err) |
||||||
|
} |
||||||
|
return orlydbv1.IdPkTsListToProto(idPkTs), nil |
||||||
|
} |
||||||
|
|
||||||
|
// === Event Deletion ===
|
||||||
|
|
||||||
|
func (s *DatabaseService) DeleteEvent(ctx context.Context, req *orlydbv1.DeleteEventRequest) (*orlydbv1.Empty, error) { |
||||||
|
if err := s.db.DeleteEvent(ctx, req.EventId); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "delete event failed: %v", err) |
||||||
|
} |
||||||
|
return &orlydbv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) DeleteEventBySerial(ctx context.Context, req *orlydbv1.DeleteEventBySerialRequest) (*orlydbv1.Empty, error) { |
||||||
|
ser := orlydbv1.ProtoToUint40(&orlydbv1.Uint40{Value: req.Serial}) |
||||||
|
ev := orlydbv1.ProtoToEvent(req.Event) |
||||||
|
if err := s.db.DeleteEventBySerial(ctx, ser, ev); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "delete event by serial failed: %v", err) |
||||||
|
} |
||||||
|
return &orlydbv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) DeleteExpired(ctx context.Context, req *orlydbv1.Empty) (*orlydbv1.Empty, error) { |
||||||
|
s.db.DeleteExpired() |
||||||
|
return &orlydbv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) ProcessDelete(ctx context.Context, req *orlydbv1.ProcessDeleteRequest) (*orlydbv1.Empty, error) { |
||||||
|
ev := orlydbv1.ProtoToEvent(req.Event) |
||||||
|
if err := s.db.ProcessDelete(ev, req.Admins); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "process delete failed: %v", err) |
||||||
|
} |
||||||
|
return &orlydbv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) CheckForDeleted(ctx context.Context, req *orlydbv1.CheckForDeletedRequest) (*orlydbv1.Empty, error) { |
||||||
|
ev := orlydbv1.ProtoToEvent(req.Event) |
||||||
|
if err := s.db.CheckForDeleted(ev, req.Admins); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "check for deleted failed: %v", err) |
||||||
|
} |
||||||
|
return &orlydbv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
// === Import/Export ===
|
||||||
|
|
||||||
|
func (s *DatabaseService) Import(stream orlydbv1.DatabaseService_ImportServer) error { |
||||||
|
pr, pw := io.Pipe() |
||||||
|
|
||||||
|
// Goroutine to read from gRPC stream and write to pipe
|
||||||
|
go func() { |
||||||
|
defer pw.Close() |
||||||
|
for { |
||||||
|
chunk, err := stream.Recv() |
||||||
|
if err == io.EOF { |
||||||
|
return |
||||||
|
} |
||||||
|
if err != nil { |
||||||
|
log.E.F("import stream error: %v", err) |
||||||
|
pw.CloseWithError(err) |
||||||
|
return |
||||||
|
} |
||||||
|
if _, err := pw.Write(chunk.Data); chk.E(err) { |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
}() |
||||||
|
|
||||||
|
// Import from pipe
|
||||||
|
s.db.Import(pr) |
||||||
|
|
||||||
|
return stream.SendAndClose(&orlydbv1.ImportResponse{ |
||||||
|
EventsImported: 0, // TODO: Track count
|
||||||
|
EventsSkipped: 0, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) Export(req *orlydbv1.ExportRequest, stream orlydbv1.DatabaseService_ExportServer) error { |
||||||
|
pr, pw := io.Pipe() |
||||||
|
|
||||||
|
// Goroutine to export to pipe
|
||||||
|
go func() { |
||||||
|
defer pw.Close() |
||||||
|
s.db.Export(stream.Context(), pw, req.Pubkeys...) |
||||||
|
}() |
||||||
|
|
||||||
|
// Read from pipe and send to stream
|
||||||
|
buf := make([]byte, 64*1024) // 64KB chunks
|
||||||
|
for { |
||||||
|
n, err := pr.Read(buf) |
||||||
|
if err == io.EOF { |
||||||
|
return nil |
||||||
|
} |
||||||
|
if err != nil { |
||||||
|
return status.Errorf(codes.Internal, "export failed: %v", err) |
||||||
|
} |
||||||
|
if err := stream.Send(&orlydbv1.ExportChunk{Data: buf[:n]}); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) ImportEventsFromStrings(ctx context.Context, req *orlydbv1.ImportEventsFromStringsRequest) (*orlydbv1.ImportResponse, error) { |
||||||
|
// Note: We can't pass policy manager over gRPC, so we pass nil
|
||||||
|
if err := s.db.ImportEventsFromStrings(ctx, req.EventJsons, nil); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "import events from strings failed: %v", err) |
||||||
|
} |
||||||
|
return &orlydbv1.ImportResponse{ |
||||||
|
EventsImported: int64(len(req.EventJsons)), |
||||||
|
}, nil |
||||||
|
} |
||||||
|
|
||||||
|
// === Relay Identity ===
|
||||||
|
|
||||||
|
func (s *DatabaseService) GetRelayIdentitySecret(ctx context.Context, req *orlydbv1.Empty) (*orlydbv1.GetRelayIdentitySecretResponse, error) { |
||||||
|
secret, err := s.db.GetRelayIdentitySecret() |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "get relay identity secret failed: %v", err) |
||||||
|
} |
||||||
|
return &orlydbv1.GetRelayIdentitySecretResponse{SecretKey: secret}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) SetRelayIdentitySecret(ctx context.Context, req *orlydbv1.SetRelayIdentitySecretRequest) (*orlydbv1.Empty, error) { |
||||||
|
if err := s.db.SetRelayIdentitySecret(req.SecretKey); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "set relay identity secret failed: %v", err) |
||||||
|
} |
||||||
|
return &orlydbv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) GetOrCreateRelayIdentitySecret(ctx context.Context, req *orlydbv1.Empty) (*orlydbv1.GetRelayIdentitySecretResponse, error) { |
||||||
|
secret, err := s.db.GetOrCreateRelayIdentitySecret() |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "get or create relay identity secret failed: %v", err) |
||||||
|
} |
||||||
|
return &orlydbv1.GetRelayIdentitySecretResponse{SecretKey: secret}, nil |
||||||
|
} |
||||||
|
|
||||||
|
// === Markers ===
|
||||||
|
|
||||||
|
func (s *DatabaseService) SetMarker(ctx context.Context, req *orlydbv1.SetMarkerRequest) (*orlydbv1.Empty, error) { |
||||||
|
if err := s.db.SetMarker(req.Key, req.Value); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "set marker failed: %v", err) |
||||||
|
} |
||||||
|
return &orlydbv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) GetMarker(ctx context.Context, req *orlydbv1.GetMarkerRequest) (*orlydbv1.GetMarkerResponse, error) { |
||||||
|
value, err := s.db.GetMarker(req.Key) |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "get marker failed: %v", err) |
||||||
|
} |
||||||
|
return &orlydbv1.GetMarkerResponse{ |
||||||
|
Value: value, |
||||||
|
Found: value != nil, |
||||||
|
}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) HasMarker(ctx context.Context, req *orlydbv1.HasMarkerRequest) (*orlydbv1.HasMarkerResponse, error) { |
||||||
|
exists := s.db.HasMarker(req.Key) |
||||||
|
return &orlydbv1.HasMarkerResponse{Exists: exists}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) DeleteMarker(ctx context.Context, req *orlydbv1.DeleteMarkerRequest) (*orlydbv1.Empty, error) { |
||||||
|
if err := s.db.DeleteMarker(req.Key); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "delete marker failed: %v", err) |
||||||
|
} |
||||||
|
return &orlydbv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
// === Subscriptions ===
|
||||||
|
|
||||||
|
func (s *DatabaseService) GetSubscription(ctx context.Context, req *orlydbv1.GetSubscriptionRequest) (*orlydbv1.Subscription, error) { |
||||||
|
sub, err := s.db.GetSubscription(req.Pubkey) |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "get subscription failed: %v", err) |
||||||
|
} |
||||||
|
return orlydbv1.SubscriptionToProto(sub, req.Pubkey), nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) IsSubscriptionActive(ctx context.Context, req *orlydbv1.IsSubscriptionActiveRequest) (*orlydbv1.IsSubscriptionActiveResponse, error) { |
||||||
|
active, err := s.db.IsSubscriptionActive(req.Pubkey) |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "is subscription active failed: %v", err) |
||||||
|
} |
||||||
|
return &orlydbv1.IsSubscriptionActiveResponse{Active: active}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) ExtendSubscription(ctx context.Context, req *orlydbv1.ExtendSubscriptionRequest) (*orlydbv1.Empty, error) { |
||||||
|
if err := s.db.ExtendSubscription(req.Pubkey, int(req.Days)); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "extend subscription failed: %v", err) |
||||||
|
} |
||||||
|
return &orlydbv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) RecordPayment(ctx context.Context, req *orlydbv1.RecordPaymentRequest) (*orlydbv1.Empty, error) { |
||||||
|
if err := s.db.RecordPayment(req.Pubkey, req.Amount, req.Invoice, req.Preimage); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "record payment failed: %v", err) |
||||||
|
} |
||||||
|
return &orlydbv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) GetPaymentHistory(ctx context.Context, req *orlydbv1.GetPaymentHistoryRequest) (*orlydbv1.PaymentList, error) { |
||||||
|
payments, err := s.db.GetPaymentHistory(req.Pubkey) |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "get payment history failed: %v", err) |
||||||
|
} |
||||||
|
return orlydbv1.PaymentListToProto(payments), nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) ExtendBlossomSubscription(ctx context.Context, req *orlydbv1.ExtendBlossomSubscriptionRequest) (*orlydbv1.Empty, error) { |
||||||
|
if err := s.db.ExtendBlossomSubscription(req.Pubkey, req.Tier, req.StorageMb, int(req.DaysExtended)); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "extend blossom subscription failed: %v", err) |
||||||
|
} |
||||||
|
return &orlydbv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) GetBlossomStorageQuota(ctx context.Context, req *orlydbv1.GetBlossomStorageQuotaRequest) (*orlydbv1.GetBlossomStorageQuotaResponse, error) { |
||||||
|
quota, err := s.db.GetBlossomStorageQuota(req.Pubkey) |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "get blossom storage quota failed: %v", err) |
||||||
|
} |
||||||
|
return &orlydbv1.GetBlossomStorageQuotaResponse{QuotaMb: quota}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) IsFirstTimeUser(ctx context.Context, req *orlydbv1.IsFirstTimeUserRequest) (*orlydbv1.IsFirstTimeUserResponse, error) { |
||||||
|
firstTime, err := s.db.IsFirstTimeUser(req.Pubkey) |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "is first time user failed: %v", err) |
||||||
|
} |
||||||
|
return &orlydbv1.IsFirstTimeUserResponse{FirstTime: firstTime}, nil |
||||||
|
} |
||||||
|
|
||||||
|
// === NIP-43 ===
|
||||||
|
|
||||||
|
func (s *DatabaseService) AddNIP43Member(ctx context.Context, req *orlydbv1.AddNIP43MemberRequest) (*orlydbv1.Empty, error) { |
||||||
|
if err := s.db.AddNIP43Member(req.Pubkey, req.InviteCode); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "add NIP-43 member failed: %v", err) |
||||||
|
} |
||||||
|
return &orlydbv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) RemoveNIP43Member(ctx context.Context, req *orlydbv1.RemoveNIP43MemberRequest) (*orlydbv1.Empty, error) { |
||||||
|
if err := s.db.RemoveNIP43Member(req.Pubkey); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "remove NIP-43 member failed: %v", err) |
||||||
|
} |
||||||
|
return &orlydbv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) IsNIP43Member(ctx context.Context, req *orlydbv1.IsNIP43MemberRequest) (*orlydbv1.IsNIP43MemberResponse, error) { |
||||||
|
isMember, err := s.db.IsNIP43Member(req.Pubkey) |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "is NIP-43 member failed: %v", err) |
||||||
|
} |
||||||
|
return &orlydbv1.IsNIP43MemberResponse{IsMember: isMember}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) GetNIP43Membership(ctx context.Context, req *orlydbv1.GetNIP43MembershipRequest) (*orlydbv1.NIP43Membership, error) { |
||||||
|
membership, err := s.db.GetNIP43Membership(req.Pubkey) |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "get NIP-43 membership failed: %v", err) |
||||||
|
} |
||||||
|
return orlydbv1.NIP43MembershipToProto(membership), nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) GetAllNIP43Members(ctx context.Context, req *orlydbv1.Empty) (*orlydbv1.PubkeyList, error) { |
||||||
|
members, err := s.db.GetAllNIP43Members() |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "get all NIP-43 members failed: %v", err) |
||||||
|
} |
||||||
|
return &orlydbv1.PubkeyList{Pubkeys: members}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) StoreInviteCode(ctx context.Context, req *orlydbv1.StoreInviteCodeRequest) (*orlydbv1.Empty, error) { |
||||||
|
expiresAt := orlydbv1.TimeFromUnix(req.ExpiresAt) |
||||||
|
if err := s.db.StoreInviteCode(req.Code, expiresAt); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "store invite code failed: %v", err) |
||||||
|
} |
||||||
|
return &orlydbv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) ValidateInviteCode(ctx context.Context, req *orlydbv1.ValidateInviteCodeRequest) (*orlydbv1.ValidateInviteCodeResponse, error) { |
||||||
|
valid, err := s.db.ValidateInviteCode(req.Code) |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "validate invite code failed: %v", err) |
||||||
|
} |
||||||
|
return &orlydbv1.ValidateInviteCodeResponse{Valid: valid}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) DeleteInviteCode(ctx context.Context, req *orlydbv1.DeleteInviteCodeRequest) (*orlydbv1.Empty, error) { |
||||||
|
if err := s.db.DeleteInviteCode(req.Code); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "delete invite code failed: %v", err) |
||||||
|
} |
||||||
|
return &orlydbv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) PublishNIP43MembershipEvent(ctx context.Context, req *orlydbv1.PublishNIP43MembershipEventRequest) (*orlydbv1.Empty, error) { |
||||||
|
if err := s.db.PublishNIP43MembershipEvent(int(req.Kind), req.Pubkey); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "publish NIP-43 membership event failed: %v", err) |
||||||
|
} |
||||||
|
return &orlydbv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
// === Query Cache ===
|
||||||
|
|
||||||
|
func (s *DatabaseService) GetCachedJSON(ctx context.Context, req *orlydbv1.GetCachedJSONRequest) (*orlydbv1.GetCachedJSONResponse, error) { |
||||||
|
f := orlydbv1.ProtoToFilter(req.Filter) |
||||||
|
jsonItems, found := s.db.GetCachedJSON(f) |
||||||
|
return &orlydbv1.GetCachedJSONResponse{ |
||||||
|
JsonItems: jsonItems, |
||||||
|
Found: found, |
||||||
|
}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) CacheMarshaledJSON(ctx context.Context, req *orlydbv1.CacheMarshaledJSONRequest) (*orlydbv1.Empty, error) { |
||||||
|
f := orlydbv1.ProtoToFilter(req.Filter) |
||||||
|
s.db.CacheMarshaledJSON(f, req.JsonItems) |
||||||
|
return &orlydbv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) GetCachedEvents(ctx context.Context, req *orlydbv1.GetCachedEventsRequest) (*orlydbv1.GetCachedEventsResponse, error) { |
||||||
|
f := orlydbv1.ProtoToFilter(req.Filter) |
||||||
|
events, found := s.db.GetCachedEvents(f) |
||||||
|
return &orlydbv1.GetCachedEventsResponse{ |
||||||
|
Events: orlydbv1.EventsToProto(events), |
||||||
|
Found: found, |
||||||
|
}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) CacheEvents(ctx context.Context, req *orlydbv1.CacheEventsRequest) (*orlydbv1.Empty, error) { |
||||||
|
f := orlydbv1.ProtoToFilter(req.Filter) |
||||||
|
events := orlydbv1.ProtoToEvents(req.Events) |
||||||
|
s.db.CacheEvents(f, events) |
||||||
|
return &orlydbv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) InvalidateQueryCache(ctx context.Context, req *orlydbv1.Empty) (*orlydbv1.Empty, error) { |
||||||
|
s.db.InvalidateQueryCache() |
||||||
|
return &orlydbv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
// === Access Tracking ===
|
||||||
|
|
||||||
|
func (s *DatabaseService) RecordEventAccess(ctx context.Context, req *orlydbv1.RecordEventAccessRequest) (*orlydbv1.Empty, error) { |
||||||
|
if err := s.db.RecordEventAccess(req.Serial, req.ConnectionId); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "record event access failed: %v", err) |
||||||
|
} |
||||||
|
return &orlydbv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) GetEventAccessInfo(ctx context.Context, req *orlydbv1.GetEventAccessInfoRequest) (*orlydbv1.GetEventAccessInfoResponse, error) { |
||||||
|
lastAccess, accessCount, err := s.db.GetEventAccessInfo(req.Serial) |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "get event access info failed: %v", err) |
||||||
|
} |
||||||
|
return &orlydbv1.GetEventAccessInfoResponse{ |
||||||
|
LastAccess: lastAccess, |
||||||
|
AccessCount: accessCount, |
||||||
|
}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) GetLeastAccessedEvents(ctx context.Context, req *orlydbv1.GetLeastAccessedEventsRequest) (*orlydbv1.SerialList, error) { |
||||||
|
serials, err := s.db.GetLeastAccessedEvents(int(req.Limit), req.MinAgeSec) |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "get least accessed events failed: %v", err) |
||||||
|
} |
||||||
|
return &orlydbv1.SerialList{Serials: serials}, nil |
||||||
|
} |
||||||
|
|
||||||
|
// === Utility ===
|
||||||
|
|
||||||
|
func (s *DatabaseService) EventIdsBySerial(ctx context.Context, req *orlydbv1.EventIdsBySerialRequest) (*orlydbv1.EventIdsBySerialResponse, error) { |
||||||
|
eventIds, err := s.db.EventIdsBySerial(req.Start, int(req.Count)) |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "event ids by serial failed: %v", err) |
||||||
|
} |
||||||
|
return &orlydbv1.EventIdsBySerialResponse{EventIds: eventIds}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) RunMigrations(ctx context.Context, req *orlydbv1.Empty) (*orlydbv1.Empty, error) { |
||||||
|
s.db.RunMigrations() |
||||||
|
return &orlydbv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
// === Blob Storage (Blossom) ===
|
||||||
|
|
||||||
|
func (s *DatabaseService) SaveBlob(ctx context.Context, req *orlydbv1.SaveBlobRequest) (*orlydbv1.Empty, error) { |
||||||
|
if err := s.db.SaveBlob(req.Sha256Hash, req.Data, req.Pubkey, req.MimeType, req.Extension); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "save blob failed: %v", err) |
||||||
|
} |
||||||
|
return &orlydbv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) GetBlob(ctx context.Context, req *orlydbv1.GetBlobRequest) (*orlydbv1.GetBlobResponse, error) { |
||||||
|
data, metadata, err := s.db.GetBlob(req.Sha256Hash) |
||||||
|
if err != nil { |
||||||
|
// Return not found as a response, not an error
|
||||||
|
return &orlydbv1.GetBlobResponse{Found: false}, nil |
||||||
|
} |
||||||
|
return &orlydbv1.GetBlobResponse{ |
||||||
|
Found: true, |
||||||
|
Data: data, |
||||||
|
Metadata: orlydbv1.BlobMetadataToProto(metadata), |
||||||
|
}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) HasBlob(ctx context.Context, req *orlydbv1.HasBlobRequest) (*orlydbv1.HasBlobResponse, error) { |
||||||
|
exists, err := s.db.HasBlob(req.Sha256Hash) |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "has blob failed: %v", err) |
||||||
|
} |
||||||
|
return &orlydbv1.HasBlobResponse{Exists: exists}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) DeleteBlob(ctx context.Context, req *orlydbv1.DeleteBlobRequest) (*orlydbv1.Empty, error) { |
||||||
|
if err := s.db.DeleteBlob(req.Sha256Hash, req.Pubkey); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "delete blob failed: %v", err) |
||||||
|
} |
||||||
|
return &orlydbv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) ListBlobs(ctx context.Context, req *orlydbv1.ListBlobsRequest) (*orlydbv1.ListBlobsResponse, error) { |
||||||
|
descriptors, err := s.db.ListBlobs(req.Pubkey, req.Since, req.Until) |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "list blobs failed: %v", err) |
||||||
|
} |
||||||
|
return &orlydbv1.ListBlobsResponse{ |
||||||
|
Descriptors: orlydbv1.BlobDescriptorListToProto(descriptors), |
||||||
|
}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) GetBlobMetadata(ctx context.Context, req *orlydbv1.GetBlobMetadataRequest) (*orlydbv1.BlobMetadata, error) { |
||||||
|
metadata, err := s.db.GetBlobMetadata(req.Sha256Hash) |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.NotFound, "blob metadata not found: %v", err) |
||||||
|
} |
||||||
|
return orlydbv1.BlobMetadataToProto(metadata), nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) GetTotalBlobStorageUsed(ctx context.Context, req *orlydbv1.GetTotalBlobStorageUsedRequest) (*orlydbv1.GetTotalBlobStorageUsedResponse, error) { |
||||||
|
totalMB, err := s.db.GetTotalBlobStorageUsed(req.Pubkey) |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "get total blob storage used failed: %v", err) |
||||||
|
} |
||||||
|
return &orlydbv1.GetTotalBlobStorageUsedResponse{TotalMb: totalMB}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) SaveBlobReport(ctx context.Context, req *orlydbv1.SaveBlobReportRequest) (*orlydbv1.Empty, error) { |
||||||
|
if err := s.db.SaveBlobReport(req.Sha256Hash, req.ReportData); err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "save blob report failed: %v", err) |
||||||
|
} |
||||||
|
return &orlydbv1.Empty{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) ListAllBlobUserStats(ctx context.Context, req *orlydbv1.Empty) (*orlydbv1.ListAllBlobUserStatsResponse, error) { |
||||||
|
stats, err := s.db.ListAllBlobUserStats() |
||||||
|
if err != nil { |
||||||
|
return nil, status.Errorf(codes.Internal, "list all blob user stats failed: %v", err) |
||||||
|
} |
||||||
|
return &orlydbv1.ListAllBlobUserStatsResponse{ |
||||||
|
Stats: orlydbv1.UserBlobStatsListToProto(stats), |
||||||
|
}, nil |
||||||
|
} |
||||||
|
|
||||||
|
// === Helper Methods ===
|
||||||
|
|
||||||
|
// streamEvents is a helper to stream events in batches.
|
||||||
|
type eventStreamer interface { |
||||||
|
Send(*orlydbv1.EventBatch) error |
||||||
|
Context() context.Context |
||||||
|
} |
||||||
|
|
||||||
|
func (s *DatabaseService) streamEvents(events []*orlydbv1.Event, stream eventStreamer) error { |
||||||
|
batchSize := s.cfg.StreamBatchSize |
||||||
|
if batchSize == 0 { |
||||||
|
batchSize = 100 |
||||||
|
} |
||||||
|
|
||||||
|
for i := 0; i < len(events); i += batchSize { |
||||||
|
end := i + batchSize |
||||||
|
if end > len(events) { |
||||||
|
end = len(events) |
||||||
|
} |
||||||
|
|
||||||
|
batch := &orlydbv1.EventBatch{ |
||||||
|
Events: events[i:end], |
||||||
|
} |
||||||
|
|
||||||
|
if err := stream.Send(batch); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
@ -0,0 +1,250 @@ |
|||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.36.11
|
||||||
|
// protoc (unknown)
|
||||||
|
// source: orlyacl/v1/acl.proto
|
||||||
|
|
||||||
|
package orlyaclv1 |
||||||
|
|
||||||
|
import ( |
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect" |
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl" |
||||||
|
reflect "reflect" |
||||||
|
unsafe "unsafe" |
||||||
|
) |
||||||
|
|
||||||
|
const ( |
||||||
|
// Verify that this generated code is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) |
||||||
|
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) |
||||||
|
) |
||||||
|
|
||||||
|
var File_orlyacl_v1_acl_proto protoreflect.FileDescriptor |
||||||
|
|
||||||
|
const file_orlyacl_v1_acl_proto_rawDesc = "" + |
||||||
|
"\n" + |
||||||
|
"\x14orlyacl/v1/acl.proto\x12\n" + |
||||||
|
"orlyacl.v1\x1a\x16orlyacl/v1/types.proto2\x91\x19\n" + |
||||||
|
"\n" + |
||||||
|
"ACLService\x12Q\n" + |
||||||
|
"\x0eGetAccessLevel\x12\x1e.orlyacl.v1.AccessLevelRequest\x1a\x1f.orlyacl.v1.AccessLevelResponse\x12N\n" + |
||||||
|
"\vCheckPolicy\x12\x1e.orlyacl.v1.PolicyCheckRequest\x1a\x1f.orlyacl.v1.PolicyCheckResponse\x12<\n" + |
||||||
|
"\n" + |
||||||
|
"GetACLInfo\x12\x11.orlyacl.v1.Empty\x1a\x1b.orlyacl.v1.ACLInfoResponse\x126\n" + |
||||||
|
"\aGetMode\x12\x11.orlyacl.v1.Empty\x1a\x18.orlyacl.v1.ModeResponse\x125\n" + |
||||||
|
"\x05Ready\x12\x11.orlyacl.v1.Empty\x1a\x19.orlyacl.v1.ReadyResponse\x12W\n" + |
||||||
|
"\x10GetThrottleDelay\x12 .orlyacl.v1.ThrottleDelayRequest\x1a!.orlyacl.v1.ThrottleDelayResponse\x12<\n" + |
||||||
|
"\tAddFollow\x12\x1c.orlyacl.v1.AddFollowRequest\x1a\x11.orlyacl.v1.Empty\x12L\n" + |
||||||
|
"\x12GetFollowedPubkeys\x12\x11.orlyacl.v1.Empty\x1a#.orlyacl.v1.FollowedPubkeysResponse\x12D\n" + |
||||||
|
"\x0eGetAdminRelays\x12\x11.orlyacl.v1.Empty\x1a\x1f.orlyacl.v1.AdminRelaysResponse\x12<\n" + |
||||||
|
"\tBanPubkey\x12\x1c.orlyacl.v1.BanPubkeyRequest\x1a\x11.orlyacl.v1.Empty\x12;\n" + |
||||||
|
"\vUnbanPubkey\x12\x19.orlyacl.v1.PubkeyRequest\x1a\x11.orlyacl.v1.Empty\x12M\n" + |
||||||
|
"\x11ListBannedPubkeys\x12\x11.orlyacl.v1.Empty\x1a%.orlyacl.v1.ListBannedPubkeysResponse\x12@\n" + |
||||||
|
"\vAllowPubkey\x12\x1e.orlyacl.v1.AllowPubkeyRequest\x1a\x11.orlyacl.v1.Empty\x12>\n" + |
||||||
|
"\x0eDisallowPubkey\x12\x19.orlyacl.v1.PubkeyRequest\x1a\x11.orlyacl.v1.Empty\x12O\n" + |
||||||
|
"\x12ListAllowedPubkeys\x12\x11.orlyacl.v1.Empty\x1a&.orlyacl.v1.ListAllowedPubkeysResponse\x12:\n" + |
||||||
|
"\bBanEvent\x12\x1b.orlyacl.v1.BanEventRequest\x1a\x11.orlyacl.v1.Empty\x129\n" + |
||||||
|
"\n" + |
||||||
|
"UnbanEvent\x12\x18.orlyacl.v1.EventRequest\x1a\x11.orlyacl.v1.Empty\x12K\n" + |
||||||
|
"\x10ListBannedEvents\x12\x11.orlyacl.v1.Empty\x1a$.orlyacl.v1.ListBannedEventsResponse\x12<\n" + |
||||||
|
"\n" + |
||||||
|
"AllowEvent\x12\x1b.orlyacl.v1.BanEventRequest\x1a\x11.orlyacl.v1.Empty\x12<\n" + |
||||||
|
"\rDisallowEvent\x12\x18.orlyacl.v1.EventRequest\x1a\x11.orlyacl.v1.Empty\x12M\n" + |
||||||
|
"\x11ListAllowedEvents\x12\x11.orlyacl.v1.Empty\x1a%.orlyacl.v1.ListAllowedEventsResponse\x128\n" + |
||||||
|
"\aBlockIP\x12\x1a.orlyacl.v1.BlockIPRequest\x1a\x11.orlyacl.v1.Empty\x125\n" + |
||||||
|
"\tUnblockIP\x12\x15.orlyacl.v1.IPRequest\x1a\x11.orlyacl.v1.Empty\x12G\n" + |
||||||
|
"\x0eListBlockedIPs\x12\x11.orlyacl.v1.Empty\x1a\".orlyacl.v1.ListBlockedIPsResponse\x12<\n" + |
||||||
|
"\tAllowKind\x12\x1c.orlyacl.v1.AllowKindRequest\x1a\x11.orlyacl.v1.Empty\x12:\n" + |
||||||
|
"\fDisallowKind\x12\x17.orlyacl.v1.KindRequest\x1a\x11.orlyacl.v1.Empty\x12K\n" + |
||||||
|
"\x10ListAllowedKinds\x12\x11.orlyacl.v1.Empty\x1a$.orlyacl.v1.ListAllowedKindsResponse\x12J\n" + |
||||||
|
"\x10UpdatePeerAdmins\x12#.orlyacl.v1.UpdatePeerAdminsRequest\x1a\x11.orlyacl.v1.Empty\x12@\n" + |
||||||
|
"\vTrustPubkey\x12\x1e.orlyacl.v1.TrustPubkeyRequest\x1a\x11.orlyacl.v1.Empty\x12=\n" + |
||||||
|
"\rUntrustPubkey\x12\x19.orlyacl.v1.PubkeyRequest\x1a\x11.orlyacl.v1.Empty\x12O\n" + |
||||||
|
"\x12ListTrustedPubkeys\x12\x11.orlyacl.v1.Empty\x1a&.orlyacl.v1.ListTrustedPubkeysResponse\x12H\n" + |
||||||
|
"\x0fBlacklistPubkey\x12\".orlyacl.v1.BlacklistPubkeyRequest\x1a\x11.orlyacl.v1.Empty\x12A\n" + |
||||||
|
"\x11UnblacklistPubkey\x12\x19.orlyacl.v1.PubkeyRequest\x1a\x11.orlyacl.v1.Empty\x12W\n" + |
||||||
|
"\x16ListBlacklistedPubkeys\x12\x11.orlyacl.v1.Empty\x1a*.orlyacl.v1.ListBlacklistedPubkeysResponse\x12:\n" + |
||||||
|
"\bMarkSpam\x12\x1b.orlyacl.v1.MarkSpamRequest\x1a\x11.orlyacl.v1.Empty\x129\n" + |
||||||
|
"\n" + |
||||||
|
"UnmarkSpam\x12\x18.orlyacl.v1.EventRequest\x1a\x11.orlyacl.v1.Empty\x12G\n" + |
||||||
|
"\x0eListSpamEvents\x12\x11.orlyacl.v1.Empty\x1a\".orlyacl.v1.ListSpamEventsResponse\x12W\n" + |
||||||
|
"\x0eRateLimitCheck\x12!.orlyacl.v1.RateLimitCheckRequest\x1a\".orlyacl.v1.RateLimitCheckResponse\x12G\n" + |
||||||
|
"\x12ProcessConfigEvent\x12\x1e.orlyacl.v1.ConfigEventRequest\x1a\x11.orlyacl.v1.Empty\x12B\n" + |
||||||
|
"\x11GetCuratingConfig\x12\x11.orlyacl.v1.Empty\x1a\x1a.orlyacl.v1.CuratingConfig\x12C\n" + |
||||||
|
"\x14IsCuratingConfigured\x12\x11.orlyacl.v1.Empty\x1a\x18.orlyacl.v1.BoolResponse\x12a\n" + |
||||||
|
"\x15ListUnclassifiedUsers\x12\x1d.orlyacl.v1.PaginationRequest\x1a).orlyacl.v1.ListUnclassifiedUsersResponse\x12`\n" + |
||||||
|
"\x12GetEventsForPubkey\x12%.orlyacl.v1.GetEventsForPubkeyRequest\x1a#.orlyacl.v1.EventsForPubkeyResponse\x12b\n" + |
||||||
|
"\x15DeleteEventsForPubkey\x12(.orlyacl.v1.DeleteEventsForPubkeyRequest\x1a\x1f.orlyacl.v1.DeleteCountResponse\x12C\n" + |
||||||
|
"\x0eScanAllPubkeys\x12\x11.orlyacl.v1.Empty\x1a\x1e.orlyacl.v1.ScanResultResponseB.Z,next.orly.dev/pkg/proto/orlyacl/v1;orlyaclv1b\x06proto3" |
||||||
|
|
||||||
|
var file_orlyacl_v1_acl_proto_goTypes = []any{ |
||||||
|
(*AccessLevelRequest)(nil), // 0: orlyacl.v1.AccessLevelRequest
|
||||||
|
(*PolicyCheckRequest)(nil), // 1: orlyacl.v1.PolicyCheckRequest
|
||||||
|
(*Empty)(nil), // 2: orlyacl.v1.Empty
|
||||||
|
(*ThrottleDelayRequest)(nil), // 3: orlyacl.v1.ThrottleDelayRequest
|
||||||
|
(*AddFollowRequest)(nil), // 4: orlyacl.v1.AddFollowRequest
|
||||||
|
(*BanPubkeyRequest)(nil), // 5: orlyacl.v1.BanPubkeyRequest
|
||||||
|
(*PubkeyRequest)(nil), // 6: orlyacl.v1.PubkeyRequest
|
||||||
|
(*AllowPubkeyRequest)(nil), // 7: orlyacl.v1.AllowPubkeyRequest
|
||||||
|
(*BanEventRequest)(nil), // 8: orlyacl.v1.BanEventRequest
|
||||||
|
(*EventRequest)(nil), // 9: orlyacl.v1.EventRequest
|
||||||
|
(*BlockIPRequest)(nil), // 10: orlyacl.v1.BlockIPRequest
|
||||||
|
(*IPRequest)(nil), // 11: orlyacl.v1.IPRequest
|
||||||
|
(*AllowKindRequest)(nil), // 12: orlyacl.v1.AllowKindRequest
|
||||||
|
(*KindRequest)(nil), // 13: orlyacl.v1.KindRequest
|
||||||
|
(*UpdatePeerAdminsRequest)(nil), // 14: orlyacl.v1.UpdatePeerAdminsRequest
|
||||||
|
(*TrustPubkeyRequest)(nil), // 15: orlyacl.v1.TrustPubkeyRequest
|
||||||
|
(*BlacklistPubkeyRequest)(nil), // 16: orlyacl.v1.BlacklistPubkeyRequest
|
||||||
|
(*MarkSpamRequest)(nil), // 17: orlyacl.v1.MarkSpamRequest
|
||||||
|
(*RateLimitCheckRequest)(nil), // 18: orlyacl.v1.RateLimitCheckRequest
|
||||||
|
(*ConfigEventRequest)(nil), // 19: orlyacl.v1.ConfigEventRequest
|
||||||
|
(*PaginationRequest)(nil), // 20: orlyacl.v1.PaginationRequest
|
||||||
|
(*GetEventsForPubkeyRequest)(nil), // 21: orlyacl.v1.GetEventsForPubkeyRequest
|
||||||
|
(*DeleteEventsForPubkeyRequest)(nil), // 22: orlyacl.v1.DeleteEventsForPubkeyRequest
|
||||||
|
(*AccessLevelResponse)(nil), // 23: orlyacl.v1.AccessLevelResponse
|
||||||
|
(*PolicyCheckResponse)(nil), // 24: orlyacl.v1.PolicyCheckResponse
|
||||||
|
(*ACLInfoResponse)(nil), // 25: orlyacl.v1.ACLInfoResponse
|
||||||
|
(*ModeResponse)(nil), // 26: orlyacl.v1.ModeResponse
|
||||||
|
(*ReadyResponse)(nil), // 27: orlyacl.v1.ReadyResponse
|
||||||
|
(*ThrottleDelayResponse)(nil), // 28: orlyacl.v1.ThrottleDelayResponse
|
||||||
|
(*FollowedPubkeysResponse)(nil), // 29: orlyacl.v1.FollowedPubkeysResponse
|
||||||
|
(*AdminRelaysResponse)(nil), // 30: orlyacl.v1.AdminRelaysResponse
|
||||||
|
(*ListBannedPubkeysResponse)(nil), // 31: orlyacl.v1.ListBannedPubkeysResponse
|
||||||
|
(*ListAllowedPubkeysResponse)(nil), // 32: orlyacl.v1.ListAllowedPubkeysResponse
|
||||||
|
(*ListBannedEventsResponse)(nil), // 33: orlyacl.v1.ListBannedEventsResponse
|
||||||
|
(*ListAllowedEventsResponse)(nil), // 34: orlyacl.v1.ListAllowedEventsResponse
|
||||||
|
(*ListBlockedIPsResponse)(nil), // 35: orlyacl.v1.ListBlockedIPsResponse
|
||||||
|
(*ListAllowedKindsResponse)(nil), // 36: orlyacl.v1.ListAllowedKindsResponse
|
||||||
|
(*ListTrustedPubkeysResponse)(nil), // 37: orlyacl.v1.ListTrustedPubkeysResponse
|
||||||
|
(*ListBlacklistedPubkeysResponse)(nil), // 38: orlyacl.v1.ListBlacklistedPubkeysResponse
|
||||||
|
(*ListSpamEventsResponse)(nil), // 39: orlyacl.v1.ListSpamEventsResponse
|
||||||
|
(*RateLimitCheckResponse)(nil), // 40: orlyacl.v1.RateLimitCheckResponse
|
||||||
|
(*CuratingConfig)(nil), // 41: orlyacl.v1.CuratingConfig
|
||||||
|
(*BoolResponse)(nil), // 42: orlyacl.v1.BoolResponse
|
||||||
|
(*ListUnclassifiedUsersResponse)(nil), // 43: orlyacl.v1.ListUnclassifiedUsersResponse
|
||||||
|
(*EventsForPubkeyResponse)(nil), // 44: orlyacl.v1.EventsForPubkeyResponse
|
||||||
|
(*DeleteCountResponse)(nil), // 45: orlyacl.v1.DeleteCountResponse
|
||||||
|
(*ScanResultResponse)(nil), // 46: orlyacl.v1.ScanResultResponse
|
||||||
|
} |
||||||
|
var file_orlyacl_v1_acl_proto_depIdxs = []int32{ |
||||||
|
0, // 0: orlyacl.v1.ACLService.GetAccessLevel:input_type -> orlyacl.v1.AccessLevelRequest
|
||||||
|
1, // 1: orlyacl.v1.ACLService.CheckPolicy:input_type -> orlyacl.v1.PolicyCheckRequest
|
||||||
|
2, // 2: orlyacl.v1.ACLService.GetACLInfo:input_type -> orlyacl.v1.Empty
|
||||||
|
2, // 3: orlyacl.v1.ACLService.GetMode:input_type -> orlyacl.v1.Empty
|
||||||
|
2, // 4: orlyacl.v1.ACLService.Ready:input_type -> orlyacl.v1.Empty
|
||||||
|
3, // 5: orlyacl.v1.ACLService.GetThrottleDelay:input_type -> orlyacl.v1.ThrottleDelayRequest
|
||||||
|
4, // 6: orlyacl.v1.ACLService.AddFollow:input_type -> orlyacl.v1.AddFollowRequest
|
||||||
|
2, // 7: orlyacl.v1.ACLService.GetFollowedPubkeys:input_type -> orlyacl.v1.Empty
|
||||||
|
2, // 8: orlyacl.v1.ACLService.GetAdminRelays:input_type -> orlyacl.v1.Empty
|
||||||
|
5, // 9: orlyacl.v1.ACLService.BanPubkey:input_type -> orlyacl.v1.BanPubkeyRequest
|
||||||
|
6, // 10: orlyacl.v1.ACLService.UnbanPubkey:input_type -> orlyacl.v1.PubkeyRequest
|
||||||
|
2, // 11: orlyacl.v1.ACLService.ListBannedPubkeys:input_type -> orlyacl.v1.Empty
|
||||||
|
7, // 12: orlyacl.v1.ACLService.AllowPubkey:input_type -> orlyacl.v1.AllowPubkeyRequest
|
||||||
|
6, // 13: orlyacl.v1.ACLService.DisallowPubkey:input_type -> orlyacl.v1.PubkeyRequest
|
||||||
|
2, // 14: orlyacl.v1.ACLService.ListAllowedPubkeys:input_type -> orlyacl.v1.Empty
|
||||||
|
8, // 15: orlyacl.v1.ACLService.BanEvent:input_type -> orlyacl.v1.BanEventRequest
|
||||||
|
9, // 16: orlyacl.v1.ACLService.UnbanEvent:input_type -> orlyacl.v1.EventRequest
|
||||||
|
2, // 17: orlyacl.v1.ACLService.ListBannedEvents:input_type -> orlyacl.v1.Empty
|
||||||
|
8, // 18: orlyacl.v1.ACLService.AllowEvent:input_type -> orlyacl.v1.BanEventRequest
|
||||||
|
9, // 19: orlyacl.v1.ACLService.DisallowEvent:input_type -> orlyacl.v1.EventRequest
|
||||||
|
2, // 20: orlyacl.v1.ACLService.ListAllowedEvents:input_type -> orlyacl.v1.Empty
|
||||||
|
10, // 21: orlyacl.v1.ACLService.BlockIP:input_type -> orlyacl.v1.BlockIPRequest
|
||||||
|
11, // 22: orlyacl.v1.ACLService.UnblockIP:input_type -> orlyacl.v1.IPRequest
|
||||||
|
2, // 23: orlyacl.v1.ACLService.ListBlockedIPs:input_type -> orlyacl.v1.Empty
|
||||||
|
12, // 24: orlyacl.v1.ACLService.AllowKind:input_type -> orlyacl.v1.AllowKindRequest
|
||||||
|
13, // 25: orlyacl.v1.ACLService.DisallowKind:input_type -> orlyacl.v1.KindRequest
|
||||||
|
2, // 26: orlyacl.v1.ACLService.ListAllowedKinds:input_type -> orlyacl.v1.Empty
|
||||||
|
14, // 27: orlyacl.v1.ACLService.UpdatePeerAdmins:input_type -> orlyacl.v1.UpdatePeerAdminsRequest
|
||||||
|
15, // 28: orlyacl.v1.ACLService.TrustPubkey:input_type -> orlyacl.v1.TrustPubkeyRequest
|
||||||
|
6, // 29: orlyacl.v1.ACLService.UntrustPubkey:input_type -> orlyacl.v1.PubkeyRequest
|
||||||
|
2, // 30: orlyacl.v1.ACLService.ListTrustedPubkeys:input_type -> orlyacl.v1.Empty
|
||||||
|
16, // 31: orlyacl.v1.ACLService.BlacklistPubkey:input_type -> orlyacl.v1.BlacklistPubkeyRequest
|
||||||
|
6, // 32: orlyacl.v1.ACLService.UnblacklistPubkey:input_type -> orlyacl.v1.PubkeyRequest
|
||||||
|
2, // 33: orlyacl.v1.ACLService.ListBlacklistedPubkeys:input_type -> orlyacl.v1.Empty
|
||||||
|
17, // 34: orlyacl.v1.ACLService.MarkSpam:input_type -> orlyacl.v1.MarkSpamRequest
|
||||||
|
9, // 35: orlyacl.v1.ACLService.UnmarkSpam:input_type -> orlyacl.v1.EventRequest
|
||||||
|
2, // 36: orlyacl.v1.ACLService.ListSpamEvents:input_type -> orlyacl.v1.Empty
|
||||||
|
18, // 37: orlyacl.v1.ACLService.RateLimitCheck:input_type -> orlyacl.v1.RateLimitCheckRequest
|
||||||
|
19, // 38: orlyacl.v1.ACLService.ProcessConfigEvent:input_type -> orlyacl.v1.ConfigEventRequest
|
||||||
|
2, // 39: orlyacl.v1.ACLService.GetCuratingConfig:input_type -> orlyacl.v1.Empty
|
||||||
|
2, // 40: orlyacl.v1.ACLService.IsCuratingConfigured:input_type -> orlyacl.v1.Empty
|
||||||
|
20, // 41: orlyacl.v1.ACLService.ListUnclassifiedUsers:input_type -> orlyacl.v1.PaginationRequest
|
||||||
|
21, // 42: orlyacl.v1.ACLService.GetEventsForPubkey:input_type -> orlyacl.v1.GetEventsForPubkeyRequest
|
||||||
|
22, // 43: orlyacl.v1.ACLService.DeleteEventsForPubkey:input_type -> orlyacl.v1.DeleteEventsForPubkeyRequest
|
||||||
|
2, // 44: orlyacl.v1.ACLService.ScanAllPubkeys:input_type -> orlyacl.v1.Empty
|
||||||
|
23, // 45: orlyacl.v1.ACLService.GetAccessLevel:output_type -> orlyacl.v1.AccessLevelResponse
|
||||||
|
24, // 46: orlyacl.v1.ACLService.CheckPolicy:output_type -> orlyacl.v1.PolicyCheckResponse
|
||||||
|
25, // 47: orlyacl.v1.ACLService.GetACLInfo:output_type -> orlyacl.v1.ACLInfoResponse
|
||||||
|
26, // 48: orlyacl.v1.ACLService.GetMode:output_type -> orlyacl.v1.ModeResponse
|
||||||
|
27, // 49: orlyacl.v1.ACLService.Ready:output_type -> orlyacl.v1.ReadyResponse
|
||||||
|
28, // 50: orlyacl.v1.ACLService.GetThrottleDelay:output_type -> orlyacl.v1.ThrottleDelayResponse
|
||||||
|
2, // 51: orlyacl.v1.ACLService.AddFollow:output_type -> orlyacl.v1.Empty
|
||||||
|
29, // 52: orlyacl.v1.ACLService.GetFollowedPubkeys:output_type -> orlyacl.v1.FollowedPubkeysResponse
|
||||||
|
30, // 53: orlyacl.v1.ACLService.GetAdminRelays:output_type -> orlyacl.v1.AdminRelaysResponse
|
||||||
|
2, // 54: orlyacl.v1.ACLService.BanPubkey:output_type -> orlyacl.v1.Empty
|
||||||
|
2, // 55: orlyacl.v1.ACLService.UnbanPubkey:output_type -> orlyacl.v1.Empty
|
||||||
|
31, // 56: orlyacl.v1.ACLService.ListBannedPubkeys:output_type -> orlyacl.v1.ListBannedPubkeysResponse
|
||||||
|
2, // 57: orlyacl.v1.ACLService.AllowPubkey:output_type -> orlyacl.v1.Empty
|
||||||
|
2, // 58: orlyacl.v1.ACLService.DisallowPubkey:output_type -> orlyacl.v1.Empty
|
||||||
|
32, // 59: orlyacl.v1.ACLService.ListAllowedPubkeys:output_type -> orlyacl.v1.ListAllowedPubkeysResponse
|
||||||
|
2, // 60: orlyacl.v1.ACLService.BanEvent:output_type -> orlyacl.v1.Empty
|
||||||
|
2, // 61: orlyacl.v1.ACLService.UnbanEvent:output_type -> orlyacl.v1.Empty
|
||||||
|
33, // 62: orlyacl.v1.ACLService.ListBannedEvents:output_type -> orlyacl.v1.ListBannedEventsResponse
|
||||||
|
2, // 63: orlyacl.v1.ACLService.AllowEvent:output_type -> orlyacl.v1.Empty
|
||||||
|
2, // 64: orlyacl.v1.ACLService.DisallowEvent:output_type -> orlyacl.v1.Empty
|
||||||
|
34, // 65: orlyacl.v1.ACLService.ListAllowedEvents:output_type -> orlyacl.v1.ListAllowedEventsResponse
|
||||||
|
2, // 66: orlyacl.v1.ACLService.BlockIP:output_type -> orlyacl.v1.Empty
|
||||||
|
2, // 67: orlyacl.v1.ACLService.UnblockIP:output_type -> orlyacl.v1.Empty
|
||||||
|
35, // 68: orlyacl.v1.ACLService.ListBlockedIPs:output_type -> orlyacl.v1.ListBlockedIPsResponse
|
||||||
|
2, // 69: orlyacl.v1.ACLService.AllowKind:output_type -> orlyacl.v1.Empty
|
||||||
|
2, // 70: orlyacl.v1.ACLService.DisallowKind:output_type -> orlyacl.v1.Empty
|
||||||
|
36, // 71: orlyacl.v1.ACLService.ListAllowedKinds:output_type -> orlyacl.v1.ListAllowedKindsResponse
|
||||||
|
2, // 72: orlyacl.v1.ACLService.UpdatePeerAdmins:output_type -> orlyacl.v1.Empty
|
||||||
|
2, // 73: orlyacl.v1.ACLService.TrustPubkey:output_type -> orlyacl.v1.Empty
|
||||||
|
2, // 74: orlyacl.v1.ACLService.UntrustPubkey:output_type -> orlyacl.v1.Empty
|
||||||
|
37, // 75: orlyacl.v1.ACLService.ListTrustedPubkeys:output_type -> orlyacl.v1.ListTrustedPubkeysResponse
|
||||||
|
2, // 76: orlyacl.v1.ACLService.BlacklistPubkey:output_type -> orlyacl.v1.Empty
|
||||||
|
2, // 77: orlyacl.v1.ACLService.UnblacklistPubkey:output_type -> orlyacl.v1.Empty
|
||||||
|
38, // 78: orlyacl.v1.ACLService.ListBlacklistedPubkeys:output_type -> orlyacl.v1.ListBlacklistedPubkeysResponse
|
||||||
|
2, // 79: orlyacl.v1.ACLService.MarkSpam:output_type -> orlyacl.v1.Empty
|
||||||
|
2, // 80: orlyacl.v1.ACLService.UnmarkSpam:output_type -> orlyacl.v1.Empty
|
||||||
|
39, // 81: orlyacl.v1.ACLService.ListSpamEvents:output_type -> orlyacl.v1.ListSpamEventsResponse
|
||||||
|
40, // 82: orlyacl.v1.ACLService.RateLimitCheck:output_type -> orlyacl.v1.RateLimitCheckResponse
|
||||||
|
2, // 83: orlyacl.v1.ACLService.ProcessConfigEvent:output_type -> orlyacl.v1.Empty
|
||||||
|
41, // 84: orlyacl.v1.ACLService.GetCuratingConfig:output_type -> orlyacl.v1.CuratingConfig
|
||||||
|
42, // 85: orlyacl.v1.ACLService.IsCuratingConfigured:output_type -> orlyacl.v1.BoolResponse
|
||||||
|
43, // 86: orlyacl.v1.ACLService.ListUnclassifiedUsers:output_type -> orlyacl.v1.ListUnclassifiedUsersResponse
|
||||||
|
44, // 87: orlyacl.v1.ACLService.GetEventsForPubkey:output_type -> orlyacl.v1.EventsForPubkeyResponse
|
||||||
|
45, // 88: orlyacl.v1.ACLService.DeleteEventsForPubkey:output_type -> orlyacl.v1.DeleteCountResponse
|
||||||
|
46, // 89: orlyacl.v1.ACLService.ScanAllPubkeys:output_type -> orlyacl.v1.ScanResultResponse
|
||||||
|
45, // [45:90] is the sub-list for method output_type
|
||||||
|
0, // [0:45] is the sub-list for method input_type
|
||||||
|
0, // [0:0] is the sub-list for extension type_name
|
||||||
|
0, // [0:0] is the sub-list for extension extendee
|
||||||
|
0, // [0:0] is the sub-list for field type_name
|
||||||
|
} |
||||||
|
|
||||||
|
func init() { file_orlyacl_v1_acl_proto_init() } |
||||||
|
func file_orlyacl_v1_acl_proto_init() { |
||||||
|
if File_orlyacl_v1_acl_proto != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
file_orlyacl_v1_types_proto_init() |
||||||
|
type x struct{} |
||||||
|
out := protoimpl.TypeBuilder{ |
||||||
|
File: protoimpl.DescBuilder{ |
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), |
||||||
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_orlyacl_v1_acl_proto_rawDesc), len(file_orlyacl_v1_acl_proto_rawDesc)), |
||||||
|
NumEnums: 0, |
||||||
|
NumMessages: 0, |
||||||
|
NumExtensions: 0, |
||||||
|
NumServices: 1, |
||||||
|
}, |
||||||
|
GoTypes: file_orlyacl_v1_acl_proto_goTypes, |
||||||
|
DependencyIndexes: file_orlyacl_v1_acl_proto_depIdxs, |
||||||
|
}.Build() |
||||||
|
File_orlyacl_v1_acl_proto = out.File |
||||||
|
file_orlyacl_v1_acl_proto_goTypes = nil |
||||||
|
file_orlyacl_v1_acl_proto_depIdxs = nil |
||||||
|
} |
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,151 @@ |
|||||||
|
syntax = "proto3"; |
||||||
|
package orlyacl.v1; |
||||||
|
option go_package = "next.orly.dev/pkg/proto/orlyacl/v1;orlyaclv1"; |
||||||
|
|
||||||
|
import "orlyacl/v1/types.proto"; |
||||||
|
|
||||||
|
// ACLService provides access control operations for the Nostr relay |
||||||
|
service ACLService { |
||||||
|
// Core ACL interface methods |
||||||
|
|
||||||
|
// GetAccessLevel returns the access level for a pubkey from an IP address |
||||||
|
rpc GetAccessLevel(AccessLevelRequest) returns (AccessLevelResponse); |
||||||
|
|
||||||
|
// CheckPolicy checks if an event passes policy checks |
||||||
|
rpc CheckPolicy(PolicyCheckRequest) returns (PolicyCheckResponse); |
||||||
|
|
||||||
|
// GetACLInfo returns information about the active ACL mode |
||||||
|
rpc GetACLInfo(Empty) returns (ACLInfoResponse); |
||||||
|
|
||||||
|
// GetMode returns the current ACL mode (none/follows/managed/curating) |
||||||
|
rpc GetMode(Empty) returns (ModeResponse); |
||||||
|
|
||||||
|
// Ready checks if the ACL service is ready |
||||||
|
rpc Ready(Empty) returns (ReadyResponse); |
||||||
|
|
||||||
|
// Follows ACL methods |
||||||
|
|
||||||
|
// GetThrottleDelay returns the progressive throttle delay for a pubkey |
||||||
|
rpc GetThrottleDelay(ThrottleDelayRequest) returns (ThrottleDelayResponse); |
||||||
|
|
||||||
|
// AddFollow adds a pubkey to the followed list |
||||||
|
rpc AddFollow(AddFollowRequest) returns (Empty); |
||||||
|
|
||||||
|
// GetFollowedPubkeys returns all followed pubkeys |
||||||
|
rpc GetFollowedPubkeys(Empty) returns (FollowedPubkeysResponse); |
||||||
|
|
||||||
|
// GetAdminRelays returns the relay URLs from admin kind 10002 events |
||||||
|
rpc GetAdminRelays(Empty) returns (AdminRelaysResponse); |
||||||
|
|
||||||
|
// Managed ACL methods |
||||||
|
|
||||||
|
// BanPubkey adds a pubkey to the ban list |
||||||
|
rpc BanPubkey(BanPubkeyRequest) returns (Empty); |
||||||
|
|
||||||
|
// UnbanPubkey removes a pubkey from the ban list |
||||||
|
rpc UnbanPubkey(PubkeyRequest) returns (Empty); |
||||||
|
|
||||||
|
// ListBannedPubkeys returns all banned pubkeys |
||||||
|
rpc ListBannedPubkeys(Empty) returns (ListBannedPubkeysResponse); |
||||||
|
|
||||||
|
// AllowPubkey adds a pubkey to the allow list |
||||||
|
rpc AllowPubkey(AllowPubkeyRequest) returns (Empty); |
||||||
|
|
||||||
|
// DisallowPubkey removes a pubkey from the allow list |
||||||
|
rpc DisallowPubkey(PubkeyRequest) returns (Empty); |
||||||
|
|
||||||
|
// ListAllowedPubkeys returns all explicitly allowed pubkeys |
||||||
|
rpc ListAllowedPubkeys(Empty) returns (ListAllowedPubkeysResponse); |
||||||
|
|
||||||
|
// BanEvent adds an event to the ban list |
||||||
|
rpc BanEvent(BanEventRequest) returns (Empty); |
||||||
|
|
||||||
|
// UnbanEvent removes an event from the ban list |
||||||
|
rpc UnbanEvent(EventRequest) returns (Empty); |
||||||
|
|
||||||
|
// ListBannedEvents returns all banned events |
||||||
|
rpc ListBannedEvents(Empty) returns (ListBannedEventsResponse); |
||||||
|
|
||||||
|
// AllowEvent adds an event to the allow list |
||||||
|
rpc AllowEvent(BanEventRequest) returns (Empty); |
||||||
|
|
||||||
|
// DisallowEvent removes an event from the allow list |
||||||
|
rpc DisallowEvent(EventRequest) returns (Empty); |
||||||
|
|
||||||
|
// ListAllowedEvents returns all explicitly allowed events |
||||||
|
rpc ListAllowedEvents(Empty) returns (ListAllowedEventsResponse); |
||||||
|
|
||||||
|
// BlockIP adds an IP to the block list |
||||||
|
rpc BlockIP(BlockIPRequest) returns (Empty); |
||||||
|
|
||||||
|
// UnblockIP removes an IP from the block list |
||||||
|
rpc UnblockIP(IPRequest) returns (Empty); |
||||||
|
|
||||||
|
// ListBlockedIPs returns all blocked IPs |
||||||
|
rpc ListBlockedIPs(Empty) returns (ListBlockedIPsResponse); |
||||||
|
|
||||||
|
// AllowKind adds a kind to the allow list |
||||||
|
rpc AllowKind(AllowKindRequest) returns (Empty); |
||||||
|
|
||||||
|
// DisallowKind removes a kind from the allow list |
||||||
|
rpc DisallowKind(KindRequest) returns (Empty); |
||||||
|
|
||||||
|
// ListAllowedKinds returns all allowed kinds |
||||||
|
rpc ListAllowedKinds(Empty) returns (ListAllowedKindsResponse); |
||||||
|
|
||||||
|
// UpdatePeerAdmins updates the peer relay identity pubkeys |
||||||
|
rpc UpdatePeerAdmins(UpdatePeerAdminsRequest) returns (Empty); |
||||||
|
|
||||||
|
// Curating ACL methods |
||||||
|
|
||||||
|
// TrustPubkey adds a pubkey to the trusted list |
||||||
|
rpc TrustPubkey(TrustPubkeyRequest) returns (Empty); |
||||||
|
|
||||||
|
// UntrustPubkey removes a pubkey from the trusted list |
||||||
|
rpc UntrustPubkey(PubkeyRequest) returns (Empty); |
||||||
|
|
||||||
|
// ListTrustedPubkeys returns all trusted pubkeys |
||||||
|
rpc ListTrustedPubkeys(Empty) returns (ListTrustedPubkeysResponse); |
||||||
|
|
||||||
|
// BlacklistPubkey adds a pubkey to the blacklist |
||||||
|
rpc BlacklistPubkey(BlacklistPubkeyRequest) returns (Empty); |
||||||
|
|
||||||
|
// UnblacklistPubkey removes a pubkey from the blacklist |
||||||
|
rpc UnblacklistPubkey(PubkeyRequest) returns (Empty); |
||||||
|
|
||||||
|
// ListBlacklistedPubkeys returns all blacklisted pubkeys |
||||||
|
rpc ListBlacklistedPubkeys(Empty) returns (ListBlacklistedPubkeysResponse); |
||||||
|
|
||||||
|
// MarkSpam marks an event as spam |
||||||
|
rpc MarkSpam(MarkSpamRequest) returns (Empty); |
||||||
|
|
||||||
|
// UnmarkSpam removes the spam flag from an event |
||||||
|
rpc UnmarkSpam(EventRequest) returns (Empty); |
||||||
|
|
||||||
|
// ListSpamEvents returns all spam-flagged events |
||||||
|
rpc ListSpamEvents(Empty) returns (ListSpamEventsResponse); |
||||||
|
|
||||||
|
// RateLimitCheck checks if a pubkey/IP can publish (rate limiting) |
||||||
|
rpc RateLimitCheck(RateLimitCheckRequest) returns (RateLimitCheckResponse); |
||||||
|
|
||||||
|
// ProcessConfigEvent processes a curating config event (kind 30078) |
||||||
|
rpc ProcessConfigEvent(ConfigEventRequest) returns (Empty); |
||||||
|
|
||||||
|
// GetCuratingConfig returns the current curating configuration |
||||||
|
rpc GetCuratingConfig(Empty) returns (CuratingConfig); |
||||||
|
|
||||||
|
// IsCuratingConfigured checks if curating mode is configured |
||||||
|
rpc IsCuratingConfigured(Empty) returns (BoolResponse); |
||||||
|
|
||||||
|
// ListUnclassifiedUsers returns users who are not trusted or blacklisted |
||||||
|
rpc ListUnclassifiedUsers(PaginationRequest) returns (ListUnclassifiedUsersResponse); |
||||||
|
|
||||||
|
// GetEventsForPubkey returns events for a specific pubkey |
||||||
|
rpc GetEventsForPubkey(GetEventsForPubkeyRequest) returns (EventsForPubkeyResponse); |
||||||
|
|
||||||
|
// DeleteEventsForPubkey deletes all events for a pubkey |
||||||
|
rpc DeleteEventsForPubkey(DeleteEventsForPubkeyRequest) returns (DeleteCountResponse); |
||||||
|
|
||||||
|
// ScanAllPubkeys scans and indexes all pubkeys in the database |
||||||
|
rpc ScanAllPubkeys(Empty) returns (ScanResultResponse); |
||||||
|
} |
||||||
@ -0,0 +1,292 @@ |
|||||||
|
syntax = "proto3"; |
||||||
|
package orlyacl.v1; |
||||||
|
option go_package = "next.orly.dev/pkg/proto/orlyacl/v1;orlyaclv1"; |
||||||
|
|
||||||
|
import "orlydb/v1/types.proto"; |
||||||
|
|
||||||
|
// Empty is used for requests/responses with no data |
||||||
|
message Empty {} |
||||||
|
|
||||||
|
// Core ACL Messages |
||||||
|
|
||||||
|
message AccessLevelRequest { |
||||||
|
bytes pubkey = 1; |
||||||
|
string address = 2; |
||||||
|
} |
||||||
|
|
||||||
|
message AccessLevelResponse { |
||||||
|
string level = 1; // none/read/write/admin/owner/banned/blocked |
||||||
|
} |
||||||
|
|
||||||
|
message ACLInfoResponse { |
||||||
|
string name = 1; |
||||||
|
string description = 2; |
||||||
|
string documentation = 3; |
||||||
|
} |
||||||
|
|
||||||
|
message ModeResponse { |
||||||
|
string mode = 1; // none/follows/managed/curating |
||||||
|
} |
||||||
|
|
||||||
|
message ReadyResponse { |
||||||
|
bool ready = 1; |
||||||
|
} |
||||||
|
|
||||||
|
message BoolResponse { |
||||||
|
bool value = 1; |
||||||
|
} |
||||||
|
|
||||||
|
message PolicyCheckRequest { |
||||||
|
orlydb.v1.Event event = 1; |
||||||
|
} |
||||||
|
|
||||||
|
message PolicyCheckResponse { |
||||||
|
bool allowed = 1; |
||||||
|
string error = 2; |
||||||
|
} |
||||||
|
|
||||||
|
// Follows ACL Messages |
||||||
|
|
||||||
|
message ThrottleDelayRequest { |
||||||
|
bytes pubkey = 1; |
||||||
|
string ip = 2; |
||||||
|
} |
||||||
|
|
||||||
|
message ThrottleDelayResponse { |
||||||
|
int64 delay_ms = 1; |
||||||
|
} |
||||||
|
|
||||||
|
message AddFollowRequest { |
||||||
|
bytes pubkey = 1; |
||||||
|
} |
||||||
|
|
||||||
|
message FollowedPubkeysResponse { |
||||||
|
repeated bytes pubkeys = 1; |
||||||
|
} |
||||||
|
|
||||||
|
message AdminRelaysResponse { |
||||||
|
repeated string urls = 1; |
||||||
|
} |
||||||
|
|
||||||
|
// Managed ACL Messages |
||||||
|
|
||||||
|
message PubkeyRequest { |
||||||
|
string pubkey = 1; |
||||||
|
} |
||||||
|
|
||||||
|
message IPRequest { |
||||||
|
string ip = 1; |
||||||
|
} |
||||||
|
|
||||||
|
message BanPubkeyRequest { |
||||||
|
string pubkey = 1; |
||||||
|
string reason = 2; |
||||||
|
} |
||||||
|
|
||||||
|
message AllowPubkeyRequest { |
||||||
|
string pubkey = 1; |
||||||
|
string reason = 2; |
||||||
|
} |
||||||
|
|
||||||
|
message BannedPubkey { |
||||||
|
string pubkey = 1; |
||||||
|
string reason = 2; |
||||||
|
int64 added = 3; |
||||||
|
} |
||||||
|
|
||||||
|
message AllowedPubkey { |
||||||
|
string pubkey = 1; |
||||||
|
string reason = 2; |
||||||
|
int64 added = 3; |
||||||
|
} |
||||||
|
|
||||||
|
message BanEventRequest { |
||||||
|
string event_id = 1; |
||||||
|
string reason = 2; |
||||||
|
} |
||||||
|
|
||||||
|
message EventRequest { |
||||||
|
string event_id = 1; |
||||||
|
} |
||||||
|
|
||||||
|
message BannedEvent { |
||||||
|
string event_id = 1; |
||||||
|
string reason = 2; |
||||||
|
int64 added = 3; |
||||||
|
} |
||||||
|
|
||||||
|
message AllowedEvent { |
||||||
|
string event_id = 1; |
||||||
|
string reason = 2; |
||||||
|
int64 added = 3; |
||||||
|
} |
||||||
|
|
||||||
|
message BlockIPRequest { |
||||||
|
string ip = 1; |
||||||
|
string reason = 2; |
||||||
|
} |
||||||
|
|
||||||
|
message BlockedIP { |
||||||
|
string ip = 1; |
||||||
|
string reason = 2; |
||||||
|
int64 added = 3; |
||||||
|
} |
||||||
|
|
||||||
|
message AllowKindRequest { |
||||||
|
int32 kind = 1; |
||||||
|
} |
||||||
|
|
||||||
|
message KindRequest { |
||||||
|
int32 kind = 1; |
||||||
|
} |
||||||
|
|
||||||
|
message UpdatePeerAdminsRequest { |
||||||
|
repeated bytes peer_pubkeys = 1; |
||||||
|
} |
||||||
|
|
||||||
|
message ListBannedPubkeysResponse { |
||||||
|
repeated BannedPubkey pubkeys = 1; |
||||||
|
} |
||||||
|
|
||||||
|
message ListAllowedPubkeysResponse { |
||||||
|
repeated AllowedPubkey pubkeys = 1; |
||||||
|
} |
||||||
|
|
||||||
|
message ListBannedEventsResponse { |
||||||
|
repeated BannedEvent events = 1; |
||||||
|
} |
||||||
|
|
||||||
|
message ListAllowedEventsResponse { |
||||||
|
repeated AllowedEvent events = 1; |
||||||
|
} |
||||||
|
|
||||||
|
message ListBlockedIPsResponse { |
||||||
|
repeated BlockedIP ips = 1; |
||||||
|
} |
||||||
|
|
||||||
|
message ListAllowedKindsResponse { |
||||||
|
repeated int32 kinds = 1; |
||||||
|
} |
||||||
|
|
||||||
|
// Curating ACL Messages |
||||||
|
|
||||||
|
message TrustPubkeyRequest { |
||||||
|
string pubkey = 1; |
||||||
|
string note = 2; |
||||||
|
} |
||||||
|
|
||||||
|
message BlacklistPubkeyRequest { |
||||||
|
string pubkey = 1; |
||||||
|
string reason = 2; |
||||||
|
} |
||||||
|
|
||||||
|
message TrustedPubkey { |
||||||
|
string pubkey = 1; |
||||||
|
string note = 2; |
||||||
|
int64 added = 3; |
||||||
|
} |
||||||
|
|
||||||
|
message BlacklistedPubkey { |
||||||
|
string pubkey = 1; |
||||||
|
string reason = 2; |
||||||
|
int64 added = 3; |
||||||
|
} |
||||||
|
|
||||||
|
message MarkSpamRequest { |
||||||
|
string event_id = 1; |
||||||
|
string pubkey = 2; |
||||||
|
string reason = 3; |
||||||
|
} |
||||||
|
|
||||||
|
message SpamEvent { |
||||||
|
string event_id = 1; |
||||||
|
string pubkey = 2; |
||||||
|
string reason = 3; |
||||||
|
int64 added = 4; |
||||||
|
} |
||||||
|
|
||||||
|
message RateLimitCheckRequest { |
||||||
|
string pubkey = 1; |
||||||
|
string ip = 2; |
||||||
|
} |
||||||
|
|
||||||
|
message RateLimitCheckResponse { |
||||||
|
bool allowed = 1; |
||||||
|
string message = 2; |
||||||
|
} |
||||||
|
|
||||||
|
message ConfigEventRequest { |
||||||
|
orlydb.v1.Event event = 1; |
||||||
|
} |
||||||
|
|
||||||
|
message CuratingConfig { |
||||||
|
string config_event_id = 1; |
||||||
|
string config_pubkey = 2; |
||||||
|
int64 configured_at = 3; |
||||||
|
int32 daily_limit = 4; |
||||||
|
int32 ip_daily_limit = 5; |
||||||
|
int32 first_ban_hours = 6; |
||||||
|
int32 second_ban_hours = 7; |
||||||
|
repeated string kind_categories = 8; |
||||||
|
repeated string allowed_ranges = 9; |
||||||
|
repeated int32 allowed_kinds = 10; |
||||||
|
} |
||||||
|
|
||||||
|
message UnclassifiedUser { |
||||||
|
string pubkey = 1; |
||||||
|
int32 event_count = 2; |
||||||
|
string first_seen = 3; |
||||||
|
} |
||||||
|
|
||||||
|
message PaginationRequest { |
||||||
|
int32 limit = 1; |
||||||
|
int32 offset = 2; |
||||||
|
} |
||||||
|
|
||||||
|
message ListTrustedPubkeysResponse { |
||||||
|
repeated TrustedPubkey pubkeys = 1; |
||||||
|
} |
||||||
|
|
||||||
|
message ListBlacklistedPubkeysResponse { |
||||||
|
repeated BlacklistedPubkey pubkeys = 1; |
||||||
|
} |
||||||
|
|
||||||
|
message ListSpamEventsResponse { |
||||||
|
repeated SpamEvent events = 1; |
||||||
|
} |
||||||
|
|
||||||
|
message ListUnclassifiedUsersResponse { |
||||||
|
repeated UnclassifiedUser users = 1; |
||||||
|
int32 total = 2; |
||||||
|
} |
||||||
|
|
||||||
|
message GetEventsForPubkeyRequest { |
||||||
|
string pubkey = 1; |
||||||
|
int32 limit = 2; |
||||||
|
int32 offset = 3; |
||||||
|
} |
||||||
|
|
||||||
|
message EventSummary { |
||||||
|
string id = 1; |
||||||
|
uint32 kind = 2; |
||||||
|
bytes content = 3; |
||||||
|
int64 created_at = 4; |
||||||
|
} |
||||||
|
|
||||||
|
message EventsForPubkeyResponse { |
||||||
|
repeated EventSummary events = 1; |
||||||
|
int32 total = 2; |
||||||
|
} |
||||||
|
|
||||||
|
message DeleteEventsForPubkeyRequest { |
||||||
|
string pubkey = 1; |
||||||
|
} |
||||||
|
|
||||||
|
message DeleteCountResponse { |
||||||
|
int32 count = 1; |
||||||
|
} |
||||||
|
|
||||||
|
message ScanResultResponse { |
||||||
|
int32 total_pubkeys = 1; |
||||||
|
int32 total_events = 2; |
||||||
|
} |
||||||
Loading…
Reference in new issue