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() } uuidQid, err := uuid.Parse(record.Qid) if err != nil { return 0, err } 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: uuidQid, 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 id, qid, accountid, deleted, archived, fingerprinting, repeatable, note_prevented, mail_notifications, unique_answers, super, group_id, name, description, config, status, limit_answers, due_to, time_of_passing, pausable, version, version_comment, parent_ids, created_at, updated_at, questions_count, answers_count, average_time_passing, sessions_count, gigachat 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, &piece.GigaChat, ); 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) { _, err := uuid.Parse(qid) if err != nil { return model.Quiz{}, err } fmt.Println("QUID", ` SELECT id, qid, accountid, deleted, archived, fingerprinting, repeatable, note_prevented, mail_notifications, unique_answers, super, group_id, name, description, config, status, limit_answers, due_to, time_of_passing, pausable, version, version_comment, parent_ids, created_at, updated_at, questions_count, answers_count, average_time_passing, sessions_count, gigachat FROM quiz WHERE deleted = false AND archived = false AND status = 'start' AND qid = $1; `) rows, err := r.pool.QueryContext(ctx, ` SELECT id, qid, accountid, deleted, archived, fingerprinting, repeatable, note_prevented, mail_notifications, unique_answers, super, group_id, name, description, config, status, limit_answers, due_to, time_of_passing, pausable, version, version_comment, parent_ids, created_at, updated_at, questions_count, answers_count, average_time_passing, sessions_count, gigachat FROM quiz WHERE deleted = false AND archived = false AND (status = 'start' OR status = 'ai') 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, &piece.GigaChat, ); 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), GigaChat: row.Gigachat, } 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 } type DepsCreateQuizAudience struct { QuizID int64 `json:"quiz_id"` Sex int32 `json:"sex"` // 0 - female, 1 - male, 2 - not sex Age string `json:"age"` } func (r *QuizRepository) CreateQuizAudience(ctx context.Context, audience DepsCreateQuizAudience) (int64, error) { result, err := r.queries.CreateQuizAudience(ctx, sqlcgen.CreateQuizAudienceParams{ Quizid: audience.QuizID, Sex: audience.Sex, Age: audience.Age, }) if err != nil { return 0, err } return result, nil } func (r *QuizRepository) GetQuizAudience(ctx context.Context, quizID int64) ([]model.GigaChatAudience, error) { rows, err := r.queries.GetQuizAudience(ctx, quizID) if err != nil { return nil, err } var audiences []model.GigaChatAudience for _, row := range rows { audiences = append(audiences, model.GigaChatAudience{ ID: row.ID, QuizID: row.ID, Sex: row.Sex, Age: row.Age, Deleted: row.Deleted, CreatedAt: row.Createdat.Time.Unix(), }) } return audiences, nil } func (r *QuizRepository) DeleteQuizAudience(ctx context.Context, quizID int64, audienceID int64) error { err := r.queries.DeleteQuizAudience(ctx, sqlcgen.DeleteQuizAudienceParams{ Quizid: quizID, ID: audienceID, }) if err != nil { return err } return nil } func (r *QuizRepository) CheckIsOwnerAudience(ctx context.Context, quizID int64, audienceID int64, accountID string) (bool, error) { ok, err := r.queries.CheckIsOwnerAudience(ctx, sqlcgen.CheckIsOwnerAudienceParams{ Quizid: quizID, ID: audienceID, Accountid: accountID, }) if err != nil { return false, err } return ok, 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 } func (r *QuizRepository) UpdateGigaChatQuizFlag(ctx context.Context, quizID int64, accountID string) error { err := r.queries.UpdateGigaChatQuizFlag(ctx, sqlcgen.UpdateGigaChatQuizFlagParams{ ID: quizID, Accountid: accountID, }) if err != nil { return err } return nil } func (r *QuizRepository) CreateQuizUTM(ctx context.Context, quizID int64, utm string) (model.QuizUTM, error) { result, err := r.queries.CreateQuizUtm(ctx, sqlcgen.CreateQuizUtmParams{ Quizid: quizID, Utm: utm, }) if err != nil { return model.QuizUTM{}, err } return model.QuizUTM{ ID: result.ID, QuizID: result.Quizid, UTM: result.Utm, Deleted: result.Deleted, CreatedAt: result.CreatedAt, }, nil } func (r *QuizRepository) DeleteQuizUTM(ctx context.Context, utmID int64) error { return r.queries.SoftDeleteQuizUtm(ctx, utmID) } func (r *QuizRepository) GetAllQuizUtms(ctx context.Context, quizID int64) ([]model.QuizUTM, error) { rows, err := r.queries.GetAllQuizUtms(ctx, quizID) if err != nil { return nil, err } result := make([]model.QuizUTM, 0, len(rows)) for _, row := range rows { result = append(result, model.QuizUTM{ ID: row.ID, QuizID: row.Quizid, UTM: row.Utm, Deleted: row.Deleted, CreatedAt: row.CreatedAt, }) } return result, nil } func (r *QuizRepository) GetQuizPrivilegeUsage(ctx context.Context, quizID int64, privilegeID string) (*model.QuizUsagePrivilege, error) { usage, err := r.queries.GetQuizPrivilegeUsage(ctx, sqlcgen.GetQuizPrivilegeUsageParams{ QuizID: quizID, PrivilegeID: privilegeID, }) if err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, nil // нет записи не ошибка } return nil, err } return &model.QuizUsagePrivilege{ ID: usage.ID, QuizID: usage.QuizID, PrivilegeID: usage.PrivilegeID, CreatedAt: usage.CreatedAt, UpdatedAt: usage.UpdatedAt, }, nil } func (r *QuizRepository) InsertQuizPrivilegeUsage(ctx context.Context, quizID int64, privilegeID string) (*model.QuizUsagePrivilege, error) { usage, err := r.queries.InsertQuizPrivilegeUsage(ctx, sqlcgen.InsertQuizPrivilegeUsageParams{ QuizID: quizID, PrivilegeID: privilegeID, }) if err != nil { return nil, err } return &model.QuizUsagePrivilege{ ID: usage.ID, QuizID: usage.QuizID, PrivilegeID: usage.PrivilegeID, CreatedAt: usage.CreatedAt, UpdatedAt: usage.UpdatedAt, }, nil } func (r *QuizRepository) IncrementQuizPrivilegeUsage(ctx context.Context, quizID int64, privilegeID string) error { return r.queries.IncrementQuizPrivilegeUsage(ctx, sqlcgen.IncrementQuizPrivilegeUsageParams{ QuizID: quizID, PrivilegeID: privilegeID, }) } func (r *QuizRepository) ResetQuizPrivilegeUsageCount(ctx context.Context, quizID int64, privilegeID string) error { return r.queries.ResetQuizPrivilegeUsageCount(ctx, sqlcgen.ResetQuizPrivilegeUsageCountParams{ QuizID: quizID, PrivilegeID: privilegeID, }) }