From d414ee7d2bb0035152b1dc5cff2801b947f4d278 Mon Sep 17 00:00:00 2001 From: jesperkha Date: Sat, 5 Jul 2025 18:03:06 +0200 Subject: [PATCH 1/5] Move db models to pkg and create LudoService interface --- api/database/database.go | 3 +- api/database/database_test.go | 29 +++++----- api/database/query.go | 82 ++++++++++++++-------------- api/server/routes/boards.go | 9 +-- api/server/routes/items.go | 7 ++- api/server/routes/users.go | 7 ++- api/server/server_test.go | 17 +++--- board/service/service.go | 38 ++++++------- {api/database => pkg/model}/model.go | 2 +- pkg/service/service.go | 78 ++++++++++++++++++++++++++ 10 files changed, 179 insertions(+), 93 deletions(-) rename {api/database => pkg/model}/model.go (98%) create mode 100644 pkg/service/service.go diff --git a/api/database/database.go b/api/database/database.go index 5def376..15ccb61 100644 --- a/api/database/database.go +++ b/api/database/database.go @@ -4,6 +4,7 @@ import ( "log" "strings" + "github.com/echo-webkom/ludo/pkg/model" _ "github.com/mattn/go-sqlite3" _ "github.com/tursodatabase/libsql-client-go/libsql" "gorm.io/driver/sqlite" @@ -16,7 +17,7 @@ type Database struct { } func newDatabase(db *gorm.DB) *Database { - if err := db.AutoMigrate(&Board{}, &Item{}, &User{}); err != nil { + if err := db.AutoMigrate(&model.Board{}, &model.Item{}, &model.User{}); err != nil { log.Fatalf("migration: %v", err) } return &Database{db} diff --git a/api/database/database_test.go b/api/database/database_test.go index 8adf6f3..5325c32 100644 --- a/api/database/database_test.go +++ b/api/database/database_test.go @@ -4,6 +4,7 @@ import ( "os" "testing" + "github.com/echo-webkom/ludo/pkg/model" "github.com/stretchr/testify/assert" ) @@ -18,7 +19,7 @@ func createTestDB(t *testing.T) *Database { }) // Auto migrate schema - err = db.db.AutoMigrate(&Board{}, &Item{}, &User{}) + err = db.db.AutoMigrate(&model.Board{}, &model.Item{}, &model.User{}) assert.NoError(t, err) return db @@ -28,7 +29,7 @@ func TestUserMethods(t *testing.T) { db := createTestDB(t) // Create user - user := User{DisplayName: "Test User", GithubUsername: "testuser"} + user := model.User{DisplayName: "Test User", GithubUsername: "testuser"} id, err := db.CreateUser(user) assert.NoError(t, err) @@ -55,7 +56,7 @@ func TestBoardMethods(t *testing.T) { db := createTestDB(t) // Create board - board := Board{Title: "Board 1", RepoURL: "https://github.com/example/repo"} + board := model.Board{Title: "Board 1", RepoURL: "https://github.com/example/repo"} _, err := db.CreateBoard(board) assert.NoError(t, err) @@ -88,13 +89,13 @@ func TestItemMethods(t *testing.T) { db := createTestDB(t) // Create board - board := Board{Title: "Board", RepoURL: "url"} + board := model.Board{Title: "Board", RepoURL: "url"} _, err := db.CreateBoard(board) assert.NoError(t, err) boards, _ := db.GetAllBoards() // Create item - item := Item{BoardID: boards[0].ID, Title: "Item 1", Status: StatusBacklog} + item := model.Item{BoardID: boards[0].ID, Title: "Item 1", Status: model.StatusBacklog} itemID, err := db.CreateItem(item) assert.NoError(t, err) @@ -104,11 +105,11 @@ func TestItemMethods(t *testing.T) { assert.Equal(t, "Item 1", got.Title) // Change status - err = db.ChangeItemStatus(itemID, StatusInProgress) + err = db.ChangeItemStatus(itemID, model.StatusInProgress) assert.NoError(t, err) // Get items with status - items, err := db.GetAllItemsWithStatus(boards[0].ID, StatusInProgress) + items, err := db.GetAllItemsWithStatus(boards[0].ID, model.StatusInProgress) assert.NoError(t, err) assert.Len(t, items, 1) @@ -128,9 +129,9 @@ func TestBoardUsers(t *testing.T) { db := createTestDB(t) // Create user and board - user := User{DisplayName: "A"} + user := model.User{DisplayName: "A"} userId, _ := db.CreateUser(user) - board := Board{Title: "Board"} + board := model.Board{Title: "Board"} _, err := db.CreateBoard(board) assert.NoError(t, err) @@ -157,19 +158,19 @@ func TestBoardUsers(t *testing.T) { func TestGetBoardItemsByStatus(t *testing.T) { db := createTestDB(t) - board := Board{Title: "Board"} + board := model.Board{Title: "Board"} _, err := db.CreateBoard(board) assert.NoError(t, err) boards, _ := db.GetAllBoards() // Create multiple items with different statuses - db.CreateItem(Item{BoardID: boards[0].ID, Title: "Item 1", Status: StatusBacklog}) - db.CreateItem(Item{BoardID: boards[0].ID, Title: "Item 2", Status: StatusInProgress}) - db.CreateItem(Item{BoardID: boards[0].ID, Title: "Item 3", Status: StatusBacklog}) + db.CreateItem(model.Item{BoardID: boards[0].ID, Title: "Item 1", Status: model.StatusBacklog}) + db.CreateItem(model.Item{BoardID: boards[0].ID, Title: "Item 2", Status: model.StatusInProgress}) + db.CreateItem(model.Item{BoardID: boards[0].ID, Title: "Item 3", Status: model.StatusBacklog}) // Fetch items with StatusBacklog - items, err := db.GetBoardItemsByStatus(boards[0].ID, StatusBacklog) + items, err := db.GetBoardItemsByStatus(boards[0].ID, model.StatusBacklog) assert.NoError(t, err) assert.Len(t, items, 2) } diff --git a/api/database/query.go b/api/database/query.go index 45c124d..177b157 100644 --- a/api/database/query.go +++ b/api/database/query.go @@ -1,14 +1,16 @@ package database -func (db *Database) GetUserById(id uint) (User, error) { - var user User +import "github.com/echo-webkom/ludo/pkg/model" + +func (db *Database) GetUserById(id uint) (model.User, error) { + var user model.User if err := db.db.First(&user, id).Error; err != nil { - return User{}, err + return model.User{}, err } return user, nil } -func (db *Database) CreateUser(user User) (uint, error) { +func (db *Database) CreateUser(user model.User) (uint, error) { if err := db.db.Create(&user).Error; err != nil { return 0, err } @@ -16,37 +18,37 @@ func (db *Database) CreateUser(user User) (uint, error) { } func (db *Database) DeleteUserById(id uint) error { - if err := db.db.Delete(&User{}, id).Error; err != nil { + if err := db.db.Delete(&model.User{}, id).Error; err != nil { return err } return nil } -func (db *Database) GetAllUsers() ([]User, error) { - var users []User +func (db *Database) GetAllUsers() ([]model.User, error) { + var users []model.User if err := db.db.Find(&users).Error; err != nil { return nil, err } return users, nil } -func (db *Database) GetItemById(id uint) (Item, error) { - var item Item +func (db *Database) GetItemById(id uint) (model.Item, error) { + var item model.Item if err := db.db.First(&item, id).Error; err != nil { - return Item{}, err + return model.Item{}, err } return item, nil } -func (db *Database) CreateItem(item Item) (id uint, err error) { +func (db *Database) CreateItem(item model.Item) (id uint, err error) { if err := db.db.Create(&item).Error; err != nil { return id, err } return item.ID, nil } -func (db *Database) GetAllItems() ([]Item, error) { - var items []Item +func (db *Database) GetAllItems() ([]model.Item, error) { + var items []model.Item if err := db.db.Find(&items).Error; err != nil { return nil, err } @@ -54,87 +56,87 @@ func (db *Database) GetAllItems() ([]Item, error) { } func (db *Database) DeleteItemByID(id uint) error { - if err := db.db.Delete(&Item{}, id).Error; err != nil { + if err := db.db.Delete(&model.Item{}, id).Error; err != nil { return err } return nil } -func (db *Database) GetAllItemsWithStatus(boardId uint, list Status) ([]Item, error) { - var items []Item +func (db *Database) GetAllItemsWithStatus(boardId uint, list model.Status) ([]model.Item, error) { + var items []model.Item if err := db.db.Where("board_id = ? AND status = ?", boardId, list).Find(&items).Error; err != nil { return nil, err } return items, nil } -func (db *Database) ChangeItemStatus(id uint, list Status) error { - if err := db.db.Model(&Item{}).Where("id = ?", id).Update("status", list).Error; err != nil { +func (db *Database) ChangeItemStatus(id uint, list model.Status) error { + if err := db.db.Model(&model.Item{}).Where("id = ?", id).Update("status", list).Error; err != nil { return err } return nil } func (db *Database) SetItemData(id uint, data string) error { - if err := db.db.Model(&Item{}).Where("id = ?", id).Update("data", data).Error; err != nil { + if err := db.db.Model(&model.Item{}).Where("id = ?", id).Update("data", data).Error; err != nil { return err } return nil } func (db *Database) GetItemData(id uint) (data string, err error) { - var item Item + var item model.Item if err := db.db.First(&item, id).Error; err != nil { return data, err } return item.Data, nil } -func (db *Database) GetAllBoards() (boards []Board, err error) { +func (db *Database) GetAllBoards() (boards []model.Board, err error) { if err := db.db.Find(&boards).Error; err != nil { return boards, err } return boards, err } -func (db *Database) CreateBoard(board Board) (id uint, err error) { +func (db *Database) CreateBoard(board model.Board) (id uint, err error) { if err := db.db.Create(&board).Error; err != nil { return id, err } return board.ID, nil } -func (db *Database) GetBoardById(id uint) (board Board, err error) { +func (db *Database) GetBoardById(id uint) (board model.Board, err error) { if err := db.db.Find(&board, id).Error; err != nil { return board, err } return board, nil } -func (db *Database) UpdateBoard(board Board, id uint) error { - if err := db.db.Model(&Board{}).Where("id = ?", id).Updates(board).Error; err != nil { +func (db *Database) UpdateBoard(board model.Board, id uint) error { + if err := db.db.Model(&model.Board{}).Where("id = ?", id).Updates(board).Error; err != nil { return err } return nil } func (db *Database) DeleteBoard(id uint) error { - if err := db.db.Delete(&Board{}, id).Error; err != nil { + if err := db.db.Delete(&model.Board{}, id).Error; err != nil { return err } return nil } -func (d *Database) GetBoardUsers(boardId uint) ([]User, error) { - var board Board +func (d *Database) GetBoardUsers(boardId uint) ([]model.User, error) { + var board model.Board if err := d.db.Preload("Users").First(&board, boardId).Error; err != nil { return nil, err } return board.Users, nil } -func (d *Database) GetBoardItems(boardId uint) ([]Item, error) { - var items []Item +func (d *Database) GetBoardItems(boardId uint) ([]model.Item, error) { + var items []model.Item if err := d.db.Where("board_id = ?", boardId).Find(&items).Error; err != nil { return nil, err } @@ -142,11 +144,11 @@ func (d *Database) GetBoardItems(boardId uint) ([]Item, error) { } func (d *Database) AddUserToBoard(boardId, userId uint) error { - var board Board + var board model.Board if err := d.db.First(&board, boardId).Error; err != nil { return err } - var user User + var user model.User if err := d.db.First(&user, userId).Error; err != nil { return err } @@ -154,34 +156,34 @@ func (d *Database) AddUserToBoard(boardId, userId uint) error { } func (d *Database) RemoveUserFromBoard(boardId, userId uint) error { - var board Board + var board model.Board if err := d.db.First(&board, boardId).Error; err != nil { return err } - var user User + var user model.User if err := d.db.First(&user, userId).Error; err != nil { return err } return d.db.Model(&board).Association("Users").Delete(&user) } -func (d *Database) GetBoardItemsByStatus(boardId uint, status Status) ([]Item, error) { - var items []Item +func (d *Database) GetBoardItemsByStatus(boardId uint, status model.Status) ([]model.Item, error) { + var items []model.Item if err := d.db.Where("board_id = ? AND status = ?", boardId, status).Find(&items).Error; err != nil { return nil, err } return items, nil } -func (db *Database) UpdateItem(item Item, id uint) error { - if err := db.db.Model(&Item{}).Where("id = ?", id).Updates(item).Error; err != nil { +func (db *Database) UpdateItem(item model.Item, id uint) error { + if err := db.db.Model(&model.Item{}).Where("id = ?", id).Updates(item).Error; err != nil { return err } return nil } -func (db *Database) UpdateUser(user User, id uint) error { - if err := db.db.Model(&User{}).Where("id = ?", id).Updates(user).Error; err != nil { +func (db *Database) UpdateUser(user model.User, id uint) error { + if err := db.db.Model(&model.User{}).Where("id = ?", id).Updates(user).Error; err != nil { return err } return nil diff --git a/api/server/routes/boards.go b/api/server/routes/boards.go index 77af668..d8d66e7 100644 --- a/api/server/routes/boards.go +++ b/api/server/routes/boards.go @@ -6,6 +6,7 @@ import ( "github.com/echo-webkom/ludo/api/database" "github.com/echo-webkom/ludo/api/rest" + "github.com/echo-webkom/ludo/pkg/model" "github.com/go-chi/chi/v5" ) @@ -22,7 +23,7 @@ func BoardsHandler(db *database.Database) chi.Router { // Create board r.Post("/", rest.Handler(func(r rest.Request) int { - var board database.Board + var board model.Board if err := r.ParseJSON(&board); err != nil { return http.StatusBadRequest } @@ -32,7 +33,7 @@ func BoardsHandler(db *database.Database) chi.Router { return http.StatusInternalServerError } - return r.RespondJSON(&database.ID{ID: id}) + return r.RespondJSON(&model.ID{ID: id}) })) r.Route("/{id}", func(r chi.Router) { @@ -48,7 +49,7 @@ func BoardsHandler(db *database.Database) chi.Router { // Update board r.Patch("/", rest.Handler(func(r rest.Request) int { - var board database.Board + var board model.Board if err := r.ParseJSON(&board); err != nil { return http.StatusBadRequest } @@ -83,7 +84,7 @@ func BoardsHandler(db *database.Database) chi.Router { return http.StatusBadRequest } - items, err := db.GetBoardItemsByStatus(r.ContextValue(idKey).(uint), database.Status(status)) + items, err := db.GetBoardItemsByStatus(r.ContextValue(idKey).(uint), model.Status(status)) if err != nil { return http.StatusInternalServerError } diff --git a/api/server/routes/items.go b/api/server/routes/items.go index f4fdfcb..f793fa8 100644 --- a/api/server/routes/items.go +++ b/api/server/routes/items.go @@ -6,6 +6,7 @@ import ( "github.com/echo-webkom/ludo/api/database" "github.com/echo-webkom/ludo/api/rest" + "github.com/echo-webkom/ludo/pkg/model" "github.com/go-chi/chi/v5" ) @@ -22,7 +23,7 @@ func ItemsHandler(db *database.Database) chi.Router { // Create item r.Post("/", rest.Handler(func(r rest.Request) int { - var item database.Item + var item model.Item if err := r.ParseJSON(&item); err != nil { return http.StatusBadRequest } @@ -32,7 +33,7 @@ func ItemsHandler(db *database.Database) chi.Router { return http.StatusInternalServerError } - return r.RespondJSON(&database.ID{ID: id}) + return r.RespondJSON(&model.ID{ID: id}) })) r.Route("/{id}", func(r chi.Router) { @@ -48,7 +49,7 @@ func ItemsHandler(db *database.Database) chi.Router { // Update item r.Patch("/", rest.Handler(func(r rest.Request) int { - var item database.Item + var item model.Item if err := r.ParseJSON(&item); err != nil { return http.StatusBadRequest } diff --git a/api/server/routes/users.go b/api/server/routes/users.go index 00b0676..8b2b990 100644 --- a/api/server/routes/users.go +++ b/api/server/routes/users.go @@ -5,6 +5,7 @@ import ( "github.com/echo-webkom/ludo/api/database" "github.com/echo-webkom/ludo/api/rest" + "github.com/echo-webkom/ludo/pkg/model" "github.com/go-chi/chi/v5" ) @@ -21,7 +22,7 @@ func UsersHandler(db *database.Database) chi.Router { // Create user r.Post("/", rest.Handler(func(r rest.Request) int { - var user database.User + var user model.User if err := r.ParseJSON(&user); err != nil { return http.StatusBadRequest } @@ -31,7 +32,7 @@ func UsersHandler(db *database.Database) chi.Router { return http.StatusInternalServerError } - return r.RespondJSON(&database.ID{ID: id}) + return r.RespondJSON(&model.ID{ID: id}) })) r.Route("/{id}", func(r chi.Router) { @@ -47,7 +48,7 @@ func UsersHandler(db *database.Database) chi.Router { // Update user r.Patch("/", rest.Handler(func(r rest.Request) int { - var user database.User + var user model.User if err := r.ParseJSON(&user); err != nil { return http.StatusBadRequest } diff --git a/api/server/server_test.go b/api/server/server_test.go index dfaf6f8..ba2a0d9 100644 --- a/api/server/server_test.go +++ b/api/server/server_test.go @@ -13,6 +13,7 @@ import ( "github.com/echo-webkom/ludo/api/config" "github.com/echo-webkom/ludo/api/database" "github.com/echo-webkom/ludo/api/server" + "github.com/echo-webkom/ludo/pkg/model" ) func setupTestServer(t *testing.T) (*server.Server, func()) { @@ -53,12 +54,12 @@ func TestUserEndpoints(t *testing.T) { defer cleanup() // Create user - user := database.User{DisplayName: "Test User"} + user := model.User{DisplayName: "Test User"} resp := makeRequest(t, s, http.MethodPost, "/users", &user) if resp.Code != http.StatusOK { t.Fatalf("CreateUser failed, code %d", resp.Code) } - var created database.ID + var created model.ID json.NewDecoder(resp.Body).Decode(&created) // Get user @@ -85,12 +86,12 @@ func TestItemEndpoints(t *testing.T) { defer cleanup() // Create item - item := database.Item{Data: "Test Data"} + item := model.Item{Data: "Test Data"} resp := makeRequest(t, s, http.MethodPost, "/items", &item) if resp.Code != http.StatusOK { t.Fatalf("CreateItem failed, code %d", resp.Code) } - var created database.ID + var created model.ID json.NewDecoder(resp.Body).Decode(&created) // Get item @@ -129,12 +130,12 @@ func TestBoardEndpoints(t *testing.T) { defer cleanup() // Create board - board := database.Board{Title: "Test Board"} + board := model.Board{Title: "Test Board"} resp := makeRequest(t, s, http.MethodPost, "/boards", &board) if resp.Code != http.StatusOK { t.Fatalf("CreateBoard failed, code %d", resp.Code) } - var createdBoard database.ID + var createdBoard model.ID json.NewDecoder(resp.Body).Decode(&createdBoard) // Get board @@ -150,12 +151,12 @@ func TestBoardEndpoints(t *testing.T) { } // Add user to board (assuming user creation tested before) - user := database.User{DisplayName: "Board User"} + user := model.User{DisplayName: "Board User"} resp = makeRequest(t, s, http.MethodPost, "/users", &user) if resp.Code != http.StatusOK { t.Fatalf("CreateUser failed, code %d", resp.Code) } - var userID database.ID + var userID model.ID json.NewDecoder(resp.Body).Decode(&userID) resp = makeRequest(t, s, http.MethodPost, "/boards/"+strconv.Itoa(int(createdBoard.ID))+"/users/"+strconv.Itoa(int(userID.ID)), nil) diff --git a/board/service/service.go b/board/service/service.go index c29dc88..6f1dd7b 100644 --- a/board/service/service.go +++ b/board/service/service.go @@ -8,8 +8,8 @@ import ( "net/http" "net/url" - "github.com/echo-webkom/ludo/api/database" "github.com/echo-webkom/ludo/board/config" + "github.com/echo-webkom/ludo/pkg/model" ) type LudoService struct { @@ -65,23 +65,23 @@ func (l *LudoService) request(method, endpoint string, body, response any) error return err } -func (l *LudoService) GetAllUsers() (users []database.User, err error) { +func (l *LudoService) GetAllUsers() (users []model.User, err error) { err = l.request("GET", "/users", nil, &users) return users, err } -func (l *LudoService) CreateUser(user database.User) (id uint, err error) { - var idRes database.ID +func (l *LudoService) CreateUser(user model.User) (id uint, err error) { + var idRes model.ID err = l.request("POST", "/users", &user, &idRes) return idRes.ID, err } -func (l *LudoService) GetUserById(id uint) (user database.User, err error) { +func (l *LudoService) GetUserById(id uint) (user model.User, err error) { err = l.request("GET", fmt.Sprintf("/users/%d", id), nil, &user) return user, err } -func (l *LudoService) UpdateUser(id uint, user database.User) error { +func (l *LudoService) UpdateUser(id uint, user model.User) error { return l.request("PATCH", fmt.Sprintf("/users/%d", id), &user, nil) } @@ -89,23 +89,23 @@ func (l *LudoService) DeleteUser(id uint) error { return l.request("DELETE", fmt.Sprintf("/users/%d", id), nil, nil) } -func (l *LudoService) GetAllBoards() (boards []database.Board, err error) { +func (l *LudoService) GetAllBoards() (boards []model.Board, err error) { err = l.request("GET", "/boards", nil, &boards) return boards, err } -func (l *LudoService) CreateBoard(board database.Board) (id uint, err error) { - var idRes database.ID +func (l *LudoService) CreateBoard(board model.Board) (id uint, err error) { + var idRes model.ID err = l.request("POST", "/boards", &board, &idRes) return idRes.ID, err } -func (l *LudoService) GetBoardById(id uint) (board database.Board, err error) { +func (l *LudoService) GetBoardById(id uint) (board model.Board, err error) { err = l.request("GET", fmt.Sprintf("/boards/%d", id), nil, &board) return board, err } -func (l *LudoService) UpdateBoard(id uint, board database.Board) error { +func (l *LudoService) UpdateBoard(id uint, board model.Board) error { return l.request("PATCH", fmt.Sprintf("/boards/%d", id), &board, nil) } @@ -113,12 +113,12 @@ func (l *LudoService) DeleteBoard(id uint) error { return l.request("DELETE", fmt.Sprintf("/boards/%d", id), nil, nil) } -func (l *LudoService) GetAllUsersInBoard(id uint) (users []database.User, err error) { +func (l *LudoService) GetAllUsersInBoard(id uint) (users []model.User, err error) { err = l.request("GET", fmt.Sprintf("/boards/%d/users", id), nil, &users) return users, err } -func (l *LudoService) GetAllItemsInBoard(id uint) (items []database.Item, err error) { +func (l *LudoService) GetAllItemsInBoard(id uint) (items []model.Item, err error) { err = l.request("GET", fmt.Sprintf("/boards/%d/items", id), nil, &items) return items, err } @@ -131,28 +131,28 @@ func (l *LudoService) RemoveUserFromBoard(boardId, userId uint) error { return l.request("DELETE", fmt.Sprintf("/boards/%d/users/%d", boardId, userId), nil, nil) } -func (l *LudoService) GetAllItemsInBoardList(boardId, list uint) (items []database.Item, err error) { +func (l *LudoService) GetAllItemsInBoardList(boardId, list uint) (items []model.Item, err error) { err = l.request("GET", fmt.Sprintf("/boards/%d/lists/%d/items", boardId, list), nil, &items) return items, err } -func (l *LudoService) GetAllItems() (items []database.Item, err error) { +func (l *LudoService) GetAllItems() (items []model.Item, err error) { err = l.request("GET", "/items", nil, &items) return items, err } -func (l *LudoService) CreateItem(item database.Item) (id uint, err error) { - var idRes database.ID +func (l *LudoService) CreateItem(item model.Item) (id uint, err error) { + var idRes model.ID err = l.request("POST", "/items", &item, &idRes) return idRes.ID, err } -func (l *LudoService) GetItemById(id uint) (item database.Item, err error) { +func (l *LudoService) GetItemById(id uint) (item model.Item, err error) { err = l.request("GET", fmt.Sprintf("/items/%d", id), nil, &item) return item, err } -func (l *LudoService) UpdateItem(id uint, item database.Item) error { +func (l *LudoService) UpdateItem(id uint, item model.Item) error { return l.request("PATCH", fmt.Sprintf("/items/%d", id), &item, nil) } diff --git a/api/database/model.go b/pkg/model/model.go similarity index 98% rename from api/database/model.go rename to pkg/model/model.go index 453295e..635935d 100644 --- a/api/database/model.go +++ b/pkg/model/model.go @@ -1,4 +1,4 @@ -package database +package model import ( "time" diff --git a/pkg/service/service.go b/pkg/service/service.go new file mode 100644 index 0000000..79d92a1 --- /dev/null +++ b/pkg/service/service.go @@ -0,0 +1,78 @@ +package service + +import "github.com/echo-webkom/ludo/pkg/model" + +type LudoService interface { + // Get the board service. + Boards() Boards + // Get the user service. + Users() Users +} + +type Users interface { + // Get all users. + All() ([]model.User, error) + // Create a new user. Returns the ID. + New(user model.User) (uint, error) + // Get a user by ID. + Id(id uint) User +} + +type User interface { + // Get the user data. + Get() (model.User, error) + // Update the user. Replaces all fields with those given. + Update(user model.User) error + // Delete the user and remove it from all boards. + Delete() error +} + +type Boards interface { + // Get all boards. + All() ([]model.Board, error) + // Create a new board. Returns the ID. + New(board model.Board) (uint, error) + // Get a board by ID. + Id(id uint) Board +} + +type Board interface { + // Get board data. + Get() (model.Board, error) + // Update the board. Replaces all fields with those given. + Update(board model.Board) error + // Delete the board and all its items. + Delete() error + // Get the items service for this board. + Items() Items + // Get all users assigned to this board. + Users() ([]model.User, error) + // Add an existing user to this board. + AddUser(userId uint) error + // Remove a user from this board. + RemoveUser(userId uint) error +} + +type Items interface { + // Get all items in this board. + All() ([]model.Item, error) + // Create new item in this board. + New(item model.Item) (uint, error) + // Get all items with a given status in this board. + WithStatus(list uint) ([]model.Item, error) + // Get an item by ID. + Id(id uint) Item +} + +type Item interface { + // Get item data. + Get() (model.Item, error) + // Update this item. Replaces all fields with those given. + Update(item model.Item) error + // Delete this item. + Delete() error + // Get this items data segment. + Data() (string, error) + // Set this items data segment. + SetData(data string) error +} From 387062b0260575f57565ddcf171614a1e4b37a63 Mon Sep 17 00:00:00 2001 From: jesperkha Date: Sat, 5 Jul 2025 18:31:57 +0200 Subject: [PATCH 2/5] Flatten LudoService --- pkg/service/service.go | 110 ++++++++++++++++------------------------- 1 file changed, 42 insertions(+), 68 deletions(-) diff --git a/pkg/service/service.go b/pkg/service/service.go index 79d92a1..f5e9e7a 100644 --- a/pkg/service/service.go +++ b/pkg/service/service.go @@ -3,76 +3,50 @@ package service import "github.com/echo-webkom/ludo/pkg/model" type LudoService interface { - // Get the board service. - Boards() Boards - // Get the user service. - Users() Users -} - -type Users interface { - // Get all users. - All() ([]model.User, error) - // Create a new user. Returns the ID. - New(user model.User) (uint, error) - // Get a user by ID. - Id(id uint) User -} - -type User interface { - // Get the user data. - Get() (model.User, error) - // Update the user. Replaces all fields with those given. - Update(user model.User) error - // Delete the user and remove it from all boards. - Delete() error -} - -type Boards interface { - // Get all boards. - All() ([]model.Board, error) - // Create a new board. Returns the ID. - New(board model.Board) (uint, error) // Get a board by ID. - Id(id uint) Board -} - -type Board interface { - // Get board data. - Get() (model.Board, error) + GetBoard(id uint) (model.Board, error) // Update the board. Replaces all fields with those given. - Update(board model.Board) error + UpdateBoard(board model.Board) error // Delete the board and all its items. - Delete() error - // Get the items service for this board. - Items() Items - // Get all users assigned to this board. - Users() ([]model.User, error) - // Add an existing user to this board. - AddUser(userId uint) error - // Remove a user from this board. - RemoveUser(userId uint) error -} - -type Items interface { - // Get all items in this board. - All() ([]model.Item, error) - // Create new item in this board. - New(item model.Item) (uint, error) - // Get all items with a given status in this board. - WithStatus(list uint) ([]model.Item, error) - // Get an item by ID. - Id(id uint) Item -} + DeleteBoard(id uint) error + // Create a new board. Returns the ID. + NewBoard(board model.Board) (uint, error) + // Get all boards. + GetAllBoards() ([]model.Board, error) -type Item interface { - // Get item data. - Get() (model.Item, error) - // Update this item. Replaces all fields with those given. - Update(item model.Item) error - // Delete this item. - Delete() error - // Get this items data segment. - Data() (string, error) - // Set this items data segment. - SetData(data string) error + // Get a user by ID. + GetUser(id uint) (model.User, error) + // Update the user. Replaces all fields with those given. + UpdateUser(user model.User) error + // Delete the user and remove it from all boards. + DeleteUser(id uint) error + // Create a new user. Returns the ID. + NewUser(user model.User) (uint, error) + // Get all users. + GetAllUsers() ([]model.User, error) + + // Get all users assigned to a board. + GetBoardUsers(boardID uint) ([]model.User, error) + // Add an existing user to a board. + AddUserToBoard(boardID uint, userID uint) error + // Remove a user from a board. + RemoveUserFromBoard(boardID uint, userID uint) error + + // Get an item in a board by ID. + GetItem(boardID uint, itemID uint) (model.Item, error) + // Update an item. Replaces all fields with those given. + UpdateItem(boardID uint, item model.Item) error + // Delete an item. + DeleteItem(boardID uint, itemID uint) error + // Create a new item in a board. Returns the ID. + NewItem(boardID uint, item model.Item) (uint, error) + // Get all items in a board. + GetBoardItems(boardID uint) ([]model.Item, error) + // Get all items in a board with a given status. + GetBoardItemsWithStatus(boardID uint, status model.Status) ([]model.Item, error) + + // Get this item's data segment. + GetItemData(boardID uint, itemID uint) (string, error) + // Set this item's data segment. + SetItemData(boardID uint, itemID uint, data string) error } From a4e01807c7f2f63897725c16dafec6eeb4ce5fd9 Mon Sep 17 00:00:00 2001 From: jesperkha Date: Sat, 5 Jul 2025 18:46:49 +0200 Subject: [PATCH 3/5] Add API service to pkg --- board/service/service.go | 165 ----------------------------------- pkg/service/api_service.go | 171 +++++++++++++++++++++++++++++++++++++ pkg/service/service.go | 32 +++---- 3 files changed, 187 insertions(+), 181 deletions(-) delete mode 100644 board/service/service.go create mode 100644 pkg/service/api_service.go diff --git a/board/service/service.go b/board/service/service.go deleted file mode 100644 index 6f1dd7b..0000000 --- a/board/service/service.go +++ /dev/null @@ -1,165 +0,0 @@ -package service - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "net/http" - "net/url" - - "github.com/echo-webkom/ludo/board/config" - "github.com/echo-webkom/ludo/pkg/model" -) - -type LudoService struct { - config *config.Config -} - -func New(config *config.Config) *LudoService { - return &LudoService{ - config: config, - } -} - -// Send request to API using config URL. Method should be "GET", "POST" etc. -// -// Endpoint is the target endpoint, eg. "/users/12482", notice path values -// should already be formatted. Body is the request body, a reference to the -// object you want to encode as json, nil for empty body. Response is a -// reference to the response type, or nil for no response. -func (l *LudoService) request(method, endpoint string, body, response any) error { - var bodyReader io.Reader - if body != nil { - bodyJson, err := json.Marshal(body) - if err != nil { - return err - } - bodyReader = bytes.NewReader(bodyJson) - } - - url, err := url.JoinPath(l.config.ApiUrl, endpoint) - if err != nil { - return err - } - - req, err := http.NewRequest(method, url, bodyReader) - if err != nil { - return err - } - - res, err := http.DefaultClient.Do(req) - if err != nil { - return err - } - defer res.Body.Close() - - if res.StatusCode == http.StatusOK && response != nil { - b, err := io.ReadAll(res.Body) - if err != nil { - return err - } - return json.Unmarshal(b, response) - } - - return err -} - -func (l *LudoService) GetAllUsers() (users []model.User, err error) { - err = l.request("GET", "/users", nil, &users) - return users, err -} - -func (l *LudoService) CreateUser(user model.User) (id uint, err error) { - var idRes model.ID - err = l.request("POST", "/users", &user, &idRes) - return idRes.ID, err -} - -func (l *LudoService) GetUserById(id uint) (user model.User, err error) { - err = l.request("GET", fmt.Sprintf("/users/%d", id), nil, &user) - return user, err -} - -func (l *LudoService) UpdateUser(id uint, user model.User) error { - return l.request("PATCH", fmt.Sprintf("/users/%d", id), &user, nil) -} - -func (l *LudoService) DeleteUser(id uint) error { - return l.request("DELETE", fmt.Sprintf("/users/%d", id), nil, nil) -} - -func (l *LudoService) GetAllBoards() (boards []model.Board, err error) { - err = l.request("GET", "/boards", nil, &boards) - return boards, err -} - -func (l *LudoService) CreateBoard(board model.Board) (id uint, err error) { - var idRes model.ID - err = l.request("POST", "/boards", &board, &idRes) - return idRes.ID, err -} - -func (l *LudoService) GetBoardById(id uint) (board model.Board, err error) { - err = l.request("GET", fmt.Sprintf("/boards/%d", id), nil, &board) - return board, err -} - -func (l *LudoService) UpdateBoard(id uint, board model.Board) error { - return l.request("PATCH", fmt.Sprintf("/boards/%d", id), &board, nil) -} - -func (l *LudoService) DeleteBoard(id uint) error { - return l.request("DELETE", fmt.Sprintf("/boards/%d", id), nil, nil) -} - -func (l *LudoService) GetAllUsersInBoard(id uint) (users []model.User, err error) { - err = l.request("GET", fmt.Sprintf("/boards/%d/users", id), nil, &users) - return users, err -} - -func (l *LudoService) GetAllItemsInBoard(id uint) (items []model.Item, err error) { - err = l.request("GET", fmt.Sprintf("/boards/%d/items", id), nil, &items) - return items, err -} - -func (l *LudoService) AddUserToBoard(boardId, userId uint) error { - return l.request("POST", fmt.Sprintf("/boards/%d/users/%d", boardId, userId), nil, nil) -} - -func (l *LudoService) RemoveUserFromBoard(boardId, userId uint) error { - return l.request("DELETE", fmt.Sprintf("/boards/%d/users/%d", boardId, userId), nil, nil) -} - -func (l *LudoService) GetAllItemsInBoardList(boardId, list uint) (items []model.Item, err error) { - err = l.request("GET", fmt.Sprintf("/boards/%d/lists/%d/items", boardId, list), nil, &items) - return items, err -} - -func (l *LudoService) GetAllItems() (items []model.Item, err error) { - err = l.request("GET", "/items", nil, &items) - return items, err -} - -func (l *LudoService) CreateItem(item model.Item) (id uint, err error) { - var idRes model.ID - err = l.request("POST", "/items", &item, &idRes) - return idRes.ID, err -} - -func (l *LudoService) GetItemById(id uint) (item model.Item, err error) { - err = l.request("GET", fmt.Sprintf("/items/%d", id), nil, &item) - return item, err -} - -func (l *LudoService) UpdateItem(id uint, item model.Item) error { - return l.request("PATCH", fmt.Sprintf("/items/%d", id), &item, nil) -} - -func (l *LudoService) DeleteItem(id uint) error { - return l.request("DELETE", fmt.Sprintf("/items/%d", id), nil, nil) -} - -func (l *LudoService) MoveItemToList(itemId, list uint) error { - return l.request("PATCH", fmt.Sprintf("/items/%d/move/%d", itemId, list), nil, nil) -} diff --git a/pkg/service/api_service.go b/pkg/service/api_service.go new file mode 100644 index 0000000..1819edc --- /dev/null +++ b/pkg/service/api_service.go @@ -0,0 +1,171 @@ +package service + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + + "github.com/echo-webkom/ludo/pkg/model" +) + +type apiService struct { + url string +} + +// Create new LudoService using the Ludo REST API as backend. +func NewApiService(apiUrl string) LudoService { + return &apiService{ + url: apiUrl, + } +} + +// Send request to API using config URL. Method should be "GET", "POST" etc. +// +// Endpoint is the target endpoint, eg. "/users/12482", notice path values +// should already be formatted. Body is the request body, a reference to the +// object you want to encode as json, nil for empty body. Response is a +// reference to the response type, or nil for no response. +func (s *apiService) request(method, endpoint string, body, response any) error { + var bodyReader io.Reader + if body != nil { + bodyJson, err := json.Marshal(body) + if err != nil { + return err + } + bodyReader = bytes.NewReader(bodyJson) + } + + url, err := url.JoinPath(s.url, endpoint) + if err != nil { + return err + } + + req, err := http.NewRequest(method, url, bodyReader) + if err != nil { + return err + } + + res, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + defer res.Body.Close() + + if res.StatusCode == http.StatusOK && response != nil { + b, err := io.ReadAll(res.Body) + if err != nil { + return err + } + return json.Unmarshal(b, response) + } + + return err +} + +func (s *apiService) Users() (users []model.User, err error) { + err = s.request("GET", "/users", nil, &users) + return users, err +} + +func (s *apiService) NewUser(user model.User) (id uint, err error) { + var idRes model.ID + err = s.request("POST", "/users", &user, &idRes) + return idRes.ID, err +} + +func (s *apiService) User(id uint) (user model.User, err error) { + err = s.request("GET", fmt.Sprintf("/users/%d", id), nil, &user) + return user, err +} + +func (s *apiService) UpdateUser(id uint, user model.User) error { + return s.request("PATCH", fmt.Sprintf("/users/%d", id), &user, nil) +} + +func (s *apiService) DeleteUser(id uint) error { + return s.request("DELETE", fmt.Sprintf("/users/%d", id), nil, nil) +} + +func (s *apiService) Boards() (boards []model.Board, err error) { + err = s.request("GET", "/boards", nil, &boards) + return boards, err +} + +func (s *apiService) NewBoard(board model.Board) (id uint, err error) { + var idRes model.ID + err = s.request("POST", "/boards", &board, &idRes) + return idRes.ID, err +} + +func (s *apiService) Board(id uint) (board model.Board, err error) { + err = s.request("GET", fmt.Sprintf("/boards/%d", id), nil, &board) + return board, err +} + +func (s *apiService) UpdateBoard(id uint, board model.Board) error { + return s.request("PATCH", fmt.Sprintf("/boards/%d", id), &board, nil) +} + +func (s *apiService) DeleteBoard(id uint) error { + return s.request("DELETE", fmt.Sprintf("/boards/%d", id), nil, nil) +} + +func (s *apiService) BoardUsers(id uint) (users []model.User, err error) { + err = s.request("GET", fmt.Sprintf("/boards/%d/users", id), nil, &users) + return users, err +} + +func (s *apiService) BoardItems(id uint) (items []model.Item, err error) { + err = s.request("GET", fmt.Sprintf("/boards/%d/items", id), nil, &items) + return items, err +} + +func (s *apiService) AddUserToBoard(boardId, userId uint) error { + return s.request("POST", fmt.Sprintf("/boards/%d/users/%d", boardId, userId), nil, nil) +} + +func (s *apiService) RemoveUserFromBoard(boardId, userId uint) error { + return s.request("DELETE", fmt.Sprintf("/boards/%d/users/%d", boardId, userId), nil, nil) +} + +func (s *apiService) BoardItemsWithStatus(boardId uint, status model.Status) (items []model.Item, err error) { + err = s.request("GET", fmt.Sprintf("/boards/%d/lists/%d/items", boardId, status), nil, &items) + return items, err +} + +func (s *apiService) Items() (items []model.Item, err error) { + err = s.request("GET", "/items", nil, &items) + return items, err +} + +func (s *apiService) NewItem(boardId uint, item model.Item) (id uint, err error) { + var idRes model.ID + err = s.request("POST", "/items", &item, &idRes) + return idRes.ID, err +} + +func (s *apiService) Item(id uint) (item model.Item, err error) { + err = s.request("GET", fmt.Sprintf("/items/%d", id), nil, &item) + return item, err +} + +func (s *apiService) UpdateItem(id uint, item model.Item) error { + return s.request("PATCH", fmt.Sprintf("/items/%d", id), &item, nil) +} + +func (s *apiService) DeleteItem(id uint) error { + return s.request("DELETE", fmt.Sprintf("/items/%d", id), nil, nil) +} + +// TODO: implement set and get item data + +func (s *apiService) SetItemData(itemId uint, data string) error { + return nil +} + +func (s *apiService) ItemData(itemId uint) (data string, err error) { + return data, err +} diff --git a/pkg/service/service.go b/pkg/service/service.go index f5e9e7a..09da5e3 100644 --- a/pkg/service/service.go +++ b/pkg/service/service.go @@ -4,49 +4,49 @@ import "github.com/echo-webkom/ludo/pkg/model" type LudoService interface { // Get a board by ID. - GetBoard(id uint) (model.Board, error) + Board(id uint) (model.Board, error) + // Get all boards. + Boards() ([]model.Board, error) // Update the board. Replaces all fields with those given. - UpdateBoard(board model.Board) error + UpdateBoard(id uint, board model.Board) error // Delete the board and all its items. DeleteBoard(id uint) error // Create a new board. Returns the ID. NewBoard(board model.Board) (uint, error) - // Get all boards. - GetAllBoards() ([]model.Board, error) // Get a user by ID. - GetUser(id uint) (model.User, error) + User(id uint) (model.User, error) + // Get all users. + Users() ([]model.User, error) // Update the user. Replaces all fields with those given. - UpdateUser(user model.User) error + UpdateUser(id uint, user model.User) error // Delete the user and remove it from all boards. DeleteUser(id uint) error // Create a new user. Returns the ID. NewUser(user model.User) (uint, error) - // Get all users. - GetAllUsers() ([]model.User, error) // Get all users assigned to a board. - GetBoardUsers(boardID uint) ([]model.User, error) + BoardUsers(boardID uint) ([]model.User, error) // Add an existing user to a board. AddUserToBoard(boardID uint, userID uint) error // Remove a user from a board. RemoveUserFromBoard(boardID uint, userID uint) error // Get an item in a board by ID. - GetItem(boardID uint, itemID uint) (model.Item, error) + Item(itemID uint) (model.Item, error) // Update an item. Replaces all fields with those given. - UpdateItem(boardID uint, item model.Item) error + UpdateItem(id uint, item model.Item) error // Delete an item. - DeleteItem(boardID uint, itemID uint) error + DeleteItem(itemID uint) error // Create a new item in a board. Returns the ID. NewItem(boardID uint, item model.Item) (uint, error) // Get all items in a board. - GetBoardItems(boardID uint) ([]model.Item, error) + BoardItems(boardID uint) ([]model.Item, error) // Get all items in a board with a given status. - GetBoardItemsWithStatus(boardID uint, status model.Status) ([]model.Item, error) + BoardItemsWithStatus(boardID uint, status model.Status) ([]model.Item, error) // Get this item's data segment. - GetItemData(boardID uint, itemID uint) (string, error) + ItemData(itemID uint) (string, error) // Set this item's data segment. - SetItemData(boardID uint, itemID uint, data string) error + SetItemData(itemID uint, data string) error } From 379278a5a51333c1a2cd75adc1c3b509bc7f5fc9 Mon Sep 17 00:00:00 2001 From: jesperkha Date: Mon, 21 Jul 2025 20:00:56 +0200 Subject: [PATCH 4/5] Move item creation endpoint to board sub path --- api/README.md | 3 +-- api/server/routes/boards.go | 20 ++++++++++++++++++++ api/server/routes/items.go | 23 ----------------------- api/server/server_test.go | 8 +------- 4 files changed, 22 insertions(+), 32 deletions(-) diff --git a/api/README.md b/api/README.md index deebd36..1d038ac 100644 --- a/api/README.md +++ b/api/README.md @@ -29,6 +29,7 @@ Boards are containers for items and have users assosiated with them. A board may | DELETE | `/boards/{boardId}` | - | Delete a board | | GET | `/boards/{boardId}/users` | `[]User` | Get all users in board | | GET | `/boards/{boardId}/items` | `[]Item` | Get all items in board | +| POST | `/boards/{boardId}/items` | `ID` | Create a new item in board | | POST | `/boards/{boardId}/users/{userId}` | - | Add user to board | | DELETE | `/boards/{boardId}/users/{userId}` | - | Remove user from board | | GET | `/boards/{boardId}/status/{status}/items` | `[]Item` | Get all items in board with given `Status` | @@ -37,8 +38,6 @@ Boards are containers for items and have users assosiated with them. A board may | Method | Path | Response Type | Description | | ------ | ---------------------- | ------------- | ----------------------- | -| GET | `/items` | `[]Item` | Get all items | -| POST | `/items` | `ID` | Create an item | | GET | `/items/{itemId}` | `Item` | Get item by ID | | PATCH | `/items/{itemId}` | - | Update an existing item | | DELETE | `/items/{itemId}` | - | Delete an item | diff --git a/api/server/routes/boards.go b/api/server/routes/boards.go index d8d66e7..745ed82 100644 --- a/api/server/routes/boards.go +++ b/api/server/routes/boards.go @@ -77,6 +77,26 @@ func BoardsHandler(db *database.Database) chi.Router { return http.StatusInternalServerError })) + // Create item + r.Post("/items", rest.Handler(func(r rest.Request) int { + var item model.Item + if err := r.ParseJSON(&item); err != nil { + return http.StatusBadRequest + } + + // Check if board exists + if _, err := db.GetBoardById(r.ContextValue(idKey).(uint)); err != nil { + return http.StatusNotFound + } + + id, err := db.CreateItem(item) + if err != nil { + return http.StatusInternalServerError + } + + return r.RespondJSON(&model.ID{ID: id}) + })) + // Get all items with status r.Get("/status/{status}/items", rest.Handler(func(r rest.Request) int { status, err := strconv.Atoi(r.R.PathValue("status")) diff --git a/api/server/routes/items.go b/api/server/routes/items.go index f793fa8..40e5dbf 100644 --- a/api/server/routes/items.go +++ b/api/server/routes/items.go @@ -13,29 +13,6 @@ import ( func ItemsHandler(db *database.Database) chi.Router { r := chi.NewRouter() - // Get all items - r.Get("/", rest.Handler(func(r rest.Request) int { - if items, err := db.GetAllItems(); err == nil { - return r.RespondJSON(&items) - } - return http.StatusInternalServerError - })) - - // Create item - r.Post("/", rest.Handler(func(r rest.Request) int { - var item model.Item - if err := r.ParseJSON(&item); err != nil { - return http.StatusBadRequest - } - - id, err := db.CreateItem(item) - if err != nil { - return http.StatusInternalServerError - } - - return r.RespondJSON(&model.ID{ID: id}) - })) - r.Route("/{id}", func(r chi.Router) { r.Use(idMiddleware) diff --git a/api/server/server_test.go b/api/server/server_test.go index ba2a0d9..f1254e5 100644 --- a/api/server/server_test.go +++ b/api/server/server_test.go @@ -87,7 +87,7 @@ func TestItemEndpoints(t *testing.T) { // Create item item := model.Item{Data: "Test Data"} - resp := makeRequest(t, s, http.MethodPost, "/items", &item) + resp := makeRequest(t, s, http.MethodPost, "/boards/1/items", &item) if resp.Code != http.StatusOK { t.Fatalf("CreateItem failed, code %d", resp.Code) } @@ -100,12 +100,6 @@ func TestItemEndpoints(t *testing.T) { t.Fatalf("GetItemById failed, code %d", resp.Code) } - // Get all items - resp = makeRequest(t, s, http.MethodGet, "/items", nil) - if resp.Code != http.StatusOK { - t.Fatalf("GetAllItems failed, code %d", resp.Code) - } - // Get item data resp = makeRequest(t, s, http.MethodGet, "/items/"+strconv.Itoa(int(created.ID))+"/data", nil) if resp.Code != http.StatusOK { From 3f18497161616d29f6cdc4eb08f03c9e2768004a Mon Sep 17 00:00:00 2001 From: jesperkha Date: Mon, 21 Jul 2025 20:13:09 +0200 Subject: [PATCH 5/5] Make Database implement LudoService --- api/database/database.go | 1 + api/database/database_test.go | 54 ++++++++++++++++++----------------- api/database/query.go | 37 +++++++++++++----------- api/server/routes/boards.go | 30 ++++++++++--------- api/server/routes/items.go | 14 ++++----- api/server/routes/users.go | 14 ++++----- 6 files changed, 80 insertions(+), 70 deletions(-) diff --git a/api/database/database.go b/api/database/database.go index 15ccb61..1eb7b1f 100644 --- a/api/database/database.go +++ b/api/database/database.go @@ -12,6 +12,7 @@ import ( "gorm.io/gorm/logger" ) +// Implements LudoService type Database struct { db *gorm.DB } diff --git a/api/database/database_test.go b/api/database/database_test.go index 5325c32..d821143 100644 --- a/api/database/database_test.go +++ b/api/database/database_test.go @@ -30,25 +30,25 @@ func TestUserMethods(t *testing.T) { // Create user user := model.User{DisplayName: "Test User", GithubUsername: "testuser"} - id, err := db.CreateUser(user) + id, err := db.NewUser(user) assert.NoError(t, err) // Get user - u, err := db.GetUserById(id) + u, err := db.User(id) assert.NoError(t, err) assert.Equal(t, "Test User", u.DisplayName) // Get all users - users, err := db.GetAllUsers() + users, err := db.Users() assert.NoError(t, err) assert.Len(t, users, 1) // Delete user - err = db.DeleteUserById(id) + err = db.DeleteUser(id) assert.NoError(t, err) // Confirm user deleted - _, err = db.GetUserById(id) + _, err = db.User(id) assert.Error(t, err) } @@ -57,26 +57,26 @@ func TestBoardMethods(t *testing.T) { // Create board board := model.Board{Title: "Board 1", RepoURL: "https://github.com/example/repo"} - _, err := db.CreateBoard(board) + _, err := db.NewBoard(board) assert.NoError(t, err) // Get boards - boards, err := db.GetAllBoards() + boards, err := db.Boards() assert.NoError(t, err) assert.Len(t, boards, 1) // Get by ID - got, err := db.GetBoardById(boards[0].ID) + got, err := db.Board(boards[0].ID) assert.NoError(t, err) assert.Equal(t, "Board 1", got.Title) // Update board got.Title = "Updated" - err = db.UpdateBoard(got, got.ID) + err = db.UpdateBoard(got.ID, got) assert.NoError(t, err) // Get updated board - got2, err := db.GetBoardById(got.ID) + got2, err := db.Board(got.ID) assert.NoError(t, err) assert.Equal(t, "Updated", got2.Title) @@ -90,17 +90,17 @@ func TestItemMethods(t *testing.T) { // Create board board := model.Board{Title: "Board", RepoURL: "url"} - _, err := db.CreateBoard(board) + id, err := db.NewBoard(board) assert.NoError(t, err) - boards, _ := db.GetAllBoards() + boards, _ := db.Boards() // Create item item := model.Item{BoardID: boards[0].ID, Title: "Item 1", Status: model.StatusBacklog} - itemID, err := db.CreateItem(item) + itemID, err := db.NewItem(id, item) assert.NoError(t, err) // Get item - got, err := db.GetItemById(itemID) + got, err := db.Item(itemID) assert.NoError(t, err) assert.Equal(t, "Item 1", got.Title) @@ -122,7 +122,7 @@ func TestItemMethods(t *testing.T) { assert.Equal(t, "some-data", data) // Delete item - assert.NoError(t, db.DeleteItemByID(itemID)) + assert.NoError(t, db.DeleteItem(itemID)) } func TestBoardUsers(t *testing.T) { @@ -130,19 +130,19 @@ func TestBoardUsers(t *testing.T) { // Create user and board user := model.User{DisplayName: "A"} - userId, _ := db.CreateUser(user) + userId, _ := db.NewUser(user) board := model.Board{Title: "Board"} - _, err := db.CreateBoard(board) + _, err := db.NewBoard(board) assert.NoError(t, err) - boards, _ := db.GetAllBoards() + boards, _ := db.Boards() // Add user to board assert.NoError(t, db.AddUserToBoard(boards[0].ID, userId)) // Get board users - users, err := db.GetBoardUsers(boards[0].ID) + users, err := db.BoardUsers(boards[0].ID) assert.NoError(t, err) assert.Len(t, users, 1) @@ -150,7 +150,7 @@ func TestBoardUsers(t *testing.T) { assert.NoError(t, db.RemoveUserFromBoard(boards[0].ID, userId)) // Check no users left - users, err = db.GetBoardUsers(boards[0].ID) + users, err = db.BoardUsers(boards[0].ID) assert.NoError(t, err) assert.Len(t, users, 0) } @@ -159,18 +159,20 @@ func TestGetBoardItemsByStatus(t *testing.T) { db := createTestDB(t) board := model.Board{Title: "Board"} - _, err := db.CreateBoard(board) + _, err := db.NewBoard(board) assert.NoError(t, err) - boards, _ := db.GetAllBoards() + boards, _ := db.Boards() + + id := boards[0].ID // Create multiple items with different statuses - db.CreateItem(model.Item{BoardID: boards[0].ID, Title: "Item 1", Status: model.StatusBacklog}) - db.CreateItem(model.Item{BoardID: boards[0].ID, Title: "Item 2", Status: model.StatusInProgress}) - db.CreateItem(model.Item{BoardID: boards[0].ID, Title: "Item 3", Status: model.StatusBacklog}) + db.NewItem(id, model.Item{BoardID: boards[0].ID, Title: "Item 1", Status: model.StatusBacklog}) + db.NewItem(id, model.Item{BoardID: boards[0].ID, Title: "Item 2", Status: model.StatusInProgress}) + db.NewItem(id, model.Item{BoardID: boards[0].ID, Title: "Item 3", Status: model.StatusBacklog}) // Fetch items with StatusBacklog - items, err := db.GetBoardItemsByStatus(boards[0].ID, model.StatusBacklog) + items, err := db.BoardItemsWithStatus(boards[0].ID, model.StatusBacklog) assert.NoError(t, err) assert.Len(t, items, 2) } diff --git a/api/database/query.go b/api/database/query.go index 177b157..a5ef59b 100644 --- a/api/database/query.go +++ b/api/database/query.go @@ -2,7 +2,7 @@ package database import "github.com/echo-webkom/ludo/pkg/model" -func (db *Database) GetUserById(id uint) (model.User, error) { +func (db *Database) User(id uint) (model.User, error) { var user model.User if err := db.db.First(&user, id).Error; err != nil { return model.User{}, err @@ -10,21 +10,21 @@ func (db *Database) GetUserById(id uint) (model.User, error) { return user, nil } -func (db *Database) CreateUser(user model.User) (uint, error) { +func (db *Database) NewUser(user model.User) (uint, error) { if err := db.db.Create(&user).Error; err != nil { return 0, err } return user.ID, nil } -func (db *Database) DeleteUserById(id uint) error { +func (db *Database) DeleteUser(id uint) error { if err := db.db.Delete(&model.User{}, id).Error; err != nil { return err } return nil } -func (db *Database) GetAllUsers() ([]model.User, error) { +func (db *Database) Users() ([]model.User, error) { var users []model.User if err := db.db.Find(&users).Error; err != nil { return nil, err @@ -32,7 +32,7 @@ func (db *Database) GetAllUsers() ([]model.User, error) { return users, nil } -func (db *Database) GetItemById(id uint) (model.Item, error) { +func (db *Database) Item(id uint) (model.Item, error) { var item model.Item if err := db.db.First(&item, id).Error; err != nil { return model.Item{}, err @@ -40,7 +40,8 @@ func (db *Database) GetItemById(id uint) (model.Item, error) { return item, nil } -func (db *Database) CreateItem(item model.Item) (id uint, err error) { +func (db *Database) NewItem(boardId uint, item model.Item) (id uint, err error) { + item.BoardID = boardId if err := db.db.Create(&item).Error; err != nil { return id, err } @@ -55,7 +56,7 @@ func (db *Database) GetAllItems() ([]model.Item, error) { return items, nil } -func (db *Database) DeleteItemByID(id uint) error { +func (db *Database) DeleteItem(id uint) error { if err := db.db.Delete(&model.Item{}, id).Error; err != nil { return err } @@ -92,28 +93,28 @@ func (db *Database) GetItemData(id uint) (data string, err error) { return item.Data, nil } -func (db *Database) GetAllBoards() (boards []model.Board, err error) { +func (db *Database) Boards() (boards []model.Board, err error) { if err := db.db.Find(&boards).Error; err != nil { return boards, err } return boards, err } -func (db *Database) CreateBoard(board model.Board) (id uint, err error) { +func (db *Database) NewBoard(board model.Board) (id uint, err error) { if err := db.db.Create(&board).Error; err != nil { return id, err } return board.ID, nil } -func (db *Database) GetBoardById(id uint) (board model.Board, err error) { +func (db *Database) Board(id uint) (board model.Board, err error) { if err := db.db.Find(&board, id).Error; err != nil { return board, err } return board, nil } -func (db *Database) UpdateBoard(board model.Board, id uint) error { +func (db *Database) UpdateBoard(id uint, board model.Board) error { if err := db.db.Model(&model.Board{}).Where("id = ?", id).Updates(board).Error; err != nil { return err } @@ -127,7 +128,7 @@ func (db *Database) DeleteBoard(id uint) error { return nil } -func (d *Database) GetBoardUsers(boardId uint) ([]model.User, error) { +func (d *Database) BoardUsers(boardId uint) ([]model.User, error) { var board model.Board if err := d.db.Preload("Users").First(&board, boardId).Error; err != nil { return nil, err @@ -135,7 +136,7 @@ func (d *Database) GetBoardUsers(boardId uint) ([]model.User, error) { return board.Users, nil } -func (d *Database) GetBoardItems(boardId uint) ([]model.Item, error) { +func (d *Database) BoardItems(boardId uint) ([]model.Item, error) { var items []model.Item if err := d.db.Where("board_id = ?", boardId).Find(&items).Error; err != nil { return nil, err @@ -167,7 +168,7 @@ func (d *Database) RemoveUserFromBoard(boardId, userId uint) error { return d.db.Model(&board).Association("Users").Delete(&user) } -func (d *Database) GetBoardItemsByStatus(boardId uint, status model.Status) ([]model.Item, error) { +func (d *Database) BoardItemsWithStatus(boardId uint, status model.Status) ([]model.Item, error) { var items []model.Item if err := d.db.Where("board_id = ? AND status = ?", boardId, status).Find(&items).Error; err != nil { return nil, err @@ -175,16 +176,20 @@ func (d *Database) GetBoardItemsByStatus(boardId uint, status model.Status) ([]m return items, nil } -func (db *Database) UpdateItem(item model.Item, id uint) error { +func (db *Database) UpdateItem(id uint, item model.Item) error { if err := db.db.Model(&model.Item{}).Where("id = ?", id).Updates(item).Error; err != nil { return err } return nil } -func (db *Database) UpdateUser(user model.User, id uint) error { +func (db *Database) UpdateUser(id uint, user model.User) error { if err := db.db.Model(&model.User{}).Where("id = ?", id).Updates(user).Error; err != nil { return err } return nil } + +func (db *Database) ItemData(id uint) (string, error) { + return "", nil +} diff --git a/api/server/routes/boards.go b/api/server/routes/boards.go index 745ed82..45eb98b 100644 --- a/api/server/routes/boards.go +++ b/api/server/routes/boards.go @@ -4,18 +4,18 @@ import ( "net/http" "strconv" - "github.com/echo-webkom/ludo/api/database" "github.com/echo-webkom/ludo/api/rest" "github.com/echo-webkom/ludo/pkg/model" + "github.com/echo-webkom/ludo/pkg/service" "github.com/go-chi/chi/v5" ) -func BoardsHandler(db *database.Database) chi.Router { +func BoardsHandler(s service.LudoService) chi.Router { r := chi.NewRouter() // Get all boards r.Get("/", rest.Handler(func(r rest.Request) int { - if boards, err := db.GetAllBoards(); err == nil { + if boards, err := s.Boards(); err == nil { return r.RespondJSON(&boards) } return http.StatusInternalServerError @@ -28,7 +28,7 @@ func BoardsHandler(db *database.Database) chi.Router { return http.StatusBadRequest } - id, err := db.CreateBoard(board) + id, err := s.NewBoard(board) if err != nil { return http.StatusInternalServerError } @@ -41,7 +41,7 @@ func BoardsHandler(db *database.Database) chi.Router { // Get board by id r.Get("/", rest.Handler(func(r rest.Request) int { - if board, err := db.GetBoardById(r.ContextValue(idKey).(uint)); err == nil { + if board, err := s.Board(r.ContextValue(idKey).(uint)); err == nil { return r.RespondJSON(&board) } return http.StatusInternalServerError @@ -54,7 +54,7 @@ func BoardsHandler(db *database.Database) chi.Router { return http.StatusBadRequest } - if err := db.UpdateBoard(board, r.ContextValue(idKey).(uint)); err != nil { + if err := s.UpdateBoard(r.ContextValue(idKey).(uint), board); err != nil { return http.StatusInternalServerError } @@ -63,7 +63,7 @@ func BoardsHandler(db *database.Database) chi.Router { // Delete baord r.Delete("/", rest.Handler(func(r rest.Request) int { - if err := db.DeleteBoard(r.ContextValue(idKey).(uint)); err != nil { + if err := s.DeleteBoard(r.ContextValue(idKey).(uint)); err != nil { return http.StatusInternalServerError } return http.StatusOK @@ -71,7 +71,7 @@ func BoardsHandler(db *database.Database) chi.Router { // Get all items in board r.Get("/items", rest.Handler(func(r rest.Request) int { - if items, err := db.GetBoardItems(r.ContextValue(idKey).(uint)); err == nil { + if items, err := s.BoardItems(r.ContextValue(idKey).(uint)); err == nil { return r.RespondJSON(&items) } return http.StatusInternalServerError @@ -84,12 +84,14 @@ func BoardsHandler(db *database.Database) chi.Router { return http.StatusBadRequest } + boardId := r.ContextValue(idKey).(uint) + // Check if board exists - if _, err := db.GetBoardById(r.ContextValue(idKey).(uint)); err != nil { + if _, err := s.Board(boardId); err != nil { return http.StatusNotFound } - id, err := db.CreateItem(item) + id, err := s.NewItem(boardId, item) if err != nil { return http.StatusInternalServerError } @@ -104,7 +106,7 @@ func BoardsHandler(db *database.Database) chi.Router { return http.StatusBadRequest } - items, err := db.GetBoardItemsByStatus(r.ContextValue(idKey).(uint), model.Status(status)) + items, err := s.BoardItemsWithStatus(r.ContextValue(idKey).(uint), model.Status(status)) if err != nil { return http.StatusInternalServerError } @@ -114,7 +116,7 @@ func BoardsHandler(db *database.Database) chi.Router { // Get all users in board r.Get("/users", rest.Handler(func(r rest.Request) int { - if users, err := db.GetBoardUsers(r.ContextValue(idKey).(uint)); err == nil { + if users, err := s.BoardUsers(r.ContextValue(idKey).(uint)); err == nil { return r.RespondJSON(&users) } return http.StatusInternalServerError @@ -127,7 +129,7 @@ func BoardsHandler(db *database.Database) chi.Router { return http.StatusBadRequest } - if err := db.AddUserToBoard(r.ContextValue(idKey).(uint), uint(userId)); err != nil { + if err := s.AddUserToBoard(r.ContextValue(idKey).(uint), uint(userId)); err != nil { return http.StatusInternalServerError } @@ -141,7 +143,7 @@ func BoardsHandler(db *database.Database) chi.Router { return http.StatusBadRequest } - if err := db.RemoveUserFromBoard(r.ContextValue(idKey).(uint), uint(userId)); err != nil { + if err := s.RemoveUserFromBoard(r.ContextValue(idKey).(uint), uint(userId)); err != nil { return http.StatusInternalServerError } diff --git a/api/server/routes/items.go b/api/server/routes/items.go index 40e5dbf..110e40f 100644 --- a/api/server/routes/items.go +++ b/api/server/routes/items.go @@ -4,13 +4,13 @@ import ( "io" "net/http" - "github.com/echo-webkom/ludo/api/database" "github.com/echo-webkom/ludo/api/rest" "github.com/echo-webkom/ludo/pkg/model" + "github.com/echo-webkom/ludo/pkg/service" "github.com/go-chi/chi/v5" ) -func ItemsHandler(db *database.Database) chi.Router { +func ItemsHandler(s service.LudoService) chi.Router { r := chi.NewRouter() r.Route("/{id}", func(r chi.Router) { @@ -18,7 +18,7 @@ func ItemsHandler(db *database.Database) chi.Router { // Get item by id r.Get("/", rest.Handler(func(r rest.Request) int { - if item, err := db.GetItemById(r.ContextValue(idKey).(uint)); err == nil { + if item, err := s.Item(r.ContextValue(idKey).(uint)); err == nil { return r.RespondJSON(&item) } return http.StatusInternalServerError @@ -31,7 +31,7 @@ func ItemsHandler(db *database.Database) chi.Router { return http.StatusBadRequest } - if err := db.UpdateItem(item, r.ContextValue(idKey).(uint)); err != nil { + if err := s.UpdateItem(r.ContextValue(idKey).(uint), item); err != nil { return http.StatusInternalServerError } @@ -40,7 +40,7 @@ func ItemsHandler(db *database.Database) chi.Router { // Delete item by id r.Delete("/", rest.Handler(func(r rest.Request) int { - if err := db.DeleteItemByID(r.ContextValue(idKey).(uint)); err != nil { + if err := s.DeleteItem(r.ContextValue(idKey).(uint)); err != nil { return http.StatusInternalServerError } return http.StatusOK @@ -48,7 +48,7 @@ func ItemsHandler(db *database.Database) chi.Router { // Get item data r.Get("/data", rest.Handler(func(r rest.Request) int { - if item, err := db.GetItemById(r.ContextValue(idKey).(uint)); err == nil { + if item, err := s.Item(r.ContextValue(idKey).(uint)); err == nil { return r.RespondString(item.Data) } return http.StatusInternalServerError @@ -62,7 +62,7 @@ func ItemsHandler(db *database.Database) chi.Router { } id := r.ContextValue(idKey).(uint) - if err := db.SetItemData(id, string(data)); err != nil { + if err := s.SetItemData(id, string(data)); err != nil { return http.StatusInternalServerError } diff --git a/api/server/routes/users.go b/api/server/routes/users.go index 8b2b990..01f11b0 100644 --- a/api/server/routes/users.go +++ b/api/server/routes/users.go @@ -3,18 +3,18 @@ package routes import ( "net/http" - "github.com/echo-webkom/ludo/api/database" "github.com/echo-webkom/ludo/api/rest" "github.com/echo-webkom/ludo/pkg/model" + "github.com/echo-webkom/ludo/pkg/service" "github.com/go-chi/chi/v5" ) -func UsersHandler(db *database.Database) chi.Router { +func UsersHandler(s service.LudoService) chi.Router { r := chi.NewRouter() // Get all users r.Get("/", rest.Handler(func(r rest.Request) int { - if users, err := db.GetAllUsers(); err == nil { + if users, err := s.Users(); err == nil { return r.RespondJSON(&users) } return http.StatusInternalServerError @@ -27,7 +27,7 @@ func UsersHandler(db *database.Database) chi.Router { return http.StatusBadRequest } - id, err := db.CreateUser(user) + id, err := s.NewUser(user) if err != nil { return http.StatusInternalServerError } @@ -40,7 +40,7 @@ func UsersHandler(db *database.Database) chi.Router { // Get user by id r.Get("/", rest.Handler(func(r rest.Request) int { - if user, err := db.GetUserById(r.ContextValue(idKey).(uint)); err == nil { + if user, err := s.User(r.ContextValue(idKey).(uint)); err == nil { return r.RespondJSON(&user) } return http.StatusInternalServerError @@ -53,7 +53,7 @@ func UsersHandler(db *database.Database) chi.Router { return http.StatusBadRequest } - if err := db.UpdateUser(user, r.ContextValue(idKey).(uint)); err != nil { + if err := s.UpdateUser(r.ContextValue(idKey).(uint), user); err != nil { return http.StatusInternalServerError } @@ -62,7 +62,7 @@ func UsersHandler(db *database.Database) chi.Router { // Delete user by id r.Delete("/", rest.Handler(func(r rest.Request) int { - if err := db.DeleteUserById(r.ContextValue(idKey).(uint)); err != nil { + if err := s.DeleteUser(r.ContextValue(idKey).(uint)); err != nil { return http.StatusInternalServerError } return http.StatusOK