package services import ( "context" "database/sql" "fmt" "log" "time" "github.com/lib/pq" _ "github.com/lib/pq" ) var dbConn *sql.DB type DB interface { // VerifyEmail(email string) error // ResetPassword(email string) (string, error) // RegisterUser(email, username, password string) (User, error) GetLog(int64, time.Time, time.Time, []LogType) ([]StatLog, error) AddLog(int64, StatLog) error UpdateProfile(int64, User) error GetProfileByID(int64) (User, error) GetProfileByEmail(string) (User, error) } func Connect(host, user, password string) (DB, error) { psqlconn := fmt.Sprintf("host=%s port=5432 user=%s password=%s dbname=bodytrack sslmode=disable", host, user, password) pgClient, err := sql.Open("postgres", psqlconn) if err != nil { return nil, err } if err := pgClient.Ping(); err != nil { return nil, err } return pgdb{DB: pgClient}, nil } type pgdb struct{ *sql.DB } func (db pgdb) GetLog(id int64, start, end time.Time, lt []LogType) ([]StatLog, error) { getLogSql := ` SELECT value, logType, recordedTs FROM stats.log WHERE userId = $1 AND recordedTs >= $2 AND recordedTs <= $3 AND logType = ANY($4) ORDER BY recordedTs DESC;` var results []StatLog rows, err := db.Query(getLogSql, id, start, end, pq.Array(lt)) if err != nil { return nil, err } defer rows.Close() if err := rows.Err(); err != nil { return nil, err } for rows.Next() { var sl StatLog if err := rows.Scan(&sl.Value, &sl.Type, &sl.RecordedTS); err != nil { log.Printf("could not scan value for statlog: %v", err) } results = append(results, sl) } return results, nil } func (db pgdb) AddLog(id int64, sl StatLog) error { statLogInsertSql := ` INSERT INTO stats.log (userId, value, logType, recordedTs) VALUES ($1, $2, $3, $4); ` tx, err := db.BeginTx(context.Background(), nil) if err != nil { return err } defer tx.Commit() if _, err := tx.Exec(statLogInsertSql, id, sl.Value, sl.Type, sl.RecordedTS); err != nil { tx.Rollback() return err } return nil } func (db pgdb) UpdateProfile(id int64, u User) error { updateProfileSql := ` UPDATE accounts.profile SET username = $2, displayunit = $3, birthdate = $4, height = $5 WHERE userId = $1; ` updateUserSql := ` UPDATE accounts.users SET email = $2, passwordHash = $3 WHERE id = $1; ` tx, err := db.BeginTx(context.Background(), &sql.TxOptions{Isolation: sql.LevelReadCommitted}) if err != nil { return err } defer tx.Commit() if _, err := tx.Exec(updateProfileSql, id, u.Username, u.DisplayUnit, u.Birthdate, u.Height); err != nil { tx.Rollback() return fmt.Errorf("could not update profile: %w", err) } if _, err := tx.Exec(updateUserSql, id, u.Email, u.PasswordHash); err != nil { tx.Rollback() return fmt.Errorf("could not update user: %w", err) } return nil } func (db pgdb) GetProfileByID(id int64) (User, error) { sql := `SELECT u.id, u.email, u.passwordhash, p.username, p.displayunit, p.birthdate, p.height FROM Accounts.Users u JOIN Accounts.Profile p on u.id = p.userId WHERE u.id = $1 LIMIT 1; ` var u User if err := db.QueryRow(sql, id).Scan( &u.ID, &u.Email, &u.PasswordHash, &u.Username, &u.DisplayUnit, &u.Birthdate, &u.Height, ); err != nil { return u, err } return u, nil } func (db pgdb) GetProfileByEmail(email string) (User, error) { sql := `SELECT u.id, u.email, u.passwordhash, p.username, p.displayunit, p.birthdate, p.height FROM Accounts.Users u JOIN Accounts.Profile p on u.id = p.userId WHERE u.email = $1 LIMIT 1; ` var u User if err := db.QueryRow(sql, email).Scan( &u.ID, &u.Email, &u.PasswordHash, &u.Username, &u.DisplayUnit, &u.Birthdate, &u.Height, ); err != nil { return u, err } return u, nil }