package question import ( "context" "database/sql" "errors" "fmt" "github.com/lib/pq" "gitea.pena/SQuiz/common/dal/sqlcgen" "gitea.pena/SQuiz/common/model" "sort" "strings" "sync" "time" ) type Deps struct { Queries *sqlcgen.Queries Pool *sql.DB } type QuestionRepository struct { queries *sqlcgen.Queries pool *sql.DB } func NewQuestionRepository(deps Deps) *QuestionRepository { return &QuestionRepository{ queries: deps.Queries, pool: deps.Pool, } } // test + func (r *QuestionRepository) CreateQuestion(ctx context.Context, record *model.Question) (uint64, error) { params := sqlcgen.InsertQuestionParams{ QuizID: int64(record.QuizId), Title: record.Title, Description: sql.NullString{String: record.Description, Valid: true}, Questiontype: record.Type, Required: sql.NullBool{Bool: record.Required, Valid: true}, Page: sql.NullInt16{Int16: int16(record.Page), Valid: true}, Content: sql.NullString{String: record.Content, Valid: true}, ParentIds: record.ParentIds, UpdatedAt: sql.NullTime{Time: time.Now(), Valid: true}, } data, err := r.queries.InsertQuestion(ctx, params) if err != nil { return 0, err } record.Id = uint64(data.ID) record.CreatedAt = data.CreatedAt.Time record.UpdatedAt = data.UpdatedAt.Time return record.Id, nil } // test + // GetQuestionList function for get data page from db func (r *QuestionRepository) GetQuestionList( ctx context.Context, limit, offset, from, to, quizId uint64, deleted, required bool, search, qType string) ([]model.Question, uint64, error) { query := ` SELECT que.* FROM question as que JOIN quiz as qui on que.quiz_id = ANY(qui.parent_ids) or que.quiz_id = qui.id %s ORDER BY que.page, que.created_at ASC LIMIT $%d OFFSET $%d; ` queryCnt := `SELECT count(1) FROM question as que JOIN quiz as qui on que.quiz_id = ANY(qui.parent_ids) or que.quiz_id = qui.id %s;` var ( whereClause []string data []interface{} ) if quizId != 0 { data = append(data, quizId) whereClause = append(whereClause, fmt.Sprintf("qui.id = $%d", len(data))) } if from != 0 { data = append(data, from) whereClause = append(whereClause, fmt.Sprintf("que.created_at >= to_timestamp($%d)", len(data))) } if to != 0 { data = append(data, to) whereClause = append(whereClause, fmt.Sprintf("que.created_at <= to_timestamp($%d)", len(data))) } if deleted { whereClause = append(whereClause, fmt.Sprintf("que.deleted = true")) } else { whereClause = append(whereClause, fmt.Sprintf("que.deleted = false")) } if required { whereClause = append(whereClause, fmt.Sprintf("que.required = true")) } if qType != "" { data = append(data, qType) whereClause = append(whereClause, fmt.Sprintf("que.questiontype = $%d", len(data))) } if search != "" { data = append(data, search) whereClause = append(whereClause, fmt.Sprintf("to_tsvector('russian', que.title) @@ to_tsquery('russian', $%d)", len(data))) } data = append(data, limit, offset) if len(whereClause) != 0 { query = fmt.Sprintf(query, fmt.Sprintf(" WHERE %s ", strings.Join(whereClause, " AND ")), len(data)-1, len(data)) queryCnt = fmt.Sprintf(queryCnt, fmt.Sprintf(" WHERE %s ", strings.Join(whereClause, " AND "))) } else { query = fmt.Sprintf(query, "", 1, 2) queryCnt = fmt.Sprintf(queryCnt, "") } var ( qerr, cerr error count uint64 result []model.Question ) fmt.Println("QUESTIONS", queryCnt, query, data, data[:len(data)-2]) wg := sync.WaitGroup{} wg.Add(2) go func() { defer wg.Done() rows, err := r.pool.QueryContext(ctx, query, data...) if err != nil { qerr = err return } defer rows.Close() var piece model.Question pIds := pq.Int32Array{} for rows.Next() { if err := rows.Scan( &piece.Id, &piece.QuizId, &piece.Title, &piece.Description, &piece.Type, &piece.Required, &piece.Deleted, &piece.Page, &piece.Content, &piece.Version, &pIds, &piece.CreatedAt, &piece.UpdatedAt, ); err != nil { qerr = err return } piece.ParentIds = pIds result = append(result, piece) } }() go func() { defer wg.Done() var ( err error rows *sql.Rows ) if len(data) == 2 { rows, err = r.pool.QueryContext(ctx, queryCnt) } else { rows, err = r.pool.QueryContext(ctx, queryCnt, data[:len(data)-2]...) } if err != nil { cerr = err return } defer rows.Close() if ok := rows.Next(); !ok { cerr = errors.New("can not next count") } if err := rows.Scan(&count); err != nil { cerr = err } }() wg.Wait() if cerr != nil { return nil, 0, cerr } if qerr != nil { return nil, 0, qerr } return result, count, nil } // test + // UpdateQuestion set new data for question func (r *QuestionRepository) UpdateQuestion(ctx context.Context, record model.Question) error { query := `UPDATE question SET` var params []interface{} if record.Title != "" { query += ` title = $%d,` params = append(params, record.Title) } if record.Description != "" { query += ` description = $%d::text,` params = append(params, record.Description) } if record.Type != "" { query += ` questiontype = $%d,` params = append(params, record.Type) } if record.Content != "" { query += ` content = $%d::text,` params = append(params, record.Content) } if record.Page != -1 { query += ` page = $%d,` params = append(params, record.Page) } query += ` required = $%d, version = $%d WHERE id = $%d` params = append(params, record.Required, record.Version, record.Id) var placeholders []any for i := 1; i <= len(params); i++ { placeholders = append(placeholders, i) } query = fmt.Sprintf(query, placeholders...) _, err := r.pool.ExecContext(ctx, query, params...) return err } // test + // DeleteQuestion set question deleted and return deleted question func (r *QuestionRepository) DeleteQuestion(ctx context.Context, id uint64) (model.Question, error) { row, err := r.queries.DeleteQuestion(ctx, int64(id)) if err != nil { return model.Question{}, err } result := model.Question{ Id: uint64(row.ID), QuizId: uint64(row.QuizID), Title: row.Title, Description: row.Description.String, Type: string(row.Questiontype.([]byte)), Required: row.Required.Bool, Deleted: row.Deleted.Bool, Page: int(row.Page.Int16), Content: row.Content.String, Version: int(row.Version.Int16), ParentIds: row.ParentIds, CreatedAt: row.CreatedAt.Time, UpdatedAt: row.UpdatedAt.Time, } return result, nil } // test + // MoveToHistoryQuestion insert deleted duplicate of question func (r *QuestionRepository) MoveToHistoryQuestion(ctx context.Context, id uint64) (model.Question, error) { row, err := r.queries.MoveToHistory(ctx, int64(id)) if err != nil { return model.Question{}, err } result := model.Question{ Id: uint64(row.ID), QuizId: uint64(row.QuizID), ParentIds: row.ParentIds, } return result, nil } // test + // CopyQuestion method for duplication of question or to copy question to another quiz func (r *QuestionRepository) CopyQuestion(ctx context.Context, id, quizId uint64) (model.Question, error) { var record model.Question if quizId == 0 { row, err := r.queries.DuplicateQuestion(ctx, int64(id)) if err != nil { return model.Question{}, err } record = model.Question{ Id: uint64(row.ID), QuizId: uint64(row.QuizID), CreatedAt: row.CreatedAt.Time, UpdatedAt: row.UpdatedAt.Time, } } else { row, err := r.queries.CopyQuestion(ctx, sqlcgen.CopyQuestionParams{ QuizID: int64(quizId), ID: int64(id), }) if err != nil { return model.Question{}, err } record = model.Question{ Id: uint64(row.ID), QuizId: uint64(row.QuizID), CreatedAt: row.CreatedAt.Time, UpdatedAt: row.UpdatedAt.Time, } } return record, nil } // test + // QuestionHistory method for obtaining question history from the database func (r *QuestionRepository) QuestionHistory(ctx context.Context, id, limit, offset uint64) ([]model.Question, error) { rows, err := r.queries.GetQuestionHistory(ctx, sqlcgen.GetQuestionHistoryParams{ ID: int64(id), Limit: int32(limit), Offset: int32(offset), }) if err != nil { return nil, err } var result []model.Question for _, row := range rows { record := model.Question{ Id: uint64(row.ID), QuizId: uint64(row.QuizID), Title: row.Title, Description: row.Description.String, Type: string(row.Questiontype.([]byte)), Required: row.Required.Bool, Deleted: row.Deleted.Bool, Page: int(row.Page.Int16), Content: row.Content.String, Version: int(row.Version.Int16), ParentIds: row.ParentIds, CreatedAt: row.CreatedAt.Time, UpdatedAt: row.UpdatedAt.Time, } result = append(result, record) } return result, nil } func (r *QuestionRepository) GetMapQuestions(ctx context.Context, allAnswers []model.ResultAnswer) (map[uint64]string, []model.ResultAnswer, error) { questionMap := make(map[uint64]string) var questions []QueTitleResp for _, answer := range allAnswers { questionData, err := r.GetQuestionTitleByID(ctx, answer.QuestionID) if err != nil { return nil, []model.ResultAnswer{}, err } if questionData.Type != model.TypeResult { questionData.AnswerID = answer.AnswerID questions = append(questions, questionData) if questionData.Title == "" || questionData.Title == " " || questionData.Title == " " { questionData.Title = "Вопрос без заголовка" } questionMap[answer.AnswerID] = questionData.Title } } sort.Slice(questions, func(i, j int) bool { return questions[i].Page < questions[j].Page }) // TODO O2 REFACTOR var sortedAllAnswers []model.ResultAnswer for _, que := range questions { for _, answer := range allAnswers { if que.AnswerID == answer.AnswerID { sortedAllAnswers = append(sortedAllAnswers, answer) } } } return questionMap, sortedAllAnswers, nil } type QueTitleResp struct { Title string Type string Page int AnswerID uint64 } // test + func (r *QuestionRepository) GetQuestionTitleByID(ctx context.Context, questionID uint64) (QueTitleResp, error) { row, err := r.queries.GetQuestionTitle(ctx, int64(questionID)) if err != nil { return QueTitleResp{}, err } resp := QueTitleResp{ Title: row.Title, Type: string(row.Questiontype.([]byte)), Page: int(row.Page.Int16), } return resp, nil } func (r *QuestionRepository) ForSortingResults(ctx context.Context, allAnswers []model.Answer) ([]model.Answer, error) { var questions []QueTitleResp for _, answer := range allAnswers { questionData, err := r.GetQuestionTitleByID(ctx, answer.QuestionId) if err != nil { return nil, err } questionData.AnswerID = answer.Id questions = append(questions, questionData) } sort.Slice(questions, func(i, j int) bool { return questions[i].Page < questions[j].Page }) // TODO O2 REFACTOR var sortedAllAnswers []model.Answer for _, que := range questions { for _, answer := range allAnswers { if que.AnswerID == answer.Id { sortedAllAnswers = append(sortedAllAnswers, answer) } } } return sortedAllAnswers, nil } func (r *QuestionRepository) GetQuestionListByIDs(ctx context.Context, ids []int32) ([]model.Question, error) { rows, err := r.queries.GetQuestionListByIDs(ctx, ids) if err != nil { return nil, err } var questions []model.Question for _, row := range rows { question := model.Question{ Id: uint64(row.ID), QuizId: uint64(row.QuizID), Title: row.Title, Description: row.Description.String, Type: string(row.Questiontype.([]byte)), Required: row.Required.Bool, Deleted: row.Deleted.Bool, Page: int(row.Page.Int16), Content: row.Content.String, Version: int(row.Version.Int16), ParentIds: row.ParentIds, CreatedAt: row.CreatedAt.Time, UpdatedAt: row.UpdatedAt.Time, } questions = append(questions, question) } return questions, nil }