feat: add knowledgeHub api

This commit is contained in:
Buravit Yenjit 2025-04-01 13:41:06 +07:00
parent b336d94277
commit 38f01d0fbe
2 changed files with 265 additions and 8 deletions

View File

@ -22,10 +22,11 @@ type api struct {
logger *slog.Logger logger *slog.Logger
httpClient *http.Client httpClient *http.Client
userRepo domain.UserRepository userRepo domain.UserRepository
cropRepo domain.CroplandRepository cropRepo domain.CroplandRepository
farmRepo domain.FarmRepository farmRepo domain.FarmRepository
plantRepo domain.PlantRepository plantRepo domain.PlantRepository
knowledgeHubRepo domain.KnowledgeHubRepository
} }
func NewAPI(ctx context.Context, logger *slog.Logger, pool *pgxpool.Pool) *api { func NewAPI(ctx context.Context, logger *slog.Logger, pool *pgxpool.Pool) *api {
@ -36,15 +37,17 @@ func NewAPI(ctx context.Context, logger *slog.Logger, pool *pgxpool.Pool) *api {
croplandRepository := repository.NewPostgresCropland(pool) croplandRepository := repository.NewPostgresCropland(pool)
farmRepository := repository.NewPostgresFarm(pool) farmRepository := repository.NewPostgresFarm(pool)
plantRepository := repository.NewPostgresPlant(pool) plantRepository := repository.NewPostgresPlant(pool)
knowledgeHubRepository := repository.NewPostgresKnowledgeHub(pool)
return &api{ return &api{
logger: logger, logger: logger,
httpClient: client, httpClient: client,
userRepo: userRepository, userRepo: userRepository,
cropRepo: croplandRepository, cropRepo: croplandRepository,
farmRepo: farmRepository, farmRepo: farmRepository,
plantRepo: plantRepository, plantRepo: plantRepository,
knowledgeHubRepo: knowledgeHubRepository,
} }
} }
@ -77,6 +80,7 @@ func (a *api) Routes() *chi.Mux {
a.registerCropRoutes(r, api) a.registerCropRoutes(r, api)
a.registerPlantRoutes(r, api) a.registerPlantRoutes(r, api)
a.registerOauthRoutes(r, api) a.registerOauthRoutes(r, api)
a.registerKnowledgeHubRoutes(r, api)
}) })
router.Group(func(r chi.Router) { router.Group(func(r chi.Router) {

View File

@ -0,0 +1,253 @@
package api
import (
"context"
"errors"
"net/http"
"time"
"github.com/danielgtaylor/huma/v2"
"github.com/forfarm/backend/internal/domain"
"github.com/go-chi/chi/v5"
"github.com/gofrs/uuid"
)
func (a *api) registerKnowledgeHubRoutes(_ chi.Router, api huma.API) {
tags := []string{"knowledge-hub"}
prefix := "/knowledge-hub"
huma.Register(api, huma.Operation{
OperationID: "getAllKnowledgeArticles",
Method: http.MethodGet,
Path: prefix,
Tags: tags,
}, a.getAllKnowledgeArticlesHandler)
huma.Register(api, huma.Operation{
OperationID: "getKnowledgeArticleByID",
Method: http.MethodGet,
Path: prefix + "/{uuid}",
Tags: tags,
}, a.getKnowledgeArticleByIDHandler)
huma.Register(api, huma.Operation{
OperationID: "getKnowledgeArticlesByCategory",
Method: http.MethodGet,
Path: prefix + "/category/{category}",
Tags: tags,
}, a.getKnowledgeArticlesByCategoryHandler)
huma.Register(api, huma.Operation{
OperationID: "createOrUpdateKnowledgeArticle",
Method: http.MethodPost,
Path: prefix,
Tags: tags,
}, a.createOrUpdateKnowledgeArticleHandler)
huma.Register(api, huma.Operation{
OperationID: "getArticleTableOfContents",
Method: http.MethodGet,
Path: prefix + "/{uuid}/toc",
Tags: tags,
}, a.getArticleTableOfContentsHandler)
huma.Register(api, huma.Operation{
OperationID: "getArticleRelatedArticles",
Method: http.MethodGet,
Path: prefix + "/{uuid}/related",
Tags: tags,
}, a.getArticleRelatedArticlesHandler)
}
type GetKnowledgeArticlesOutput struct {
Body struct {
Articles []domain.KnowledgeArticle `json:"articles"`
} `json:"body"`
}
type GetKnowledgeArticleByIDOutput struct {
Body struct {
Article domain.KnowledgeArticle `json:"article"`
} `json:"body"`
}
type CreateOrUpdateKnowledgeArticleInput struct {
Body struct {
UUID string `json:"uuid,omitempty"` // Optional for create, required for update
Title string `json:"title"`
Content string `json:"content"`
Author string `json:"author"`
PublishDate time.Time `json:"publish_date"`
ReadTime string `json:"read_time"`
Categories []string `json:"categories"`
} `json:"body"`
}
type CreateOrUpdateKnowledgeArticleOutput struct {
Body struct {
Article domain.KnowledgeArticle `json:"article"`
} `json:"body"`
}
type GetTableOfContentsOutput struct {
Body struct {
TableOfContents []domain.TableOfContent `json:"table_of_contents"`
} `json:"body"`
}
type GetRelatedArticlesOutput struct {
Body struct {
RelatedArticles []domain.RelatedArticle `json:"related_articles"`
} `json:"body"`
}
func (a *api) getAllKnowledgeArticlesHandler(ctx context.Context, input *struct{}) (*GetKnowledgeArticlesOutput, error) {
resp := &GetKnowledgeArticlesOutput{}
articles, err := a.knowledgeHubRepo.GetAllArticles(ctx)
if err != nil {
return nil, err
}
resp.Body.Articles = articles
return resp, nil
}
func (a *api) getKnowledgeArticleByIDHandler(ctx context.Context, input *struct {
UUID string `path:"uuid" example:"550e8400-e29b-41d4-a716-446655440000"`
}) (*GetKnowledgeArticleByIDOutput, error) {
resp := &GetKnowledgeArticleByIDOutput{}
if input.UUID == "" {
return nil, huma.Error400BadRequest("UUID parameter is required")
}
_, err := uuid.FromString(input.UUID)
if err != nil {
return nil, huma.Error400BadRequest("invalid UUID format")
}
article, err := a.knowledgeHubRepo.GetArticleByID(ctx, input.UUID)
if err != nil {
if errors.Is(err, domain.ErrNotFound) {
return nil, huma.Error404NotFound("article not found")
}
return nil, err
}
resp.Body.Article = article
return resp, nil
}
func (a *api) getKnowledgeArticlesByCategoryHandler(ctx context.Context, input *struct {
Category string `path:"category" example:"Sustainability"`
}) (*GetKnowledgeArticlesOutput, error) {
resp := &GetKnowledgeArticlesOutput{}
if input.Category == "" {
return nil, huma.Error400BadRequest("category parameter is required")
}
articles, err := a.knowledgeHubRepo.GetArticlesByCategory(ctx, input.Category)
if err != nil {
return nil, err
}
resp.Body.Articles = articles
return resp, nil
}
func (a *api) createOrUpdateKnowledgeArticleHandler(ctx context.Context, input *CreateOrUpdateKnowledgeArticleInput) (*CreateOrUpdateKnowledgeArticleOutput, error) {
resp := &CreateOrUpdateKnowledgeArticleOutput{}
if input.Body.Title == "" {
return nil, huma.Error400BadRequest("title is required")
}
if input.Body.Content == "" {
return nil, huma.Error400BadRequest("content is required")
}
if input.Body.Author == "" {
return nil, huma.Error400BadRequest("author is required")
}
if len(input.Body.Categories) == 0 {
return nil, huma.Error400BadRequest("at least one category is required")
}
if input.Body.UUID != "" {
_, err := uuid.FromString(input.Body.UUID)
if err != nil {
return nil, huma.Error400BadRequest("invalid UUID format")
}
}
article := &domain.KnowledgeArticle{
UUID: input.Body.UUID,
Title: input.Body.Title,
Content: input.Body.Content,
Author: input.Body.Author,
PublishDate: input.Body.PublishDate,
ReadTime: input.Body.ReadTime,
Categories: input.Body.Categories,
}
err := a.knowledgeHubRepo.CreateOrUpdateArticle(ctx, article)
if err != nil {
return nil, err
}
resp.Body.Article = *article
return resp, nil
}
func (a *api) getArticleTableOfContentsHandler(ctx context.Context, input *struct {
UUID string `path:"uuid" example:"550e8400-e29b-41d4-a716-446655440000"`
}) (*GetTableOfContentsOutput, error) {
resp := &GetTableOfContentsOutput{}
if input.UUID == "" {
return nil, huma.Error400BadRequest("UUID parameter is required")
}
_, err := uuid.FromString(input.UUID)
if err != nil {
return nil, huma.Error400BadRequest("invalid UUID format")
}
toc, err := a.knowledgeHubRepo.GetTableOfContents(ctx, input.UUID)
if err != nil {
if errors.Is(err, domain.ErrNotFound) {
return nil, huma.Error404NotFound("article not found")
}
return nil, err
}
resp.Body.TableOfContents = toc
return resp, nil
}
func (a *api) getArticleRelatedArticlesHandler(ctx context.Context, input *struct {
UUID string `path:"uuid" example:"550e8400-e29b-41d4-a716-446655440000"`
}) (*GetRelatedArticlesOutput, error) {
resp := &GetRelatedArticlesOutput{}
if input.UUID == "" {
return nil, huma.Error400BadRequest("UUID parameter is required")
}
_, err := uuid.FromString(input.UUID)
if err != nil {
return nil, huma.Error400BadRequest("invalid UUID format")
}
related, err := a.knowledgeHubRepo.GetRelatedArticles(ctx, input.UUID)
if err != nil {
if errors.Is(err, domain.ErrNotFound) {
return nil, huma.Error404NotFound("article not found")
}
return nil, err
}
resp.Body.RelatedArticles = related
return resp, nil
}