Initial commit;

This commit is contained in:
2026-01-17 22:45:02 +03:00
commit 2b8c774d0a
34 changed files with 1728 additions and 0 deletions

33
pkg/logger/logger.go Normal file
View File

@@ -0,0 +1,33 @@
package logger
import (
"log/slog"
"os"
)
const (
envLocal = "local"
envDevelopment = "development"
envProduction = "production"
)
func New(env string) *slog.Logger {
var logger *slog.Logger
switch env {
case envLocal:
logger = slog.New(
slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug}),
)
case envDevelopment:
logger = slog.New(
slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug}),
)
case envProduction:
logger = slog.New(
slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}),
)
}
return logger
}

11
pkg/postgresql/errors.go Normal file
View File

@@ -0,0 +1,11 @@
package postgresql
import "github.com/jackc/pgx/v5"
var (
ErrNoRows = pgx.ErrNoRows
)
const (
ErrConstraintUnique = "23505"
)

View File

@@ -0,0 +1,82 @@
package postgresql
import (
"errors"
"fmt"
"log"
"github.com/golang-migrate/migrate/v4"
"github.com/golang-migrate/migrate/v4/database/postgres"
_ "github.com/golang-migrate/migrate/v4/source/file"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/jackc/pgx/v5/stdlib"
)
func RunMigrationsWithString(connectionString string, migrationDir string) error {
m, err := migrate.New(
"file://"+migrationDir,
connectionString)
if err != nil {
return fmt.Errorf("apply migrations: %w", err)
}
if err := m.Up(); err != nil {
return fmt.Errorf("apply migrations: %w", err)
}
if err = m.Up(); err != nil && !errors.Is(err, migrate.ErrNoChange) {
return fmt.Errorf("apply migrations: %w", err)
}
log.Println("✅ Migrations applied")
return nil
}
type MigrationHandler struct {
migrate *migrate.Migrate
}
func (migration *MigrationHandler) Up() error {
if err := migration.migrate.Up(); err != nil && !errors.Is(err, migrate.ErrNoChange) {
return fmt.Errorf("apply migrations: %w", err)
}
log.Println("✅ Migrations applied")
return nil
}
func (migration *MigrationHandler) Down() error {
if err := migration.migrate.Down(); err != nil && !errors.Is(err, migrate.ErrNoChange) {
return fmt.Errorf("apply migrations: %w", err)
}
log.Println("✅ Migrations applied")
return nil
}
func NewMigrationWithInstance(pool *pgxpool.Pool, migrationDir string) (*MigrationHandler, error) {
db := stdlib.OpenDBFromPool(pool)
driver, err := postgres.WithInstance(db, &postgres.Config{})
if err != nil {
return nil, fmt.Errorf("create migration driver: %w", err)
}
m, err := migrate.NewWithDatabaseInstance(
"file://"+migrationDir,
"postgres", driver)
if err != nil {
return nil, fmt.Errorf("create migrator: %w", err)
}
//m.Down()
/*if err = m.Up(); err != nil && !errors.Is(err, migrate.ErrNoChange) {
return fmt.Errorf("apply migrations: %w", err)
}
log.Println("✅ Migrations applied")*/
return &MigrationHandler{
migrate: m,
}, nil
}

33
pkg/postgresql/pgx.go Normal file
View File

@@ -0,0 +1,33 @@
package postgresql
import (
"context"
"database/sql"
"fmt"
_ "github.com/lib/pq"
)
type PGXClient interface {
Prepare(query string) (*sql.Stmt, error)
ExecContext(ctx context.Context, query string, arguments ...interface{}) (sql.Result, error)
QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row
BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error)
}
func NewPGXClient(psqlInfo string) (*sql.DB, error) {
const op = "repository.postgresql.pq.NewConnection"
db, err := sql.Open("pgx", psqlInfo)
if err != nil {
return nil, fmt.Errorf("%s: %w", op, err)
}
err = db.Ping()
if err != nil {
return nil, fmt.Errorf("%s: %w", op, err)
}
return db, nil
}

View File

@@ -0,0 +1,26 @@
package postgresql
import (
"context"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgconn"
"github.com/jackc/pgx/v5/pgxpool"
)
type PGXPool interface {
Exec(ctx context.Context, query string, args ...any) (pgconn.CommandTag, error)
Query(ctx context.Context, query string, args ...any) (pgx.Rows, error)
QueryRow(ctx context.Context, query string, args ...any) pgx.Row
SendBatch(ctx context.Context, b *pgx.Batch) pgx.BatchResults
BeginTx(ctx context.Context, opts pgx.TxOptions) (pgx.Tx, error)
}
func NewPGXPool(ctx context.Context, connectionString string) (*pgxpool.Pool, error) {
pool, err := pgxpool.New(ctx, connectionString)
if err != nil {
return nil, err
}
return pool, nil
}

