bottin/db.go

358 lines
7 KiB
Go

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
}
}