refactor: isolate playback and upload use cases
ci / module boundaries / boundaries (push) Successful in 6s
ci / api / api (push) Successful in 47s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 1m2s
ci / db / db (push) Successful in 1m4s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 1m30s
ci / ts / ts (packages + web) (push) Successful in 2m54s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m14s
ci / module boundaries / boundaries (push) Successful in 6s
ci / api / api (push) Successful in 47s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 1m2s
ci / db / db (push) Successful in 1m4s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 1m30s
ci / ts / ts (packages + web) (push) Successful in 2m54s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m14s
This commit is contained in:
1 parent
c84f90e2cc
commit
e158558da3
56 files changed
+3337
-2196
No files matched your search
@@ -34,11 +34,11 @@ const (
|
||||
)
|
||||
|
||||
// Handler реализует playback- и collection-части gen.StrictServerInterface,
|
||||
// а также library.Personalizer (ссылки на стрим и реакции в треках).
|
||||
// персонализация каталога вынесена в Personalizer.
|
||||
type Handler struct {
|
||||
store *Store
|
||||
catalog *library.Store
|
||||
dto func() library.DTO
|
||||
dto library.DTO
|
||||
signer *sign.Signer
|
||||
transcoder *Transcoder
|
||||
// RootFor — каталог хранилища в файловой системе api (для отдачи файлов без nginx и транскода).
|
||||
@@ -48,8 +48,8 @@ type Handler struct {
|
||||
now func() time.Time
|
||||
}
|
||||
|
||||
// NewHandler создаёт обработчик. dto — DTO каталога с персонализацией (замыкание: DTO ссылается на сам Handler).
|
||||
func NewHandler(store *Store, catalog *library.Store, dto func() library.DTO, signer *sign.Signer, tr *Transcoder,
|
||||
// NewHandler создаёт обработчик. dto — независимо собранный DTO каталога с персонализацией.
|
||||
func NewHandler(store *Store, catalog *library.Store, dto library.DTO, signer *sign.Signer, tr *Transcoder,
|
||||
rootFor func(context.Context, uuid.UUID) (string, error), xAccel bool, log *slog.Logger,
|
||||
) *Handler {
|
||||
return &Handler{
|
||||
@@ -66,34 +66,6 @@ func principal(ctx context.Context) (auth.Principal, error) {
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// Personalize реализует library.Personalizer.
|
||||
func (h *Handler) Personalize(ctx context.Context, rows []library.TrackRow, out []gen.Track) error {
|
||||
p, ok := auth.PrincipalFrom(ctx)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
ids := make([]uuid.UUID, len(rows))
|
||||
for i, r := range rows {
|
||||
ids[i] = r.ID
|
||||
}
|
||||
reactions, err := h.store.Reactions(ctx, p.UserID, ids)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
now := h.now()
|
||||
for i := range out {
|
||||
if out[i].Available {
|
||||
u := StreamURL(h.signer, p.UserID, rows[i].ID, now)
|
||||
out[i].StreamUrl = &u
|
||||
}
|
||||
if r, ok := reactions[rows[i].ID]; ok {
|
||||
rr := gen.Reaction(r)
|
||||
out[i].Reaction = &rr
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// --- стрим ---
|
||||
|
||||
// StreamTrack реализует gen.StrictServerInterface.
|
||||
@@ -236,7 +208,7 @@ func (h *Handler) GetTracksBatch(ctx context.Context, req gen.GetTracksBatchRequ
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items, err := h.dto().Tracks(ctx, rows)
|
||||
items, err := h.dto.Tracks(ctx, rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -251,74 +223,7 @@ func (h *Handler) PostEvents(ctx context.Context, req gen.PostEventsRequestObjec
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
now := h.now()
|
||||
ids := make([]uuid.UUID, 0, len(req.Body.Events))
|
||||
for _, e := range req.Body.Events {
|
||||
ids = append(ids, e.TrackId)
|
||||
}
|
||||
durations, err := h.store.TrackDurations(ctx, ids)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
clientType := h.store.SessionClientType(ctx, p.SessionID)
|
||||
|
||||
out := gen.PostEvents200JSONResponse{}
|
||||
tx, err := h.store.Begin(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = tx.Rollback(ctx) }()
|
||||
for _, e := range req.Body.Events {
|
||||
dur, known := durations[e.TrackId]
|
||||
at := e.OccurredAt.UTC()
|
||||
if !known || at.Before(now.Add(-maxEventAge)) {
|
||||
out.Rejected++
|
||||
continue
|
||||
}
|
||||
if at.After(now.Add(maxEventClockSkew)) {
|
||||
at = now // часы клиента спешат
|
||||
}
|
||||
ev := Event{
|
||||
ID: e.Id, Type: string(e.Type), TrackID: e.TrackId, OccurredAt: at, PositionMs: e.PositionMs,
|
||||
PlayedMs: e.PlayedMs, ContextID: e.ContextId, WaveSessionID: e.WaveSessionId,
|
||||
Offline: e.Offline != nil && *e.Offline,
|
||||
}
|
||||
if e.ContextType != nil {
|
||||
ct := string(*e.ContextType)
|
||||
ev.ContextType = &ct
|
||||
}
|
||||
inserted, err := h.store.InsertEvent(ctx, tx, p.UserID, clientType, ev)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !inserted {
|
||||
out.Duplicates++
|
||||
continue
|
||||
}
|
||||
out.Accepted++
|
||||
played := 0
|
||||
if e.PlayedMs != nil {
|
||||
played = *e.PlayedMs
|
||||
}
|
||||
o := Judge(ev.Type, played, dur)
|
||||
if o != (Outcome{}) {
|
||||
if err := h.store.ApplyOutcome(ctx, tx, p.UserID, ev, o); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if outcome := impressionOutcome(ev.Type, o); outcome != "" && ev.ContextType != nil && *ev.ContextType == "wave" &&
|
||||
ev.ContextID != nil {
|
||||
if sid, err := uuid.Parse(*ev.ContextID); err == nil {
|
||||
if err := h.store.MarkImpression(ctx, tx, sid, ev.TrackID, outcome); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := tx.Commit(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
return (EventService{store: h.store, now: h.now}).Accept(ctx, p, req.Body.Events)
|
||||
}
|
||||
|
||||
// --- состояние плеера ---
|
||||
@@ -441,7 +346,7 @@ func (h *Handler) hydrate(ctx context.Context, ids []uuid.UUID) (map[uuid.UUID]g
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dtos, err := h.dto().Tracks(ctx, rows)
|
||||
dtos, err := h.dto.Tracks(ctx, rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -591,8 +496,6 @@ func (h *Handler) ClearTrackReaction(ctx context.Context, req gen.ClearTrackReac
|
||||
return gen.ClearTrackReaction204Response{}, nil
|
||||
}
|
||||
|
||||
var _ library.Personalizer = (*Handler)(nil)
|
||||
|
||||
// impressionOutcome — исход показа волны по событию: complete | skip_early | skip | like | dislike.
|
||||
func impressionOutcome(eventType string, o Outcome) string {
|
||||
switch {
|
||||
|
||||
Reference in new issue
Block a user