removed all "paid" features - no more stars, gifts, or grams

This commit is contained in:
onysd 2026-08-07 01:50:10 +03:00
parent d4451d753c
commit 21d8e91756
165 changed files with 318 additions and 40948 deletions

View file

@ -150,32 +150,10 @@ func (s *ChannelStore) ToggleSuggestedPostApproval(ctx context.Context, req doma
return domain.ToggleSuggestedPostApprovalResult{}, err
}
} else {
stars, ton, enough, err := reserveSuggestedPostPaymentTx(ctx, tx, original.SavedPeer.ID, parent.ID, price, req.Date)
if err != nil {
return domain.ToggleSuggestedPostApprovalResult{}, err
}
result.PayerStarsBalance, result.PayerTONBalance = stars, ton
if !enough {
if found {
result, err = s.loadSuggestedPostResultTx(ctx, tx, existing, true)
if err != nil {
return domain.ToggleSuggestedPostApprovalResult{}, err
}
result.PayerStarsBalance, result.PayerTONBalance = stars, ton
} else {
result.ServiceMessage, result.ServiceEvent, err = s.insertSuggestedPostServiceTx(ctx, tx, mono, parent, req.UserID, fromSubscriber && manager.CanManageDirectMessages(), original.SavedPeer, original.ID, req.Date, domain.ChannelMessageAction{
Type: domain.ChannelActionSuggestedPostApproval, SuggestedPostBalanceTooLow: true,
SuggestedPostScheduleDate: scheduleDate, SuggestedPostPrice: price,
})
if err != nil {
return domain.ToggleSuggestedPostApprovalResult{}, err
}
result.State = domain.SuggestedPostStateBalanceLow
if err := upsertSuggestedPostApprovalTx(ctx, tx, persistedSuggestedPostApproval{monoforumID: mono.ID, messageID: original.ID, parentID: parent.ID, actorID: req.UserID, payerID: original.SavedPeer.ID, state: result.State, price: price, scheduleDate: scheduleDate, approvalServiceID: result.ServiceMessage.ID}, req.Date); err != nil {
return domain.ToggleSuggestedPostApprovalResult{}, err
}
}
} else {
// telesrv has no Stars economy: a suggested post is approved for free
// regardless of any price attached to it, so the balance-check/collect
// step and its "balance too low" retry state are skipped entirely.
{
effectivePublishDate := scheduleDate
original.SuggestedPost.Accepted, original.SuggestedPost.Rejected, original.SuggestedPost.ScheduleDate = true, false, effectivePublishDate
original, result.OriginalEvent, err = s.persistSuggestedPostEditTx(ctx, tx, original, req.UserID, req.Date)
@ -195,12 +173,10 @@ func (s *ChannelStore) ToggleSuggestedPostApproval(ctx context.Context, req doma
}
result.Published = &published
record.publishedMessageID = published.Message.ID
if price == nil {
record.state = domain.SuggestedPostStateCompleted
} else {
record.state = domain.SuggestedPostStatePublished
record.settlementDue = req.Date + suggestedPostSettlementAge
}
// telesrv has no Stars economy: there is nothing to settle after
// publish (no charge was ever collected), so a published post
// goes straight to Completed regardless of any attached price.
record.state = domain.SuggestedPostStateCompleted
}
result.State = record.state
if err := upsertSuggestedPostApprovalTx(ctx, tx, record, req.Date); err != nil {
@ -301,56 +277,6 @@ func (s *ChannelStore) publishSuggestedPostTx(ctx context.Context, tx pgx.Tx, pa
return domain.SendChannelMessageResult{Channel: parent, Message: msg, Event: event, Recipients: recipients}, nil
}
func reserveSuggestedPostPaymentTx(ctx context.Context, tx pgx.Tx, payerID, parentID int64, price *domain.SuggestedPostPrice, date int) (*domain.StarsBalance, *int64, bool, error) {
if price == nil {
return nil, nil, true, nil
}
switch price.Kind {
case domain.SuggestedPostPriceStars:
if price.Nanos != 0 || price.Amount <= 0 {
return nil, nil, false, domain.ErrSuggestedPostInvalid
}
balance := domain.StarsBalance{UserID: payerID}
err := tx.QueryRow(ctx, `SELECT balance,granted FROM stars_balances WHERE user_id=$1 FOR UPDATE`, payerID).Scan(&balance.Balance, &balance.Granted)
if errors.Is(err, pgx.ErrNoRows) {
return &balance, nil, false, nil
}
if err != nil {
return nil, nil, false, err
}
if balance.Balance < price.Amount {
return &balance, nil, false, nil
}
if err := tx.QueryRow(ctx, `UPDATE stars_balances SET balance=balance-$2,updated_at=now() WHERE user_id=$1 RETURNING balance`, payerID, price.Amount).Scan(&balance.Balance); err != nil {
return nil, nil, false, err
}
if err := insertStarsTxn(ctx, tx, payerID, -price.Amount, domain.StarsReasonSuggestedPost, domain.Peer{Type: domain.PeerTypeChannel, ID: parentID}, date, "Suggested post escrow", ""); err != nil {
return nil, nil, false, err
}
return &balance, nil, true, nil
case domain.SuggestedPostPriceTON:
var balance int64
err := tx.QueryRow(ctx, `SELECT balance_nanoton FROM ton_balances WHERE user_id=$1 FOR UPDATE`, payerID).Scan(&balance)
if errors.Is(err, pgx.ErrNoRows) {
return nil, &balance, false, nil
}
if err != nil {
return nil, nil, false, err
}
if balance < price.Amount {
return nil, &balance, false, nil
}
if err := tx.QueryRow(ctx, `UPDATE ton_balances SET balance_nanoton=balance_nanoton-$2,updated_at=now() WHERE user_id=$1 RETURNING balance_nanoton`, payerID, price.Amount).Scan(&balance); err != nil {
return nil, nil, false, err
}
if _, err := tx.Exec(ctx, `INSERT INTO ton_transactions(user_id,amount_nanoton,reason,peer_type,peer_id,date) VALUES($1,$2,$3,'channel',$4,$5)`, payerID, -price.Amount, string(domain.StarsReasonSuggestedPost), parentID, date); err != nil {
return nil, nil, false, err
}
return nil, &balance, true, nil
default:
return nil, nil, false, domain.ErrSuggestedPostInvalid
}
}
func monoforumManagerRecipientsTx(ctx context.Context, tx pgx.Tx, parentID, subscriberID int64) ([]int64, error) {
rows, err := tx.Query(ctx, `SELECT user_id FROM channel_members WHERE channel_id=$1 AND status='active' AND (role='creator' OR (role='admin' AND COALESCE((admin_rights->>'ManageDirectMessages')::boolean,false))) ORDER BY user_id`, parentID)
@ -560,19 +486,12 @@ func (s *ChannelStore) processSuggestedPostLifecycleOne(ctx context.Context, mon
}
result := domain.ToggleSuggestedPostApprovalResult{Monoforum: mono, Parent: parent, SavedPeer: domain.Peer{Type: domain.PeerTypeUser, ID: row.payerID}, State: row.state, Recipients: recipients}
changed := false
// telesrv has no Stars economy: nothing was ever charged, so a deleted
// scheduled post is simply dropped (Refunded is the closest existing
// terminal state, reused here so downstream event handling stays uniform)
// and a published post is Completed immediately -- there is no settlement
// window and no refund path to run.
if row.state == domain.SuggestedPostStateScheduled && original.Deleted {
if row.price != nil {
stars, ton, err := refundSuggestedPostPaymentTx(ctx, tx, row.payerID, row.parentID, row.price, now)
if err != nil {
return result, false, err
}
result.PayerStarsBalance, result.PayerTONBalance = stars, ton
service, event, err := s.insertSuggestedPostServiceTx(ctx, tx, mono, parent, row.actorID, true, row.savedPeer(), row.messageID, now, domain.ChannelMessageAction{Type: domain.ChannelActionSuggestedPostRefund})
if err != nil {
return result, false, err
}
result.ServiceMessage, result.ServiceEvent, row.finalServiceID = service, event, service.ID
}
row.state, result.State, changed = domain.SuggestedPostStateRefunded, domain.SuggestedPostStateRefunded, true
}
if row.state == domain.SuggestedPostStateScheduled && row.scheduleDate <= now {
@ -582,47 +501,10 @@ func (s *ChannelStore) processSuggestedPostLifecycleOne(ctx context.Context, mon
}
result.Published = &published
row.publishedMessageID = published.Message.ID
if row.price == nil {
row.state = domain.SuggestedPostStateCompleted
} else {
row.state = domain.SuggestedPostStatePublished
row.settlementDue = now + suggestedPostSettlementAge
}
row.state = domain.SuggestedPostStateCompleted
result.State = row.state
changed = true
}
if row.state == domain.SuggestedPostStatePublished {
var deleted bool
var deleteDate int
if err := tx.QueryRow(ctx, `SELECT deleted,delete_date FROM channel_messages WHERE channel_id=$1 AND id=$2 FOR SHARE`, row.parentID, row.publishedMessageID).Scan(&deleted, &deleteDate); err != nil {
return result, false, err
}
if deleted && (deleteDate == 0 || deleteDate < row.settlementDue) {
stars, ton, err := refundSuggestedPostPaymentTx(ctx, tx, row.payerID, row.parentID, row.price, now)
if err != nil {
return result, false, err
}
result.PayerStarsBalance, result.PayerTONBalance = stars, ton
service, event, err := s.insertSuggestedPostServiceTx(ctx, tx, mono, parent, row.actorID, true, row.savedPeer(), row.messageID, now, domain.ChannelMessageAction{Type: domain.ChannelActionSuggestedPostRefund})
if err != nil {
return result, false, err
}
result.ServiceMessage, result.ServiceEvent = service, event
row.state, row.finalServiceID, result.State = domain.SuggestedPostStateRefunded, service.ID, domain.SuggestedPostStateRefunded
changed = true
} else if row.settlementDue <= now {
if err := settleSuggestedPostPaymentTx(ctx, tx, row.actorID, row.payerID, row.parentID, row.price, now); err != nil {
return result, false, err
}
service, event, err := s.insertSuggestedPostServiceTx(ctx, tx, mono, parent, row.actorID, true, row.savedPeer(), row.messageID, now, domain.ChannelMessageAction{Type: domain.ChannelActionSuggestedPostSuccess, SuggestedPostPrice: cloneSuggestedPricePG(row.price)})
if err != nil {
return result, false, err
}
result.ServiceMessage, result.ServiceEvent = service, event
row.state, row.finalServiceID, result.State = domain.SuggestedPostStateCompleted, service.ID, domain.SuggestedPostStateCompleted
changed = true
}
}
if !changed {
if err := tx.Commit(ctx); err != nil {
return result, false, err
@ -652,56 +534,4 @@ func (r persistedSuggestedPostApproval) savedPeer() domain.Peer {
return domain.Peer{Type: domain.PeerTypeUser, ID: r.payerID}
}
func refundSuggestedPostPaymentTx(ctx context.Context, tx pgx.Tx, payerID, parentID int64, price *domain.SuggestedPostPrice, date int) (*domain.StarsBalance, *int64, error) {
if price == nil {
return nil, nil, nil
}
switch price.Kind {
case domain.SuggestedPostPriceStars:
balance := domain.StarsBalance{UserID: payerID, Granted: true}
if err := tx.QueryRow(ctx, `INSERT INTO stars_balances(user_id,balance,granted) VALUES($1,$2,true) ON CONFLICT(user_id) DO UPDATE SET balance=stars_balances.balance+EXCLUDED.balance,updated_at=now() RETURNING balance,granted`, payerID, price.Amount).Scan(&balance.Balance, &balance.Granted); err != nil {
return nil, nil, err
}
if err := insertStarsTxn(ctx, tx, payerID, price.Amount, domain.StarsReasonSuggestedPost, domain.Peer{Type: domain.PeerTypeChannel, ID: parentID}, date, "Suggested post refund", ""); err != nil {
return nil, nil, err
}
return &balance, nil, nil
case domain.SuggestedPostPriceTON:
var balance int64
if err := tx.QueryRow(ctx, `INSERT INTO ton_balances(user_id,balance_nanoton,granted) VALUES($1,$2,true) ON CONFLICT(user_id) DO UPDATE SET balance_nanoton=ton_balances.balance_nanoton+EXCLUDED.balance_nanoton,updated_at=now() RETURNING balance_nanoton`, payerID, price.Amount).Scan(&balance); err != nil {
return nil, nil, err
}
if _, err := tx.Exec(ctx, `INSERT INTO ton_transactions(user_id,amount_nanoton,reason,peer_type,peer_id,date) VALUES($1,$2,$3,'channel',$4,$5)`, payerID, price.Amount, string(domain.StarsReasonSuggestedPost), parentID, date); err != nil {
return nil, nil, err
}
return nil, &balance, nil
default:
return nil, nil, domain.ErrSuggestedPostInvalid
}
}
func settleSuggestedPostPaymentTx(ctx context.Context, tx pgx.Tx, actorID, payerID, parentID int64, price *domain.SuggestedPostPrice, date int) error {
if price == nil {
return nil
}
credit := price.Amount * paidMessageChannelCommissionPermille / 1000
if credit <= 0 {
return nil
}
switch price.Kind {
case domain.SuggestedPostPriceStars:
if _, err := tx.Exec(ctx, `INSERT INTO channel_stars_balances(channel_id,balance) VALUES($1,$2) ON CONFLICT(channel_id) DO UPDATE SET balance=channel_stars_balances.balance+EXCLUDED.balance,updated_at=now()`, parentID, credit); err != nil {
return err
}
_, err := tx.Exec(ctx, `INSERT INTO channel_stars_transactions(channel_id,actor_user_id,amount,reason,peer_type,peer_id,date) VALUES($1,$2,$3,$4,'user',$5,$6)`, parentID, actorID, credit, string(domain.StarsReasonSuggestedPost), payerID, date)
return err
case domain.SuggestedPostPriceTON:
if _, err := tx.Exec(ctx, `INSERT INTO channel_ton_balances(channel_id,balance_nanoton) VALUES($1,$2) ON CONFLICT(channel_id) DO UPDATE SET balance_nanoton=channel_ton_balances.balance_nanoton+EXCLUDED.balance_nanoton,updated_at=now()`, parentID, credit); err != nil {
return err
}
_, err := tx.Exec(ctx, `INSERT INTO channel_ton_transactions(channel_id,actor_user_id,amount_nanoton,reason,peer_type,peer_id,date) VALUES($1,$2,$3,$4,'user',$5,$6)`, parentID, actorID, credit, string(domain.StarsReasonSuggestedPost), payerID, date)
return err
default:
return domain.ErrSuggestedPostInvalid
}
}