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  }