diff --git a/cmd/server/wire_gen.go b/cmd/server/wire_gen.go index 218ed90..5508ca2 100644 --- a/cmd/server/wire_gen.go +++ b/cmd/server/wire_gen.go @@ -12,6 +12,7 @@ import ( "github.com/team-nerd-planet/api-server/infra/database/repository" "github.com/team-nerd-planet/api-server/infra/router" "github.com/team-nerd-planet/api-server/internal/controller/rest" + "github.com/team-nerd-planet/api-server/internal/usecase/feed" "github.com/team-nerd-planet/api-server/internal/usecase/item" "github.com/team-nerd-planet/api-server/internal/usecase/subscription" "github.com/team-nerd-planet/api-server/internal/usecase/tag" @@ -39,6 +40,9 @@ func InitServer() (router.Router, error) { subscriptionRepo := repository.NewSubscriptionRepo(databaseDatabase) subscriptionUsecase := subscription.NewSubscriptionUsecase(subscriptionRepo, configConfig) subscriptionController := rest.NewSubscriptionController(subscriptionUsecase) - routerRouter := router.NewRouter(configConfig, itemController, tagController, subscriptionController) + feedRepo := repository.NewFeedRepo(databaseDatabase) + feedUsecase := feed.NewFeedUsecase(feedRepo) + feedController := rest.NewFeedController(feedUsecase) + routerRouter := router.NewRouter(configConfig, itemController, tagController, subscriptionController, feedController) return routerRouter, nil } diff --git a/infra/database/repository/feed_repo.go b/infra/database/repository/feed_repo.go new file mode 100644 index 0000000..b35f26d --- /dev/null +++ b/infra/database/repository/feed_repo.go @@ -0,0 +1,40 @@ +package repository + +import ( + "fmt" + "strings" + + "github.com/team-nerd-planet/api-server/infra/database" + "github.com/team-nerd-planet/api-server/internal/entity" +) + +type FeedRepo struct { + db *database.Database +} + +func NewFeedRepo(db *database.Database) entity.FeedRepo { + return &FeedRepo{ + db: db, + } +} + +// FindAll implements entity.FeedRepo. +func (fr *FeedRepo) FindAll(keyword *string) ([]entity.Feed, error) { + var ( + feeds []entity.Feed + where = make([]string, 0) + param = make([]interface{}, 0) + ) + + if keyword != nil { + where = append(where, "name LIKE ?") + param = append(param, fmt.Sprintf("%s%%", *keyword)) + } + + err := fr.db. + Where(strings.Join(where, " AND "), param...). + Order("name"). + Find(&feeds).Error + + return feeds, err +} diff --git a/infra/database/repository/item_repo.go b/infra/database/repository/item_repo.go index 30a8ab6..d5d6ec5 100644 --- a/infra/database/repository/item_repo.go +++ b/infra/database/repository/item_repo.go @@ -29,7 +29,7 @@ func (clr *ItemRepo) CountView(company *string, companySizes *[]entity.CompanySi if company != nil { where = append(where, "feed_name LIKE ?") - param = append(param, fmt.Sprintf("%%%s%%", *company)) + param = append(param, fmt.Sprintf("%s%%", *company)) } if companySizes != nil { diff --git a/infra/infra.go b/infra/infra.go index 0254b2d..1934e29 100644 --- a/infra/infra.go +++ b/infra/infra.go @@ -16,4 +16,5 @@ var InfraSet = wire.NewSet( repository.NewJobTagRepo, repository.NewSkillTagRepo, repository.NewSubscriptionRepo, + repository.NewFeedRepo, ) diff --git a/infra/router/handler/feed_handler.go b/infra/router/handler/feed_handler.go new file mode 100644 index 0000000..5bb5d31 --- /dev/null +++ b/infra/router/handler/feed_handler.go @@ -0,0 +1,39 @@ +package handler + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/team-nerd-planet/api-server/infra/router/util" + "github.com/team-nerd-planet/api-server/internal/controller/rest" + "github.com/team-nerd-planet/api-server/internal/controller/rest/dto/feed_dto" +) + +// SearchFeedName +// +// @Summary Search Feed Name +// @Description search feed's name +// @Tags feed +// @Schemes http +// @Accept json +// @Produce json +// @Param name_keyword query string false "회사 이름 검색 키워드" +// @Success 200 {object} []feed_dto.SearchRes +// @Failure 400 {object} util.HTTPError +// @Failure 500 {object} util.HTTPError +// @Router /v1/feed/search [get] +func SearchFeedName(c *gin.Context, ctrl rest.FeedController) { + req, err := util.ValidateQuery[feed_dto.SearchReq](c) + if err != nil { + util.NewError(c, http.StatusBadRequest, err) + return + } + + res, ok := ctrl.Search(*req) + if !ok { + util.NewError(c, http.StatusInternalServerError) + return + } + + c.JSON(http.StatusOK, res) +} diff --git a/infra/router/handler/item_handler.go b/infra/router/handler/item_handler.go index 95d8b91..c20b7ba 100644 --- a/infra/router/handler/item_handler.go +++ b/infra/router/handler/item_handler.go @@ -26,7 +26,6 @@ import ( // @Param page query int true "페이지" // @Success 200 {object} dto.Paginated[[]item_dto.FindAllItemRes] // @Failure 400 {object} util.HTTPError -// @Failure 404 {object} util.HTTPError // @Failure 500 {object} util.HTTPError // @Router /v1/item [get] func ListItems(c *gin.Context, ctrl rest.ItemController) { diff --git a/infra/router/router.go b/infra/router/router.go index f0da001..f42d1a3 100644 --- a/infra/router/router.go +++ b/infra/router/router.go @@ -22,7 +22,13 @@ type Router struct { conf *config.Config } -func NewRouter(conf *config.Config, itemCtrl rest.ItemController, tagCtrl rest.TagController, subscriptionCtrl rest.SubscriptionController) Router { +func NewRouter( + conf *config.Config, + itemCtrl rest.ItemController, + tagCtrl rest.TagController, + subscriptionCtrl rest.SubscriptionController, + feedCtrl rest.FeedController, +) Router { if conf.Rest.Mode == "release" { gin.SetMode(gin.ReleaseMode) } @@ -39,20 +45,25 @@ func NewRouter(conf *config.Config, itemCtrl rest.ItemController, tagCtrl rest.T item := v1.Group("/item") { - item.GET("/", cache.CachePage(s, time.Hour, func(c *gin.Context) { handler.ListItems(c, itemCtrl) })) + item.GET("/", cache.CachePage(s, time.Hour, func(ctx *gin.Context) { handler.ListItems(ctx, itemCtrl) })) } tag := v1.Group("/tag") { - tag.GET("/job", cache.CachePage(s, time.Hour, func(c *gin.Context) { handler.ListJobTags(c, tagCtrl) })) - tag.GET("/skill", cache.CachePage(s, time.Hour, func(c *gin.Context) { handler.ListSkillTags(c, tagCtrl) })) + tag.GET("/job", cache.CachePage(s, time.Hour, func(ctx *gin.Context) { handler.ListJobTags(ctx, tagCtrl) })) + tag.GET("/skill", cache.CachePage(s, time.Hour, func(ctx *gin.Context) { handler.ListSkillTags(ctx, tagCtrl) })) } subscription := v1.Group("/subscription") { - subscription.POST("/apply", func(c *gin.Context) { handler.Apply(c, subscriptionCtrl) }) - subscription.GET("/approve", func(c *gin.Context) { handler.ApproveGet(c, subscriptionCtrl) }) - subscription.POST("/approve", func(c *gin.Context) { handler.Approve(c, subscriptionCtrl) }) + subscription.POST("/apply", func(ctx *gin.Context) { handler.Apply(ctx, subscriptionCtrl) }) + subscription.GET("/approve", func(ctx *gin.Context) { handler.ApproveGet(ctx, subscriptionCtrl) }) + subscription.POST("/approve", func(ctx *gin.Context) { handler.Approve(ctx, subscriptionCtrl) }) + } + + feed := v1.Group("/feed") + { + feed.GET("/search", func(ctx *gin.Context) { handler.SearchFeedName(ctx, feedCtrl) }) } } diff --git a/internal/controller/controller.go b/internal/controller/controller.go index b6dc164..c069026 100644 --- a/internal/controller/controller.go +++ b/internal/controller/controller.go @@ -5,4 +5,9 @@ import ( "github.com/team-nerd-planet/api-server/internal/controller/rest" ) -var ControllerSet = wire.NewSet(rest.NewItemController, rest.NewTagController, rest.NewSubscriptionController) +var ControllerSet = wire.NewSet( + rest.NewItemController, + rest.NewTagController, + rest.NewSubscriptionController, + rest.NewFeedController, +) diff --git a/internal/controller/rest/dto/feed_dto/search_dto.go b/internal/controller/rest/dto/feed_dto/search_dto.go new file mode 100644 index 0000000..6366591 --- /dev/null +++ b/internal/controller/rest/dto/feed_dto/search_dto.go @@ -0,0 +1,25 @@ +package feed_dto + +import "github.com/team-nerd-planet/api-server/internal/entity" + +type SearchReq struct { + NameKeyword string `form:"name_keyword" binding:"min=1"` // 회사 이름 검색어 +} + +type SearchRes struct { + ID uint `json:"id"` + Name string `json:"name"` +} + +func NewSearchRes(feeds []entity.Feed) []SearchRes { + res := make([]SearchRes, len(feeds)) + + for i, feed := range feeds { + res[i] = SearchRes{ + ID: feed.ID, + Name: feed.Name, + } + } + + return res +} diff --git a/internal/controller/rest/dto/subscription_dto/approve_dto.go b/internal/controller/rest/dto/subscription_dto/approve_dto.go index 8e4eb8a..4ed128c 100644 --- a/internal/controller/rest/dto/subscription_dto/approve_dto.go +++ b/internal/controller/rest/dto/subscription_dto/approve_dto.go @@ -6,4 +6,4 @@ type ApproveReq struct { type ApproveRes struct { Ok bool `json:"ok"` // 구독 인증 결과 -} \ No newline at end of file +} diff --git a/internal/controller/rest/feed_controller.go b/internal/controller/rest/feed_controller.go new file mode 100644 index 0000000..7963e4b --- /dev/null +++ b/internal/controller/rest/feed_controller.go @@ -0,0 +1,27 @@ +package rest + +import ( + "github.com/team-nerd-planet/api-server/internal/controller/rest/dto/feed_dto" + "github.com/team-nerd-planet/api-server/internal/usecase/feed" +) + +type FeedController struct { + feedUcase feed.FeedUsecase +} + +func NewFeedController(feedUsecase feed.FeedUsecase) FeedController { + return FeedController{ + feedUcase: feedUsecase, + } +} + +func (fc FeedController) Search(req feed_dto.SearchReq) ([]feed_dto.SearchRes, bool) { + res := make([]feed_dto.SearchRes, 0) + + feeds, ok := fc.feedUcase.FindAll(&req.NameKeyword) + if !ok { + return res, false + } + + return feed_dto.NewSearchRes(*feeds), true +} diff --git a/internal/entity/feed.go b/internal/entity/feed.go index 0a798a2..5807c1f 100644 --- a/internal/entity/feed.go +++ b/internal/entity/feed.go @@ -24,3 +24,7 @@ type Feed struct { RssID uint `gorm:"column:rss_id;type:int8;not null"` Items []Item `gorm:"foreignKey:FeedID"` } + +type FeedRepo interface { + FindAll(keyword *string) ([]Feed, error) +} diff --git a/internal/usecase/feed/feed_ucase.go b/internal/usecase/feed/feed_ucase.go new file mode 100644 index 0000000..5e8cc52 --- /dev/null +++ b/internal/usecase/feed/feed_ucase.go @@ -0,0 +1,27 @@ +package feed + +import ( + "log/slog" + + "github.com/team-nerd-planet/api-server/internal/entity" +) + +type FeedUsecase struct { + feedRepo entity.FeedRepo +} + +func NewFeedUsecase(feedRepo entity.FeedRepo) FeedUsecase { + return FeedUsecase{ + feedRepo: feedRepo, + } +} + +func (fu FeedUsecase) FindAll(keyword *string) (*[]entity.Feed, bool) { + feeds, err := fu.feedRepo.FindAll(keyword) + if err != nil { + slog.Error(err.Error(), "error", err) + return nil, false + } + + return &feeds, true +} diff --git a/internal/usecase/usecase.go b/internal/usecase/usecase.go index 38dda06..09476fd 100644 --- a/internal/usecase/usecase.go +++ b/internal/usecase/usecase.go @@ -2,9 +2,16 @@ package usecase import ( "github.com/google/wire" + "github.com/team-nerd-planet/api-server/internal/usecase/feed" "github.com/team-nerd-planet/api-server/internal/usecase/item" "github.com/team-nerd-planet/api-server/internal/usecase/subscription" "github.com/team-nerd-planet/api-server/internal/usecase/tag" ) -var UsecaseSet = wire.NewSet(item.NewItemUsecase, tag.NewJobTagUsecase, tag.NewSkillTagUsecase, subscription.NewSubscriptionUsecase) +var UsecaseSet = wire.NewSet( + item.NewItemUsecase, + tag.NewJobTagUsecase, + tag.NewSkillTagUsecase, + subscription.NewSubscriptionUsecase, + feed.NewFeedUsecase, +) diff --git a/third_party/docs/docs.go b/third_party/docs/docs.go index 286e3dc..45ed84d 100644 --- a/third_party/docs/docs.go +++ b/third_party/docs/docs.go @@ -15,6 +15,52 @@ const docTemplate = `{ "host": "{{.Host}}", "basePath": "{{.BasePath}}", "paths": { + "/v1/feed/search": { + "get": { + "description": "search feed's name", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "feed" + ], + "summary": "Search Feed Name", + "parameters": [ + { + "type": "string", + "description": "회사 이름 검색 키워드", + "name": "name_keyword", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_team-nerd-planet_api-server_internal_controller_rest_dto_feed_dto.SearchRes" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_team-nerd-planet_api-server_infra_router_util.HTTPError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_team-nerd-planet_api-server_infra_router_util.HTTPError" + } + } + } + } + }, "/v1/item": { "get": { "description": "list items", @@ -93,12 +139,6 @@ const docTemplate = `{ "$ref": "#/definitions/github_com_team-nerd-planet_api-server_infra_router_util.HTTPError" } }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/github_com_team-nerd-planet_api-server_infra_router_util.HTTPError" - } - }, "500": { "description": "Internal Server Error", "schema": { @@ -327,6 +367,17 @@ const docTemplate = `{ } } }, + "github_com_team-nerd-planet_api-server_internal_controller_rest_dto_feed_dto.SearchRes": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + } + }, "github_com_team-nerd-planet_api-server_internal_controller_rest_dto_item_dto.FindAllItemRes": { "type": "object", "properties": { diff --git a/third_party/docs/swagger.json b/third_party/docs/swagger.json index 00a3eda..e03bf06 100644 --- a/third_party/docs/swagger.json +++ b/third_party/docs/swagger.json @@ -10,6 +10,52 @@ "version": "1.0" }, "paths": { + "/v1/feed/search": { + "get": { + "description": "search feed's name", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "feed" + ], + "summary": "Search Feed Name", + "parameters": [ + { + "type": "string", + "description": "회사 이름 검색 키워드", + "name": "name_keyword", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_team-nerd-planet_api-server_internal_controller_rest_dto_feed_dto.SearchRes" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_team-nerd-planet_api-server_infra_router_util.HTTPError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_team-nerd-planet_api-server_infra_router_util.HTTPError" + } + } + } + } + }, "/v1/item": { "get": { "description": "list items", @@ -88,12 +134,6 @@ "$ref": "#/definitions/github_com_team-nerd-planet_api-server_infra_router_util.HTTPError" } }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/github_com_team-nerd-planet_api-server_infra_router_util.HTTPError" - } - }, "500": { "description": "Internal Server Error", "schema": { @@ -322,6 +362,17 @@ } } }, + "github_com_team-nerd-planet_api-server_internal_controller_rest_dto_feed_dto.SearchRes": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + } + }, "github_com_team-nerd-planet_api-server_internal_controller_rest_dto_item_dto.FindAllItemRes": { "type": "object", "properties": { diff --git a/third_party/docs/swagger.yaml b/third_party/docs/swagger.yaml index 246296e..f6d835d 100644 --- a/third_party/docs/swagger.yaml +++ b/third_party/docs/swagger.yaml @@ -23,6 +23,13 @@ definitions: total_page: type: integer type: object + github_com_team-nerd-planet_api-server_internal_controller_rest_dto_feed_dto.SearchRes: + properties: + id: + type: integer + name: + type: string + type: object github_com_team-nerd-planet_api-server_internal_controller_rest_dto_item_dto.FindAllItemRes: properties: company_size: @@ -168,6 +175,36 @@ info: title: nerd-planet-api-server version: "1.0" paths: + /v1/feed/search: + get: + consumes: + - application/json + description: search feed's name + parameters: + - description: 회사 이름 검색 키워드 + in: query + name: name_keyword + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/github_com_team-nerd-planet_api-server_internal_controller_rest_dto_feed_dto.SearchRes' + type: array + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_team-nerd-planet_api-server_infra_router_util.HTTPError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_team-nerd-planet_api-server_infra_router_util.HTTPError' + summary: Search Feed Name + tags: + - feed /v1/item: get: consumes: @@ -221,10 +258,6 @@ paths: description: Bad Request schema: $ref: '#/definitions/github_com_team-nerd-planet_api-server_infra_router_util.HTTPError' - "404": - description: Not Found - schema: - $ref: '#/definitions/github_com_team-nerd-planet_api-server_infra_router_util.HTTPError' "500": description: Internal Server Error schema: