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.WithBatcher(
|
||||
tracerExporter,
|
||||
traceSdk.WithMaxQueueSize(4096),
|
||||
traceSdk.WithMaxExportBatchSize(1024),
|
||||
traceSdk.WithMaxQueueSize(8192),
|
||||
traceSdk.WithMaxExportBatchSize(2048),
|
||||
),
|
||||
)
|
||||
tracer = tracerProvider.Tracer("backend")
|
||||
@ -137,6 +137,7 @@ func (a *App) Run(p RunParams) {
|
||||
userRepo = repos.NewUserRepo(sqlDb, tracer)
|
||||
emailRepo = repos.NewEmailRepo()
|
||||
actionTokenRepo = repos.NewActionTokenRepo(sqlDb)
|
||||
shortlinkRepo = repos.NewShortlinkRepo(sqlDb, tracer)
|
||||
|
||||
userCache = cache.NewCacheInmemSharded[models.UserDTO](cache.ShardingTypeInteger)
|
||||
jwtCache = cache.NewCacheInmemSharded[string](cache.ShardingTypeJWT)
|
||||
@ -176,8 +177,12 @@ func (a *App) Run(p RunParams) {
|
||||
shortlinkService = services.NewShortlinkSevice(
|
||||
services.NewShortlinkServiceParams{
|
||||
Cache: linksCache,
|
||||
Repo: shortlinkRepo,
|
||||
},
|
||||
)
|
||||
|
||||
// TODO: Run cleanup routine
|
||||
// go shortlinkService.ShortlinkRoutine(ctx)
|
||||
}
|
||||
|
||||
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) {
|
||||
_, span := u.tracer.Start(ctx, "postgres")
|
||||
_, span := u.tracer.Start(ctx, "postgres::CreateUser")
|
||||
defer span.End()
|
||||
|
||||
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 {
|
||||
_, span := u.tracer.Start(ctx, "postgres")
|
||||
_, span := u.tracer.Start(ctx, "postgres::UpdateUser")
|
||||
defer span.End()
|
||||
|
||||
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) {
|
||||
_, span := u.tracer.Start(ctx, "postgres")
|
||||
_, span := u.tracer.Start(ctx, "postgres::GetUserById")
|
||||
defer span.End()
|
||||
|
||||
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) {
|
||||
_, span := u.tracer.Start(ctx, "postgres")
|
||||
_, span := u.tracer.Start(ctx, "postgres::GetUserByEmail")
|
||||
defer span.End()
|
||||
|
||||
query := `select id, email, secret, name from users where email = $1;`
|
||||
|
||||
@ -3,46 +3,75 @@ package services
|
||||
import (
|
||||
"backend/src/cache"
|
||||
"backend/src/charsets"
|
||||
"backend/src/core/repos"
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ShortlinkService interface {
|
||||
CreateLink(in string) (string, error)
|
||||
GetLink(id string) (string, error)
|
||||
CreateShortlink(ctx context.Context, url string) (string, error)
|
||||
GetShortlink(ctx context.Context, id string) (string, error)
|
||||
ShortlinkRoutine(ctx context.Context) error
|
||||
}
|
||||
|
||||
type NewShortlinkServiceParams struct {
|
||||
Endpoint string
|
||||
Cache cache.Cache[string, string]
|
||||
Cache cache.Cache[string, string]
|
||||
Repo repos.ShortlinkRepo
|
||||
}
|
||||
|
||||
func NewShortlinkSevice(params NewShortlinkServiceParams) ShortlinkService {
|
||||
return &shortlinkService{
|
||||
cache: params.Cache,
|
||||
repo: params.Repo,
|
||||
}
|
||||
}
|
||||
|
||||
type shortlinkService struct {
|
||||
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)
|
||||
|
||||
src := rand.NewSource(time.Now().UnixMicro())
|
||||
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})
|
||||
return str, nil
|
||||
expiration := time.Now().Add(7 * 24 * time.Hour)
|
||||
|
||||
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) {
|
||||
val, ok := s.cache.Get(id)
|
||||
if !ok {
|
||||
func (s *shortlinkService) GetShortlink(ctx context.Context, id string) (string, error) {
|
||||
if link, ok := s.cache.Get(id); 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 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"
|
||||
|
||||
linkId, err := shortlinkService.CreateLink(u.String())
|
||||
linkId, err := shortlinkService.CreateShortlink(ctx, u.String())
|
||||
if err != nil {
|
||||
ctx.Data(500, "plain/text", []byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
resultBody, err := json.Marshal(shortlinkCreateOutput{
|
||||
Link: "https:/nucrea.ru/s/" + linkId,
|
||||
Link: "https://nucrea.ru/s/" + linkId,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.AbortWithError(500, err)
|
||||
@ -50,7 +50,7 @@ func NewShortlinkResolveHandler(shortlinkService services.ShortlinkService) gin.
|
||||
return func(ctx *gin.Context) {
|
||||
linkId := ctx.Param("linkId")
|
||||
|
||||
linkUrl, err := shortlinkService.GetLink(linkId)
|
||||
linkUrl, err := shortlinkService.GetShortlink(ctx, linkId)
|
||||
if err != nil {
|
||||
ctx.AbortWithError(500, err)
|
||||
return
|
||||
|
||||
@ -19,6 +19,7 @@ func NewRequestLogMiddleware(logger log.Logger, tracer trace.Tracer, prometheus
|
||||
if requestId == "" {
|
||||
requestId = uuid.New().String()
|
||||
}
|
||||
c.Header("X-Request-Id", requestId)
|
||||
|
||||
log.SetCtxRequestId(c, requestId)
|
||||
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.opentelemetry.io/otel"
|
||||
"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, 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()
|
||||
|
||||
traceId := span.SpanContext().TraceID()
|
||||
c.Header("X-Trace-Id", traceId.String())
|
||||
|
||||
c.Request = c.Request.WithContext(ctx)
|
||||
|
||||
c.Next()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user