121 lines
4 KiB
Go
121 lines
4 KiB
Go
package welcomemessages
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"telesrv/internal/domain"
|
|
"telesrv/internal/store"
|
|
)
|
|
|
|
type ChannelAccess interface {
|
|
ResolveChannel(ctx context.Context, userID, channelID int64) (domain.ChannelView, error)
|
|
}
|
|
|
|
type Option func(*Service)
|
|
|
|
func WithClock(now func() time.Time) Option {
|
|
return func(s *Service) {
|
|
if now != nil {
|
|
s.now = now
|
|
}
|
|
}
|
|
}
|
|
|
|
type Service struct {
|
|
messages store.WelcomeMessageStore
|
|
channels ChannelAccess
|
|
now func() time.Time
|
|
}
|
|
|
|
func NewService(messages store.WelcomeMessageStore, channels ChannelAccess, options ...Option) *Service {
|
|
s := &Service{messages: messages, channels: channels, now: time.Now}
|
|
for _, option := range options {
|
|
if option != nil {
|
|
option(s)
|
|
}
|
|
}
|
|
return s
|
|
}
|
|
|
|
// Authorize is the cheap gate RPC uses before resolving upload/media/rich
|
|
// content. Mutations call authorize again immediately before the store write so
|
|
// a concurrent demotion cannot turn this preflight into a stale capability.
|
|
func (s *Service) Authorize(ctx context.Context, userID int64, peer domain.Peer) error {
|
|
return s.authorize(ctx, userID, peer)
|
|
}
|
|
|
|
func (s *Service) Create(ctx context.Context, userID int64, peer domain.Peer, randomID int64, content domain.WelcomeMessageContent) (domain.WelcomeMessage, bool, error) {
|
|
if err := s.authorize(ctx, userID, peer); err != nil {
|
|
return domain.WelcomeMessage{}, false, err
|
|
}
|
|
if err := content.Validate(); err != nil {
|
|
return domain.WelcomeMessage{}, false, err
|
|
}
|
|
fingerprint, err := domain.WelcomeCreateFingerprint(peer, userID, randomID, content)
|
|
if err != nil {
|
|
return domain.WelcomeMessage{}, false, domain.ErrWelcomeMessageInvalid
|
|
}
|
|
return s.messages.CreateWelcomeMessage(ctx, domain.CreateWelcomeMessageRequest{
|
|
Peer: peer, CreatorUserID: userID, Date: int(s.now().Unix()), RandomID: randomID,
|
|
Content: content, CreateFingerprint: fingerprint,
|
|
})
|
|
}
|
|
|
|
func (s *Service) Edit(ctx context.Context, userID int64, peer domain.Peer, id int, fields domain.WelcomeMessageEditFields) (domain.WelcomeMessage, error) {
|
|
if err := s.authorize(ctx, userID, peer); err != nil {
|
|
return domain.WelcomeMessage{}, err
|
|
}
|
|
return s.messages.EditWelcomeMessage(ctx, domain.EditWelcomeMessageRequest{
|
|
Peer: peer, ID: id, EditDate: int(s.now().Unix()), Fields: fields,
|
|
})
|
|
}
|
|
|
|
func (s *Service) List(ctx context.Context, userID int64, peer domain.Peer, hash int64) (domain.WelcomeMessageList, error) {
|
|
if err := s.authorize(ctx, userID, peer); err != nil {
|
|
return domain.WelcomeMessageList{}, err
|
|
}
|
|
return s.messages.ListWelcomeMessages(ctx, peer, hash)
|
|
}
|
|
|
|
func (s *Service) Delete(ctx context.Context, userID int64, peer domain.Peer, id int) (bool, error) {
|
|
if err := s.authorize(ctx, userID, peer); err != nil {
|
|
return false, err
|
|
}
|
|
return s.messages.DeleteWelcomeMessage(ctx, peer, id)
|
|
}
|
|
|
|
func (s *Service) DeleteAll(ctx context.Context, userID int64, peer domain.Peer) (bool, error) {
|
|
if err := s.authorize(ctx, userID, peer); err != nil {
|
|
return false, err
|
|
}
|
|
return s.messages.DeleteAllWelcomeMessages(ctx, peer)
|
|
}
|
|
|
|
// HasAny is used only after the ordinary full-chat access check has succeeded.
|
|
// It deliberately does not require manage_welcome_messages so non-admin members
|
|
// receive the same has_welcome_messages projection as official clients.
|
|
func (s *Service) HasAny(ctx context.Context, peer domain.Peer) (bool, error) {
|
|
if s == nil || s.messages == nil || peer.Type != domain.PeerTypeChannel || peer.ID <= 0 {
|
|
return false, domain.ErrWelcomeMessageInvalid
|
|
}
|
|
return s.messages.HasWelcomeMessages(ctx, peer)
|
|
}
|
|
|
|
func (s *Service) authorize(ctx context.Context, userID int64, peer domain.Peer) error {
|
|
if s == nil || s.messages == nil || s.channels == nil || userID <= 0 ||
|
|
peer.Type != domain.PeerTypeChannel || peer.ID <= 0 {
|
|
return domain.ErrWelcomeMessageInvalid
|
|
}
|
|
view, err := s.channels.ResolveChannel(ctx, userID, peer.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if view.Channel.Monoforum {
|
|
return domain.ErrWelcomeMessagePeerInvalid
|
|
}
|
|
if !view.Self.CanManageWelcomeMessages() {
|
|
return domain.ErrWelcomeMessageForbidden
|
|
}
|
|
return nil
|
|
}
|