First prototype
Change-Id: Ieceb55636bda133466609471f25508d0ae83c52c
diff --git a/cmd/server/server.go b/cmd/server/server.go
new file mode 100644
index 0000000..53ab224
--- /dev/null
+++ b/cmd/server/server.go
@@ -0,0 +1,141 @@
+package main
+
+import (
+ "context"
+ "database/sql"
+ "flag"
+ "fmt"
+ "log"
+ "net"
+ "time"
+
+ _ "github.com/go-sql-driver/mysql"
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/codes"
+ "google.golang.org/grpc/reflection"
+ "google.golang.org/grpc/status"
+ "google.golang.org/protobuf/proto"
+
+ pb "gomodules.avm99963.com/twpt-server/api_proto"
+ db "gomodules.avm99963.com/twpt-server/internal/db"
+)
+
+var (
+ port = flag.Int("port", 10000, "The server port")
+ dbDsn = flag.String("db", "", "MySQL/MariaDB database data source name (DSN)") // https://github.com/go-sql-driver/mysql#dsn-data-source-name
+ dbConnMaxLifetime = flag.Int("dbConnMaxLifetime", 3*60, "Maximum amount of time a connection to the database may be reused in seconds.")
+ dbMaxOpenConns = flag.Int("dbMaxOpenConns", 5, "Maximum number of open connections to the database.")
+ dbMaxIdleConns = flag.Int("dbMaxIdleConns", *dbMaxOpenConns, "Maximum number of connections to the database in the idle connection pool.")
+)
+
+type killSwitchServiceServer struct {
+ pb.UnimplementedKillSwitchServiceServer
+ dbPool *sql.DB
+}
+
+func newKillSwitchServiceServer() *killSwitchServiceServer {
+ s := &killSwitchServiceServer{}
+ db, err := sql.Open("mysql", *dbDsn)
+ if err != nil {
+ log.Fatalf("unable to open database connection: %v", err)
+ }
+ db.SetConnMaxLifetime(time.Duration(*dbConnMaxLifetime) * time.Second)
+ db.SetMaxOpenConns(*dbMaxOpenConns)
+ db.SetMaxIdleConns(*dbMaxIdleConns)
+
+ ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
+ defer cancel()
+
+ if err := db.PingContext(ctx); err != nil {
+ log.Fatalf("unable to connect to database: %v", err)
+ }
+
+ s.dbPool = db
+ return s
+}
+
+func (s *killSwitchServiceServer) GetKillSwitchStatus(ctx context.Context, req *pb.GetKillSwitchStatusRequest) (*pb.GetKillSwitchStatusResponse, error) {
+ return nil, status.Errorf(codes.Unimplemented, "Unimplemented method.")
+}
+
+func (s *killSwitchServiceServer) GetKillSwitchOverview(ctx context.Context, req *pb.GetKillSwitchOverviewRequest) (*pb.GetKillSwitchOverviewResponse, error) {
+ return nil, status.Errorf(codes.Unimplemented, "Unimplemented method.")
+}
+
+func (s *killSwitchServiceServer) SyncFeatures(ctx context.Context, req *pb.SyncFeaturesRequest) (*pb.SyncFeaturesResponse, error) {
+ log.Println("Syncing features...")
+
+ for _, feature := range req.Features {
+ existingFeature, err := db.GetFeatureByCodename(s.dbPool, ctx, feature.Codename)
+ if err != nil {
+ return nil, status.Errorf(codes.Unavailable, err.Error())
+ }
+ // If the feature didn't exist in the db, add it. Otherwise, update it if applicable.
+ if existingFeature == nil {
+ if err := db.AddFeature(s.dbPool, ctx, feature); err != nil {
+ return nil, status.Error(codes.Unavailable, err.Error())
+ }
+ } else {
+ canonicalExistingFeature := *existingFeature
+ canonicalExistingFeature.Id = 0
+ if !proto.Equal(&canonicalExistingFeature, feature) {
+ if err := db.UpdateFeature(s.dbPool, ctx, existingFeature.Id, feature); err != nil {
+ return nil, status.Error(codes.Unavailable, err.Error())
+ }
+ }
+ }
+ }
+
+ res := &pb.SyncFeaturesResponse{}
+ return res, nil
+}
+
+func (s *killSwitchServiceServer) ListFeatures(ctx context.Context, req *pb.ListFeaturesRequest) (*pb.ListFeaturesResponse, error) {
+ features, err := db.ListFeatures(s.dbPool, ctx, req.WithDeprecatedFeatures)
+ if err != nil {
+ return nil, status.Errorf(codes.Unavailable, err.Error())
+ }
+ res := &pb.ListFeaturesResponse{
+ Features: features,
+ }
+ return res, nil
+}
+
+func (s *killSwitchServiceServer) EnableKillSwitch(ctx context.Context, req *pb.EnableKillSwitchRequest) (*pb.EnableKillSwitchResponse, error) {
+ return nil, status.Errorf(codes.Unimplemented, "Unimplemented method.")
+}
+
+func (s *killSwitchServiceServer) DisableKillSwitch(ctx context.Context, req *pb.DisableKillSwitchRequest) (*pb.DisableKillSwitchResponse, error) {
+ return nil, status.Errorf(codes.Unimplemented, "Unimplemented method.")
+}
+
+func (s *killSwitchServiceServer) ListAuthorizedUsers(ctx context.Context, req *pb.ListAuthorizedUsersRequest) (*pb.ListAuthorizedUsersResponse, error) {
+ return nil, status.Errorf(codes.Unimplemented, "Unimplemented method.")
+}
+
+func (s *killSwitchServiceServer) AddAuthorizedUser(ctx context.Context, req *pb.AddAuthorizedUserRequest) (*pb.AddAuthorizedUserResponse, error) {
+ return nil, status.Errorf(codes.Unimplemented, "Unimplemented method.")
+}
+
+func (s *killSwitchServiceServer) UpdateAuthorizedUser(ctx context.Context, req *pb.UpdateAuthorizedUserRequest) (*pb.UpdateAuthorizedUserResponse, error) {
+ return nil, status.Errorf(codes.Unimplemented, "Unimplemented method.")
+}
+
+func (s *killSwitchServiceServer) DeleteAuthorizedUser(ctx context.Context, req *pb.DeleteAuthorizedUserRequest) (*pb.DeleteAuthorizedUserResponse, error) {
+ return nil, status.Errorf(codes.Unimplemented, "Unimplemented method.")
+}
+
+func main() {
+ flag.Parse()
+
+ lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", *port))
+ if err != nil {
+ log.Fatalf("Failed to listen: %v", err)
+ }
+
+ grpcServer := grpc.NewServer()
+ pb.RegisterKillSwitchServiceServer(grpcServer, newKillSwitchServiceServer())
+ // Register reflection service on gRPC server.
+ reflection.Register(grpcServer)
+ grpcServer.Serve(lis)
+}