diff --git a/api/server.go b/api/server.go index 3c5745e..8e21bb9 100644 --- a/api/server.go +++ b/api/server.go @@ -64,6 +64,10 @@ func (server *Server) setupRouter() { { categoryRoutes.GET("/index", server.GetCategories) } + tagRoutes := router.Group("/v1/tags").Use(authMiddleware(server.tokenMaker)) + { + tagRoutes.GET("/index", server.GetTags) + } // Serve the bundled static files statikFS, err := fs.New() diff --git a/api/tag.go b/api/tag.go new file mode 100644 index 0000000..b7310b1 --- /dev/null +++ b/api/tag.go @@ -0,0 +1,27 @@ +package api + +import ( + "net/http" + + "github.com/gin-gonic/gin" +) + +func (server *Server) GetTags(ctx *gin.Context) { + + tags, err := server.store.GetTags(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: tags, + }) +} diff --git a/api/tag_test.go b/api/tag_test.go new file mode 100644 index 0000000..9b583cc --- /dev/null +++ b/api/tag_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 TestGetTags(t *testing.T) { + user, _ := RandomUser(t) + + tags := []db.Tag{ + {ID: 1, Name: "Tag 1"}, + {ID: 2, Name: "Tag 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(). + GetTags(gomock.Any()). + Times(1). + Return(tags, nil) + }, + checkResponse: func(recorder *httptest.ResponseRecorder) { + require.Equal(t, http.StatusOK, recorder.Code) + requireBodyMatchTags(t, recorder.Body, tags) + }, + }, + { + 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(). + GetTags(gomock.Any()). + Times(1).Return([]db.Tag{}, 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/tags/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 requireBodyMatchTags(t *testing.T, body *bytes.Buffer, Tags []db.Tag) { + 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) + + TagsJSON, err := json.Marshal(Tags) + require.NoError(t, err) + + responseDataJSON, err := json.Marshal(response.Data) + require.NoError(t, err) + + require.JSONEq(t, string(TagsJSON), string(responseDataJSON)) +} diff --git a/db/mock/store.go b/db/mock/store.go index aca3774..f1230a8 100644 --- a/db/mock/store.go +++ b/db/mock/store.go @@ -429,6 +429,21 @@ func (mr *MockStoreMockRecorder) GetTag(arg0, arg1 interface{}) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTag", reflect.TypeOf((*MockStore)(nil).GetTag), arg0, arg1) } +// GetTags mocks base method. +func (m *MockStore) GetTags(arg0 context.Context) ([]db.Tag, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetTags", arg0) + ret0, _ := ret[0].([]db.Tag) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetTags indicates an expected call of GetTags. +func (mr *MockStoreMockRecorder) GetTags(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTags", reflect.TypeOf((*MockStore)(nil).GetTags), arg0) +} + // GetTagsForPost mocks base method. func (m *MockStore) GetTagsForPost(arg0 context.Context, arg1 int32) ([]db.Tag, error) { m.ctrl.T.Helper() diff --git a/db/query/tags.sql b/db/query/tags.sql index 50b1f6f..4b6b6b2 100644 --- a/db/query/tags.sql +++ b/db/query/tags.sql @@ -8,6 +8,10 @@ SELECT id, name FROM tags WHERE id = $1; +-- name: GetTags :many +SELECT id, name +FROM tags; + -- name: UpdateTag :one UPDATE tags SET name = $1 diff --git a/db/sqlc/querier.go b/db/sqlc/querier.go index b550f0b..8333cc0 100644 --- a/db/sqlc/querier.go +++ b/db/sqlc/querier.go @@ -38,6 +38,7 @@ type Querier interface { GetProfile(ctx context.Context, userID int32) (Profile, error) GetSession(ctx context.Context, id uuid.UUID) (Session, error) GetTag(ctx context.Context, id int32) (Tag, error) + GetTags(ctx context.Context) ([]Tag, error) GetTagsForPost(ctx context.Context, postID int32) ([]Tag, error) GetUser(ctx context.Context, username string) (User, error) GetUserPost(ctx context.Context, arg GetUserPostParams) (GetUserPostRow, error) diff --git a/db/sqlc/tags.sql.go b/db/sqlc/tags.sql.go index c882f30..5cef444 100644 --- a/db/sqlc/tags.sql.go +++ b/db/sqlc/tags.sql.go @@ -32,6 +32,31 @@ func (q *Queries) GetTag(ctx context.Context, id int32) (Tag, error) { return i, err } +const getTags = `-- name: GetTags :many +SELECT id, name +FROM tags +` + +func (q *Queries) GetTags(ctx context.Context) ([]Tag, error) { + rows, err := q.db.Query(ctx, getTags) + if err != nil { + return nil, err + } + defer rows.Close() + items := []Tag{} + for rows.Next() { + var i Tag + 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 insertTag = `-- name: InsertTag :one INSERT INTO tags (name) VALUES ($1)