389 lines
9.4 KiB
Go
389 lines
9.4 KiB
Go
|
package question
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"database/sql"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"github.com/lib/pq"
|
||
|
"penahub.gitlab.yandexcloud.net/backend/quiz/common/dal/sqlcgen"
|
||
|
"penahub.gitlab.yandexcloud.net/backend/quiz/common/model"
|
||
|
"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) 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 err
|
||
|
}
|
||
|
|
||
|
record.Id = uint64(data.ID)
|
||
|
record.CreatedAt = data.CreatedAt.Time
|
||
|
record.UpdatedAt = data.UpdatedAt.Time
|
||
|
|
||
|
return 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 %s
|
||
|
WHERE id=$1;`
|
||
|
|
||
|
var values []string
|
||
|
|
||
|
if record.Title != "" {
|
||
|
values = append(values, fmt.Sprintf(` title='%s' `, record.Title))
|
||
|
}
|
||
|
|
||
|
if record.Description != "" {
|
||
|
values = append(values, fmt.Sprintf(` description='%s' `, record.Description))
|
||
|
}
|
||
|
|
||
|
if record.Type != "" {
|
||
|
values = append(values, fmt.Sprintf(` questiontype='%s' `, record.Type))
|
||
|
}
|
||
|
|
||
|
values = append(values, fmt.Sprintf(`required=%t `, record.Required), fmt.Sprintf(` version=%d `, record.Version))
|
||
|
|
||
|
if record.Content != "" {
|
||
|
values = append(values, fmt.Sprintf(` content='%s' `, record.Content))
|
||
|
}
|
||
|
|
||
|
if record.Page != -1 {
|
||
|
values = append(values, fmt.Sprintf(` page=%d `, record.Page))
|
||
|
}
|
||
|
_, err := r.pool.ExecContext(ctx, fmt.Sprintf(query, strings.Join(values, ",")), record.Id)
|
||
|
|
||
|
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, error) {
|
||
|
questionMap := make(map[uint64]string)
|
||
|
|
||
|
for _, answer := range allAnswers {
|
||
|
|
||
|
title, questionType, err := r.GetQuestionTitleByID(ctx, answer.QuestionID)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
if questionType != model.TypeResult {
|
||
|
questionMap[answer.AnswerID] = title
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return questionMap, nil
|
||
|
}
|
||
|
|
||
|
// test +
|
||
|
func (r *QuestionRepository) GetQuestionTitleByID(ctx context.Context, questionID uint64) (string, string, error) {
|
||
|
row, err := r.queries.GetQuestionTitle(ctx, int64(questionID))
|
||
|
if err != nil {
|
||
|
return "", "", err
|
||
|
}
|
||
|
|
||
|
return row.Title, string(row.Questiontype.([]byte)), nil
|
||
|
}
|