owpengram-server/internal/app/groupcalls/service.go

203 lines
7.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package groupcalls 实现超级群语音聊天group call的信令业务层
// ID/access_hash 分配与 store 编排。权限admin/成员资格)由 rpc 层校验,
// version 单调性与并发串行化由 store 层事务保证。
package groupcalls
import (
"context"
"crypto/rand"
"encoding/base64"
"encoding/binary"
"fmt"
"telesrv/internal/domain"
"telesrv/internal/store"
)
// Service 是群通话业务服务。
type Service struct {
store store.GroupCallStore
}
// NewService 创建群通话服务。
func NewService(st store.GroupCallStore) *Service {
return &Service{store: st}
}
// Create 分配 id/access_hash 并建会。
func (s *Service) Create(ctx context.Context, channelID, creatorUserID int64, title string, now int) (domain.GroupCall, error) {
id, err := randomPositiveInt64()
if err != nil {
return domain.GroupCall{}, err
}
accessHash, err := randomPositiveInt64()
if err != nil {
return domain.GroupCall{}, err
}
return s.store.CreateGroupCall(ctx, domain.GroupCall{
ID: id,
AccessHash: accessHash,
ChannelID: channelID,
CreatorUserID: creatorUserID,
Title: title,
Version: 1,
CreatedAt: now,
})
}
// CreateConference 分配 id/access_hash/slug 并创建 ad-hoc conference call。
func (s *Service) CreateConference(ctx context.Context, creatorUserID, randomID, migratedFromPhoneCallID int64, now int) (domain.GroupCall, error) {
for i := 0; i < 8; i++ {
id, err := randomPositiveInt64()
if err != nil {
return domain.GroupCall{}, err
}
accessHash, err := randomPositiveInt64()
if err != nil {
return domain.GroupCall{}, err
}
slug, err := randomSlug()
if err != nil {
return domain.GroupCall{}, err
}
call, err := s.store.CreateConferenceCall(ctx, domain.GroupCall{
ID: id,
AccessHash: accessHash,
CreatorUserID: creatorUserID,
Kind: domain.GroupCallKindConference,
Version: 1,
CreatedAt: now,
InviteSlug: slug,
InviteLink: conferenceInviteLink(slug),
RandomID: randomID,
MigratedFromPhoneCallID: migratedFromPhoneCallID,
})
if err == nil {
return call, nil
}
if err != domain.ErrGroupCallInvalid {
return domain.GroupCall{}, err
}
}
return domain.GroupCall{}, fmt.Errorf("groupcalls: exhausted conference slug attempts")
}
func (s *Service) Get(ctx context.Context, callID int64) (domain.GroupCall, bool, error) {
return s.store.GetGroupCall(ctx, callID)
}
func (s *Service) GetBySlug(ctx context.Context, slug string) (domain.GroupCall, bool, error) {
return s.store.GetGroupCallBySlug(ctx, slug)
}
func (s *Service) GetByInviteMessage(ctx context.Context, userID int64, msgID int) (domain.GroupCall, domain.GroupCallInvite, bool, error) {
return s.store.GetGroupCallByInviteMessage(ctx, userID, msgID)
}
func (s *Service) Join(ctx context.Context, req domain.JoinGroupCallRequest) (domain.GroupCallMutation, error) {
return s.store.JoinGroupCall(ctx, req)
}
func (s *Service) Leave(ctx context.Context, callID, userID int64, now int) (domain.GroupCallMutation, error) {
return s.store.LeaveGroupCall(ctx, callID, userID, now)
}
func (s *Service) RemoveConferenceParticipants(ctx context.Context, req domain.RemoveConferenceCallParticipantsRequest) (domain.RemoveConferenceCallParticipantsResult, error) {
return s.store.RemoveConferenceCallParticipants(ctx, req)
}
func (s *Service) Discard(ctx context.Context, callID int64, now int) (domain.GroupCall, []domain.GroupCallParticipant, error) {
return s.store.DiscardGroupCall(ctx, callID, now)
}
func (s *Service) Touch(ctx context.Context, callID, userID int64, now int) ([]int64, bool, error) {
return s.store.TouchParticipant(ctx, callID, userID, now)
}
func (s *Service) Participant(ctx context.Context, callID, userID int64) (domain.GroupCallParticipant, bool, error) {
return s.store.GetParticipant(ctx, callID, userID)
}
func (s *Service) Participants(ctx context.Context, callID int64, offset string, limit int) (domain.GroupCallParticipantPage, error) {
return s.store.ListParticipants(ctx, callID, offset, limit)
}
func (s *Service) UpdateParticipant(ctx context.Context, callID, userID int64, update domain.GroupCallParticipantUpdate) (domain.GroupCallMutation, bool, error) {
return s.store.UpdateParticipant(ctx, callID, userID, update)
}
func (s *Service) SetTitle(ctx context.Context, callID int64, title string) (domain.GroupCall, bool, error) {
return s.store.SetGroupCallTitle(ctx, callID, title)
}
func (s *Service) SetJoinMuted(ctx context.Context, callID int64, joinMuted bool) (domain.GroupCall, bool, error) {
return s.store.SetGroupCallJoinMuted(ctx, callID, joinMuted)
}
func (s *Service) SetStartedMessageID(ctx context.Context, callID int64, msgID int) error {
return s.store.SetStartedMessageID(ctx, callID, msgID)
}
func (s *Service) SweepStale(ctx context.Context, checkOlderThan, now, limit int) ([]domain.GroupCallMutation, error) {
return s.store.SweepStaleParticipants(ctx, checkOlderThan, now, limit)
}
func (s *Service) ResetAllParticipants(ctx context.Context, now int) ([]domain.GroupCall, error) {
return s.store.ResetAllParticipants(ctx, now)
}
func (s *Service) NextRaiseHandRating(ctx context.Context, callID int64) (int64, error) {
return s.store.NextRaiseHandRating(ctx, callID)
}
func (s *Service) SetParticipantOverride(ctx context.Context, callID, setterUserID, targetUserID int64, override domain.GroupCallParticipantOverride, clear bool) error {
return s.store.SetParticipantOverride(ctx, callID, setterUserID, targetUserID, override, clear)
}
func (s *Service) ParticipantOverride(ctx context.Context, callID, setterUserID, targetUserID int64) (domain.GroupCallParticipantOverride, bool, error) {
return s.store.GetParticipantOverride(ctx, callID, setterUserID, targetUserID)
}
func (s *Service) CreateConferenceInvite(ctx context.Context, invite domain.GroupCallInvite) (domain.GroupCallInvite, error) {
return s.store.CreateConferenceInvite(ctx, invite)
}
func (s *Service) SetConferenceInviteStatus(ctx context.Context, callID, inviteeUserID int64, msgID int, status domain.GroupCallInviteStatus, now int) (domain.GroupCallInvite, bool, error) {
return s.store.SetConferenceInviteStatus(ctx, callID, inviteeUserID, msgID, status, now)
}
func (s *Service) ConferenceRecipients(ctx context.Context, callID int64) ([]int64, error) {
return s.store.ListConferenceRecipientUserIDs(ctx, callID)
}
func (s *Service) AppendChainBlock(ctx context.Context, block domain.GroupCallChainBlock) (domain.GroupCallChainBlock, error) {
return s.store.AppendGroupCallChainBlock(ctx, block)
}
func (s *Service) ChainBlocks(ctx context.Context, callID int64, subChainID, offset, limit int) (domain.GroupCallChainBlockPage, error) {
return s.store.ListGroupCallChainBlocks(ctx, callID, subChainID, offset, limit)
}
func randomPositiveInt64() (int64, error) {
var buf [8]byte
if _, err := rand.Read(buf[:]); err != nil {
return 0, fmt.Errorf("groupcalls: random id: %w", err)
}
v := int64(binary.BigEndian.Uint64(buf[:]) >> 1)
if v == 0 {
v = 1
}
return v, nil
}
func randomSlug() (string, error) {
var buf [18]byte
if _, err := rand.Read(buf[:]); err != nil {
return "", fmt.Errorf("groupcalls: random slug: %w", err)
}
return base64.RawURLEncoding.EncodeToString(buf[:]), nil
}
func conferenceInviteLink(slug string) string {
return "https://telesrv.net/call/" + slug + "?slug=" + slug
}