github.com/oinume/lekcije@v0.0.0-20231017100347-5b4c5eb6ab24/backend/infrastructure/mysql/lesson.go (about) 1 package mysql 2 3 import ( 4 "context" 5 "database/sql" 6 "fmt" 7 "strings" 8 "time" 9 10 "github.com/morikuni/failure" 11 "github.com/volatiletech/sqlboiler/v4/boil" 12 "github.com/volatiletech/sqlboiler/v4/queries/qm" 13 "go.opentelemetry.io/otel" 14 "go.opentelemetry.io/otel/attribute" 15 16 "github.com/oinume/lekcije/backend/domain/config" 17 "github.com/oinume/lekcije/backend/domain/repository" 18 "github.com/oinume/lekcije/backend/errors" 19 "github.com/oinume/lekcije/backend/model" 20 "github.com/oinume/lekcije/backend/model2" 21 "github.com/oinume/lekcije/backend/util" 22 ) 23 24 type lessonRepository struct { 25 db *sql.DB 26 } 27 28 func NewLessonRepository(db *sql.DB) repository.Lesson { 29 return &lessonRepository{db: db} 30 } 31 32 func (r *lessonRepository) Create(ctx context.Context, lesson *model2.Lesson, reload bool) error { 33 if err := lesson.Insert(ctx, r.db, boil.Infer()); err != nil { 34 return failure.MarkUnexpected( 35 err, failure.Messagef("Create failed: teacherID=%v", lesson.TeacherID), 36 failure.Context{"teacherID": fmt.Sprint(lesson.TeacherID), "datetime": lesson.Datetime.String()}, 37 ) 38 } 39 if reload { 40 if err := lesson.Reload(ctx, r.db); err != nil { 41 return failure.MarkUnexpected( 42 err, failure.Messagef("Reload after Create failed: teacherID=%v", lesson.TeacherID), 43 failure.Context{"teacherID": fmt.Sprint(lesson.TeacherID), "datetime": lesson.Datetime.String()}, 44 ) 45 } 46 } 47 return nil 48 } 49 50 func (r *lessonRepository) FindAllByTeacherIDAndDatetimeAsMap( 51 ctx context.Context, teacherID uint, lessonsArgs []*model2.Lesson, 52 ) (map[string]*model2.Lesson, error) { 53 if len(lessonsArgs) == 0 { 54 return nil, nil 55 } 56 57 datetimes := make([]string, len(lessonsArgs)) 58 for i, l := range lessonsArgs { 59 datetimes[i] = l.Datetime.Format(model2.DBDatetimeFormat) 60 } 61 62 placeholder := model.Placeholders(util.StringToInterfaceSlice(datetimes...)) 63 values := []interface{}{teacherID} 64 values = append(values, util.StringToInterfaceSlice(datetimes...)...) 65 where := fmt.Sprintf("teacher_id = ? AND datetime IN (%s)", placeholder) 66 lessons, err := model2.Lessons(qm.Where(where, values...)).All(ctx, r.db) 67 if err != nil { 68 return nil, err 69 } 70 71 lessonsMap := make(map[string]*model2.Lesson, len(lessons)) 72 for _, l := range lessons { 73 // TODO: Use LessonDatetime type as key 74 lessonsMap[model2.LessonDatetime(l.Datetime).String()] = l 75 } 76 return lessonsMap, nil 77 } 78 79 func (r *lessonRepository) FindAllByTeacherIDsDatetimeBetween( 80 ctx context.Context, teacherID uint, 81 fromDate, toDate time.Time, 82 ) ([]*model2.Lesson, error) { 83 _, span := otel.Tracer(config.DefaultTracerName).Start(ctx, "lessonRepository.FindLessons") 84 span.SetAttributes(attribute.KeyValue{ 85 Key: "teacherID", 86 Value: attribute.Int64Value(int64(teacherID)), 87 }) 88 defer span.End() 89 90 const midnightAdd = 2 91 const format = "2006-01-02" 92 toDateAdded := toDate.Add(24 * midnightAdd * time.Hour) 93 lessons, err := model2.Lessons( 94 qm.Where( 95 "teacher_id = ? AND DATE(datetime) BETWEEN ? AND ?", 96 teacherID, fromDate.Format(format), toDateAdded.Format(format), 97 ), 98 qm.Limit(1000), 99 ).All(ctx, r.db) 100 if err != nil { 101 return nil, err 102 } 103 return lessons, nil 104 } 105 106 func (r *lessonRepository) FindByID(ctx context.Context, id uint64) (*model2.Lesson, error) { 107 return model2.Lessons(qm.Where("id = ?", id)).One(ctx, r.db) 108 } 109 110 func (r *lessonRepository) FindOrCreate(ctx context.Context, lesson *model2.Lesson, reload bool) (*model2.Lesson, error) { 111 var condition qm.QueryMod 112 if lesson.ID != 0 { 113 condition = qm.Where("id = ?", lesson.ID) 114 } else { 115 condition = qm.Where( 116 "teacher_id = ? AND datetime = ?", 117 lesson.TeacherID, lesson.Datetime.Format(model2.DBDatetimeFormat), 118 ) 119 } 120 found, err := model2.Lessons(condition).One(ctx, r.db) 121 if err != nil { 122 if errors.IsNotFound(err) { 123 if err := r.Create(ctx, lesson, reload); err != nil { 124 return nil, err 125 } 126 return lesson, nil 127 } 128 return nil, err 129 } 130 return found, nil // Do nothing when the lesson exists 131 } 132 133 func (r *lessonRepository) GetNewAvailableLessons(ctx context.Context, oldLessons, newLessons []*model2.Lesson) []*model2.Lesson { 134 // Pattern 135 // 2016-01-01 00:00@Any -> Available 136 oldLessonsMap := make(map[string]*model2.Lesson, len(oldLessons)) 137 newLessonsMap := make(map[string]*model2.Lesson, len(newLessons)) 138 availableLessons := make([]*model2.Lesson, 0, len(oldLessons)+len(newLessons)) 139 availableLessonsMap := make(map[string]*model2.Lesson, len(oldLessons)+len(newLessons)) 140 for _, l := range oldLessons { 141 oldLessonsMap[model2.LessonDatetime(l.Datetime).String()] = l // TODO: Use LessonDatetime type as key 142 } 143 for _, l := range newLessons { 144 newLessonsMap[model2.LessonDatetime(l.Datetime).String()] = l 145 } 146 for datetime, oldLesson := range oldLessonsMap { 147 newLesson, newLessonExists := newLessonsMap[datetime] 148 oldStatus := strings.ToLower(oldLesson.Status) 149 if newLessonExists && oldStatus != "available" && strings.ToLower(newLesson.Status) == "available" { 150 // exists in oldLessons and newLessons and "any status" -> "available" 151 availableLessons = append(availableLessons, newLesson) 152 availableLessonsMap[datetime] = newLesson 153 } 154 } 155 for _, l := range newLessons { 156 datetime := model2.LessonDatetime(l.Datetime).String() 157 if _, ok := oldLessonsMap[datetime]; !ok && strings.ToLower(l.Status) == "available" { 158 // not exists in oldLessons 159 availableLessons = append(availableLessons, l) 160 availableLessonsMap[datetime] = l 161 } 162 } 163 164 // TODO: sort availableLessonsMap by datetime 165 return availableLessons 166 } 167 168 func (r *lessonRepository) UpdateStatus(ctx context.Context, id uint64, newStatus string) (int64, error) { 169 lesson := &model2.Lesson{ 170 ID: id, 171 Status: newStatus, 172 } 173 rows, err := lesson.Update(ctx, r.db, boil.Whitelist("status")) 174 if err != nil { 175 return 0, failure.Translate(err, errors.Internal, failure.Messagef("UpdateStatus failed for %v", id)) 176 } 177 return rows, nil 178 }