diff --git a/cmd/yans/main.go b/cmd/yans/main.go index 538d10d..e38143f 100644 --- a/cmd/yans/main.go +++ b/cmd/yans/main.go @@ -30,11 +30,11 @@ func main() { log.Printf("Starting %s...", common.ServerName) ns, err := server.NewNNTPServer(cfg) if err != nil { - log.Fatal(err) + log.Fatalf("Error occurred while starting the server: %s", err) } if err := ns.Start(); err != nil { - log.Fatal(err) + log.Fatalf("Error occurred while starting the server: %s", err) } log.Printf("%s has been successfully started!", common.ServerName) log.Printf("Version: %s", common.ServerVersion) diff --git a/config.sample.toml b/config.sample.toml new file mode 100644 index 0000000..fe3fe47 --- /dev/null +++ b/config.sample.toml @@ -0,0 +1,6 @@ +address = "localhost" +port = 1119 +backend_type = "sqlite" + +[sqlite] +path = "yans.db" \ No newline at end of file diff --git a/internal/migrations/001_init_schema.sql b/internal/backend/sqlite/migrations/001_init_schema.sql similarity index 100% rename from internal/migrations/001_init_schema.sql rename to internal/backend/sqlite/migrations/001_init_schema.sql diff --git a/internal/backend/sqlite/sqlite.go b/internal/backend/sqlite/sqlite.go new file mode 100644 index 0000000..fe98f21 --- /dev/null +++ b/internal/backend/sqlite/sqlite.go @@ -0,0 +1,47 @@ +package sqlite + +import ( + "embed" + "github.com/ChronosX88/yans/internal/config" + "github.com/ChronosX88/yans/internal/models" + "github.com/jmoiron/sqlx" + _ "github.com/mattn/go-sqlite3" + "github.com/pressly/goose/v3" +) + +//go:embed migrations/*.sql +var migrations embed.FS + +type SQLiteBackend struct { + db *sqlx.DB +} + +func NewSQLiteBackend(cfg config.SQLiteBackendConfig) (*SQLiteBackend, error) { + db, err := sqlx.Open("sqlite3", cfg.Path) + if err != nil { + return nil, err + } + goose.SetBaseFS(migrations) + + if err := goose.SetDialect("sqlite3"); err != nil { + return nil, err + } + + if err := goose.Up(db.DB, "migrations"); err != nil { + return nil, err + } + + return &SQLiteBackend{ + db: db, + }, nil +} + +func (sb *SQLiteBackend) ListGroups() ([]models.Group, error) { + var groups []models.Group + return groups, sb.db.Select(&groups, "SELECT * FROM groups") +} + +func (sb *SQLiteBackend) GetArticlesCount(g models.Group) (int, error) { + var count int + return count, sb.db.Select(&count, "SELECT COUNT(*) FROM articles_to_groups WHERE group_id = ?", g.ID) +} diff --git a/internal/backend/storage_backend.go b/internal/backend/storage_backend.go new file mode 100644 index 0000000..2b82015 --- /dev/null +++ b/internal/backend/storage_backend.go @@ -0,0 +1,12 @@ +package backend + +import "github.com/ChronosX88/yans/internal/models" + +const ( + SupportedBackendList = "sqlite" +) + +type StorageBackend interface { + ListGroups() ([]models.Group, error) + GetArticlesCount(g models.Group) (int, error) +} diff --git a/internal/config/config.go b/internal/config/config.go index 0a986b5..f9b5aea 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -5,9 +5,19 @@ import ( "os" ) +const ( + SQLiteBackendType = "sqlite" +) + type Config struct { - Port int - DatabasePath string + Address string `toml:"address"` + Port int `toml:"port"` + BackendType string `toml:"backend_type"` + SQLite SQLiteBackendConfig `toml:"sqlite"` +} + +type SQLiteBackendConfig struct { + Path string `toml:"path"` } func ParseConfig(path string) (Config, error) { diff --git a/internal/migrations.go b/internal/migrations.go deleted file mode 100644 index 7798b32..0000000 --- a/internal/migrations.go +++ /dev/null @@ -1,6 +0,0 @@ -package internal - -import "embed" - -//go:embed migrations/*.sql -var Migrations embed.FS diff --git a/internal/server/handler.go b/internal/server/handler.go index 8301d7d..788de1e 100644 --- a/internal/server/handler.go +++ b/internal/server/handler.go @@ -2,21 +2,20 @@ package server import ( "fmt" - "github.com/ChronosX88/yans/internal/models" + "github.com/ChronosX88/yans/internal/backend" "github.com/ChronosX88/yans/internal/protocol" - "github.com/jmoiron/sqlx" "strings" "time" ) type Handler struct { handlers map[string]func(s *Session, arguments []string) error - db *sqlx.DB + backend backend.StorageBackend } -func NewHandler(db *sqlx.DB) *Handler { +func NewHandler(b backend.StorageBackend) *Handler { h := &Handler{} - h.db = db + h.backend = b h.handlers = map[string]func(s *Session, arguments []string) error{ protocol.CommandCapabilities: h.handleCapabilities, protocol.CommandDate: h.handleDate, @@ -54,7 +53,7 @@ func (h *Handler) handleList(s *Session, arguments []string) error { fallthrough case "ACTIVE": { - groups, err := h.listGroups() + groups, err := h.backend.ListGroups() if err != nil { return err } @@ -66,7 +65,7 @@ func (h *Handler) handleList(s *Session, arguments []string) error { } case "NEWSGROUPS": { - groups, err := h.listGroups() + groups, err := h.backend.ListGroups() if err != nil { return err } @@ -117,14 +116,3 @@ func (h *Handler) Handle(s *Session, message string) error { } return handler(s, splittedMessage[1:]) } - -// TODO Refactor to "storage backend" entity -func (h *Handler) listGroups() ([]models.Group, error) { - var groups []models.Group - return groups, h.db.Select(&groups, "SELECT * FROM groups") -} - -func (h *Handler) getArticlesCount(g models.Group) (int, error) { - var count int - return count, h.db.Select(&count, "SELECT COUNT(*) FROM articles_to_groups WHERE group_id = ?", g.ID) -} diff --git a/internal/server/nntp_server.go b/internal/server/nntp_server.go index bc2f2a4..71673d6 100644 --- a/internal/server/nntp_server.go +++ b/internal/server/nntp_server.go @@ -3,14 +3,12 @@ package server import ( "context" "fmt" - "github.com/ChronosX88/yans/internal" + "github.com/ChronosX88/yans/internal/backend" + "github.com/ChronosX88/yans/internal/backend/sqlite" "github.com/ChronosX88/yans/internal/common" "github.com/ChronosX88/yans/internal/config" "github.com/ChronosX88/yans/internal/protocol" "github.com/google/uuid" - "github.com/jmoiron/sqlx" - _ "github.com/mattn/go-sqlite3" - "github.com/pressly/goose/v3" "log" "net" "sync" @@ -28,47 +26,61 @@ type NNTPServer struct { ctx context.Context cancelFunc context.CancelFunc - ln net.Listener - port int + ln net.Listener + cfg config.Config - db *sqlx.DB + backend backend.StorageBackend sessionPool map[string]*Session sessionPoolMutex sync.Mutex } func NewNNTPServer(cfg config.Config) (*NNTPServer, error) { - db, err := sqlx.Open("sqlite3", cfg.DatabasePath) + b, err := initBackend(cfg) if err != nil { return nil, err } - goose.SetBaseFS(internal.Migrations) - - if err := goose.SetDialect("sqlite3"); err != nil { - return nil, err - } - - if err := goose.Up(db.DB, "migrations"); err != nil { - return nil, err - } ctx, cancel := context.WithCancel(context.Background()) ns := &NNTPServer{ ctx: ctx, cancelFunc: cancel, - port: cfg.Port, - db: db, + cfg: cfg, + backend: b, sessionPool: map[string]*Session{}, } return ns, nil } +func initBackend(cfg config.Config) (backend.StorageBackend, error) { + var sb backend.StorageBackend + + switch cfg.BackendType { + case config.SQLiteBackendType: + { + sqliteBackend, err := sqlite.NewSQLiteBackend(cfg.SQLite) + if err != nil { + return nil, err + } + sb = sqliteBackend + } + default: + { + return nil, fmt.Errorf("invalid backend type, supported backends: %s", backend.SupportedBackendList) + } + } + return sb, nil +} + func (ns *NNTPServer) Start() error { - ln, err := net.Listen("tcp", fmt.Sprintf(":%d", ns.port)) + address := fmt.Sprintf("%s:%d", ns.cfg.Address, ns.cfg.Port) + ln, err := net.Listen("tcp", address) if err != nil { return err } + log.Printf("Listening on %s...", address) + go func(ctx context.Context) { for { select { @@ -84,7 +96,7 @@ func (ns *NNTPServer) Start() error { id, _ := uuid.NewUUID() closed := make(chan bool) - session, err := NewSession(ctx, conn, Capabilities, id.String(), closed, NewHandler(ns.db)) + session, err := NewSession(ctx, conn, Capabilities, id.String(), closed, NewHandler(ns.backend)) ns.sessionPoolMutex.Lock() ns.sessionPool[id.String()] = session ns.sessionPoolMutex.Unlock()