package repository import ( "database/sql" "fmt" "vidarchive/internal/models" ) type PresetRepository struct { db *sql.DB } func NewPresetRepository(db *sql.DB) *PresetRepository { return &PresetRepository{db: db} } func insertPreset(tx *sql.Tx, p *models.Preset) error { result, err := tx.Exec( `INSERT INTO presets (name, description, is_default, format_mode, format, quality, custom_format, extract_audio, audio_format, embed_subs, sub_langs, embed_thumbnail, embed_metadata, write_info_json, write_comments, comment_sort, max_comments, comment_extractor_args, custom_flags) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, p.Name, p.Description, boolToInt(p.IsDefault), p.FormatMode, p.Format, p.Quality, p.CustomFormat, boolToInt(p.ExtractAudio), p.AudioFormat, boolToInt(p.EmbedSubs), p.SubLangs, boolToInt(p.EmbedThumbnail), boolToInt(p.EmbedMetadata), boolToInt(p.WriteInfoJSON), boolToInt(p.WriteComments), p.CommentSort, p.MaxComments, p.CommentExtractorArgs, p.CustomFlags, ) if err != nil { return err } id, err := result.LastInsertId() if err != nil { return fmt.Errorf("preset last insert id: %w", err) } p.ID = id return nil } func updatePreset(tx *sql.Tx, p *models.Preset) error { _, err := tx.Exec( `UPDATE presets SET name=?, description=?, is_default=?, format_mode=?, format=?, quality=?, custom_format=?, extract_audio=?, audio_format=?, embed_subs=?, sub_langs=?, embed_thumbnail=?, embed_metadata=?, write_info_json=?, write_comments=?, comment_sort=?, max_comments=?, comment_extractor_args=?, custom_flags=? WHERE id=?`, p.Name, p.Description, boolToInt(p.IsDefault), p.FormatMode, p.Format, p.Quality, p.CustomFormat, boolToInt(p.ExtractAudio), p.AudioFormat, boolToInt(p.EmbedSubs), p.SubLangs, boolToInt(p.EmbedThumbnail), boolToInt(p.EmbedMetadata), boolToInt(p.WriteInfoJSON), boolToInt(p.WriteComments), p.CommentSort, p.MaxComments, p.CommentExtractorArgs, p.CustomFlags, p.ID, ) return err } func clearDefault(tx *sql.Tx) error { _, err := tx.Exec(`UPDATE presets SET is_default = 0`) return err } // Save clears the previous default in the same transaction, so a mid-sequence // failure cannot leave the library with no default set. Create and Update both // route through here, so the exclusive-default invariant can't be bypassed. func (r *PresetRepository) Save(p *models.Preset) error { tx, err := r.db.Begin() if err != nil { return err } defer tx.Rollback() if p.IsDefault { if err := clearDefault(tx); err != nil { return err } } if p.ID == 0 { err = insertPreset(tx, p) } else { err = updatePreset(tx, p) } if err != nil { return err } return tx.Commit() } func (r *PresetRepository) GetByID(id int64) (*models.Preset, error) { row := r.db.QueryRow( `SELECT id, name, description, is_default, format_mode, format, quality, custom_format, extract_audio, audio_format, embed_subs, sub_langs, embed_thumbnail, embed_metadata, write_info_json, write_comments, comment_sort, max_comments, comment_extractor_args, custom_flags, created_at FROM presets WHERE id = ?`, id, ) return scanPreset(row) } func (r *PresetRepository) GetAll() ([]*models.Preset, error) { rows, err := r.db.Query( `SELECT id, name, description, is_default, format_mode, format, quality, custom_format, extract_audio, audio_format, embed_subs, sub_langs, embed_thumbnail, embed_metadata, write_info_json, write_comments, comment_sort, max_comments, comment_extractor_args, custom_flags, created_at FROM presets ORDER BY name`, ) if err != nil { return nil, err } defer rows.Close() var presets []*models.Preset for rows.Next() { p, err := scanPreset(rows) if err != nil { return nil, err } presets = append(presets, p) } return presets, rows.Err() } func (r *PresetRepository) GetDefault() (*models.Preset, error) { row := r.db.QueryRow( `SELECT id, name, description, is_default, format_mode, format, quality, custom_format, extract_audio, audio_format, embed_subs, sub_langs, embed_thumbnail, embed_metadata, write_info_json, write_comments, comment_sort, max_comments, comment_extractor_args, custom_flags, created_at FROM presets WHERE is_default = 1 LIMIT 1`, ) return scanPreset(row) } func (r *PresetRepository) Delete(id int64) error { _, err := r.db.Exec(`DELETE FROM presets WHERE id = ?`, id) return err } func scanPreset(row interface{ Scan(...interface{}) error }) (*models.Preset, error) { var p models.Preset var isDefault, extractAudio, embedSubs, embedThumbnail, embedMetadata, writeInfoJSON, writeComments, maxComments int var description, formatMode, format, quality, customFormat, audioFormat, subLangs, commentSort, commentExtractorArgs, customFlags sql.NullString err := row.Scan( &p.ID, &p.Name, &description, &isDefault, &formatMode, &format, &quality, &customFormat, &extractAudio, &audioFormat, &embedSubs, &subLangs, &embedThumbnail, &embedMetadata, &writeInfoJSON, &writeComments, &commentSort, &maxComments, &commentExtractorArgs, &customFlags, &p.CreatedAt, ) if err != nil { return nil, err } p.Description = description.String p.FormatMode = formatMode.String p.Format = format.String p.Quality = quality.String p.CustomFormat = customFormat.String p.AudioFormat = audioFormat.String p.SubLangs = subLangs.String p.CustomFlags = customFlags.String p.CommentSort = commentSort.String p.CommentExtractorArgs = commentExtractorArgs.String p.MaxComments = maxComments p.IsDefault = isDefault == 1 p.ExtractAudio = extractAudio == 1 p.EmbedSubs = embedSubs == 1 p.EmbedThumbnail = embedThumbnail == 1 p.EmbedMetadata = embedMetadata == 1 p.WriteInfoJSON = writeInfoJSON == 1 p.WriteComments = writeComments == 1 return &p, nil } func boolToInt(b bool) int { if b { return 1 } return 0 }