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:
@@ -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
|
||||
}
|
||||
|
||||
66
internal/repository/schedule_repo.go
Normal file
66
internal/repository/schedule_repo.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"carrot_bbs/internal/model"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type ScheduleRepository interface {
|
||||
ListByUserID(userID string) ([]*model.ScheduleCourse, error)
|
||||
GetByID(id string) (*model.ScheduleCourse, error)
|
||||
Create(course *model.ScheduleCourse) error
|
||||
Update(course *model.ScheduleCourse) error
|
||||
DeleteByID(id string) error
|
||||
ExistsColorByUser(userID, color, excludeID string) (bool, error)
|
||||
}
|
||||
|
||||
type scheduleRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewScheduleRepository(db *gorm.DB) ScheduleRepository {
|
||||
return &scheduleRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *scheduleRepository) ListByUserID(userID string) ([]*model.ScheduleCourse, error) {
|
||||
var courses []*model.ScheduleCourse
|
||||
err := r.db.
|
||||
Where("user_id = ?", userID).
|
||||
Order("day_of_week ASC, start_section ASC, created_at ASC").
|
||||
Find(&courses).Error
|
||||
return courses, err
|
||||
}
|
||||
|
||||
func (r *scheduleRepository) Create(course *model.ScheduleCourse) error {
|
||||
return r.db.Create(course).Error
|
||||
}
|
||||
|
||||
func (r *scheduleRepository) GetByID(id string) (*model.ScheduleCourse, error) {
|
||||
var course model.ScheduleCourse
|
||||
if err := r.db.Where("id = ?", id).First(&course).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &course, nil
|
||||
}
|
||||
|
||||
func (r *scheduleRepository) Update(course *model.ScheduleCourse) error {
|
||||
return r.db.Save(course).Error
|
||||
}
|
||||
|
||||
func (r *scheduleRepository) DeleteByID(id string) error {
|
||||
return r.db.Delete(&model.ScheduleCourse{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
func (r *scheduleRepository) ExistsColorByUser(userID, color, excludeID string) (bool, error) {
|
||||
var count int64
|
||||
query := r.db.Model(&model.ScheduleCourse{}).
|
||||
Where("user_id = ? AND LOWER(color) = LOWER(?)", userID, color)
|
||||
if excludeID != "" {
|
||||
query = query.Where("id <> ?", excludeID)
|
||||
}
|
||||
if err := query.Count(&count).Error; err != nil {
|
||||
return false, err
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
Reference in New Issue
Block a user