package main import ( "context" _ "embed" "fmt" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" ) //go:embed sql/schema.sql var sqlSchema string //go:embed sql/views.sql var sqlViews string type PostgresClient struct { //TODO move context out of client Ctx context.Context Pool *pgxpool.Pool } func (db *PostgresClient) CreateOrReplaceSchema() error { _, err := db.Pool.Exec(db.Ctx, sqlSchema) return err } func (db *PostgresClient) CreateOrReplaceViews() error { _, err := db.Pool.Exec(db.Ctx, sqlViews) return err } // InsertMembres inserts a slice of Membre into a database, returning the amount inserted and any error encountered func (d *PostgresClient) InsertMembres(membres ...Membre) (inserted int64, err error) { select { case <-d.Ctx.Done(): return inserted, fmt.Errorf("PostgresClient.Ctx closed: %s", d.Ctx.Err()) default: tx, err := d.Pool.Begin(d.Ctx) if err != nil { return inserted, err } defer tx.Rollback(d.Ctx) for i, membre := range membres { if membre.ID == "" { return inserted, fmt.Errorf("insertion ligne %d: membre requiert numéro étudiant valide", i) } result, err := tx.Exec(d.Ctx, ` INSERT INTO membres (id, last_name, first_name, prefered_name, programme_id) VALUES ($1, $2, $3, $4, $5) ON CONFLICT (id) DO NOTHING;`, membre.ID, membre.LastName, membre.FirstName, membre.PreferedName, membre.ProgrammeID, ) if err != nil { return 0, err } inserted += result.RowsAffected() } if err = tx.Commit(d.Ctx); err != nil { return 0, err } return inserted, err } } func (d *PostgresClient) InsertProgrammes(programmes ...Programme) (inserted int64, err error) { select { case <-d.Ctx.Done(): return inserted, fmt.Errorf("PostgresClient.Ctx closed: %s", d.Ctx.Err()) default: tx, err := d.Pool.Begin(d.Ctx) if err != nil { return inserted, err } defer tx.Rollback(d.Ctx) for _, programme := range programmes { if programme.ID == "" { return 0, fmt.Errorf("Cannot insert programme with no programme_id") } result, err := tx.Exec(d.Ctx, ` INSERT INTO programmes (id, name) VALUES ($1, $2) ON CONFLICT DO NOTHING;`, programme.ID, programme.Name) if err != nil { return 0, err } inserted += result.RowsAffected() } if err := tx.Commit(d.Ctx); err != nil { return inserted, err } return inserted, err } } func (d *PostgresClient) GetMembre(membreID string) (membre Membre, err error) { select { case <-d.Ctx.Done(): err = fmt.Errorf("PostgresClient.Ctx closed: %s", d.Ctx.Err()) return default: if err = d.Pool.QueryRow(d.Ctx, ` SELECT "membres".id, "membres".last_name, "membres".first_name, "membres".prefered_name, "membres".programme_id FROM "membres" WHERE "membres".id = $1 LIMIT 1; `, membreID).Scan( &membre.ID, &membre.LastName, &membre.FirstName, &membre.PreferedName, &membre.ProgrammeID, ); err != nil { return } if membre.ID == "" { return membre, fmt.Errorf("Aucun membre trouvé avec numéro '%s'", membre.ID) } return membre, nil } } /* func (d *PostgresClient) UpdateMembreName(membreID, newName string) (int64, error) { result, err := d.Pool.Exec("UPDATE membres SET prefered_name = $1 WHERE id = $2;", newName, membreID) if err != nil { return 0, err } rows, err := result.RowsAffected() if err != nil { return rows, err } return rows, nil } */ func (d *PostgresClient) GetMembres(limit int) (membres []Membre, err error) { select { case <-d.Ctx.Done(): return nil, fmt.Errorf("PostgresClient.Ctx closed: %s", d.Ctx.Err()) default: rows, err := d.Pool.Query(d.Ctx, ` SELECT "membres".id, "membres".last_name, "membres".first_name, "membres".prefered_name, "membres".programme_id FROM "membres" ORDER BY "membres".id LIMIT $1; `, limit) if err != nil { return nil, err } defer rows.Close() for rows.Next() { var membre Membre if err = rows.Scan( &membre.ID, &membre.LastName, &membre.FirstName, &membre.PreferedName, &membre.ProgrammeID, ); err != nil { return nil, err } membres = append(membres, membre) } if rows.Err() != nil { return membres, rows.Err() } return membres, nil } } func (d *PostgresClient) GetProgrammes(limit int) (programmes []Programme, err error) { select { case <-d.Ctx.Done(): return nil, fmt.Errorf("PostgresClient.Ctx closed: %s", d.Ctx.Err()) default: rows, err := d.Pool.Query(d.Ctx, ` SELECT "programmes".id, "programmes".name FROM "programmes" ORDER BY "programmes".id LIMIT $1; `, limit) if err != nil { return nil, err } defer rows.Close() for rows.Next() { var programme Programme if err = rows.Scan( &programme.ID, &programme.Name, ); err != nil { return nil, err } programmes = append(programmes, programme) } if rows.Err() != nil { return programmes, rows.Err() } return programmes, nil } } func (d *PostgresClient) UpdateMembrePreferedName(membreID string, name string) (err error) { select { case <-d.Ctx.Done(): return fmt.Errorf("PostgresClient.Ctx closed: %s", d.Ctx.Err()) default: if !IsMembreID(membreID) { return fmt.Errorf("Numéro étudiant '%s' invalide", membreID) } _, err = d.Pool.Exec(d.Ctx, ` UPDATE "membres" SET prefered_name = $1 WHERE "membres".id = $2; `, name, membreID) } return } func (d *PostgresClient) GetMembreForDisplay(membreID string) (membre MembreForDisplay, err error) { select { case <-d.Ctx.Done(): err = fmt.Errorf("PostgresClient.Ctx closed: %s", d.Ctx.Err()) return default: if err = d.Pool.QueryRow(d.Ctx, ` SELECT "membres_for_display".id, "membres_for_display".name, "membres_for_display".programme_id, "membres_for_display".programme_name FROM "membres_for_display" WHERE "membres_for_display".id = $1 LIMIT 1; `, membreID).Scan( &membre.ID, &membre.Name, &membre.ProgrammeID, &membre.ProgrammeName, ); err != nil { if err == pgx.ErrNoRows { err = fmt.Errorf("Numéro étudiant valide mais aucun·e membre trouvé·e") } return } if membre.ID == "" { return membre, fmt.Errorf("Aucun membre trouvé avec numéro '%s'", membre.ID) } return membre, nil } } func (d *PostgresClient) GetMembresForDisplay(limit int) (membres []MembreForDisplay, err error) { select { case <-d.Ctx.Done(): return nil, fmt.Errorf("PostgresClient.Ctx closed: %s", d.Ctx.Err()) default: rows, err := d.Pool.Query(d.Ctx, ` SELECT "membres_for_display".id, "membres_for_display".name, "membres_for_display".programme_id, "membres_for_display".programme_name FROM "membres_for_display" ORDER BY "membres_for_display".id LIMIT $1; `, limit) if err != nil { return nil, err } defer rows.Close() for rows.Next() { var membre MembreForDisplay if err = rows.Scan( &membre.ID, &membre.Name, &membre.ProgrammeID, &membre.ProgrammeName, ); err != nil { return nil, err } membres = append(membres, membre) } if rows.Err() != nil { return membres, rows.Err() } return membres, nil } }