From 5eec21a2b11419eccf3ddaf1f3b59501dba06d6d Mon Sep 17 00:00:00 2001 From: Buravit Yenjit Date: Thu, 3 Apr 2025 23:36:17 +0700 Subject: [PATCH] feat: add image_url attribute and handle method --- backend/internal/api/knowledgeHub.go | 29 +++++++++---------- backend/internal/domain/knowledgeHub.go | 13 +++++++++ .../repository/postgres_knowledgeHub.go | 23 ++++++++++----- .../00005_add_image_url_to_articles.sql | 7 +++++ 4 files changed, 48 insertions(+), 24 deletions(-) create mode 100644 backend/migrations/00005_add_image_url_to_articles.sql diff --git a/backend/internal/api/knowledgeHub.go b/backend/internal/api/knowledgeHub.go index f0eb603..8cb21f4 100644 --- a/backend/internal/api/knowledgeHub.go +++ b/backend/internal/api/knowledgeHub.go @@ -81,13 +81,14 @@ type GetKnowledgeArticleByIDOutput struct { type CreateOrUpdateKnowledgeArticleInput struct { Body struct { - UUID string `json:"uuid,omitempty"` // Optional for create, required for update + 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"` } @@ -130,7 +131,7 @@ func (a *api) getAllKnowledgeArticlesHandler(ctx context.Context, input *struct{ } func (a *api) getKnowledgeArticleByIDHandler(ctx context.Context, input *struct { - UUID string `path:"uuid" example:"550e8400-e29b-41d4-a716-446655440000"` + UUID string `path:"uuid"` }) (*GetKnowledgeArticleByIDOutput, error) { resp := &GetKnowledgeArticleByIDOutput{} @@ -138,8 +139,7 @@ func (a *api) getKnowledgeArticleByIDHandler(ctx context.Context, input *struct return nil, huma.Error400BadRequest("UUID parameter is required") } - _, err := uuid.FromString(input.UUID) - if err != nil { + if _, err := uuid.FromString(input.UUID); err != nil { return nil, huma.Error400BadRequest("invalid UUID format") } @@ -156,7 +156,7 @@ func (a *api) getKnowledgeArticleByIDHandler(ctx context.Context, input *struct } func (a *api) getKnowledgeArticlesByCategoryHandler(ctx context.Context, input *struct { - Category string `path:"category" example:"Sustainability"` + Category string `path:"category"` }) (*GetKnowledgeArticlesOutput, error) { resp := &GetKnowledgeArticlesOutput{} @@ -190,8 +190,7 @@ func (a *api) createOrUpdateKnowledgeArticleHandler(ctx context.Context, input * } if input.Body.UUID != "" { - _, err := uuid.FromString(input.Body.UUID) - if err != nil { + if _, err := uuid.FromString(input.Body.UUID); err != nil { return nil, huma.Error400BadRequest("invalid UUID format") } } @@ -204,10 +203,10 @@ func (a *api) createOrUpdateKnowledgeArticleHandler(ctx context.Context, input * PublishDate: input.Body.PublishDate, ReadTime: input.Body.ReadTime, Categories: input.Body.Categories, + ImageURL: input.Body.ImageURL, } - err := a.knowledgeHubRepo.CreateOrUpdateArticle(ctx, article) - if err != nil { + if err := a.knowledgeHubRepo.CreateOrUpdateArticle(ctx, article); err != nil { return nil, err } @@ -216,7 +215,7 @@ func (a *api) createOrUpdateKnowledgeArticleHandler(ctx context.Context, input * } func (a *api) getArticleTableOfContentsHandler(ctx context.Context, input *struct { - UUID string `path:"uuid" example:"550e8400-e29b-41d4-a716-446655440000"` + UUID string `path:"uuid"` }) (*GetTableOfContentsOutput, error) { resp := &GetTableOfContentsOutput{} @@ -224,8 +223,7 @@ func (a *api) getArticleTableOfContentsHandler(ctx context.Context, input *struc return nil, huma.Error400BadRequest("UUID parameter is required") } - _, err := uuid.FromString(input.UUID) - if err != nil { + if _, err := uuid.FromString(input.UUID); err != nil { return nil, huma.Error400BadRequest("invalid UUID format") } @@ -242,7 +240,7 @@ func (a *api) getArticleTableOfContentsHandler(ctx context.Context, input *struc } func (a *api) getArticleRelatedArticlesHandler(ctx context.Context, input *struct { - UUID string `path:"uuid" example:"550e8400-e29b-41d4-a716-446655440000"` + UUID string `path:"uuid"` }) (*GetRelatedArticlesOutput, error) { resp := &GetRelatedArticlesOutput{} @@ -250,8 +248,7 @@ func (a *api) getArticleRelatedArticlesHandler(ctx context.Context, input *struc return nil, huma.Error400BadRequest("UUID parameter is required") } - _, err := uuid.FromString(input.UUID) - if err != nil { + if _, err := uuid.FromString(input.UUID); err != nil { return nil, huma.Error400BadRequest("invalid UUID format") } @@ -286,5 +283,5 @@ func (a *api) createRelatedArticleHandler( return nil, huma.Error500InternalServerError("failed to create related article") } - return nil, nil // HTTP 204 No Content + return nil, nil } diff --git a/backend/internal/domain/knowledgeHub.go b/backend/internal/domain/knowledgeHub.go index 45b4b22..04ad316 100644 --- a/backend/internal/domain/knowledgeHub.go +++ b/backend/internal/domain/knowledgeHub.go @@ -2,6 +2,8 @@ package domain import ( "context" + "fmt" + "strings" "time" validation "github.com/go-ozzo/ozzo-validation/v4" @@ -15,6 +17,7 @@ type KnowledgeArticle struct { PublishDate time.Time ReadTime string Categories []string + ImageURL string CreatedAt time.Time UpdatedAt time.Time } @@ -25,6 +28,16 @@ func (k *KnowledgeArticle) Validate() error { validation.Field(&k.Content, validation.Required), validation.Field(&k.Author, validation.Required), validation.Field(&k.PublishDate, validation.Required), + validation.Field(&k.ImageURL, + validation.By(func(value interface{}) error { + if url, ok := value.(string); ok && url != "" { + if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") { + return fmt.Errorf("must be a valid URL starting with http:// or https://") + } + } + return nil + }), + ), ) } diff --git a/backend/internal/repository/postgres_knowledgeHub.go b/backend/internal/repository/postgres_knowledgeHub.go index 8e5018c..75925a1 100644 --- a/backend/internal/repository/postgres_knowledgeHub.go +++ b/backend/internal/repository/postgres_knowledgeHub.go @@ -34,6 +34,7 @@ func (p *postgresKnowledgeHubRepository) fetchArticles(ctx context.Context, quer &a.PublishDate, &a.ReadTime, &a.Categories, + &a.ImageURL, &a.CreatedAt, &a.UpdatedAt, ); err != nil { @@ -46,7 +47,7 @@ func (p *postgresKnowledgeHubRepository) fetchArticles(ctx context.Context, quer func (p *postgresKnowledgeHubRepository) GetArticleByID(ctx context.Context, uuid string) (domain.KnowledgeArticle, error) { query := ` - SELECT uuid, title, content, author, publish_date, read_time, categories, created_at, updated_at + SELECT uuid, title, content, author, publish_date, read_time, categories, image_url, created_at, updated_at FROM knowledge_articles WHERE uuid = $1` @@ -62,7 +63,7 @@ func (p *postgresKnowledgeHubRepository) GetArticleByID(ctx context.Context, uui func (p *postgresKnowledgeHubRepository) GetArticlesByCategory(ctx context.Context, category string) ([]domain.KnowledgeArticle, error) { query := ` - SELECT uuid, title, content, author, publish_date, read_time, categories, created_at, updated_at + SELECT uuid, title, content, author, publish_date, read_time, categories, image_url, created_at, updated_at FROM knowledge_articles WHERE $1 = ANY(categories)` @@ -71,20 +72,24 @@ func (p *postgresKnowledgeHubRepository) GetArticlesByCategory(ctx context.Conte func (p *postgresKnowledgeHubRepository) GetAllArticles(ctx context.Context) ([]domain.KnowledgeArticle, error) { query := ` - SELECT uuid, title, content, author, publish_date, read_time, categories, created_at, updated_at + SELECT uuid, title, content, author, publish_date, read_time, categories, image_url, created_at, updated_at FROM knowledge_articles` return p.fetchArticles(ctx, query) } -func (p *postgresKnowledgeHubRepository) CreateOrUpdateArticle(ctx context.Context, article *domain.KnowledgeArticle) error { +func (p *postgresKnowledgeHubRepository) CreateOrUpdateArticle( + ctx context.Context, + article *domain.KnowledgeArticle, +) error { if strings.TrimSpace(article.UUID) == "" { article.UUID = uuid.New().String() } query := ` - INSERT INTO knowledge_articles (uuid, title, content, author, publish_date, read_time, categories, created_at, updated_at) - VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), NOW()) + INSERT INTO knowledge_articles + (uuid, title, content, author, publish_date, read_time, categories, image_url, created_at, updated_at) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, NOW(), NOW()) ON CONFLICT (uuid) DO UPDATE SET title = EXCLUDED.title, content = EXCLUDED.content, @@ -92,6 +97,7 @@ func (p *postgresKnowledgeHubRepository) CreateOrUpdateArticle(ctx context.Conte publish_date = EXCLUDED.publish_date, read_time = EXCLUDED.read_time, categories = EXCLUDED.categories, + image_url = EXCLUDED.image_url, updated_at = NOW() RETURNING uuid, created_at, updated_at` @@ -105,6 +111,7 @@ func (p *postgresKnowledgeHubRepository) CreateOrUpdateArticle(ctx context.Conte article.PublishDate, article.ReadTime, article.Categories, + article.ImageURL, ).Scan(&article.UUID, &article.CreatedAt, &article.UpdatedAt) } @@ -188,8 +195,8 @@ func (p *postgresKnowledgeHubRepository) CreateRelatedArticle( articleID string, related *domain.RelatedArticle, ) error { - related.UUID = uuid.New().String() // Generate UUID - related.ArticleID = articleID // Link to main article + related.UUID = uuid.New().String() + related.ArticleID = articleID query := ` INSERT INTO related_articles diff --git a/backend/migrations/00005_add_image_url_to_articles.sql b/backend/migrations/00005_add_image_url_to_articles.sql new file mode 100644 index 0000000..2361add --- /dev/null +++ b/backend/migrations/00005_add_image_url_to_articles.sql @@ -0,0 +1,7 @@ +-- +goose Up +ALTER TABLE knowledge_articles + ADD COLUMN image_url TEXT; + +-- +goose Down +ALTER TABLE knowledge_articles +DROP COLUMN image_url; \ No newline at end of file