common/repository/quiz/quiz.go

669 lines
17 KiB
Go

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
}