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, face, number 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, &status.Face, &status.Number, ) 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, face, number) VALUES ($1, $2, $3, $4) RETURNING id`, status.User, status.Content, status.Face, status.Number, ).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 } return tx.Commit() } func (s *Storage) StatusById(id int64) (model.Status, error) { var status model.Status err := s.db.QueryRow( `SELECT id, author, content, face, created_at, number FROM statuses WHERE id=$1`, id, ).Scan( &status.Id, &status.User, &status.Content, &status.Face, &status.CreatedAt, &status.Number, ) 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[:perPage], true, nil } return statuses, false, nil } func (s *Storage) StatusFeed() ([]model.Status, error) { rows, err := s.db.Query(statusQueryBuilder{ limit: strconv.Itoa(20), }.build()) if err != nil { return nil, err } var statuses []model.Status for rows.Next() { post, err := s.populateStatus(rows) if err != nil { return nil, err } statuses = append(statuses, post) } return statuses, nil } 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 nil, false, err } statuses = append(statuses, post) } if len(statuses) > perPage { return statuses[:perPage], true, nil } return statuses, false, nil } func (s *Storage) LatestStatuses() ([]model.Status, error) { rows, err := s.db.Query(` SELECT statuses.id, users.name, statuses.content, statuses.created_at, statuses.face, statuses.number 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, nil } func (s *Storage) UpdateStatus(status model.Status) error { stmt, err := s.db.Prepare(` UPDATE statuses SET content = $1, face = $2, number = $3 WHERE id = $4 AND author = $5; `) if err != nil { return err } _, err = stmt.Exec( status.Content, status.Face, status.Number, 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 err := tx.QueryRowContext(ctx, `SELECT id FROM statuses WHERE author = $1 ORDER BY created_at DESC LIMIT 1`, author, ).Scan(&newId) if err == sql.ErrNoRows { _, err = tx.ExecContext(ctx, `UPDATE users SET status_id = NULL WHERE name = $1`, author) if err != nil { tx.Rollback() return err } } else if err == nil { _, err = tx.ExecContext(ctx, `UPDATE users SET status_id = $1 WHERE name = $2`, newId, author) if err != nil { tx.Rollback() return err } } else { tx.Rollback() return err } } else { _, err := tx.ExecContext(ctx, `UPDATE users SET status_id = $1 WHERE name = $2`, latestId, author) if err != nil { tx.Rollback() return err } } return tx.Commit() }