feat: implemented service/transport/main layers

This commit is contained in:
Anatoly Antonov 2025-06-21 22:06:32 +03:00
parent 5158c5d7c9
commit bcb9ace54c
29 changed files with 804 additions and 393 deletions

View file

@ -0,0 +1,7 @@
package ds
type PredictionParameters struct {
}
type PredicitonResult struct {
}

View file

@ -0,0 +1,25 @@
package errcodes
import (
"strings"
)
type ErrorCode struct {
StatusCode int
Message string
Details string
}
var errorCodeCounter int32
func New(statusCode int, message string, details ...string) *ErrorCode {
return &ErrorCode{
StatusCode: statusCode,
Message: message,
Details: strings.Join(details, " "),
}
}
func (e *ErrorCode) Error() string {
return e.Message
}

23
internal/pkg/log/log.go Normal file
View file

@ -0,0 +1,23 @@
package log
import (
"context"
"go.uber.org/zap"
)
type ctxLogKey struct{}
func ToCtx(ctx context.Context, lg *zap.Logger) context.Context {
return context.WithValue(ctx, ctxLogKey{}, lg)
}
func Ctx(ctx context.Context) *zap.Logger {
lg, ok := ctx.Value(ctxLogKey{}).(*zap.Logger)
if !ok || lg == nil {
zap.L().Error("no logger in context, using global")
return zap.L()
}
return lg
}

16
internal/service/deps.go Normal file
View file

@ -0,0 +1,16 @@
package service
import (
"context"
"time"
)
type Redis interface {
Lock(ctx context.Context, key string, ttl time.Duration) (func(context.Context), error)
Set(key string, value []byte, ttl time.Duration) error
Get(key string) ([]byte, error)
}
type Downloader interface {
Download(ctx context.Context)
}

View file

@ -0,0 +1,13 @@
package service
import (
"context"
"net/http"
"git.intra.yksa.space/gsn/predictor/internal/pkg/ds"
"git.intra.yksa.space/gsn/predictor/internal/pkg/errcodes"
)
func (s *Service) PerformPrediction(ctx context.Context, params ds.PredictionParameters) ([]ds.PredicitonResult, error) {
return nil, errcodes.New(http.StatusNotImplemented, "not implemented", "please wait")
}

View file

@ -0,0 +1,10 @@
package service
type Service struct {
redis Redis
downloader Downloader
}
func New() *Service {
return &Service{}
}

View file

@ -0,0 +1,44 @@
package middleware
import (
"time"
"git.intra.yksa.space/gsn/predictor/internal/pkg/errcodes"
"git.intra.yksa.space/gsn/predictor/internal/pkg/log"
"github.com/ogen-go/ogen/middleware"
"go.uber.org/zap"
)
func Logging(logger *zap.Logger) middleware.Middleware {
return func(req middleware.Request, next func(req middleware.Request) (middleware.Response, error)) (middleware.Response, error) {
lg := logger.With(
zap.String("operationId", req.OperationID),
)
lg.Info("started request")
req.Context = log.ToCtx(req.Context, lg)
start := time.Now()
resp, err := next(req)
dur := time.Since(start).Microseconds()
if err != nil {
if errcode, ok := err.(*errcodes.ErrorCode); ok {
lg.Error("request error",
zap.Int("status_code", errcode.StatusCode),
zap.String("message", errcode.Message),
zap.String("details", errcode.Details),
)
} else {
lg.Error("request internal error",
zap.Error(err),
)
}
}
lg.Info("done request", zap.Float64("duration_ms", float64(dur)/float64(1000)))
return resp, err
}
}

View file

@ -0,0 +1,23 @@
package rest
import (
"fmt"
env "github.com/caarlos0/env/v11"
)
type Config struct {
Port int `env:"PORT" envDefault:"8080"`
}
func NewConfig(servicePrefix string) (*Config, error) {
cfg := &Config{}
if err := env.ParseWithOptions(cfg, env.Options{
PrefixTagName: fmt.Sprintf("%s_REST_", servicePrefix),
}); err != nil {
return nil, err
}
return cfg, nil
}

View file

@ -0,0 +1,11 @@
package handler
import (
"context"
"git.intra.yksa.space/gsn/predictor/internal/pkg/ds"
)
type Service interface {
PerformPrediction(ctx context.Context, params ds.PredictionParameters) ([]ds.PredicitonResult, error)
}

View file

@ -0,0 +1,52 @@
package handler
import (
"context"
"net/http"
"git.intra.yksa.space/gsn/predictor/internal/pkg/errcodes"
api "git.intra.yksa.space/gsn/predictor/pkg/rest"
)
var (
_ api.Handler = (*Handler)(nil)
)
type Handler struct {
svc Service
}
func New(svc Service) *Handler {
return &Handler{
svc: svc,
}
}
func (h *Handler) PerformPrediction(ctx context.Context, req api.OptPredictionParameters, params api.PerformPredictionParams) (*api.PredictionResult, error) {
return nil, errcodes.New(http.StatusNotImplemented, "not implemented", "please wait")
}
func (h *Handler) NewError(ctx context.Context, err error) *api.ErrorStatusCode {
if errcode, ok := err.(*errcodes.ErrorCode); ok {
resp := api.Error{
Message: errcode.Message,
}
if errcode.Details != "" {
resp.Details = api.NewOptString(errcode.Details)
}
return &api.ErrorStatusCode{
StatusCode: errcode.StatusCode,
Response: resp,
}
}
return &api.ErrorStatusCode{
StatusCode: http.StatusInternalServerError,
Response: api.Error{
Message: "undefined internal error",
Details: api.NewOptString(err.Error()),
},
}
}

View file

@ -0,0 +1,38 @@
package rest
import (
"fmt"
"net/http"
"git.intra.yksa.space/gsn/predictor/internal/transport/middleware"
handler "git.intra.yksa.space/gsn/predictor/internal/transport/rest/handler"
api "git.intra.yksa.space/gsn/predictor/pkg/rest"
"go.uber.org/zap"
)
type Transport struct {
lg *zap.Logger
cfg *Config
srv *api.Server
}
func New(lg *zap.Logger, handler *handler.Handler, cfg *Config) (*Transport, error) {
srv, err := api.NewServer(handler, api.WithMiddleware(middleware.Logging(lg)))
if err != nil {
return nil, err
}
return &Transport{
lg: lg,
srv: srv,
cfg: cfg,
}, nil
}
func (t *Transport) Run() {
t.lg.Info("started")
if err := http.ListenAndServe(fmt.Sprintf(":%d", t.cfg.Port), t.srv); err != nil {
panic(err)
}
}