From c0a70a18e160680ec9755ea65f47208985e29c48 Mon Sep 17 00:00:00 2001 From: Las Zenow Date: Mon, 9 Apr 2018 00:15:58 +0000 Subject: [PATCH] Add book list support in the database --- lib/database/database.go | 8 ++- lib/database/list.go | 132 ++++++++++++++++++++++++++++++++++++++ lib/database/list_test.go | 123 +++++++++++++++++++++++++++++++++++ lib/database/ro.go | 28 ++++++++ lib/database/users.go | 8 +++ 5 files changed, 298 insertions(+), 1 deletion(-) create mode 100644 lib/database/list.go create mode 100644 lib/database/list_test.go diff --git a/lib/database/database.go b/lib/database/database.go index 6c67624..d70b3d6 100644 --- a/lib/database/database.go +++ b/lib/database/database.go @@ -36,6 +36,8 @@ type DB interface { UpdateSubmissionComment(submissionID, bookID, comment string) error GetSubmission(submissionID string) (submission []Submission, err error) GetComment(bookID string) (string, error) + + BookLister } const ( @@ -101,7 +103,7 @@ func (db pgDB) Close() error { } func (db pgDB) create() error { - models := []interface{}{&Book{}, &New{}, &User{}, &Visit{}, &Submission{}} + models := []interface{}{&Book{}, &New{}, &User{}, &Visit{}, &Submission{}, &BookList{}, &BookListEntry{}} for _, model := range models { options := &orm.CreateTableOptions{ IfNotExists: true, @@ -230,4 +232,8 @@ ALTER TABLE ONLY submissions DROP CONSTRAINT IF EXISTS submissions_book_id_fkey; ALTER TABLE ONLY submissions ADD CONSTRAINT submissions_book_id_fkey FOREIGN KEY (book_id) REFERENCES books(id) ON DELETE SET NULL; + +-- BookLists indexes +CREATE INDEX IF NOT EXISTS book_lists_list_id_idx on book_lists(list_id); +CREATE INDEX IF NOT EXISTS book_lists_user_idx on book_lists(user_id); ` diff --git a/lib/database/list.go b/lib/database/list.go new file mode 100644 index 0000000..25c5f13 --- /dev/null +++ b/lib/database/list.go @@ -0,0 +1,132 @@ +package database + +import ( + "github.com/go-pg/pg" +) + +type BookLister interface { + NewBookList(listID, title, username string, description []string) error + AddBookToList(listID, bookID string) error + DeleteBookFromList(listID, bookID string) error + UpdateBookList(listID, title string, description []string) error + GetBookList(listID string) (*BookList, error) + GetListsByUser(username string) ([]BookList, error) + GetListsByBook(bookID string) ([]BookList, error) +} + +type BookList struct { + ID int `sql:"type:serial"` + ListID string `sql:"type:varchar(16)"` + Title string + Description []string `sql:"description" pg:",array"` + UserID int `sql:"type:integer"` + User *User + Books []Book `pg:"many2many:book_list_entries"` +} + +type BookListEntry struct { + ID int `sql:"type:serial"` + BookListID int `sql:"type:integer"` + BookList *BookList + BookID string `sql:"type:varchar(16)"` + Book *Book +} + +func (db *pgDB) NewBookList(listID, title, username string, description []string) error { + user, err := db.getUser(username) + if err != nil { + return err + } + return db.sql.Insert(&BookList{ + ListID: listID, + Title: title, + Description: description, + UserID: user.ID, + }) +} + +func (db *pgDB) AddBookToList(listID, bookID string) error { + list, err := db.GetBookList(listID) + if err != nil { + return err + } + + return db.sql.Insert(&BookListEntry{ + BookListID: list.ID, + BookID: bookID, + }) +} + +func (db *pgDB) DeleteBookFromList(listID, bookID string) error { + list, err := db.GetBookList(listID) + if err != nil { + return err + } + + _, err = db.sql.Model(&BookListEntry{}). + Where("book_list_id = ? AND book_id = ?", list.ID, bookID). + Delete() + return err +} + +func (db *pgDB) UpdateBookList(listID, title string, description []string) error { + _, err := db.sql.Model(&BookList{}). + Set("title = ?, description = ?", title, pg.Array(description)). + Where("list_id = ?", listID). + Update() + return err +} + +func (db *pgDB) GetBookList(listID string) (*BookList, error) { + var bookList BookList + err := db.sql.Model(&bookList). + Column("Books"). + Where("list_id = ?", listID). + Select() + return &bookList, err +} + +func (db *pgDB) GetListsByUser(username string) ([]BookList, error) { + var bookLists []BookList + + user, err := db.getUser(username) + if err != nil { + return bookLists, err + } + + err = db.sql.Model(&bookLists). + Column("Books"). + Where("user_id = ?", user.ID). + Select() + return bookLists, err +} + +func (db *pgDB) GetListsByBook(bookID string) ([]BookList, error) { + var bookLists []BookList + var bookListEntries []BookListEntry + + err := db.sql.Model(&bookListEntries). + Where("book_id = ?", bookID). + Select() + if err != nil { + return bookLists, err + } + + whereQuery := "id IN (" + listIDs := make([]interface{}, len(bookListEntries)) + for i, entry := range bookListEntries { + whereQuery += "?" + if i < len(bookListEntries)-1 { + whereQuery += ", " + } else { + whereQuery += ")" + } + listIDs[i] = entry.ID + } + + err = db.sql.Model(&bookLists). + Column("Books"). + Where(whereQuery, listIDs...). + Select() + return bookLists, err +} diff --git a/lib/database/list_test.go b/lib/database/list_test.go new file mode 100644 index 0000000..cf8d001 --- /dev/null +++ b/lib/database/list_test.go @@ -0,0 +1,123 @@ +package database + +import "testing" + +const ( + listID = "1234567890" + title = "My title" + username = "username" +) + +func TestNewBookList(t *testing.T) { + db, dbclose := testDbInit(t) + defer dbclose() + testCreateList(t, db) + + list, err := db.GetBookList(listID) + if err != nil { + t.Fatal("db.GetBookList() return an error: ", err) + } + if list.ListID != listID { + t.Fatal("list id doesn't match: ", list.ListID, " <=> ", listID) + } + if list.Title != title { + t.Fatal("title doesn't match: ", list.Title, " <=> ", title) + } +} + +func TestBookListAddDelBooks(t *testing.T) { + db, dbclose := testDbInit(t) + defer dbclose() + + testCreateList(t, db) + testAddBook(t, db) + + err := db.AddBookToList(listID, book.ID) + if err != nil { + t.Fatal("db.AddBookToList() return an error: ", err) + } + + list, err := db.GetBookList(listID) + if err != nil { + t.Fatal("db.GetBookList() return an error: ", err) + } + if len(list.Books) != 1 { + t.Fatal("We got a un expected number of books: ", list.Books) + } + if list.Books[0].ID != book.ID { + t.Fatal("book id doesn't match: ", list.Books[0].ID, " <=> ", book.ID) + } + + lists, err := db.GetListsByBook(book.ID) + if err != nil { + t.Fatal("db.GetListsByBook() return an error: ", err) + } + if len(lists) != 1 { + t.Fatal("We got a un expected number of lists: ", lists) + } + if lists[0].Books[0].Title != book.Title { + t.Fatal("book id doesn't match: ", lists[0].Books[0].Title, " <=> ", book.Title) + } + + err = db.DeleteBookFromList(listID, book.ID) + if err != nil { + t.Fatal("db.DeleteBookFromList() return an error: ", err) + } + + list, err = db.GetBookList(listID) + if err != nil { + t.Fatal("db.GetBookList() return an error: ", err) + } + if len(list.Books) != 0 { + t.Fatal("We got a un expected number of books: ", list.Books) + } +} + +func TestBookListByUser(t *testing.T) { + db, dbclose := testDbInit(t) + defer dbclose() + testCreateList(t, db) + + lists, err := db.GetListsByUser(username) + if err != nil { + t.Fatal("db.GetBookListsByUser() return an error: ", err) + } + if len(lists) != 1 { + t.Fatal("We got a un expected number of lists: ", lists) + } + if lists[0].ListID != listID { + t.Fatal("list id doesn't match: ", lists[0].ListID, " <=> ", listID) + } +} + +func TestBookListUpdate(t *testing.T) { + const otherTitle = "other title" + + db, dbclose := testDbInit(t) + defer dbclose() + testCreateList(t, db) + + err := db.UpdateBookList(listID, otherTitle, []string{}) + if err != nil { + t.Fatal("db.UpdateBookList() return an error: ", err) + } + + list, err := db.GetBookList(listID) + if err != nil { + t.Fatal("db.GetBookList() return an error: ", err) + } + if list.Title != otherTitle { + t.Fatal("title doesn't match: ", list.Title, " <=> ", otherTitle) + } +} + +func testCreateList(t *testing.T, db DB) { + err := db.AddUser(username, "pass") + if err != nil { + t.Fatal("db.AddUser() return an error: ", err) + } + err = db.NewBookList(listID, title, username, []string{}) + if err != nil { + t.Fatal("db.NewBookList() return an error: ", err) + } +} diff --git a/lib/database/ro.go b/lib/database/ro.go index 42907dd..cd5a453 100644 --- a/lib/database/ro.go +++ b/lib/database/ro.go @@ -120,3 +120,31 @@ func (db *roDB) GetComment(bookID string) (string, error) { func (db *roDB) GetSubmission(submissionID string) (submission []Submission, err error) { return db.db.GetSubmission(submissionID) } + +func (db *roDB) NewBookList(listID, title, username string, description []string) error { + return errors.New("RO database") +} + +func (db *roDB) AddBookToList(listID, bookID string) error { + return errors.New("RO database") +} + +func (db *roDB) DeleteBookFromList(listID, bookID string) error { + return errors.New("RO database") +} + +func (db *roDB) UpdateBookList(listID, title string, description []string) error { + return errors.New("RO database") +} + +func (db *roDB) GetBookList(listID string) (*BookList, error) { + return db.db.GetBookList(listID) +} + +func (db *roDB) GetListsByUser(username string) ([]BookList, error) { + return db.db.GetListsByUser(username) +} + +func (db *roDB) GetListsByBook(bookID string) ([]BookList, error) { + return db.db.GetListsByBook(bookID) +} diff --git a/lib/database/users.go b/lib/database/users.go index 76a26d0..2c9db34 100644 --- a/lib/database/users.go +++ b/lib/database/users.go @@ -109,6 +109,14 @@ func (db *pgDB) ListUsers() ([]User, error) { return users, err } +func (db *pgDB) getUser(name string) (User, error) { + var user User + err := db.sql.Model(&user). + Where("username = ?", name). + Select() + return user, err +} + func validUserName(name string) bool { switch name { case "", "admin", "webmaster", "postmaster", "info", "root", "news":