save shortlink in postgres, improve traces
This commit is contained in:
parent
e9ce51b2ca
commit
ceac105645
5
sql/02_shortlinks.sql
Normal file
5
sql/02_shortlinks.sql
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
create table if not exists shortlinks (
|
||||||
|
id text primary key,
|
||||||
|
url text,
|
||||||
|
expiration date
|
||||||
|
);
|
||||||
@ -117,8 +117,8 @@ func (a *App) Run(p RunParams) {
|
|||||||
traceSdk.WithSampler(traceSdk.AlwaysSample()),
|
traceSdk.WithSampler(traceSdk.AlwaysSample()),
|
||||||
traceSdk.WithBatcher(
|
traceSdk.WithBatcher(
|
||||||
tracerExporter,
|
tracerExporter,
|
||||||
traceSdk.WithMaxQueueSize(4096),
|
traceSdk.WithMaxQueueSize(8192),
|
||||||
traceSdk.WithMaxExportBatchSize(1024),
|
traceSdk.WithMaxExportBatchSize(2048),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
tracer = tracerProvider.Tracer("backend")
|
tracer = tracerProvider.Tracer("backend")
|
||||||
@ -137,6 +137,7 @@ func (a *App) Run(p RunParams) {
|
|||||||
userRepo = repos.NewUserRepo(sqlDb, tracer)
|
userRepo = repos.NewUserRepo(sqlDb, tracer)
|
||||||
emailRepo = repos.NewEmailRepo()
|
emailRepo = repos.NewEmailRepo()
|
||||||
actionTokenRepo = repos.NewActionTokenRepo(sqlDb)
|
actionTokenRepo = repos.NewActionTokenRepo(sqlDb)
|
||||||
|
shortlinkRepo = repos.NewShortlinkRepo(sqlDb, tracer)
|
||||||
|
|
||||||
userCache = cache.NewCacheInmemSharded[models.UserDTO](cache.ShardingTypeInteger)
|
userCache = cache.NewCacheInmemSharded[models.UserDTO](cache.ShardingTypeInteger)
|
||||||
jwtCache = cache.NewCacheInmemSharded[string](cache.ShardingTypeJWT)
|
jwtCache = cache.NewCacheInmemSharded[string](cache.ShardingTypeJWT)
|
||||||
@ -176,8 +177,12 @@ func (a *App) Run(p RunParams) {
|
|||||||
shortlinkService = services.NewShortlinkSevice(
|
shortlinkService = services.NewShortlinkSevice(
|
||||||
services.NewShortlinkServiceParams{
|
services.NewShortlinkServiceParams{
|
||||||
Cache: linksCache,
|
Cache: linksCache,
|
||||||
|
Repo: shortlinkRepo,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO: Run cleanup routine
|
||||||
|
// go shortlinkService.ShortlinkRoutine(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
clientNotifier := client_notifier.NewBasicNotifier()
|
clientNotifier := client_notifier.NewBasicNotifier()
|
||||||
|
|||||||
95
src/core/repos/shortlink_repo.go
Normal file
95
src/core/repos/shortlink_repo.go
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
package repos
|
||||||
|
|
||||||
|
import (
|
||||||
|
"backend/src/integrations"
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ShortlinkDTO struct {
|
||||||
|
Id string
|
||||||
|
Url string
|
||||||
|
Expiration time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type ShortlinkRepo interface {
|
||||||
|
AddShortlink(ctx context.Context, dto ShortlinkDTO) error
|
||||||
|
GetShortlink(ctx context.Context, id string) (*ShortlinkDTO, error)
|
||||||
|
DeleteExpiredShortlinks(ctx context.Context, limit int) (int, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewShortlinkRepo(db integrations.SqlDB, tracer trace.Tracer) ShortlinkRepo {
|
||||||
|
return &shortlinkRepo{db, tracer}
|
||||||
|
}
|
||||||
|
|
||||||
|
type shortlinkRepo struct {
|
||||||
|
db integrations.SqlDB
|
||||||
|
tracer trace.Tracer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *shortlinkRepo) AddShortlink(ctx context.Context, dto ShortlinkDTO) error {
|
||||||
|
_, span := u.tracer.Start(ctx, "postgres::AddShortlink")
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
query := `insert into shortlinks (id, url, expiration) values ($1, $2, $3);`
|
||||||
|
_, err := u.db.ExecContext(ctx, query, dto.Id, dto.Url, dto.Expiration)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *shortlinkRepo) GetShortlink(ctx context.Context, id string) (*ShortlinkDTO, error) {
|
||||||
|
_, span := u.tracer.Start(ctx, "postgres::GetShortlink")
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
query := `select url, expiration from shortlinks where id = $1;`
|
||||||
|
row := u.db.QueryRowContext(ctx, query, id)
|
||||||
|
if err := row.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dto := &ShortlinkDTO{Id: id}
|
||||||
|
err := row.Scan(&dto.Url, &dto.Expiration)
|
||||||
|
if err == nil {
|
||||||
|
return dto, nil
|
||||||
|
}
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *shortlinkRepo) DeleteExpiredShortlinks(ctx context.Context, limit int) (int, error) {
|
||||||
|
_, span := u.tracer.Start(ctx, "postgres::CheckExpiredShortlinks")
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
query := `
|
||||||
|
select count(*) from (
|
||||||
|
delete from shortlinks
|
||||||
|
where id in (
|
||||||
|
select id
|
||||||
|
from shortlinks
|
||||||
|
where current_date > expiration
|
||||||
|
limit $1
|
||||||
|
)
|
||||||
|
returning *
|
||||||
|
);`
|
||||||
|
row := u.db.QueryRowContext(ctx, query, limit)
|
||||||
|
if err := row.Err(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
count := 0
|
||||||
|
err := row.Scan(&count)
|
||||||
|
if err == nil {
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
@ -34,7 +34,7 @@ type userRepo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *userRepo) CreateUser(ctx context.Context, dto models.UserDTO) (*models.UserDTO, error) {
|
func (u *userRepo) CreateUser(ctx context.Context, dto models.UserDTO) (*models.UserDTO, error) {
|
||||||
_, span := u.tracer.Start(ctx, "postgres")
|
_, span := u.tracer.Start(ctx, "postgres::CreateUser")
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
query := `insert into users (email, secret, name) values ($1, $2, $3) returning id;`
|
query := `insert into users (email, secret, name) values ($1, $2, $3) returning id;`
|
||||||
@ -54,7 +54,7 @@ func (u *userRepo) CreateUser(ctx context.Context, dto models.UserDTO) (*models.
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *userRepo) UpdateUser(ctx context.Context, userId string, dto models.UserUpdateDTO) error {
|
func (u *userRepo) UpdateUser(ctx context.Context, userId string, dto models.UserUpdateDTO) error {
|
||||||
_, span := u.tracer.Start(ctx, "postgres")
|
_, span := u.tracer.Start(ctx, "postgres::UpdateUser")
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
query := `update users set secret=$1, name=$2 where id = $3;`
|
query := `update users set secret=$1, name=$2 where id = $3;`
|
||||||
@ -67,7 +67,7 @@ func (u *userRepo) UpdateUser(ctx context.Context, userId string, dto models.Use
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *userRepo) GetUserById(ctx context.Context, id string) (*models.UserDTO, error) {
|
func (u *userRepo) GetUserById(ctx context.Context, id string) (*models.UserDTO, error) {
|
||||||
_, span := u.tracer.Start(ctx, "postgres")
|
_, span := u.tracer.Start(ctx, "postgres::GetUserById")
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
query := `select id, email, secret, name from users where id = $1;`
|
query := `select id, email, secret, name from users where id = $1;`
|
||||||
@ -86,7 +86,7 @@ func (u *userRepo) GetUserById(ctx context.Context, id string) (*models.UserDTO,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *userRepo) GetUserByEmail(ctx context.Context, login string) (*models.UserDTO, error) {
|
func (u *userRepo) GetUserByEmail(ctx context.Context, login string) (*models.UserDTO, error) {
|
||||||
_, span := u.tracer.Start(ctx, "postgres")
|
_, span := u.tracer.Start(ctx, "postgres::GetUserByEmail")
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
query := `select id, email, secret, name from users where email = $1;`
|
query := `select id, email, secret, name from users where email = $1;`
|
||||||
|
|||||||
@ -3,46 +3,75 @@ package services
|
|||||||
import (
|
import (
|
||||||
"backend/src/cache"
|
"backend/src/cache"
|
||||||
"backend/src/charsets"
|
"backend/src/charsets"
|
||||||
|
"backend/src/core/repos"
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ShortlinkService interface {
|
type ShortlinkService interface {
|
||||||
CreateLink(in string) (string, error)
|
CreateShortlink(ctx context.Context, url string) (string, error)
|
||||||
GetLink(id string) (string, error)
|
GetShortlink(ctx context.Context, id string) (string, error)
|
||||||
|
ShortlinkRoutine(ctx context.Context) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type NewShortlinkServiceParams struct {
|
type NewShortlinkServiceParams struct {
|
||||||
Endpoint string
|
|
||||||
Cache cache.Cache[string, string]
|
Cache cache.Cache[string, string]
|
||||||
|
Repo repos.ShortlinkRepo
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewShortlinkSevice(params NewShortlinkServiceParams) ShortlinkService {
|
func NewShortlinkSevice(params NewShortlinkServiceParams) ShortlinkService {
|
||||||
return &shortlinkService{
|
return &shortlinkService{
|
||||||
cache: params.Cache,
|
cache: params.Cache,
|
||||||
|
repo: params.Repo,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type shortlinkService struct {
|
type shortlinkService struct {
|
||||||
cache cache.Cache[string, string]
|
cache cache.Cache[string, string]
|
||||||
|
repo repos.ShortlinkRepo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *shortlinkService) CreateLink(in string) (string, error) {
|
func (s *shortlinkService) CreateShortlink(ctx context.Context, url string) (string, error) {
|
||||||
charset := charsets.GetCharset(charsets.CharsetTypeAll)
|
charset := charsets.GetCharset(charsets.CharsetTypeAll)
|
||||||
|
|
||||||
src := rand.NewSource(time.Now().UnixMicro())
|
src := rand.NewSource(time.Now().UnixMicro())
|
||||||
randGen := rand.New(src)
|
randGen := rand.New(src)
|
||||||
str := charset.RandomString(randGen, 10)
|
id := charset.RandomString(randGen, 10)
|
||||||
|
|
||||||
s.cache.Set(str, in, cache.Expiration{Ttl: 7 * 24 * time.Hour})
|
expiration := time.Now().Add(7 * 24 * time.Hour)
|
||||||
return str, nil
|
|
||||||
|
dto := repos.ShortlinkDTO{
|
||||||
|
Id: id,
|
||||||
|
Url: url,
|
||||||
|
Expiration: expiration,
|
||||||
|
}
|
||||||
|
if err := s.repo.AddShortlink(ctx, dto); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.cache.Set(id, url, cache.Expiration{ExpiresAt: expiration})
|
||||||
|
|
||||||
|
return id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *shortlinkService) GetLink(id string) (string, error) {
|
func (s *shortlinkService) GetShortlink(ctx context.Context, id string) (string, error) {
|
||||||
val, ok := s.cache.Get(id)
|
if link, ok := s.cache.Get(id); ok {
|
||||||
if !ok {
|
return link, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
link, err := s.repo.GetShortlink(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if link == nil {
|
||||||
return "", fmt.Errorf("link does not exist or expired")
|
return "", fmt.Errorf("link does not exist or expired")
|
||||||
}
|
}
|
||||||
return val, nil
|
|
||||||
|
return link.Url, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *shortlinkService) ShortlinkRoutine(ctx context.Context) error {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,14 +28,14 @@ func NewShortlinkCreateHandler(shortlinkService services.ShortlinkService) gin.H
|
|||||||
}
|
}
|
||||||
u.Scheme = "https"
|
u.Scheme = "https"
|
||||||
|
|
||||||
linkId, err := shortlinkService.CreateLink(u.String())
|
linkId, err := shortlinkService.CreateShortlink(ctx, u.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Data(500, "plain/text", []byte(err.Error()))
|
ctx.Data(500, "plain/text", []byte(err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resultBody, err := json.Marshal(shortlinkCreateOutput{
|
resultBody, err := json.Marshal(shortlinkCreateOutput{
|
||||||
Link: "https:/nucrea.ru/s/" + linkId,
|
Link: "https://nucrea.ru/s/" + linkId,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.AbortWithError(500, err)
|
ctx.AbortWithError(500, err)
|
||||||
@ -50,7 +50,7 @@ func NewShortlinkResolveHandler(shortlinkService services.ShortlinkService) gin.
|
|||||||
return func(ctx *gin.Context) {
|
return func(ctx *gin.Context) {
|
||||||
linkId := ctx.Param("linkId")
|
linkId := ctx.Param("linkId")
|
||||||
|
|
||||||
linkUrl, err := shortlinkService.GetLink(linkId)
|
linkUrl, err := shortlinkService.GetShortlink(ctx, linkId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.AbortWithError(500, err)
|
ctx.AbortWithError(500, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@ -19,6 +19,7 @@ func NewRequestLogMiddleware(logger log.Logger, tracer trace.Tracer, prometheus
|
|||||||
if requestId == "" {
|
if requestId == "" {
|
||||||
requestId = uuid.New().String()
|
requestId = uuid.New().String()
|
||||||
}
|
}
|
||||||
|
c.Header("X-Request-Id", requestId)
|
||||||
|
|
||||||
log.SetCtxRequestId(c, requestId)
|
log.SetCtxRequestId(c, requestId)
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
"go.opentelemetry.io/otel/propagation"
|
"go.opentelemetry.io/otel/propagation"
|
||||||
@ -18,9 +20,12 @@ func NewTracingMiddleware(tracer trace.Tracer) gin.HandlerFunc {
|
|||||||
|
|
||||||
ctx := prop.Extract(savedCtx, propagation.HeaderCarrier(c.Request.Header))
|
ctx := prop.Extract(savedCtx, propagation.HeaderCarrier(c.Request.Header))
|
||||||
|
|
||||||
ctx, span := tracer.Start(ctx, c.Request.URL.Path)
|
ctx, span := tracer.Start(ctx, fmt.Sprintf("%s %s", c.Request.Method, c.Request.URL.Path))
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
|
traceId := span.SpanContext().TraceID()
|
||||||
|
c.Header("X-Trace-Id", traceId.String())
|
||||||
|
|
||||||
c.Request = c.Request.WithContext(ctx)
|
c.Request = c.Request.WithContext(ctx)
|
||||||
|
|
||||||
c.Next()
|
c.Next()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user