package quiz import ( "context" "database/sql" "encoding/json" "errors" "fmt" "gitea.pena/SQuiz/common/dal/sqlcgen" "gitea.pena/SQuiz/common/model" "github.com/google/uuid" "github.com/lib/pq" "strings" "sync" ) type Deps struct { Queries *sqlcgen.Queries Pool *sql.DB } type QuizRepository struct { queries *sqlcgen.Queries pool *sql.DB } func NewQuizRepository(deps Deps) *QuizRepository { return &QuizRepository{ queries: deps.Queries, pool: deps.Pool, } } // test + func (r *QuizRepository) CreateQuiz(ctx context.Context, record *model.Quiz) (uint64, error) { if record.Qid == "" { record.Qid = uuid.NewString() } params := sqlcgen.InsertQuizParams{ Accountid: record.AccountId, Fingerprinting: sql.NullBool{Bool: record.Fingerprinting, Valid: true}, Repeatable: sql.NullBool{Bool: record.Repeatable, Valid: true}, NotePrevented: sql.NullBool{Bool: record.NotePrevented, Valid: true}, MailNotifications: sql.NullBool{Bool: record.MailNotifications, Valid: true}, UniqueAnswers: sql.NullBool{Bool: record.UniqueAnswers, Valid: true}, Super: sql.NullBool{Bool: record.Super, Valid: true}, GroupID: sql.NullInt64{Int64: int64(record.GroupId), Valid: true}, Name: sql.NullString{String: record.Name, Valid: true}, Description: sql.NullString{String: record.Description, Valid: true}, Config: sql.NullString{String: record.Config, Valid: true}, Status: record.Status, LimitAnswers: sql.NullInt32{Int32: int32(record.Limit), Valid: true}, DueTo: sql.NullInt32{Int32: int32(record.DueTo), Valid: true}, TimeOfPassing: sql.NullInt32{Int32: int32(record.TimeOfPassing), Valid: true}, Pausable: sql.NullBool{Bool: record.Pausable, Valid: true}, ParentIds: record.ParentIds, QuestionsCount: sql.NullInt32{Int32: int32(record.QuestionsCount), Valid: true}, Qid: uuid.NullUUID{UUID: uuid.MustParse(record.Qid), Valid: true}, } data, err := r.queries.InsertQuiz(ctx, params) if err != nil { return 0, err } record.Id = uint64(data.ID) record.CreatedAt = data.CreatedAt.Time record.UpdatedAt = data.UpdatedAt.Time record.Qid = data.Qid.UUID.String() return record.Id, nil } type GetQuizListDeps struct { Limit, Offset, From, To, Group uint64 Deleted, Archived, Super bool Search, Status, AccountId string } // test + // GetQuizList function for get data page from db func (r *QuizRepository) GetQuizList( ctx context.Context, deps GetQuizListDeps) ([]model.Quiz, uint64, error) { query := ` SELECT * FROM quiz %s ORDER BY created_at DESC LIMIT $1 OFFSET $2; ` queryCnt := `SELECT count(1) FROM quiz %s;` var ( whereClause []string data []interface{} ) whereClause = append(whereClause, fmt.Sprintf(`accountid = '%s'`, deps.AccountId)) if deps.From != 0 { data = append(data, deps.From) whereClause = append(whereClause, fmt.Sprintf("created_at >= to_timestamp($%d)", len(data))) } if deps.To != 0 { data = append(data, deps.To) whereClause = append(whereClause, fmt.Sprintf("created_at <= to_timestamp($%d)", len(data))) } if deps.Deleted { whereClause = append(whereClause, fmt.Sprintf("deleted = true")) } else { whereClause = append(whereClause, fmt.Sprintf("deleted = false")) } if deps.Archived { whereClause = append(whereClause, fmt.Sprintf("archived = true")) } else { whereClause = append(whereClause, fmt.Sprintf("archived = false")) } if deps.Super { whereClause = append(whereClause, fmt.Sprintf("super = true")) } if deps.Group > 0 { whereClause = append(whereClause, fmt.Sprintf("group_id = %d", deps.Group)) } if deps.Status != "" { data = append(data, deps.Status) whereClause = append(whereClause, fmt.Sprintf("status = $%d", len(data))) } if deps.Search != "" { data = append(data, deps.Search) whereClause = append(whereClause, fmt.Sprintf("to_tsvector('russian', name) @@ to_tsquery('russian', $%d)", len(data))) } if len(whereClause) != 0 { query = fmt.Sprintf(query, fmt.Sprintf(" WHERE %s ", strings.Join(whereClause, " AND "))) queryCnt = fmt.Sprintf(queryCnt, fmt.Sprintf(" WHERE %s ", strings.Join(whereClause, " AND "))) } else { query = fmt.Sprintf(query, "") queryCnt = fmt.Sprintf(queryCnt, "") } var ( qerr, cerr error count uint64 result []model.Quiz ) data = append(data, deps.Limit, deps.Offset) wg := sync.WaitGroup{} wg.Add(2) go func() { defer wg.Done() fmt.Println("Q1", query, deps.Limit, deps.Offset) rows, err := r.pool.QueryContext(ctx, query, deps.Limit, deps.Offset) if err != nil { qerr = err return } defer rows.Close() var piece model.Quiz pIds := pq.Int32Array{} for rows.Next() { if err := rows.Scan( &piece.Id, &piece.Qid, &piece.AccountId, &piece.Deleted, &piece.Archived, &piece.Fingerprinting, &piece.Repeatable, &piece.NotePrevented, &piece.MailNotifications, &piece.UniqueAnswers, &piece.Super, &piece.GroupId, &piece.Name, &piece.Description, &piece.Config, &piece.Status, &piece.Limit, &piece.DueTo, &piece.TimeOfPassing, &piece.Pausable, &piece.Version, &piece.VersionComment, &pIds, &piece.CreatedAt, &piece.UpdatedAt, &piece.QuestionsCount, &piece.PassedCount, &piece.AverageTime, &piece.SessionCount, ); err != nil { qerr = err return } piece.ParentIds = pIds result = append(result, piece) } }() go func() { defer wg.Done() fmt.Println("Q2", queryCnt) 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 !rows.Next() { cerr = errors.New("can not next count") } if err := rows.Scan(&count); err != nil { cerr = err } }() wg.Wait() fmt.Println("res", result, count, "!", cerr, "?", qerr) if cerr != nil { return nil, 0, cerr } if qerr != nil { return nil, 0, qerr } return result, count, nil } // test + // GetQuizByQid method for obtain quiz model by secured id func (r *QuizRepository) GetQuizByQid(ctx context.Context, qid string) (model.Quiz, error) { fmt.Println("QUID", ` SELECT * FROM quiz WHERE deleted = false AND archived = false AND status = 'start' AND qid = $1; `) rows, err := r.pool.QueryContext(ctx, ` SELECT * FROM quiz WHERE deleted = false AND archived = false AND status = 'start' AND qid = $1; `, qid) if err != nil { return model.Quiz{}, err } defer rows.Close() if !rows.Next() { return model.Quiz{}, rows.Err() } var piece model.Quiz pIds := pq.Int32Array{} if err := rows.Scan( &piece.Id, &piece.Qid, &piece.AccountId, &piece.Deleted, &piece.Archived, &piece.Fingerprinting, &piece.Repeatable, &piece.NotePrevented, &piece.MailNotifications, &piece.UniqueAnswers, &piece.Super, &piece.GroupId, &piece.Name, &piece.Description, &piece.Config, &piece.Status, &piece.Limit, &piece.DueTo, &piece.TimeOfPassing, &piece.Pausable, &piece.Version, &piece.VersionComment, &pIds, &piece.CreatedAt, &piece.UpdatedAt, &piece.QuestionsCount, &piece.PassedCount, &piece.AverageTime, &piece.SessionCount, ); err != nil { return model.Quiz{}, err } piece.ParentIds = pIds return piece, nil } // test + func (r *QuizRepository) DeleteQuiz(ctx context.Context, accountId string, id uint64) (model.Quiz, error) { row, err := r.queries.DeleteQuizByID(ctx, sqlcgen.DeleteQuizByIDParams{ ID: int64(id), Accountid: accountId, }) if err != nil { return model.Quiz{}, err } piece := model.Quiz{ Id: uint64(row.ID), Qid: row.Qid.UUID.String(), AccountId: row.Accountid, Deleted: row.Deleted.Bool, Archived: row.Archived.Bool, Fingerprinting: row.Fingerprinting.Bool, Repeatable: row.Repeatable.Bool, NotePrevented: row.NotePrevented.Bool, MailNotifications: row.MailNotifications.Bool, UniqueAnswers: row.UniqueAnswers.Bool, Super: row.Super.Bool, GroupId: uint64(row.GroupID.Int64), Name: row.Name.String, Description: row.Description.String, Config: row.Config.String, Status: string(row.Status.([]byte)), Limit: uint64(row.LimitAnswers.Int32), DueTo: uint64(row.DueTo.Int32), TimeOfPassing: uint64(row.TimeOfPassing.Int32), Pausable: row.Pausable.Bool, Version: int(row.Version.Int16), VersionComment: row.VersionComment.String, ParentIds: row.ParentIds, CreatedAt: row.CreatedAt.Time, UpdatedAt: row.UpdatedAt.Time, QuestionsCount: uint64(row.QuestionsCount.Int32), PassedCount: uint64(row.AnswersCount.Int32), AverageTime: uint64(row.AverageTimePassing.Int32), SessionCount: uint64(row.SessionsCount.Int32), } return piece, nil } // test + // MoveToHistoryQuiz insert deleted duplicate of quiz func (r *QuizRepository) MoveToHistoryQuiz(ctx context.Context, id uint64, accountId string) (model.Quiz, error) { row, err := r.queries.MoveToHistoryQuiz(ctx, sqlcgen.MoveToHistoryQuizParams{ ID: int64(id), Accountid: accountId, }) if err != nil { return model.Quiz{}, err } result := model.Quiz{ Id: uint64(row.ID), Qid: row.Qid.UUID.String(), ParentIds: row.ParentIds, } return result, nil } // test + // UpdateQuiz set new data for quiz func (r *QuizRepository) UpdateQuiz(ctx context.Context, accountId string, record model.Quiz) error { query := `UPDATE quiz SET` var params []interface{} if record.Name != "" { query += ` name = $%d,` params = append(params, record.Name) } if record.Description != "" { query += ` description = $%d::text,` params = append(params, record.Description) } if record.Status != "" { query += ` status = $%d,` params = append(params, record.Status) } if record.Config != "" { query += ` config = $%d::text,` params = append(params, record.Config) } query += ` group_id = $%d, version = $%d WHERE id = $%d AND accountid = $%d` params = append(params, record.GroupId, record.Version, record.Id, accountId) 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 + // CopyQuiz method for copy quiz with all of his questions func (r *QuizRepository) CopyQuiz(ctx context.Context, accountId string, id uint64) (model.Quiz, error) { row, err := r.queries.CopyQuiz(ctx, sqlcgen.CopyQuizParams{ ID: int64(id), Accountid: accountId, }) if err != nil { return model.Quiz{}, err } result := model.Quiz{ Id: uint64(row.ID), Qid: row.Qid.UUID.String(), CreatedAt: row.CreatedAt.Time, UpdatedAt: row.UpdatedAt.Time, } err = r.queries.CopyQuizQuestions(ctx, sqlcgen.CopyQuizQuestionsParams{ QuizID: int64(id), QuizID_2: row.ID, }) if err != nil { return model.Quiz{}, err } return result, nil } type QuizHistoryDeps struct { Id, Limit, Offset uint64 AccountId string } // test + // QuizHistory method for obtain quiz history from db func (r *QuizRepository) QuizHistory(ctx context.Context, deps QuizHistoryDeps) ([]model.Quiz, error) { rows, err := r.queries.GetQuizHistory(ctx, sqlcgen.GetQuizHistoryParams{ ID: int64(deps.Id), Limit: int32(deps.Limit), Offset: int32(deps.Offset), Accountid: deps.AccountId, }) if err != nil { return nil, err } var result []model.Quiz for _, row := range rows { piece := model.Quiz{ Id: uint64(row.ID), Qid: row.Qid.UUID.String(), AccountId: row.Accountid, Deleted: row.Deleted.Bool, Archived: row.Archived.Bool, Fingerprinting: row.Fingerprinting.Bool, Repeatable: row.Repeatable.Bool, NotePrevented: row.NotePrevented.Bool, MailNotifications: row.MailNotifications.Bool, UniqueAnswers: row.UniqueAnswers.Bool, Super: row.Super.Bool, GroupId: uint64(row.GroupID.Int64), Name: row.Name.String, Description: row.Description.String, Config: row.Config.String, Status: string(row.Status.([]byte)), Limit: uint64(row.LimitAnswers.Int32), DueTo: uint64(row.DueTo.Int32), TimeOfPassing: uint64(row.TimeOfPassing.Int32), Pausable: row.Pausable.Bool, Version: int(row.Version.Int16), VersionComment: row.VersionComment.String, ParentIds: row.ParentIds, CreatedAt: row.CreatedAt.Time, UpdatedAt: row.UpdatedAt.Time, QuestionsCount: uint64(row.QuestionsCount.Int32), PassedCount: uint64(row.AnswersCount.Int32), AverageTime: uint64(row.AverageTimePassing.Int32), SessionCount: uint64(row.SessionsCount.Int32), } result = append(result, piece) } return result, nil } func (r *QuizRepository) ArchiveQuiz(ctx context.Context, accountId string, id uint64) error { err := r.queries.ArchiveQuiz(ctx, sqlcgen.ArchiveQuizParams{ ID: int64(id), Accountid: accountId, }) if err != nil { return err } return nil } // test + func (r *QuizRepository) GetQuizById(ctx context.Context, accountId string, id uint64) (*model.Quiz, error) { row, err := r.queries.GetQuizById(ctx, sqlcgen.GetQuizByIdParams{ ID: int64(id), Accountid: accountId, }) if err != nil { return nil, err } piece := model.Quiz{ Id: uint64(row.ID), Qid: row.Qid.UUID.String(), AccountId: row.Accountid, Deleted: row.Deleted.Bool, Archived: row.Archived.Bool, Fingerprinting: row.Fingerprinting.Bool, Repeatable: row.Repeatable.Bool, NotePrevented: row.NotePrevented.Bool, MailNotifications: row.MailNotifications.Bool, UniqueAnswers: row.UniqueAnswers.Bool, Super: row.Super.Bool, GroupId: uint64(row.GroupID.Int64), Name: row.Name.String, Description: row.Description.String, Config: row.Config.String, Status: string(row.Status.([]byte)), Limit: uint64(row.LimitAnswers.Int32), DueTo: uint64(row.DueTo.Int32), TimeOfPassing: uint64(row.TimeOfPassing.Int32), Pausable: row.Pausable.Bool, Version: int(row.Version.Int16), VersionComment: row.VersionComment.String, ParentIds: row.ParentIds, CreatedAt: row.CreatedAt.Time, UpdatedAt: row.UpdatedAt.Time, QuestionsCount: uint64(row.QuestionsCount.Int32), PassedCount: uint64(row.AnswersCount.Int32), AverageTime: uint64(row.AverageTimePassing.Int32), SessionCount: uint64(row.SessionsCount.Int32), } return &piece, nil } // test + func (r *QuizRepository) GetQuizConfig(ctx context.Context, quizID uint64) (model.QuizConfig, string, error) { row, err := r.queries.GetQuizConfig(ctx, int64(quizID)) if err != nil { return model.QuizConfig{}, "", err } var config model.QuizConfig if err := json.Unmarshal([]byte(row.Config.String), &config); err != nil { return model.QuizConfig{}, "", err } return config, row.Accountid, nil } func (r *QuizRepository) QuizMove(ctx context.Context, qID, accountID string) (string, error) { qUUID, err := uuid.Parse(qID) if err != nil { return "", err } qNullUUID := uuid.NullUUID{UUID: qUUID, Valid: true} data, err := r.queries.QuizCopyQid(ctx, sqlcgen.QuizCopyQidParams{ Qid: qNullUUID, Accountid: accountID, }) if err != nil { return "", err } err = r.queries.CopyQuestionQuizID(ctx, sqlcgen.CopyQuestionQuizIDParams{ QuizID: data.ID, QuizID_2: data.ID_2, }) if err != nil { return "", err } return data.Qid.UUID.String(), err } func (r *QuizRepository) GetAllQuizzesID(ctx context.Context, accountID string) ([]int64, error) { ids, err := r.queries.GetListCreatedQuizzes(ctx, accountID) if err != nil { return []int64{}, err } return ids, nil } func (r *QuizRepository) GetStartedQuizzesID(ctx context.Context, accountID string) ([]int64, error) { ids, err := r.queries.GetListStartQuiz(ctx, accountID) if err != nil { return []int64{}, err } return ids, nil } func (r *QuizRepository) TemplateCopy(ctx context.Context, accountID, qID string) (int64, error) { qUUID, err := uuid.Parse(qID) if err != nil { return 0, err } qNullUUID := uuid.NullUUID{UUID: qUUID, Valid: true} quizID, err := r.queries.TemplateCopy(ctx, sqlcgen.TemplateCopyParams{ Accountid: accountID, Qid: qNullUUID, }) if err != nil { return 0, err } return quizID, nil } func (r *QuizRepository) CheckQuizOwner(ctx context.Context, accountID string, quizID uint64) (bool, error) { id, err := r.queries.CheckQuizOwner(ctx, sqlcgen.CheckQuizOwnerParams{ Accountid: accountID, ID: int64(quizID), }) if err != nil { if errors.Is(err, sql.ErrNoRows) { return false, nil } return false, err } return id == accountID, nil }