Files
tunnel-please-controller/server/server.go

149 lines
3.7 KiB
Go

package server
import (
"context"
"log"
mathrand "math/rand"
"net"
"strings"
"sync"
"time"
"git.fossy.my.id/bagas/tunnel-please-controller/db/sqlc/repository"
proto "git.fossy.my.id/bagas/tunnel-please-grpc/gen"
"github.com/google/uuid"
"google.golang.org/grpc"
"google.golang.org/grpc/health"
"google.golang.org/grpc/health/grpc_health_v1"
"google.golang.org/grpc/reflection"
"google.golang.org/protobuf/types/known/emptypb"
)
type Server struct {
Database *repository.Queries
Subscriber []chan *proto.Event
mu sync.RWMutex
proto.UnimplementedIdentityServer
proto.UnimplementedEventServiceServer
proto.UnimplementedSlugServer
}
func (s *Server) ChangeSlug(ctx context.Context, request *proto.ChangeSlugRequest) (*proto.ChangeSlugResponse, error) {
s.NotifyAllSubscriber(&proto.Event{
Type: proto.EventType_SLUG_CHANGE,
TimestampUnixMs: time.Now().Unix(),
Data: &proto.Event_DataEvent{DataEvent: &proto.SlugChangeEvent{
Old: request.GetOld(),
New: request.GetNew(),
}},
})
return &proto.ChangeSlugResponse{}, nil
}
func (s *Server) Subscribe(request *emptypb.Empty, g grpc.ServerStreamingServer[proto.Event]) error {
sr := make(chan *proto.Event)
s.AddSubscriberChan(sr)
defer s.RemoveSubscriberChan(sr)
for ev := range sr {
if err := g.Send(ev); err != nil {
return err
}
}
return nil
}
func (s *Server) NotifyAllSubscriber(event *proto.Event) {
for _, subs := range s.Subscriber {
subs <- event
}
}
func (s *Server) AddSubscriberChan(event chan *proto.Event) {
s.mu.Lock()
defer s.mu.Unlock()
s.Subscriber = append(s.Subscriber, event)
}
func (s *Server) RemoveSubscriberChan(ch chan *proto.Event) {
s.mu.Lock()
defer s.mu.Unlock()
if len(s.Subscriber) == 0 || ch == nil {
return
}
newSubs := s.Subscriber[:0]
for _, c := range s.Subscriber {
if c == ch {
continue
}
newSubs = append(newSubs, c)
}
s.Subscriber = newSubs
close(ch)
}
func (s *Server) Get(ctx context.Context, request *proto.IdentifierRequest) (*proto.IdentifierResponse, error) {
parse, err := uuid.Parse(request.GetId())
if err != nil {
return nil, err
}
data, err := s.Database.GetIdentifierById(ctx, parse)
if err != nil {
return nil, err
}
return &proto.IdentifierResponse{
Id: data.ID.String(),
Slug: data.Slug,
}, nil
}
func (s *Server) Create(ctx context.Context, request *emptypb.Empty) (*proto.IdentifierResponse, error) {
createIdentifier, err := s.Database.CreateIdentifier(ctx, GenerateRandomString(32))
if err != nil {
return nil, err
}
return &proto.IdentifierResponse{
Id: createIdentifier.ID.String(),
Slug: createIdentifier.Slug,
}, nil
}
func GenerateRandomString(length int) string {
const charset = "abcdefghijklmnopqrstuvwxyz"
seededRand := mathrand.New(mathrand.NewSource(time.Now().UnixNano() + int64(mathrand.Intn(9999))))
var result strings.Builder
for i := 0; i < length; i++ {
randomIndex := seededRand.Intn(len(charset))
result.WriteString(string(charset[randomIndex]))
}
return result.String()
}
func New(database *repository.Queries) *Server {
return &Server{Database: database, Subscriber: make([]chan *proto.Event, 0)}
}
func (s *Server) ListenAndServe(Addr string) error {
listener, err := net.Listen("tcp", Addr)
if err != nil {
return err
}
grpcServer := grpc.NewServer()
reflection.Register(grpcServer)
proto.RegisterIdentityServer(grpcServer, s)
proto.RegisterEventServiceServer(grpcServer, s)
proto.RegisterSlugServer(grpcServer, s)
healthServer := health.NewServer()
grpc_health_v1.RegisterHealthServer(grpcServer, healthServer)
healthServer.SetServingStatus("", grpc_health_v1.HealthCheckResponse_SERVING)
if err := grpcServer.Serve(listener); err != nil {
log.Fatalf("failed to serve: %v", err)
}
return nil
}