package training import ( "context" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" "time" ) type PostgresTrainingRepository struct { pg *pgxpool.Pool } func NewPostgresTrainingRepository(pg *pgxpool.Pool) *PostgresTrainingRepository { return &PostgresTrainingRepository{pg: pg} } func (r *PostgresTrainingRepository) Create(training *Training) error { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() tx, txErr := r.pg.Begin(ctx) if txErr != nil { return txErr } queryErr := tx.QueryRow(ctx, ` INSERT INTO training.trainings (name, description, days) VALUES ($1, $2, $3) RETURNING id `, training.Name, training.Description, training.Days).Scan(&training.ID) if queryErr != nil { return queryErr } queryBatch := &pgx.Batch{} for _, price := range training.Pricing { queryBatch.Queue(` INSERT INTO training.prices (training_id, amount, currency, type) VALUES ($1, $2, $3, $4) `, training.ID, price.Amount, price.Currency, price.Type) } var err error batch := tx.SendBatch(ctx, queryBatch) defer func() { if e := batch.Close(); e != nil { err = e } if err != nil { _ = tx.Rollback(ctx) } else { if e := tx.Commit(ctx); e != nil { err = e } } }() _, err = batch.Exec() if err != nil { return err } return nil } func (r *PostgresTrainingRepository) FindByID(id TrainingID) (*Training, error) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() var training Training err := r.pg.QueryRow(ctx, ` SELECT id, name, description, days FROM training.trainings WHERE id = $1 `, id).Scan(&training.ID, &training.Name, &training.Description, &training.Days) if err != nil { return nil, err } rows, err := r.pg.Query(ctx, ` SELECT amount, currency, type FROM training.prices WHERE training_id = $1 `, id) if err != nil { return nil, err } defer rows.Close() training.Pricing = make([]TrainingPrice, 0) training.Pricing, err = pgx.CollectRows[TrainingPrice](rows, func(row pgx.CollectableRow) (TrainingPrice, error) { var price TrainingPrice err := row.Scan(&price.Amount, &price.Currency, &price.Type) if err != nil { return TrainingPrice{}, err } return price, nil }) if err != nil { return nil, err } return &training, nil } func (r *PostgresTrainingRepository) FindAll() ([]Training, error) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() rows, err := r.pg.Query(ctx, ` SELECT * FROM training.trainings `) if err != nil { return nil, err } defer rows.Close() trainings, err := pgx.CollectRows(rows, func(row pgx.CollectableRow) (Training, error) { var training Training scanErr := row.Scan(&training.ID, &training.Name, &training.Description, &training.Days) if scanErr != nil { return Training{}, scanErr } priceRows, queryErr := r.pg.Query(ctx, `SELECT amount, currency, type FROM training.prices WHERE training_id = $1`, training.ID) if queryErr != nil { return Training{}, queryErr } training.Pricing, scanErr = pgx.CollectRows(priceRows, pgx.RowToStructByName[TrainingPrice]) if scanErr != nil { return Training{}, scanErr } return training, nil }) if err != nil { return nil, err } return trainings, nil } func (r *PostgresTrainingRepository) Update(training *Training) error { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() tx, err := r.pg.Begin(ctx) if err != nil { return err } _, err = tx.Exec(ctx, ` UPDATE training.trainings SET name = $1, description = $2, days = $3 WHERE id = $4 `, training.Name, training.Description, training.Days, training.ID) if err != nil { return err } queryBatch := &pgx.Batch{} for _, price := range training.Pricing { queryBatch.Queue(` INSERT INTO training.prices (training_id, amount, currency, type) VALUES ($1, $2, $3, $4) `, training.ID, price.Amount, price.Currency, price.Type) } batch := tx.SendBatch(ctx, queryBatch) defer func() { if e := batch.Close(); e != nil { err = e } if err != nil { _ = tx.Rollback(ctx) } else { if e := tx.Commit(ctx); e != nil { err = e } } }() _, err = batch.Exec() if err != nil { return err } return nil } func (r *PostgresTrainingRepository) Delete(id TrainingID) error { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() tx, err := r.pg.Begin(ctx) if err != nil { return err } _, err = tx.Exec(context.Background(), ` DELETE FROM training.prices WHERE training_id = $1 `, id) if err != nil { return err } _, err = tx.Exec(context.Background(), ` DELETE FROM training.trainings WHERE id = $1 `, id) if err != nil { return err } err = tx.Commit(ctx) if err != nil { return err } return nil } type PostgresTrainingDateRepository struct { pg *pgxpool.Pool } func NewPostgresTrainingDateRepository(pg *pgxpool.Pool) *PostgresTrainingDateRepository { return &PostgresTrainingDateRepository{pg: pg} } func (r *PostgresTrainingDateRepository) Create(trainingID TrainingID, trainingDate *TrainingDate) error { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() err := r.pg.QueryRow(ctx, ` INSERT INTO training.dates (training_id, date, start_time, days, is_online, location, address, capacity, price_amount, price_currency) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING id`, trainingID, trainingDate.Date, trainingDate.StartTime, trainingDate.Days, trainingDate.IsOnline, trainingDate.Location, trainingDate.Address, trainingDate.Capacity, trainingDate.PriceAmount, trainingDate.PriceCurrency, ).Scan(&trainingDate.ID) if err != nil { return err } return nil } func (r *PostgresTrainingDateRepository) FindByID(id TrainingDateID) (*TrainingDate, error) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() var trainingDate TrainingDate err := r.pg.QueryRow(ctx, `SELECT * FROM training.dates WHERE id = $1`, id). Scan( &trainingDate.ID, &trainingDate.trainingID, &trainingDate.Date, &trainingDate.StartTime, &trainingDate.Days, &trainingDate.IsOnline, &trainingDate.Location, &trainingDate.Address, &trainingDate.Capacity, &trainingDate.PriceAmount, &trainingDate.PriceCurrency, ) if err != nil { return nil, err } return &trainingDate, nil } func (r *PostgresTrainingDateRepository) FindAll() ([]TrainingDate, error) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() rows, err := r.pg.Query(ctx, `SELECT * FROM training.dates`) if err != nil { return nil, err } defer rows.Close() var trainingDates []TrainingDate for rows.Next() { var trainingDate TrainingDate err := rows.Scan( &trainingDate.ID, &trainingDate.trainingID, &trainingDate.Date, &trainingDate.StartTime, &trainingDate.Days, &trainingDate.IsOnline, &trainingDate.Location, &trainingDate.Address, &trainingDate.Capacity, &trainingDate.PriceAmount, &trainingDate.PriceCurrency, ) if err != nil { return nil, err } trainingDates = append(trainingDates, trainingDate) } return trainingDates, nil } func (r *PostgresTrainingDateRepository) FindAllByTrainingID(trainingID TrainingID) ([]TrainingDate, error) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() rows, err := r.pg.Query(ctx, `SELECT * FROM training.dates WHERE training_id = $1`, trainingID) if err != nil { return nil, err } defer rows.Close() var trainingDates []TrainingDate for rows.Next() { var trainingDate TrainingDate err := rows.Scan( &trainingDate.ID, &trainingDate.trainingID, &trainingDate.Date, &trainingDate.StartTime, &trainingDate.Days, &trainingDate.IsOnline, &trainingDate.Location, &trainingDate.Address, &trainingDate.Capacity, &trainingDate.PriceAmount, &trainingDate.PriceCurrency, ) if err != nil { return nil, err } trainingDates = append(trainingDates, trainingDate) } return trainingDates, nil } func (r *PostgresTrainingDateRepository) FindUpcomingByTrainingID(trainingID TrainingID) ([]TrainingDate, error) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() rows, err := r.pg.Query(ctx, ` SELECT * FROM training.dates WHERE training_id = $1 AND date > $2 `, trainingID, time.Now()) if err != nil { return nil, err } defer rows.Close() var trainingDates []TrainingDate for rows.Next() { var trainingDate TrainingDate err := rows.Scan( &trainingDate.ID, &trainingDate.trainingID, &trainingDate.Date, &trainingDate.StartTime, &trainingDate.Days, &trainingDate.IsOnline, &trainingDate.Location, &trainingDate.Address, &trainingDate.Capacity, &trainingDate.PriceAmount, &trainingDate.PriceCurrency, ) if err != nil { return nil, err } trainingDates = append(trainingDates, trainingDate) } return trainingDates, nil } func (r *PostgresTrainingDateRepository) Update(trainingDate *TrainingDate) error { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() _, err := r.pg.Exec(ctx, ` UPDATE training.dates SET date = $1, start_time = $2, days = $3, is_online = $4, location = $5, address = $6, capacity = $7, price_amount = $8, price_currency = $9 WHERE id = $10 `, trainingDate.Date, trainingDate.StartTime, trainingDate.Days, trainingDate.IsOnline, trainingDate.Location, trainingDate.Address, trainingDate.Capacity, trainingDate.PriceAmount, trainingDate.PriceCurrency, trainingDate.ID) if err != nil { return err } return nil } func (r *PostgresTrainingDateRepository) Delete(id TrainingDateID) error { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() _, err := r.pg.Exec(ctx, `DELETE FROM training.dates WHERE id = $1`, id) if err != nil { return err } return nil } type PostgresTrainingDateAttendeeRepository struct { pg *pgxpool.Pool } func NewPostgresTrainingDateAttendeeRepository(pg *pgxpool.Pool) *PostgresTrainingDateAttendeeRepository { return &PostgresTrainingDateAttendeeRepository{pg: pg} } func (r *PostgresTrainingDateAttendeeRepository) Create(trainingDateID TrainingDateID, attendee *TrainingDateAttendee) error { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() err := r.pg.QueryRow(ctx, ` INSERT INTO training.date_attendees (date_id, name, email, phone, company, position, is_student, has_paid, has_attended, bill_amount, bill_currency) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING id `, trainingDateID, attendee.Name, attendee.Email, attendee.Phone, attendee.Company, attendee.Position, attendee.IsStudent, attendee.HasPaid, attendee.HasAttended, attendee.BillAmount, attendee.BillCurrency). Scan(&attendee.ID) if err != nil { return err } return nil } func (r *PostgresTrainingDateAttendeeRepository) FindByID(id TrainingDateAttendeeID) (*TrainingDateAttendee, error) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() var attendee TrainingDateAttendee err := r.pg.QueryRow(ctx, ` SELECT id, date_id, name, email, phone, company, position, is_student, has_paid, has_attended, bill_amount, bill_currency FROM training.date_attendees WHERE id = $1 `, id).Scan(&attendee.ID, &attendee.trainingDateID, &attendee.Name, &attendee.Email, &attendee.Phone, &attendee.Company, &attendee.Position, &attendee.IsStudent, &attendee.HasPaid, &attendee.HasAttended, &attendee.BillAmount, &attendee.BillCurrency) if err != nil { return nil, err } return &attendee, nil } func (r *PostgresTrainingDateAttendeeRepository) FindAll() ([]TrainingDateAttendee, error) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() rows, err := r.pg.Query(ctx, ` SELECT id, date_id, name, email, phone, company, position, is_student, has_paid, has_attended, bill_amount, bill_currency FROM training.date_attendees `) if err != nil { return nil, err } defer rows.Close() var attendees []TrainingDateAttendee for rows.Next() { var attendee TrainingDateAttendee err := rows.Scan(&attendee.ID, &attendee.trainingDateID, &attendee.Name, &attendee.Email, &attendee.Phone, &attendee.Company, &attendee.Position, &attendee.IsStudent, &attendee.HasPaid, &attendee.HasAttended, &attendee.BillAmount, &attendee.BillCurrency) if err != nil { return nil, err } attendees = append(attendees, attendee) } return attendees, nil } func (r *PostgresTrainingDateAttendeeRepository) FindAllByTrainingDateID(trainingDateID TrainingDateID) ([]TrainingDateAttendee, error) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() rows, err := r.pg.Query(ctx, ` SELECT id, date_id, name, email, phone, company, position, is_student, has_paid, has_attended, bill_amount, bill_currency FROM training.date_attendees WHERE date_id = $1 `, trainingDateID) if err != nil { return nil, err } defer rows.Close() var attendees []TrainingDateAttendee for rows.Next() { var attendee TrainingDateAttendee err := rows.Scan(&attendee.ID, &attendee.trainingDateID, &attendee.Name, &attendee.Email, &attendee.Phone, &attendee.Company, &attendee.Position, &attendee.IsStudent, &attendee.HasPaid, &attendee.HasAttended, &attendee.BillAmount, &attendee.BillCurrency) if err != nil { return nil, err } attendees = append(attendees, attendee) } return attendees, nil } func (r *PostgresTrainingDateAttendeeRepository) Update(attendee *TrainingDateAttendee) error { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() _, err := r.pg.Exec(ctx, ` UPDATE training.date_attendees SET name = $1, email = $2, phone = $3, company = $4, position = $5, is_student = $6, has_paid = $7, has_attended = $8, bill_amount = $9, bill_currency = $10 WHERE id = $10 `, attendee.Name, attendee.Email, attendee.Phone, attendee.Company, attendee.Position, attendee.IsStudent, attendee.HasPaid, attendee.HasAttended, attendee.BillAmount, attendee.BillCurrency, attendee.ID) if err != nil { return err } return nil } func (r *PostgresTrainingDateAttendeeRepository) Delete(id TrainingDateAttendeeID) error { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() _, err := r.pg.Exec(ctx, `DELETE FROM training.date_attendees WHERE id = $1`, id) if err != nil { return err } return nil }