package data import ( "database/sql" "fmt" "log" "github.com/go-sql-driver/mysql" "golang.org/x/crypto/bcrypt" ) type DB struct { *sql.DB } func OpenDB(dbName string) (*DB, error) { var err error db := DB{DB: &sql.DB{}} cfg := mysql.NewConfig() cfg.DBName = dbName cfg.User, cfg.Passwd, err = getCredentials() if err != nil { return nil, fmt.Errorf("error reading user credentials for DB: %v", err) } db.DB, err = sql.Open("mysql", cfg.FormatDSN()) if err != nil { return nil, fmt.Errorf("error opening DB: %v", err) } if err = db.Ping(); err != nil { return nil, fmt.Errorf("error pinging DB: %v", err) } return &db, nil } func (db *DB) AddUser(user *User, pass string) error { hashedPass, err := bcrypt.GenerateFromPassword([]byte(pass), bcrypt.DefaultCost) if err != nil { return fmt.Errorf("error creating password hash: %v", err) } query := ` INSERT INTO users (username, password, first_name, last_name, role) VALUES (?, ?, ?, ?, ?) ` if _, err = db.Exec(query, user.UserName, string(hashedPass), user.FirstName, user.LastName, user.Role); err != nil { return fmt.Errorf("error inserting user into DB: %v", err) } return nil } func (db *DB) GetID(userName string) (int64, error) { var id int64 query := ` SELECT id FROM users WHERE username = ? ` row := db.QueryRow(query, userName) if err := row.Scan(&id); err != nil { return 0, fmt.Errorf("user not in DB: %v", err) } return id, nil } func (db *DB) CheckPassword(id int64, pass string) error { var queriedPass string query := ` SELECT password FROM users WHERE id = ? ` row := db.QueryRow(query, id) if err := row.Scan(&queriedPass); err != nil { return fmt.Errorf("error reading password from DB: %v", err) } if err := bcrypt.CompareHashAndPassword([]byte(queriedPass), []byte(pass)); err != nil { return fmt.Errorf("incorrect password: %v", err) } return nil } func (db *DB) ChangePassword(id int64, oldPass, newPass string) error { tx, err := db.Begin() if err != nil { return fmt.Errorf("error starting transaction: %v", err) } var queriedPass string getQuery := ` SELECT password FROM users WHERE id = ? ` row := tx.QueryRow(getQuery, id) if err := row.Scan(&queriedPass); err != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil { log.Fatalf("error: transaction error: %v, rollback error: %v", err, rollbackErr) } return fmt.Errorf("error reading password from DB: %v", err) } if err := bcrypt.CompareHashAndPassword([]byte(queriedPass), []byte(oldPass)); err != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil { log.Fatalf("error: transaction error: %v, rollback error: %v", err, rollbackErr) } return fmt.Errorf("incorrect password: %v", err) } newHashedPass, err := bcrypt.GenerateFromPassword([]byte(newPass), bcrypt.DefaultCost) if err != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil { log.Fatalf("error: transaction error: %v, rollback error: %v", err, rollbackErr) } return fmt.Errorf("error creating password hash: %v", err) } setQuery := ` UPDATE users SET password = ? WHERE id = ? ` if _, err = tx.Exec(setQuery, string(newHashedPass), id); err != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil { log.Fatalf("error: transaction error: %v, rollback error: %v", err, rollbackErr) } return fmt.Errorf("error updating password in DB: %v", err) } if err = tx.Commit(); err != nil { return fmt.Errorf("error committing transaction: %v", err) } return nil } func (db *DB) CountEntries() (int64, error) { var count int64 query := `SELECT COUNT(*) FROM users` row := db.QueryRow(query) if err := row.Scan(&count); err != nil { return 0, fmt.Errorf("error counting rows in user DB: %v", err) } return count, nil } // TODO: No need for ID field in general func (db *DB) GetUser(id int64) (*User, error) { user := new(User) query := ` SELECT id, username, first_name, last_name, role FROM users WHERE id = ? ` row := db.QueryRow(query, id) if err := row.Scan(&user.ID, &user.UserName, &user.FirstName, &user.LastName, &user.Role); err != nil { return nil, fmt.Errorf("error reading user information: %v", err) } return user, nil } func (db *DB) AddTag(tagName string) error { query := "INSERT INTO tags name VALUES ?" if _, err := db.Exec(query, tagName); err != nil { return fmt.Errorf("error inserting tag into DB: %v", err) } return nil } func (db *DB) GetTagList() ([]Tag, error) { query := "SELECT id, name FROM tags" rows, err := db.Query(query) if err != nil { return nil, fmt.Errorf("error querying tags: %v", err) } var tagList []Tag for rows.Next() { var tag Tag if err = rows.Scan(&tag.ID, &tag.Name); err != nil { return nil, fmt.Errorf("error scanning tag row: %v", err) } tagList = append(tagList, tag) } return tagList, nil } func (db *DB) AddArticle(a *Article) error { query := ` INSERT INTO articles (title, description, content, published, author_id) VALUES (?, ?, ?, ?) ` if _, err := db.Exec(query, a.Title, a.Desc, a.Content, a.Published, a.AuthorID); err != nil { return fmt.Errorf("error inserting article into DB: %v", err) } return nil } func (db *DB) GetArticle(id int64) (*Article, error) { query := ` SELECT title, created, description, content, published, author_id FROM articles WHERE id = ? ` row := db.QueryRow(query, id) article := new(Article) if err := row.Scan(&article.Title, &article.Created, &article.Desc, &article.Content, &article.Published, &article.AuthorID); err != nil { return nil, fmt.Errorf("error scanning article row: %v", err) } return article, nil } func (db *DB) GetUnpublishedArticles() ([]Article, error) { query := ` SELECT id, title, created, description, content, published, author_id FROM articles WHERE published = ? ` rows, err := db.Query(query, false) if err != nil { return nil, fmt.Errorf("error querying articles: %v", err) } var articleList []Article for rows.Next() { var article Article if err = rows.Scan(&article.ID, &article.Title, &article.Created, &article.Desc, &article.Content, &article.Published, &article.AuthorID); err != nil { return nil, fmt.Errorf("error scanning article row: %v", err) } articleList = append(articleList, article) } return articleList, nil } func (db *DB) UpdateArticle(id int64, attribute string, val interface{}) error { query := ` UPDATE articles SET ? = ? WHERE id = ? ` if _, err := db.Exec(query, attribute, val, id); err != nil { return fmt.Errorf("error updating article in DB: %v", err) } return nil }