first commit
This commit is contained in:
214
internal/db/spots_store.go
Normal file
214
internal/db/spots_store.go
Normal file
@@ -0,0 +1,214 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/user/flexdxcluster2/internal/spot"
|
||||
)
|
||||
|
||||
type SpotsStore struct {
|
||||
db *DB
|
||||
}
|
||||
|
||||
func NewSpotsStore(db *DB) *SpotsStore {
|
||||
return &SpotsStore{db: db}
|
||||
}
|
||||
|
||||
// Create insère un nouveau spot et retourne son ID
|
||||
func (s *SpotsStore) Create(ctx context.Context, sp spot.Spot) (int64, error) {
|
||||
res, err := s.db.conn.ExecContext(ctx, `
|
||||
INSERT INTO spots (
|
||||
dx, spotter, frequency_khz, frequency_mhz, band, mode,
|
||||
comment, original_comment, time, timestamp,
|
||||
dxcc, country_name,
|
||||
new_dxcc, new_band, new_mode, new_slot, callsign_worked, in_watchlist,
|
||||
pota_ref, sota_ref, park_name, summit_name,
|
||||
flex_spot_number, command_number,
|
||||
color, background_color, priority, life_time,
|
||||
cluster_name, source
|
||||
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
|
||||
sp.DX, sp.Spotter, sp.FrequencyKHz, sp.FrequencyMHz, sp.Band, sp.Mode,
|
||||
sp.Comment, sp.OriginalComment, sp.Time, sp.Timestamp,
|
||||
sp.DXCC, sp.CountryName,
|
||||
boolToInt(sp.NewDXCC), boolToInt(sp.NewBand), boolToInt(sp.NewMode),
|
||||
boolToInt(sp.NewSlot), boolToInt(sp.CallsignWorked), boolToInt(sp.InWatchlist),
|
||||
sp.POTARef, sp.SOTARef, sp.ParkName, sp.SummitName,
|
||||
sp.FlexSpotNumber, sp.CommandNumber,
|
||||
sp.Color, sp.BackgroundColor, sp.Priority, sp.LifeTime,
|
||||
sp.ClusterName, int(sp.Source),
|
||||
)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("create spot: %w", err)
|
||||
}
|
||||
return res.LastInsertId()
|
||||
}
|
||||
|
||||
// GetAll retourne tous les spots, optionnellement filtrés par bande
|
||||
func (s *SpotsStore) GetAll(ctx context.Context, band string) ([]spot.Spot, error) {
|
||||
var rows *sql.Rows
|
||||
var err error
|
||||
|
||||
if band == "" || band == "0" || band == "ALL" {
|
||||
rows, err = s.db.conn.QueryContext(ctx,
|
||||
`SELECT * FROM spots ORDER BY timestamp DESC`)
|
||||
} else {
|
||||
rows, err = s.db.conn.QueryContext(ctx,
|
||||
`SELECT * FROM spots WHERE band = ? ORDER BY timestamp DESC`, band)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
return scanSpots(rows)
|
||||
}
|
||||
|
||||
// FindByDXAndBand cherche un spot existant pour le même DX sur la même bande
|
||||
func (s *SpotsStore) FindByDXAndBand(ctx context.Context, dx, band string) (*spot.Spot, error) {
|
||||
row := s.db.conn.QueryRowContext(ctx,
|
||||
`SELECT * FROM spots WHERE dx = ? AND band = ? ORDER BY timestamp DESC LIMIT 1`,
|
||||
dx, band)
|
||||
sp, err := scanSpot(row)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
return sp, err
|
||||
}
|
||||
|
||||
// FindByCommandNumber cherche un spot par son numéro de commande Flex
|
||||
func (s *SpotsStore) FindByCommandNumber(ctx context.Context, cmdNum int) (*spot.Spot, error) {
|
||||
row := s.db.conn.QueryRowContext(ctx,
|
||||
`SELECT * FROM spots WHERE command_number = ? LIMIT 1`, cmdNum)
|
||||
sp, err := scanSpot(row)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
return sp, err
|
||||
}
|
||||
|
||||
// FindByFlexSpotNumber cherche un spot par son numéro de spot Flex
|
||||
func (s *SpotsStore) FindByFlexSpotNumber(ctx context.Context, flexNum int) (*spot.Spot, error) {
|
||||
row := s.db.conn.QueryRowContext(ctx,
|
||||
`SELECT * FROM spots WHERE flex_spot_number = ? LIMIT 1`, flexNum)
|
||||
sp, err := scanSpot(row)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
return sp, err
|
||||
}
|
||||
|
||||
// UpdateFlexSpotNumber met à jour le numéro de spot Flex après confirmation du Flex
|
||||
func (s *SpotsStore) UpdateFlexSpotNumber(ctx context.Context, cmdNum, flexNum int) error {
|
||||
_, err := s.db.conn.ExecContext(ctx,
|
||||
`UPDATE spots SET flex_spot_number = ? WHERE command_number = ?`,
|
||||
flexNum, cmdNum)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteByFlexSpotNumber supprime un spot par son numéro Flex
|
||||
func (s *SpotsStore) DeleteByFlexSpotNumber(ctx context.Context, flexNum int) error {
|
||||
_, err := s.db.conn.ExecContext(ctx,
|
||||
`DELETE FROM spots WHERE flex_spot_number = ?`, flexNum)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteByID supprime un spot par son ID
|
||||
func (s *SpotsStore) DeleteByID(ctx context.Context, id int64) error {
|
||||
_, err := s.db.conn.ExecContext(ctx,
|
||||
`DELETE FROM spots WHERE id = ?`, id)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteExpired supprime les spots expirés selon leur lifetime
|
||||
func (s *SpotsStore) DeleteExpired(ctx context.Context, lifetimeSeconds int64) ([]spot.Spot, error) {
|
||||
cutoff := time.Now().Unix() - lifetimeSeconds
|
||||
rows, err := s.db.conn.QueryContext(ctx,
|
||||
`SELECT * FROM spots WHERE timestamp < ?`, cutoff)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
expired, err := scanSpots(rows)
|
||||
rows.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = s.db.conn.ExecContext(ctx,
|
||||
`DELETE FROM spots WHERE timestamp < ?`, cutoff)
|
||||
return expired, err
|
||||
}
|
||||
|
||||
// --- Helpers ---
|
||||
|
||||
func boolToInt(b bool) int {
|
||||
if b {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func scanSpots(rows *sql.Rows) ([]spot.Spot, error) {
|
||||
var spots []spot.Spot
|
||||
for rows.Next() {
|
||||
sp, err := scanSpotFromRows(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spots = append(spots, *sp)
|
||||
}
|
||||
return spots, rows.Err()
|
||||
}
|
||||
|
||||
func scanSpot(row *sql.Row) (*spot.Spot, error) {
|
||||
var sp spot.Spot
|
||||
var newDXCC, newBand, newMode, newSlot, worked, inWatchlist, source int
|
||||
err := row.Scan(
|
||||
&sp.ID, &sp.DX, &sp.Spotter, &sp.FrequencyKHz, &sp.FrequencyMHz,
|
||||
&sp.Band, &sp.Mode, &sp.Comment, &sp.OriginalComment, &sp.Time, &sp.Timestamp,
|
||||
&sp.DXCC, &sp.CountryName,
|
||||
&newDXCC, &newBand, &newMode, &newSlot, &worked, &inWatchlist,
|
||||
&sp.POTARef, &sp.SOTARef, &sp.ParkName, &sp.SummitName,
|
||||
&sp.FlexSpotNumber, &sp.CommandNumber,
|
||||
&sp.Color, &sp.BackgroundColor, &sp.Priority, &sp.LifeTime,
|
||||
&sp.ClusterName, &source,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sp.NewDXCC = newDXCC == 1
|
||||
sp.NewBand = newBand == 1
|
||||
sp.NewMode = newMode == 1
|
||||
sp.NewSlot = newSlot == 1
|
||||
sp.CallsignWorked = worked == 1
|
||||
sp.InWatchlist = inWatchlist == 1
|
||||
sp.Source = spot.SpotSource(source)
|
||||
return &sp, nil
|
||||
}
|
||||
|
||||
func scanSpotFromRows(rows *sql.Rows) (*spot.Spot, error) {
|
||||
var sp spot.Spot
|
||||
var newDXCC, newBand, newMode, newSlot, worked, inWatchlist, source int
|
||||
err := rows.Scan(
|
||||
&sp.ID, &sp.DX, &sp.Spotter, &sp.FrequencyKHz, &sp.FrequencyMHz,
|
||||
&sp.Band, &sp.Mode, &sp.Comment, &sp.OriginalComment, &sp.Time, &sp.Timestamp,
|
||||
&sp.DXCC, &sp.CountryName,
|
||||
&newDXCC, &newBand, &newMode, &newSlot, &worked, &inWatchlist,
|
||||
&sp.POTARef, &sp.SOTARef, &sp.ParkName, &sp.SummitName,
|
||||
&sp.FlexSpotNumber, &sp.CommandNumber,
|
||||
&sp.Color, &sp.BackgroundColor, &sp.Priority, &sp.LifeTime,
|
||||
&sp.ClusterName, &source,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sp.NewDXCC = newDXCC == 1
|
||||
sp.NewBand = newBand == 1
|
||||
sp.NewMode = newMode == 1
|
||||
sp.NewSlot = newSlot == 1
|
||||
sp.CallsignWorked = worked == 1
|
||||
sp.InWatchlist = inWatchlist == 1
|
||||
sp.Source = spot.SpotSource(source)
|
||||
return &sp, nil
|
||||
}
|
||||
Reference in New Issue
Block a user