diff --git a/api/category.go b/api/category.go new file mode 100644 index 0000000..acba29e --- /dev/null +++ b/api/category.go @@ -0,0 +1,27 @@ +package api + +import ( + "net/http" + + "github.com/gin-gonic/gin" +) + +func (server *Server) GetCategories(ctx *gin.Context) { + + categories, err := server.store.GetCategories(ctx) + if err != nil { + BuildResponse(ctx, BaseResponse{ + Code: http.StatusInternalServerError, + Message: ResponseMessage{ + Type: ERROR, + Content: "Internal error: " + err.Error(), + }, + }) + return + } + + BuildResponse(ctx, BaseResponse{ + Code: http.StatusOK, + Data: categories, + }) +} diff --git a/api/category_test.go b/api/category_test.go new file mode 100644 index 0000000..df695c0 --- /dev/null +++ b/api/category_test.go @@ -0,0 +1,104 @@ +package api + +import ( + "bytes" + "database/sql" + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/golang/mock/gomock" + mockdb "github.com/mustafayilmazdev/musarchive/db/mock" + db "github.com/mustafayilmazdev/musarchive/db/sqlc" + "github.com/mustafayilmazdev/musarchive/token" + "github.com/stretchr/testify/require" +) + +func TestGetCategories(t *testing.T) { + user, _ := RandomUser(t) + + categories := []db.Category{ + {ID: 1, Name: "Category 1"}, + {ID: 2, Name: "Category 2"}, + } + + testCases := []struct { + name string + setupAuth func(t *testing.T, request *http.Request, tokenMaker token.Maker) + buildStubs func(store *mockdb.MockStore) + checkResponse func(recorder *httptest.ResponseRecorder) + }{ + { + name: "OK", + setupAuth: func(t *testing.T, request *http.Request, tokenMaker token.Maker) { + addAuthorization(t, request, tokenMaker, authorizationTypeBearer, int(user.ID), user.Role, time.Minute) + }, + buildStubs: func(store *mockdb.MockStore) { + store.EXPECT(). + GetCategories(gomock.Any()). + Times(1). + Return(categories, nil) + }, + checkResponse: func(recorder *httptest.ResponseRecorder) { + require.Equal(t, http.StatusOK, recorder.Code) + requireBodyMatchCategories(t, recorder.Body, categories) + }, + }, + { + name: "InternalError", + setupAuth: func(t *testing.T, request *http.Request, tokenMaker token.Maker) { + addAuthorization(t, request, tokenMaker, authorizationTypeBearer, int(user.ID), user.Role, time.Minute) + }, + buildStubs: func(store *mockdb.MockStore) { + store.EXPECT(). + GetCategories(gomock.Any()). + Times(1).Return([]db.Category{}, sql.ErrConnDone) + }, + checkResponse: func(recorder *httptest.ResponseRecorder) { + require.Equal(t, http.StatusInternalServerError, recorder.Code) + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + store := mockdb.NewMockStore(ctrl) + tc.buildStubs(store) + + server := newTestServer(t, store) + recorder := httptest.NewRecorder() + + url := "/v1/categories/index" + request, err := http.NewRequest(http.MethodGet, url, nil) + require.NoError(t, err) + + tc.setupAuth(t, request, server.tokenMaker) + server.Router.ServeHTTP(recorder, request) + tc.checkResponse(recorder) + }) + } +} + +func requireBodyMatchCategories(t *testing.T, body *bytes.Buffer, categories []db.Category) { + data, err := io.ReadAll(body) + require.NoError(t, err) + + var response BaseResponse + err = json.Unmarshal(data, &response) + require.NoError(t, err) + require.Equal(t, http.StatusOK, response.Code) + + categoriesJSON, err := json.Marshal(categories) + require.NoError(t, err) + + responseDataJSON, err := json.Marshal(response.Data) + require.NoError(t, err) + + require.JSONEq(t, string(categoriesJSON), string(responseDataJSON)) +} diff --git a/api/login_test.go b/api/login_test.go index 0e1f3c5..557fee5 100644 --- a/api/login_test.go +++ b/api/login_test.go @@ -128,7 +128,7 @@ func TestLoginUserAPI(t *testing.T) { data, err := json.Marshal(tc.body) require.NoError(t, err) - url := "/v1/login" + url := "/v1/auth/login" request, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(data)) require.NoError(t, err) diff --git a/api/post_test.go b/api/post_test.go index 3e02a5a..85168dc 100644 --- a/api/post_test.go +++ b/api/post_test.go @@ -309,7 +309,7 @@ func TestGetPostsAPI(t *testing.T) { data, err := json.Marshal(tc.body) require.NoError(t, err) - url := "/v1/posts" + url := "/v1/posts/index" request, err := http.NewRequest(http.MethodGet, url, bytes.NewReader(data)) require.NoError(t, err) diff --git a/api/register_test.go b/api/register_test.go index 0c94086..353a950 100644 --- a/api/register_test.go +++ b/api/register_test.go @@ -198,7 +198,7 @@ func TestCreateUserAPI(t *testing.T) { data, err := json.Marshal(tc.body) require.NoError(t, err) - url := "/v1/register" + url := "/v1/auth/register" request, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(data)) require.NoError(t, err) diff --git a/api/server.go b/api/server.go index 74396fb..3c5745e 100644 --- a/api/server.go +++ b/api/server.go @@ -60,6 +60,10 @@ func (server *Server) setupRouter() { postRoutes.GET("/followed", server.GetFollowedPosts) postRoutes.POST("/create", server.CreatePost) } + categoryRoutes := router.Group("/v1/categories").Use(authMiddleware(server.tokenMaker)) + { + categoryRoutes.GET("/index", server.GetCategories) + } // Serve the bundled static files statikFS, err := fs.New() diff --git a/db/mock/store.go b/db/mock/store.go index 9355e3a..aca3774 100644 --- a/db/mock/store.go +++ b/db/mock/store.go @@ -204,6 +204,21 @@ func (mr *MockStoreMockRecorder) DeleteUserPost(arg0, arg1 interface{}) *gomock. return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteUserPost", reflect.TypeOf((*MockStore)(nil).DeleteUserPost), arg0, arg1) } +// GetCategories mocks base method. +func (m *MockStore) GetCategories(arg0 context.Context) ([]db.Category, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCategories", arg0) + ret0, _ := ret[0].([]db.Category) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetCategories indicates an expected call of GetCategories. +func (mr *MockStoreMockRecorder) GetCategories(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCategories", reflect.TypeOf((*MockStore)(nil).GetCategories), arg0) +} + // GetCategoriesForPost mocks base method. func (m *MockStore) GetCategoriesForPost(arg0 context.Context, arg1 int32) ([]db.Category, error) { m.ctrl.T.Helper() diff --git a/db/query/categories.sql b/db/query/categories.sql index 6a2f97a..7d39979 100644 --- a/db/query/categories.sql +++ b/db/query/categories.sql @@ -9,6 +9,11 @@ SELECT id, name FROM categories WHERE id = $1; + +-- name: GetCategories :many +SELECT id, name +FROM categories; + -- name: UpdateCategory :one UPDATE categories SET name = $1 diff --git a/db/sqlc/categories.sql.go b/db/sqlc/categories.sql.go index 0a3450d..610f37e 100644 --- a/db/sqlc/categories.sql.go +++ b/db/sqlc/categories.sql.go @@ -19,6 +19,31 @@ func (q *Queries) DeleteCategory(ctx context.Context, id int32) error { return err } +const getCategories = `-- name: GetCategories :many +SELECT id, name +FROM categories +` + +func (q *Queries) GetCategories(ctx context.Context) ([]Category, error) { + rows, err := q.db.Query(ctx, getCategories) + if err != nil { + return nil, err + } + defer rows.Close() + items := []Category{} + for rows.Next() { + var i Category + if err := rows.Scan(&i.ID, &i.Name); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const getCategory = `-- name: GetCategory :one SELECT id, name FROM categories diff --git a/db/sqlc/querier.go b/db/sqlc/querier.go index 61854d3..b550f0b 100644 --- a/db/sqlc/querier.go +++ b/db/sqlc/querier.go @@ -23,6 +23,7 @@ type Querier interface { DeleteUser(ctx context.Context, id int32) error DeleteUserFollower(ctx context.Context, arg DeleteUserFollowerParams) error DeleteUserPost(ctx context.Context, arg DeleteUserPostParams) error + GetCategories(ctx context.Context) ([]Category, error) GetCategoriesForPost(ctx context.Context, postID int32) ([]Category, error) GetCategory(ctx context.Context, id int32) (Category, error) GetCommentsForPost(ctx context.Context, postID int32) ([]Comment, error)