Initial commit;
This commit is contained in:
33
pkg/logger/logger.go
Normal file
33
pkg/logger/logger.go
Normal 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
11
pkg/postgresql/errors.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package postgresql
|
||||
|
||||
import "github.com/jackc/pgx/v5"
|
||||
|
||||
var (
|
||||
ErrNoRows = pgx.ErrNoRows
|
||||
)
|
||||
|
||||
const (
|
||||
ErrConstraintUnique = "23505"
|
||||
)
|
||||
82
pkg/postgresql/migrations.go
Normal file
82
pkg/postgresql/migrations.go
Normal 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
33
pkg/postgresql/pgx.go
Normal 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
|
||||
}
|
||||
26
pkg/postgresql/pgx_pool.go
Normal file
26
pkg/postgresql/pgx_pool.go
Normal 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
41
pkg/postgresql/pq.go
Normal 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
19
pkg/postgresql/utils.go
Normal 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
58
pkg/server/server.go
Normal 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
27
pkg/server/utils.go
Normal 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)
|
||||
}
|
||||
5
pkg/utils/httputils/httputils.go
Normal file
5
pkg/utils/httputils/httputils.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package httputils
|
||||
|
||||
type HTTPError struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
19
pkg/utils/jsonutils/jsonutils.go
Normal file
19
pkg/utils/jsonutils/jsonutils.go
Normal 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)
|
||||
}
|
||||
Reference in New Issue
Block a user