feat(schedule): add course table screens and navigation

Add complete schedule functionality including:
- Schedule screen with weekly course table view
- Course detail screen with transparent modal presentation
- New ScheduleStack navigator integrated into main tab bar
- Schedule service for API interactions
- Type definitions for course entities

Also includes bug fixes for group invite/request handlers
to include required groupId parameter.
This commit is contained in:
2026-03-12 08:38:14 +08:00
parent 21293644b8
commit 0a0cbacbcc
25 changed files with 3050 additions and 260 deletions

View File

@@ -2,6 +2,9 @@ package repository
import (
"carrot_bbs/internal/model"
"context"
"fmt"
"strings"
"time"
"gorm.io/gorm"
@@ -172,7 +175,7 @@ func (r *MessageRepository) GetParticipant(conversationID string, userID string)
if err == gorm.ErrRecordNotFound {
// 检查会话是否存在
var conv model.Conversation
if err := r.db.First(&conv, conversationID).Error; err == nil {
if err := r.db.Where("id = ?", conversationID).First(&conv).Error; err == nil {
// 会话存在,添加参与者
participant = model.ConversationParticipant{
ConversationID: conversationID,
@@ -284,7 +287,7 @@ func (r *MessageRepository) UpdateConversationLastSeq(conversationID string, seq
// GetNextSeq 获取会话的下一个seq值
func (r *MessageRepository) GetNextSeq(conversationID string) (int64, error) {
var conv model.Conversation
err := r.db.Select("last_seq").First(&conv, conversationID).Error
err := r.db.Select("last_seq").Where("id = ?", conversationID).First(&conv).Error
if err != nil {
return 0, err
}
@@ -296,7 +299,7 @@ func (r *MessageRepository) CreateMessageWithSeq(msg *model.Message) error {
return r.db.Transaction(func(tx *gorm.DB) error {
// 获取当前seq并+1
var conv model.Conversation
if err := tx.Select("last_seq").First(&conv, msg.ConversationID).Error; err != nil {
if err := tx.Select("last_seq").Where("id = ?", msg.ConversationID).First(&conv).Error; err != nil {
return err
}
@@ -522,3 +525,117 @@ func (r *MessageRepository) HideConversationForUser(conversationID, userID strin
Where("conversation_id = ? AND user_id = ?", conversationID, userID).
Update("hidden_at", &now).Error
}
// ParticipantUpdate 参与者更新数据
type ParticipantUpdate struct {
ConversationID string
UserID string
LastReadSeq int64
}
// BatchWriteMessages 批量写入消息
// 使用 GORM 的 CreateInBatches 实现高效批量插入
func (r *MessageRepository) BatchWriteMessages(ctx context.Context, messages []*model.Message) error {
if len(messages) == 0 {
return nil
}
return r.db.WithContext(ctx).CreateInBatches(messages, 100).Error
}
// BatchUpdateParticipants 批量更新参与者(使用 CASE WHEN 优化)
// 使用单条 SQL 更新多条记录,避免循环执行 UPDATE
func (r *MessageRepository) BatchUpdateParticipants(ctx context.Context, updates []ParticipantUpdate) error {
if len(updates) == 0 {
return nil
}
// 构建 CASE WHEN 批量更新 SQL
// UPDATE conversation_participants
// SET last_read_seq = CASE
// WHEN (conversation_id = '1' AND user_id = 'a') THEN 10
// WHEN (conversation_id = '2' AND user_id = 'b') THEN 20
// END,
// updated_at = ?
// WHERE (conversation_id = '1' AND user_id = 'a')
// OR (conversation_id = '2' AND user_id = 'b')
var cases []string
var whereClauses []string
var args []interface{}
for _, u := range updates {
cases = append(cases, "WHEN (conversation_id = ? AND user_id = ?) THEN ?")
whereClauses = append(whereClauses, "(conversation_id = ? AND user_id = ?)")
args = append(args, u.ConversationID, u.UserID, u.LastReadSeq, u.ConversationID, u.UserID)
}
sql := fmt.Sprintf(`
UPDATE conversation_participants
SET last_read_seq = CASE %s END,
updated_at = ?
WHERE %s
`, strings.Join(cases, " "), strings.Join(whereClauses, " OR "))
args = append(args, time.Now())
return r.db.WithContext(ctx).Exec(sql, args...).Error
}
// UpdateConversationLastSeqWithContext 更新会话最后消息序号
func (r *MessageRepository) UpdateConversationLastSeqWithContext(ctx context.Context, convID string, lastSeq int64, lastMsgTime time.Time) error {
return r.db.WithContext(ctx).
Model(&model.Conversation{}).
Where("id = ?", convID).
Updates(map[string]interface{}{
"last_seq": lastSeq,
"last_msg_time": lastMsgTime,
"updated_at": time.Now(),
}).Error
}
// BatchWriteMessagesWithTx 在事务中批量写入消息
func (r *MessageRepository) BatchWriteMessagesWithTx(tx *gorm.DB, messages []*model.Message) error {
if len(messages) == 0 {
return nil
}
return tx.CreateInBatches(messages, 100).Error
}
// BatchUpdateParticipantsWithTx 在事务中批量更新参与者
func (r *MessageRepository) BatchUpdateParticipantsWithTx(tx *gorm.DB, updates []ParticipantUpdate) error {
if len(updates) == 0 {
return nil
}
var cases []string
var whereClauses []string
var args []interface{}
for _, u := range updates {
cases = append(cases, "WHEN (conversation_id = ? AND user_id = ?) THEN ?")
whereClauses = append(whereClauses, "(conversation_id = ? AND user_id = ?)")
args = append(args, u.ConversationID, u.UserID, u.LastReadSeq, u.ConversationID, u.UserID)
}
sql := fmt.Sprintf(`
UPDATE conversation_participants
SET last_read_seq = CASE %s END,
updated_at = ?
WHERE %s
`, strings.Join(cases, " "), strings.Join(whereClauses, " OR "))
args = append(args, time.Now())
return tx.Exec(sql, args...).Error
}
// UpdateConversationLastSeqWithTx 在事务中更新会话最后消息序号
func (r *MessageRepository) UpdateConversationLastSeqWithTx(tx *gorm.DB, convID string, lastSeq int64, lastMsgTime time.Time) error {
return tx.Model(&model.Conversation{}).
Where("id = ?", convID).
Updates(map[string]interface{}{
"last_seq": lastSeq,
"last_msg_time": lastMsgTime,
"updated_at": time.Now(),
}).Error
}