feat(runner): implement empty classroom sync functionality
Some checks failed
Build Backend / build (push) Successful in 2m22s
Build Backend / build-docker (push) Failing after 3m3s

Add support for fetching and synchronizing empty classroom data. This includes new protobuf definitions for task payloads and results, database models, repository, service, and HTTP handlers.

- Add `TASK_TYPE_GET_EMPTY_CLASSROOM` to `TaskType` enum
- Implement `EmptyClassroom` model and auto-migration
- Add `EmptyClassroomHandler` with `GET` and `POST /sync` routes
- Implement `EmptyClassroomSyncService` and `EmptyClassroomRepository`
- Update dependency injection with wire sets
This commit is contained in:
2026-05-31 21:29:45 +08:00
parent 2084473fb5
commit 9356cb6876
13 changed files with 3007 additions and 48 deletions

View File

@@ -0,0 +1,135 @@
package service
import (
"context"
"encoding/json"
"fmt"
"with_you/internal/grpc/runner"
"with_you/internal/model"
"with_you/internal/repository"
pb "with_you/proto/runner"
"github.com/google/uuid"
"go.uber.org/zap"
)
type SyncEmptyClassroomsRequest struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
Semester string `json:"semester"`
WeekStart string `json:"week_start"`
WeekEnd string `json:"week_end"`
CampusCode string `json:"campus_code"`
}
type SyncEmptyClassroomsResult struct {
Success bool `json:"success"`
Message string `json:"message"`
SyncedCount int `json:"synced_count"`
ErrorMessage string `json:"error_message,omitempty"`
}
type EmptyClassroomSyncService interface {
SyncUserEmptyClassrooms(ctx context.Context, userID string, req *SyncEmptyClassroomsRequest) (*SyncEmptyClassroomsResult, error)
}
type emptyClassroomSyncService struct {
taskManager *runner.TaskManager
classroomRepo repository.EmptyClassroomRepository
logger *zap.Logger
}
func NewEmptyClassroomSyncService(
taskManager *runner.TaskManager,
classroomRepo repository.EmptyClassroomRepository,
logger *zap.Logger,
) EmptyClassroomSyncService {
return &emptyClassroomSyncService{
taskManager: taskManager,
classroomRepo: classroomRepo,
logger: logger,
}
}
func (s *emptyClassroomSyncService) SyncUserEmptyClassrooms(ctx context.Context, userID string, req *SyncEmptyClassroomsRequest) (*SyncEmptyClassroomsResult, error) {
payload := &pb.GetEmptyClassroomPayload{
Username: req.Username,
Password: req.Password,
Semester: req.Semester,
WeekStart: req.WeekStart,
WeekEnd: req.WeekEnd,
CampusCode: req.CampusCode,
}
taskResult, err := s.taskManager.SubmitTaskSync(ctx, pb.TaskType_TASK_TYPE_GET_EMPTY_CLASSROOM, payload)
if err != nil {
s.logger.Error("failed to submit empty classroom sync task",
zap.String("user_id", userID),
zap.Error(err))
return nil, fmt.Errorf("failed to submit task: %w", err)
}
if taskResult.Status != pb.TaskStatus_TASK_STATUS_SUCCESS {
return &SyncEmptyClassroomsResult{
Success: false,
Message: "空教室查询失败",
ErrorMessage: taskResult.ErrorMessage,
}, nil
}
var resultData pb.EmptyClassroomResultData
if err := json.Unmarshal(taskResult.Data, &resultData); err != nil {
s.logger.Error("failed to parse empty classroom result",
zap.String("user_id", userID),
zap.Error(err))
return nil, fmt.Errorf("failed to parse result: %w", err)
}
classrooms := s.convertClassrooms(userID, &resultData)
if err := s.saveClassrooms(ctx, userID, resultData.Semester, classrooms); err != nil {
s.logger.Error("failed to save empty classrooms",
zap.String("user_id", userID),
zap.Error(err))
return nil, fmt.Errorf("failed to save classrooms: %w", err)
}
s.logger.Info("empty classrooms sync completed",
zap.String("user_id", userID),
zap.Int("classroom_count", len(classrooms)))
return &SyncEmptyClassroomsResult{
Success: true,
Message: "空教室查询成功",
SyncedCount: len(classrooms),
}, nil
}
func (s *emptyClassroomSyncService) convertClassrooms(userID string, data *pb.EmptyClassroomResultData) []*model.EmptyClassroom {
classrooms := make([]*model.EmptyClassroom, 0, len(data.Classrooms))
for _, c := range data.Classrooms {
classrooms = append(classrooms, &model.EmptyClassroom{
ID: uuid.New().String(),
UserID: userID,
Semester: data.Semester,
Classroom: c.Classroom,
Weekday: c.Weekday,
Periods: c.Periods,
Weeks: c.Weeks,
})
}
return classrooms
}
func (s *emptyClassroomSyncService) saveClassrooms(ctx context.Context, userID, semester string, classrooms []*model.EmptyClassroom) error {
if err := s.classroomRepo.DeleteByUserAndSemester(ctx, userID, semester); err != nil {
return fmt.Errorf("failed to delete old classrooms: %w", err)
}
if len(classrooms) == 0 {
return nil
}
if err := s.classroomRepo.BatchCreate(ctx, classrooms); err != nil {
return fmt.Errorf("failed to create classrooms: %w", err)
}
return nil
}