Files
pipu-music/services/api/internal/social/handler.go
T
mrleo1nid 11a16cd023
ci / api / api (push) Canceled after 0s
ci / module boundaries / boundaries (push) Canceled after 0s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Canceled after 11m29s
ci / db / db (push) Canceled after 11m22s
ci / mobile / mobile (Metro-бандл Android) (push) Canceled after 6m43s
ci / recommender / recommender (push) Canceled after 6m47s
ci / ts / ts (packages + web) (push) Canceled after 1s
feat(social): add listening activity, shared rooms and modern chat
2026-10-23 10:45:40 +03:00

128 lines
3.6 KiB
Go

// Package social owns friendships, private conversations and the persistent inbox.
package social
import (
"context"
"strings"
"unicode/utf8"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgxpool"
"pipu/api/internal/api/gen"
"pipu/api/internal/auth"
"pipu/api/internal/platform/apperr"
)
type ActivityPlaylistReader interface {
ReadSocialPlaylist(context.Context, uuid.UUID) (gen.Playlist, error)
}
type Handler struct {
store *Store
playlists ActivityPlaylistReader
}
func NewHandler(pool *pgxpool.Pool, playlists ActivityPlaylistReader) *Handler {
return &Handler{store: &Store{pool: pool}, playlists: playlists}
}
func userID(ctx context.Context) (uuid.UUID, error) {
p, ok := auth.PrincipalFrom(ctx)
if !ok {
return uuid.Nil, apperr.Unauthorized("auth.unauthorized", "sign in required")
}
return p.UserID, nil
}
func invalid() error {
return apperr.Validation("social", "social.invalid", "invalid social operation", nil)
}
func denied() error {
return apperr.Forbidden("social.unavailable", "friendship required or person unavailable")
}
func (h *Handler) GetSocial(ctx context.Context, _ gen.GetSocialRequestObject) (gen.GetSocialResponseObject, error) {
id, e := userID(ctx)
if e != nil {
return nil, e
}
v, e := h.store.inbox(ctx, id)
return gen.GetSocial200JSONResponse(v), e
}
func (h *Handler) SearchPeople(ctx context.Context, r gen.SearchPeopleRequestObject) (gen.SearchPeopleResponseObject, error) {
id, e := userID(ctx)
if e != nil {
return nil, e
}
q := strings.TrimSpace(r.Params.Q)
if utf8.RuneCountInString(q) < 2 || utf8.RuneCountInString(q) > 80 {
return nil, invalid()
}
v, e := h.store.people(ctx, id, q)
return gen.SearchPeople200JSONResponse(v), e
}
func (h *Handler) SocialAction(ctx context.Context, r gen.SocialActionRequestObject) (gen.SocialActionResponseObject, error) {
id, e := userID(ctx)
if e != nil {
return nil, e
}
if r.Body == nil || r.Body.TargetId == uuid.Nil || r.Body.TargetId == id {
return nil, invalid()
}
if e = h.store.action(ctx, id, *r.Body); e != nil {
return nil, e
}
return gen.SocialAction204Response{}, nil
}
func (h *Handler) GetDirectMessages(ctx context.Context, r gen.GetDirectMessagesRequestObject) (gen.GetDirectMessagesResponseObject, error) {
id, e := userID(ctx)
if e != nil {
return nil, e
}
if r.PeerId == uuid.Nil || r.PeerId == id || (r.Params.Before != nil && *r.Params.Before < 1) {
return nil, invalid()
}
q := ""
if r.Params.Q != nil {
q = strings.TrimSpace(*r.Params.Q)
}
if utf8.RuneCountInString(q) > 100 {
return nil, invalid()
}
v, e := h.store.messagePage(ctx, id, r.PeerId, r.Params.Before, q)
return gen.GetDirectMessages200JSONResponse(v), e
}
func validateMessage(m gen.SendDirectMessage) error {
if m.ReplyTo != nil && *m.ReplyTo == uuid.Nil {
return invalid()
}
if m.Id == uuid.Nil || utf8.RuneCountInString(m.Text) > 4000 {
return invalid()
}
switch m.AttachmentKind {
case "none":
if strings.TrimSpace(m.Text) == "" || m.AttachmentId != nil {
return invalid()
}
case "track", "playlist":
if m.AttachmentId == nil || *m.AttachmentId == uuid.Nil {
return invalid()
}
default:
return invalid()
}
return nil
}
func (h *Handler) SendDirectMessage(ctx context.Context, r gen.SendDirectMessageRequestObject) (gen.SendDirectMessageResponseObject, error) {
id, e := userID(ctx)
if e != nil {
return nil, e
}
if r.Body == nil || r.PeerId == id || r.PeerId == uuid.Nil {
return nil, invalid()
}
if e = validateMessage(*r.Body); e != nil {
return nil, e
}
v, e := h.store.send(ctx, id, r.PeerId, *r.Body)
return gen.SendDirectMessage200JSONResponse(v), e
}