package training import ( "context" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" "gitlab.mareshq.com/hq/yggdrasil/pkg/slug" "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() if training.Slug == "" { training.Slug = slug.NewString(training.Name) } tx, txErr := r.pg.Begin(ctx) if txErr != nil { return txErr } queryErr := tx.QueryRow(ctx, ` INSERT INTO training.trainings (name, slug, description, days) VALUES ($1, $2, $3, $4) RETURNING id `, training.Name, training.Slug, 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 ID) (*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.Slug, &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([]Price, 0) training.Pricing, err = pgx.CollectRows[Price](rows, func(row pgx.CollectableRow) (Price, error) { var price Price err := row.Scan(&price.Amount, &price.Currency, &price.Type) if err != nil { return Price{}, 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.Slug, &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[Price]) 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() if training.Slug == "" { training.Slug = slug.NewString(training.Name) } else { slugValidateErr := slug.Validate(training.Slug) if slugValidateErr != nil { return slugValidateErr } } tx, err := r.pg.Begin(ctx) if err != nil { return err } _, err = tx.Exec(ctx, ` UPDATE training.trainings SET name = $1, slug = $2, description = $3, days = $4 WHERE id = $4 `, training.Name, training.Slug, 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 ID) 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 PostgresDateRepository struct { pg *pgxpool.Pool } func NewPostgresDateRepository(pg *pgxpool.Pool) *PostgresDateRepository { return &PostgresDateRepository{pg: pg} } func (r *PostgresDateRepository) Create(trainingID ID, trainingDate *Date) 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 *PostgresDateRepository) FindByID(id DateID) (*Date, error) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() var trainingDate Date 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 *PostgresDateRepository) FindAll() ([]Date, 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 []Date for rows.Next() { var trainingDate Date 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 *PostgresDateRepository) FindAllByTrainingID(trainingID ID) ([]Date, 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 []Date for rows.Next() { var trainingDate Date 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 *PostgresDateRepository) FindUpcomingByTrainingID(trainingID ID) ([]Date, 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 []Date for rows.Next() { var trainingDate Date 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 *PostgresDateRepository) Update(trainingDate *Date) 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 *PostgresDateRepository) Delete(id DateID) 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 PostgresAttendeeRepository struct { pg *pgxpool.Pool } func NewPostgresAttendeeRepository(pg *pgxpool.Pool) *PostgresAttendeeRepository { return &PostgresAttendeeRepository{pg: pg} } func (r *PostgresAttendeeRepository) Create(trainingDateID DateID, attendee *Attendee) error { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() err := r.pg.QueryRow(ctx, ` INSERT INTO training.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 *PostgresAttendeeRepository) FindByID(id AttendeeID) (*Attendee, error) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() var attendee Attendee 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.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 *PostgresAttendeeRepository) FindAll() ([]Attendee, 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.attendees `) if err != nil { return nil, err } defer rows.Close() var attendees []Attendee for rows.Next() { var attendee Attendee 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 *PostgresAttendeeRepository) FindAllByTrainingDateID(trainingDateID DateID) ([]Attendee, 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.attendees WHERE date_id = $1 `, trainingDateID) if err != nil { return nil, err } defer rows.Close() var attendees []Attendee for rows.Next() { var attendee Attendee 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 *PostgresAttendeeRepository) Update(attendee *Attendee) error { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() _, err := r.pg.Exec(ctx, ` UPDATE training.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 *PostgresAttendeeRepository) Delete(id AttendeeID) error { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() _, err := r.pg.Exec(ctx, `DELETE FROM training.attendees WHERE id = $1`, id) if err != nil { return err } return nil }