package storage import ( "context" "database/sql" "status/model" "strconv" "strings" ) type statusQueryBuilder struct { where string limit string offset string } func (p statusQueryBuilder) build() string { query := []string{`SELECT id, author, content, created_at from statuses`} if p.where != "" { query = append(query, `WHERE`, p.where) } query = append(query, `ORDER BY created_at desc`) if p.limit != "" { query = append(query, `LIMIT`, p.limit) } if p.offset != "" { query = append(query, `OFFSET`, p.offset) } return strings.Join(query, " ") } func (s *Storage) populateStatus(rows *sql.Rows) (model.Status, error) { var status model.Status err := rows.Scan(&status.Id, &status.User, &status.Content, &status.CreatedAt) if err != nil { return status, err } return status, nil } func (s *Storage) CreateStatus(status model.Status) error { var statusId int64 ctx := context.Background() tx, err := s.db.BeginTx(ctx, nil) if err != nil { return err } if err := tx.QueryRowContext(ctx, `INSERT INTO statuses (author, content) VALUES ($1, $2) RETURNING id`, status.User, status.Content).Scan(&statusId); err != nil { tx.Rollback() return err } if _, err := tx.ExecContext(ctx, `UPDATE users set status_id=$1 where name=$2`, statusId, status.User); err != nil { tx.Rollback() return err } err = tx.Commit() return err } func (s *Storage) StatusById(id int64) (model.Status, error) { var status model.Status err := s.db.QueryRow( `SELECT id, author, content from statuses WHERE id=$1`, id).Scan( &status.Id, &status.User, &status.Content, ) return status, err } func (s *Storage) StatusByUsername(user string, perPage int, page int64) ([]model.Status, bool, error) { rows, err := s.db.Query(statusQueryBuilder{ where: `author = $1`, limit: strconv.Itoa(perPage + 1), offset: `$2`, }.build(), user, page*int64(perPage)) if err != nil { return nil, false, err } var statuses []model.Status for rows.Next() { post, err := s.populateStatus(rows) if err != nil { return nil, false, err } statuses = append(statuses, post) } if len(statuses) > perPage { return statuses[0:perPage], true, err } return statuses, false, err } func (s *Storage) Statuses(page int64, perPage int) ([]model.Status, bool, error) { rows, err := s.db.Query(statusQueryBuilder{ limit: strconv.Itoa(perPage + 1), offset: `$1`, }.build(), page*int64(perPage)) if err != nil { return nil, false, err } var statuses []model.Status for rows.Next() { post, err := s.populateStatus(rows) if err != nil { return statuses, false, err } statuses = append(statuses, post) } if len(statuses) > perPage { return statuses[0:perPage], true, err } return statuses, false, err } func (s *Storage) LatestStatuses() ([]model.Status, error) { rows, err := s.db.Query(` select statuses.id, users.name, statuses.content, statuses.created_at from users inner join statuses on users.status_id = statuses.id order by statuses.created_at desc; `) if err != nil { return nil, err } var statuses []model.Status for rows.Next() { post, err := s.populateStatus(rows) if err != nil { return statuses, err } statuses = append(statuses, post) } return statuses, err } func (s *Storage) UpdateStatus(status model.Status) error { stmt, err := s.db.Prepare(`UPDATE statuses SET content = $1 WHERE id = $2 and author = $3;`) if err != nil { return err } _, err = stmt.Exec(status.Content, status.Id, status.User) return err } func (s *Storage) DeleteStatus(id int64, author string) error { ctx := context.Background() tx, err := s.db.BeginTx(ctx, nil) if err != nil { return err } var latestId int64 if err := tx.QueryRowContext(ctx, `select status_id from users where name = $1`, author).Scan(&latestId); err != nil { tx.Rollback() return err } if _, err := tx.ExecContext(ctx, `UPDATE users set status_id=$1 where name=$2`, nil, author); err != nil { tx.Rollback() return err } if _, err := tx.ExecContext(ctx, `DELETE from statuses WHERE id = $1 and author = $2;`, id, author); err != nil { tx.Rollback() return err } if latestId == id { var newId int64 if err := tx.QueryRowContext(ctx, `select id from statuses where author = $1 order by created_at desc limit 1;`, author).Scan(&newId); err != nil { tx.Rollback() return err } if _, err := tx.ExecContext(ctx, `UPDATE users set status_id=$1 where name=$2`, newId, author); err != nil { tx.Rollback() return err } } err = tx.Commit() return err }