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) huma.Register(api, huma.Operation{ OperationID: "createRelatedArticle", Method: http.MethodPost, Path: prefix + "/{uuid}/related", Tags: tags, }, a.createRelatedArticleHandler) } 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"` 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"` ImageURL string `json:"image_url"` } `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"` } type CreateRelatedArticleInput struct { UUID string `path:"uuid"` Body struct { RelatedTitle string `json:"related_title"` RelatedTag string `json:"related_tag"` } `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"` }) (*GetKnowledgeArticleByIDOutput, error) { resp := &GetKnowledgeArticleByIDOutput{} if input.UUID == "" { return nil, huma.Error400BadRequest("UUID parameter is required") } if _, err := uuid.FromString(input.UUID); 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"` }) (*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 != "" { if _, err := uuid.FromString(input.Body.UUID); 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, ImageURL: input.Body.ImageURL, } if err := a.knowledgeHubRepo.CreateOrUpdateArticle(ctx, article); 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"` }) (*GetTableOfContentsOutput, error) { resp := &GetTableOfContentsOutput{} if input.UUID == "" { return nil, huma.Error400BadRequest("UUID parameter is required") } if _, err := uuid.FromString(input.UUID); 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"` }) (*GetRelatedArticlesOutput, error) { resp := &GetRelatedArticlesOutput{} if input.UUID == "" { return nil, huma.Error400BadRequest("UUID parameter is required") } if _, err := uuid.FromString(input.UUID); 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 } func (a *api) createRelatedArticleHandler( ctx context.Context, input *CreateRelatedArticleInput, ) (*struct{}, error) { // Validate main article exists if _, err := a.knowledgeHubRepo.GetArticleByID(ctx, input.UUID); err != nil { return nil, huma.Error404NotFound("main article not found") } // Create related article related := &domain.RelatedArticle{ RelatedTitle: input.Body.RelatedTitle, RelatedTag: input.Body.RelatedTag, } if err := a.knowledgeHubRepo.CreateRelatedArticle(ctx, input.UUID, related); err != nil { return nil, huma.Error500InternalServerError("failed to create related article") } return nil, nil }