feat(api): streaming, transcoding, events, history and likes (M3)
ci / api / api (push) Successful in 29s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 35s
ci / ts / ts (packages + web) (push) Failing after 49s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m1s

- tracks carry a stream_url signed for the user for 12 h and the
  user's reaction; /stream checks the signature, expiry and that the
  user is not blocked, then hands the file to nginx by X-Accel-Redirect
  (or serves it with Range support when X_ACCEL=false)
- original by default; formats browsers can't play and explicit
  high/medium/low go through Opus transcoding, never upwards: a lossy
  source under the target bitrate stays original; transcodes are
  cached per file and size, deduplicated in flight, limited by
  TRANSCODE_CONCURRENCY and trimmed hourly by LRU to the size limit
- /events takes up to 500 events with client UUIDs, drops repeats and
  events older than 30 days, counts plays (≥ half or 4 min, or
  finished), early skips and history entries (≥ 30 s)
- history with clearing, likes and dislikes with cursors and totals,
  track reactions, playback state with last-write-wins, tracks batch

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mrleo1nidandClaude Opus 5.5 committed 2026-10-20 07:25:15 +03:00
1 parent 8b76aa3964
commit c05e523cbe
16 files changed
+3913 -155

No files matched your search

+14 -8
View File
@@ -12,6 +12,7 @@ import (
"pipu/api/internal/auth"
"pipu/api/internal/library"
"pipu/api/internal/platform/httpx"
"pipu/api/internal/playback"
"pipu/api/internal/search"
"pipu/api/internal/system"
)
@@ -24,6 +25,8 @@ type Deps struct {
// Library — хранилища, сканы и каталог.
Library *library.Handler
Search *search.Handler
// Playback — стрим, события, история, реакции, состояние плеера.
Playback *playback.Handler
// AuthService проверяет access-токены в middleware. nil — только для тестов служебных эндпоинтов.
AuthService *auth.Service
}
@@ -32,10 +35,11 @@ type Deps struct {
// По мере появления модулей (catalog, ...) они встраиваются сюда.
// Алиасы дают встроенным полям разные имена: у модулей одинаковое имя типа Handler.
type (
systemHandler = system.Handler
authHandler = auth.Handler
libraryHandler = library.Handler
searchHandler = search.Handler
systemHandler = system.Handler
authHandler = auth.Handler
libraryHandler = library.Handler
searchHandler = search.Handler
playbackHandler = playback.Handler
)
type handlers struct {
@@ -43,6 +47,7 @@ type handlers struct {
*authHandler
*libraryHandler
*searchHandler
*playbackHandler
}
var _ gen.StrictServerInterface = handlers{}
@@ -62,10 +67,11 @@ func NewRouter(d Deps) http.Handler {
r.NotFound(httpx.NotFound)
r.MethodNotAllowed(httpx.MethodNotAllowed)
strict := gen.NewStrictHandlerWithOptions(handlers{systemHandler: d.System, authHandler: d.Auth, libraryHandler: d.Library, searchHandler: d.Search}, nil, gen.StrictHTTPServerOptions{
RequestErrorHandlerFunc: httpx.RequestError,
ResponseErrorHandlerFunc: httpx.ResponseError(log),
})
strict := gen.NewStrictHandlerWithOptions(handlers{systemHandler: d.System, authHandler: d.Auth, libraryHandler: d.Library, searchHandler: d.Search, playbackHandler: d.Playback},
[]gen.StrictMiddlewareFunc{httpx.StrictRequest}, gen.StrictHTTPServerOptions{
RequestErrorHandlerFunc: httpx.RequestError,
ResponseErrorHandlerFunc: httpx.ResponseError(log),
})
return gen.HandlerWithOptions(strict, gen.ChiServerOptions{
BaseRouter: r,
// Ошибки разбора path- и query-параметров — тоже problem+json, а не текст по умолчанию.