Files
2026-06-22 14:31:01 +05:00

118 lines
3.2 KiB
Go

package app
import (
"context"
"database/sql"
"errors"
"net/http"
"os"
"path/filepath"
"strings"
"time"
_ "github.com/go-sql-driver/mysql"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/redis/go-redis/v9"
"secunda-test/internal/cache"
"secunda-test/internal/config"
"secunda-test/internal/email"
"secunda-test/internal/handler"
"secunda-test/internal/httpx"
mysqlrepo "secunda-test/internal/repository/mysql"
"secunda-test/internal/service"
)
type Container struct {
Config config.Config
DB *sql.DB
Redis *redis.Client
Server *http.Server
}
func New(ctx context.Context) (*Container, error) {
cfg, err := config.Load()
if err != nil {
return nil, err
}
db, err := sql.Open("mysql", cfg.Database.DSN)
if err != nil {
return nil, err
}
db.SetMaxOpenConns(cfg.Database.MaxOpenConns)
db.SetMaxIdleConns(cfg.Database.MaxIdleConns)
db.SetConnMaxLifetime(time.Hour)
if err := db.PingContext(ctx); err != nil {
return nil, err
}
redisClient := redis.NewClient(&redis.Options{Addr: cfg.Redis.Addr})
requests := prometheus.NewCounterVec(prometheus.CounterOpts{Name: "http_requests_total", Help: "Total HTTP requests."}, []string{"method", "path", "status"})
duration := prometheus.NewHistogramVec(prometheus.HistogramOpts{Name: "http_request_duration_seconds", Help: "HTTP request duration."}, []string{"method", "path"})
prometheus.MustRegister(requests, duration)
users := mysqlrepo.NewUserRepository(db)
teams := mysqlrepo.NewTeamRepository(db)
tasks := mysqlrepo.NewTaskRepository(db)
taskCache := cache.NewTaskCache(redisClient, time.Duration(cfg.Redis.TTLSeconds)*time.Second)
emailSender := email.NewSender(cfg.Email.Endpoint)
authService := service.NewAuthService(users, cfg.JWT.Secret, time.Duration(cfg.JWT.TTLMinutes)*time.Minute)
teamService := service.NewTeamService(teams, users, emailSender)
taskService := service.NewTaskService(tasks, teams, taskCache)
middleware := httpx.NewMiddleware(cfg.JWT.Secret, cfg.RateLimit.RequestsPerMinute, requests, duration)
h := handler.New(authService, teamService, taskService, middleware)
root := http.NewServeMux()
root.Handle("/", h.Routes())
root.Handle("/metrics", promhttp.Handler())
return &Container{
Config: cfg,
DB: db,
Redis: redisClient,
Server: &http.Server{Addr: cfg.HTTP.Addr, Handler: root, ReadHeaderTimeout: 5 * time.Second},
}, nil
}
func (c *Container) Migrate(ctx context.Context) error {
files, err := filepath.Glob("migrations/*.sql")
if err != nil {
return err
}
if len(files) == 0 {
files, err = filepath.Glob("/app/migrations/*.sql")
if err != nil {
return err
}
}
for _, file := range files {
data, err := os.ReadFile(file)
if err != nil {
return err
}
for _, stmt := range strings.Split(string(data), ";") {
stmt = strings.TrimSpace(stmt)
if stmt == "" {
continue
}
if _, err := c.DB.ExecContext(ctx, stmt); err != nil {
return err
}
}
}
return nil
}
func (c *Container) Close() error {
var errs []error
if c.Redis != nil {
errs = append(errs, c.Redis.Close())
}
if c.DB != nil {
errs = append(errs, c.DB.Close())
}
return errors.Join(errs...)
}