41
pkg/postgresql/pq.go Normal file
View File

@@ -0,0 +1,41 @@
package postgresql
import (
"context"
"database/sql"
"fmt"
_ "github.com/lib/pq"
)
type PQClient interface {
Prepare(query string) (*sql.Stmt, error)
Exec(query string, arguments ...interface{}) (sql.Result, error)
Query(query string, args ...interface{}) (*sql.Rows, error)
QueryRow(query string, args ...interface{}) *sql.Row
// Begin(ctx context.Context) (*sql.Tx, error)
}
type PQClientContext interface {
Prepare(query string) (*sql.Stmt, error)
ExecContext(ctx context.Context, query string, arguments ...interface{}) (sql.Result, error)
QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row
BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error)
}
func NewPQClient(psqlInfo string) (*sql.DB, error) {
const op = "repository.postgresql.pq.NewConnection"
db, err := sql.Open("postgres", psqlInfo)
if err != nil {
return nil, fmt.Errorf("%s: %w", op, err)
}
err = db.Ping()
if err != nil {
return nil, fmt.Errorf("%s: %w", op, err)
}
return db, nil
}

19
pkg/postgresql/utils.go Normal file
View File

@@ -0,0 +1,19 @@
package postgresql
import (
"fmt"
"net/url"
"steam_analyzer/internal/config"
)
func CreateDBConnectionString(cfg config.Database) string {
return fmt.Sprintf(
"postgres://%s:%s@%s:%s/%s?sslmode=%s",
cfg.User,
url.QueryEscape(cfg.Password),
cfg.Host,
cfg.Port,
cfg.Name,
cfg.Sslmode,
)
}

58
pkg/server/server.go Normal file
View File

@@ -0,0 +1,58 @@
// Package server
// Contain struct and interface for project server
package server
import (
"context"
"fmt"
"log/slog"
"net/http"
"steam_analyzer/internal/config"
"time"
)
type ILogger interface {
Info(msg string, args ...any)
Debug(msg string, args ...any)
Error(msg string, args ...any)
}
type Server struct {
httpServer *http.Server
logger ILogger
}
type ServerParam struct {
Host string
Port string
Origins []string
IdleTimeout time.Duration
WriteTimeout time.Duration
ReadTimeout time.Duration
}
func New(cfg config.Server, log ILogger, h http.Handler) *Server {
return &Server{
httpServer: &http.Server{
Addr: cfg.Host + ":" + cfg.Port,
Handler: configureCORSFor(h, cfg.Origins),
// ErrorLog: log,
IdleTimeout: cfg.IdleTimeout,
WriteTimeout: cfg.WriteTimeout,
ReadTimeout: cfg.ReadTimeout,
},
logger: log,
}
}
func (s *Server) Run() {
s.logger.Info(fmt.Sprintf("Server started at %s", s.httpServer.Addr))
if err := s.httpServer.ListenAndServe(); err != nil {
s.logger.Error("Cannot start server", slog.String("error", err.Error()))
}
}
func (s *Server) Stop(ctx context.Context) error {
return s.httpServer.Shutdown(ctx)
}

27
pkg/server/utils.go Normal file
View File

@@ -0,0 +1,27 @@
package server
import (
"net/http"
"github.com/rs/cors"
)
func configureCORSFor(handler http.Handler, origins []string) http.Handler {
ch := cors.New(cors.Options{
// # http://mywebsite-domain.com/ is configured in hosts (localhost:80 alias)
AllowedOrigins: origins,
AllowedMethods: []string{
http.MethodPost,
http.MethodGet,
http.MethodPut,
http.MethodDelete,
// http.MethodOptions,
},
OptionsPassthrough: false,
AllowCredentials: true,
// Debug: true,
})
return ch.Handler(handler)
}

View File

@@ -0,0 +1,5 @@
package httputils
type HTTPError struct {
Message string `json:"message"`
}

View File

@@ -0,0 +1,19 @@
package jsonutils
import (
"encoding/json"
"io"
)
// ToJSON serializes the given interface into a string based JSON format
func ToJSON(i interface{}, w io.Writer) error {
e := json.NewEncoder(w)
return e.Encode(i)
}
// FromJSON deserializes the object from JSON string
// in an io.Reader to the given interface
func FromJSON(i interface{}, r io.Reader) error {
d := json.NewDecoder(r)
return d.Decode(i)
}