562 lines
14 KiB
Go
562 lines
14 KiB
Go
package question
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
"gitea.pena/SQuiz/common/dal/sqlcgen"
|
|
"gitea.pena/SQuiz/common/model"
|
|
"github.com/lib/pq"
|
|
"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},
|
|
Session: record.Session,
|
|
Auditory: record.Auditory,
|
|
}
|
|
|
|
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, auditory int64) ([]model.Question, uint64, error) {
|
|
query := `
|
|
SELECT que.id, que.quiz_id, que.title, que.description, que.questiontype,
|
|
que.required, que.deleted, que.page, que.content, que.version,
|
|
que.parent_ids, que.created_at, que.updated_at, que.session, que.auditory
|
|
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)))
|
|
}
|
|
|
|
whereClause = append(whereClause, fmt.Sprintf("que.auditory = %d", auditory))
|
|
|
|
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,
|
|
&piece.Session,
|
|
&piece.Auditory,
|
|
); 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,
|
|
Auditory: row.Auditory,
|
|
}
|
|
|
|
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,
|
|
Auditory: row.Auditory,
|
|
}
|
|
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,
|
|
Auditory: row.Auditory,
|
|
}
|
|
|
|
questions = append(questions, question)
|
|
}
|
|
|
|
return questions, nil
|
|
}
|
|
|
|
func (r *QuestionRepository) GetQuestionsAI(ctx context.Context, quizID int64, session string, limit, offset int32, auditory int64) ([]model.Question, uint64, error) {
|
|
rows, err := r.queries.GetQuestionsAI(ctx, sqlcgen.GetQuestionsAIParams{
|
|
QuizID: quizID,
|
|
Session: session,
|
|
Limit: limit,
|
|
Offset: offset,
|
|
Auditory: auditory,
|
|
})
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
var questions []model.Question
|
|
var count int64
|
|
|
|
for _, row := range rows {
|
|
questions = append(questions, 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,
|
|
Session: row.Session,
|
|
Auditory: row.Auditory,
|
|
})
|
|
}
|
|
|
|
count, err = r.queries.GetQuestionsAICount(ctx, sqlcgen.GetQuestionsAICountParams{
|
|
QuizID: quizID,
|
|
Session: session,
|
|
Auditory: auditory,
|
|
})
|
|
|
|
return questions, uint64(count), nil
|
|
}
|
|
|
|
func (r *QuestionRepository) CheckQuestionOwner(ctx context.Context, accountID string, questionID uint64) (bool, error) {
|
|
id, err := r.queries.CheckQuestionOwner(ctx, sqlcgen.CheckQuestionOwnerParams{
|
|
ID: int64(questionID),
|
|
Accountid: accountID,
|
|
})
|
|
if err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return false, nil
|
|
}
|
|
return false, err
|
|
}
|
|
return accountID == id, nil
|
|
}
|