From 4fb34ef46c85f0ff74bfbe3b3331670fd75533bb Mon Sep 17 00:00:00 2001 From: hanif salafi Date: Sat, 15 Feb 2025 08:56:13 +0700 Subject: [PATCH] feat: update article and stats --- app/database/entity/activity_logs.entity.go | 2 +- app/database/entity/articles.entity.go | 4 +- .../seeds/activity_log_types.seeds.go | 5 -- .../request/activity_logs.request.go | 4 +- .../response/activity_logs.response.go | 2 +- .../service/activity_logs.service.go | 30 ++++++++--- app/module/articles/articles.module.go | 1 + .../controller/articles.controller.go | 27 ++++++++++ app/module/articles/mapper/articles.mapper.go | 2 +- .../repository/articles.repository.go | 25 ++++++++++ .../articles/response/articles.response.go | 11 +++- .../articles/service/articles.service.go | 44 ++++++++++++++++ docs/swagger/docs.go | 50 +++++++++++++++++++ docs/swagger/swagger.json | 50 +++++++++++++++++++ docs/swagger/swagger.yaml | 32 ++++++++++++ 15 files changed, 268 insertions(+), 21 deletions(-) diff --git a/app/database/entity/activity_logs.entity.go b/app/database/entity/activity_logs.entity.go index 5a5bc95..3728d96 100644 --- a/app/database/entity/activity_logs.entity.go +++ b/app/database/entity/activity_logs.entity.go @@ -6,7 +6,7 @@ type ActivityLogs struct { ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` ActivityTypeId int `json:"activity_type_id" gorm:"type:int4"` Url string `json:"url" gorm:"type:varchar"` - ArticleId *int `json:"article_id" gorm:"type:int4"` + ArticleId *uint `json:"article_id" gorm:"type:int4"` UserId *uint `json:"user_id" gorm:"type:int4"` CreatedAt time.Time `json:"created_at" gorm:"default:now()"` } diff --git a/app/database/entity/articles.entity.go b/app/database/entity/articles.entity.go index d356175..ddd62ee 100644 --- a/app/database/entity/articles.entity.go +++ b/app/database/entity/articles.entity.go @@ -15,10 +15,10 @@ type Articles struct { ThumbnailPath *string `json:"thumbnail_path" gorm:"type:varchar"` PageUrl *string `json:"page_url" gorm:"type:varchar"` CreatedById *uint `json:"created_by_id" gorm:"type:int4"` + AiArticleId *int `json:"ai_article_id" gorm:"type:int4"` + CommentCount *int `json:"comment_count" gorm:"type:int4"` ShareCount *int `json:"share_count" gorm:"type:int4"` ViewCount *int `json:"view_count" gorm:"type:int4"` - AiArticleId *int `json:"ai_article_id" gorm:"type:int4"` - DownloadCount *int `json:"download_count" gorm:"type:int4"` StatusId *int `json:"status_id" gorm:"type:int4"` OldId *uint `json:"old_id" gorm:"type:int4"` IsPublish *bool `json:"is_publish" gorm:"type:bool;default:false"` diff --git a/app/database/seeds/activity_log_types.seeds.go b/app/database/seeds/activity_log_types.seeds.go index ec7c294..3d154b7 100644 --- a/app/database/seeds/activity_log_types.seeds.go +++ b/app/database/seeds/activity_log_types.seeds.go @@ -25,11 +25,6 @@ var activityLogTypes = []entity.ActivityLogTypes{ }, { ID: 4, - Name: "Like", - IsActive: true, - }, - { - ID: 5, Name: "Comment", IsActive: true, }, diff --git a/app/module/activity_logs/request/activity_logs.request.go b/app/module/activity_logs/request/activity_logs.request.go index a3fbb7a..1546a74 100644 --- a/app/module/activity_logs/request/activity_logs.request.go +++ b/app/module/activity_logs/request/activity_logs.request.go @@ -22,7 +22,7 @@ type ActivityLogsQueryRequest struct { type ActivityLogsCreateRequest struct { ActivityTypeId int `json:"activityTypeId" validate:"required"` Url string `json:"url" validate:"required"` - ArticleId *int `json:"articleId"` + ArticleId *uint `json:"articleId"` UserId *uint `json:"userId"` } @@ -40,7 +40,7 @@ type ActivityLogsUpdateRequest struct { ID uint `json:"id" validate:"required"` ActivityTypeId int `json:"activityTypeId" validate:"required"` Url string `json:"url" validate:"required"` - ArticleId *int `json:"articleId"` + ArticleId *uint `json:"articleId"` UserId *uint `json:"userId"` } diff --git a/app/module/activity_logs/response/activity_logs.response.go b/app/module/activity_logs/response/activity_logs.response.go index 201d002..7cee225 100644 --- a/app/module/activity_logs/response/activity_logs.response.go +++ b/app/module/activity_logs/response/activity_logs.response.go @@ -6,7 +6,7 @@ type ActivityLogsResponse struct { ID uint `json:"id"` ActivityTypeId int `json:"activityTypeId"` Url string `json:"url"` - ArticleId *int `json:"articleId"` + ArticleId *uint `json:"articleId"` UserId *uint `json:"userId"` CreatedAt time.Time `json:"createdAt"` } diff --git a/app/module/activity_logs/service/activity_logs.service.go b/app/module/activity_logs/service/activity_logs.service.go index a14d02c..ad32d0f 100644 --- a/app/module/activity_logs/service/activity_logs.service.go +++ b/app/module/activity_logs/service/activity_logs.service.go @@ -7,6 +7,7 @@ import ( "go-humas-be/app/module/activity_logs/repository" "go-humas-be/app/module/activity_logs/request" "go-humas-be/app/module/activity_logs/response" + "go-humas-be/app/module/articles/service" usersRepository "go-humas-be/app/module/users/repository" "go-humas-be/utils/paginator" utilSvc "go-humas-be/utils/service" @@ -14,9 +15,10 @@ import ( // ActivityLogsService type activityLogsService struct { - Repo repository.ActivityLogsRepository - UsersRepo usersRepository.UsersRepository - Log zerolog.Logger + Repo repository.ActivityLogsRepository + UsersRepo usersRepository.UsersRepository + ArticleService service.ArticlesService + Log zerolog.Logger } // ActivityLogsService define interface of IActivityLogsService @@ -29,12 +31,13 @@ type ActivityLogsService interface { } // NewActivityLogsService init ActivityLogsService -func NewActivityLogsService(repo repository.ActivityLogsRepository, log zerolog.Logger, usersRepo usersRepository.UsersRepository) ActivityLogsService { +func NewActivityLogsService(repo repository.ActivityLogsRepository, log zerolog.Logger, usersRepo usersRepository.UsersRepository, articleService service.ArticlesService) ActivityLogsService { return &activityLogsService{ - Repo: repo, - Log: log, - UsersRepo: usersRepo, + Repo: repo, + Log: log, + UsersRepo: usersRepo, + ArticleService: articleService, } } @@ -71,7 +74,18 @@ func (_i *activityLogsService) Save(req request.ActivityLogsCreateRequest, authT newReq.UserId = &createdBy.ID } - return _i.Repo.Create(newReq) + result, err := _i.Repo.Create(newReq) + if err != nil { + return nil, err + } + + // update article + err = _i.ArticleService.UpdateActivityCount(*req.ArticleId, req.ActivityTypeId) + if err != nil { + return nil, err + } + + return result, nil } func (_i *activityLogsService) Update(id uint, req request.ActivityLogsUpdateRequest) (err error) { diff --git a/app/module/articles/articles.module.go b/app/module/articles/articles.module.go index cef17a7..cf26b02 100644 --- a/app/module/articles/articles.module.go +++ b/app/module/articles/articles.module.go @@ -51,5 +51,6 @@ func (_i *ArticlesRouter) RegisterArticlesRoutes() { router.Post("/thumbnail/:id", articlesController.SaveThumbnail) router.Get("/thumbnail/viewer/:thumbnailName", articlesController.Viewer) router.Delete("/:id", articlesController.Delete) + router.Get("/statistic/summary", articlesController.SummaryStats) }) } diff --git a/app/module/articles/controller/articles.controller.go b/app/module/articles/controller/articles.controller.go index 3903e73..3a75da1 100644 --- a/app/module/articles/controller/articles.controller.go +++ b/app/module/articles/controller/articles.controller.go @@ -23,6 +23,7 @@ type ArticlesController interface { Update(c *fiber.Ctx) error Delete(c *fiber.Ctx) error Viewer(c *fiber.Ctx) error + SummaryStats(c *fiber.Ctx) error } func NewArticlesController(articlesService service.ArticlesService) ArticlesController { @@ -237,3 +238,29 @@ func (_i *articlesController) Delete(c *fiber.Ctx) error { func (_i *articlesController) Viewer(c *fiber.Ctx) error { return _i.articlesService.Viewer(c) } + +// SummaryStats Articles +// @Summary SummaryStats Articles +// @Description API for Summary Stats of Article +// @Tags Articles +// @Security Bearer +// @Param Authorization header string true "Insert your access token" default(Bearer ) +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /articles/statistic/summary [get] +func (_i *articlesController) SummaryStats(c *fiber.Ctx) error { + authToken := c.Get("Authorization") + + response, err := _i.articlesService.SummaryStats(authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Summary Stats of Articles successfully retrieved"}, + Data: response, + }) +} diff --git a/app/module/articles/mapper/articles.mapper.go b/app/module/articles/mapper/articles.mapper.go index c3cfcf8..9c7dbc1 100644 --- a/app/module/articles/mapper/articles.mapper.go +++ b/app/module/articles/mapper/articles.mapper.go @@ -67,7 +67,7 @@ func ArticlesResponseMapper( CreatedByName: &createdByName, ShareCount: articlesReq.ShareCount, ViewCount: articlesReq.ViewCount, - DownloadCount: articlesReq.DownloadCount, + CommentCount: articlesReq.CommentCount, StatusId: articlesReq.StatusId, IsPublish: articlesReq.IsPublish, PublishedAt: articlesReq.PublishedAt, diff --git a/app/module/articles/repository/articles.repository.go b/app/module/articles/repository/articles.repository.go index a91838a..7f5af64 100644 --- a/app/module/articles/repository/articles.repository.go +++ b/app/module/articles/repository/articles.repository.go @@ -6,8 +6,10 @@ import ( "go-humas-be/app/database" "go-humas-be/app/database/entity" "go-humas-be/app/module/articles/request" + "go-humas-be/app/module/articles/response" "go-humas-be/utils/paginator" "strings" + "time" ) type articlesRepository struct { @@ -23,6 +25,7 @@ type ArticlesRepository interface { Create(articles *entity.Articles) (articleReturn *entity.Articles, err error) Update(id uint, articles *entity.Articles) (err error) Delete(id uint) (err error) + SummaryStats(userID uint) (articleSummaryStats *response.ArticleSummaryStats, err error) } func NewArticlesRepository(db *database.Database, log zerolog.Logger) ArticlesRepository { @@ -128,3 +131,25 @@ func (_i *articlesRepository) Update(id uint, articles *entity.Articles) (err er func (_i *articlesRepository) Delete(id uint) error { return _i.DB.DB.Delete(&entity.Articles{}, id).Error } + +func (_i *articlesRepository) SummaryStats(userID uint) (articleSummaryStats *response.ArticleSummaryStats, err error) { + now := time.Now() + startOfDay := now.Truncate(24 * time.Hour) + startOfWeek := now.AddDate(0, 0, -int(now.Weekday())+1).Truncate(24 * time.Hour) + + // Query + err = _i.DB.DB.Model(&entity.Articles{}). + Select( + "COUNT(*) AS total_all, "+ + "COALESCE(SUM(view_count), 0) AS total_views, "+ + "COALESCE(SUM(share_count), 0) AS total_shares, "+ + "COALESCE(SUM(comment_count), 0) AS total_comments, "+ + "COUNT(CASE WHEN created_at >= ? THEN 1 END) AS total_today, "+ + "COUNT(CASE WHEN created_at >= ? THEN 1 END) AS total_this_week", + startOfDay, startOfWeek, + ). + Where("created_by_id = ?", userID). + Scan(&articleSummaryStats).Error + + return articleSummaryStats, err +} diff --git a/app/module/articles/response/articles.response.go b/app/module/articles/response/articles.response.go index 702f057..e3f5a52 100644 --- a/app/module/articles/response/articles.response.go +++ b/app/module/articles/response/articles.response.go @@ -22,7 +22,7 @@ type ArticlesResponse struct { CreatedByName *string `json:"createdByName"` ShareCount *int `json:"shareCount"` ViewCount *int `json:"viewCount"` - DownloadCount *int `json:"downloadCount"` + CommentCount *int `json:"commentCount"` AiArticleId *int `json:"aiArticleId"` StatusId *int `json:"statusId"` IsPublish *bool `json:"isPublish"` @@ -34,3 +34,12 @@ type ArticlesResponse struct { ArticleFiles []*articleFilesResponse.ArticleFilesResponse `json:"files"` ArticleCategories []*articleCategoriesResponse.ArticleCategoriesResponse `json:"categories"` } + +type ArticleSummaryStats struct { + TotalToday int `json:"totalToday"` + TotalThisWeek int `json:"totalThisWeek"` + TotalAll int `json:"totalAll"` + TotalViews int `json:"totalViews"` + TotalShares int `json:"totalShares"` + TotalComments int `json:"totalComments"` +} diff --git a/app/module/articles/service/articles.service.go b/app/module/articles/service/articles.service.go index 9fd1e74..d760e58 100644 --- a/app/module/articles/service/articles.service.go +++ b/app/module/articles/service/articles.service.go @@ -51,7 +51,9 @@ type ArticlesService interface { SaveThumbnail(c *fiber.Ctx) (err error) Update(id uint, req request.ArticlesUpdateRequest) (err error) Delete(id uint) error + UpdateActivityCount(id uint, activityTypeId int) (err error) Viewer(c *fiber.Ctx) error + SummaryStats(authToken string) (summaryStats *response.ArticleSummaryStats, err error) } // NewArticlesService init ArticlesService @@ -356,6 +358,48 @@ func (_i *articlesService) Viewer(c *fiber.Ctx) (err error) { return } +func (_i *articlesService) UpdateActivityCount(id uint, activityTypeId int) error { + result, err := _i.Repo.FindOne(id) + if err != nil { + return err + } + + viewCount := 0 + if result.ViewCount != nil { + viewCount = *result.ViewCount + } + shareCount := 0 + if result.ShareCount != nil { + shareCount = *result.ShareCount + } + commentCount := 0 + if result.CommentCount != nil { + commentCount = *result.CommentCount + } + + if activityTypeId == 2 { + viewCount++ + } else if activityTypeId == 3 { + shareCount++ + } else if activityTypeId == 4 { + commentCount++ + } + result.ViewCount = &viewCount + result.ShareCount = &shareCount + result.CommentCount = &commentCount + return _i.Repo.Update(id, result) +} + +func (_i *articlesService) SummaryStats(authToken string) (summaryStats *response.ArticleSummaryStats, err error) { + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + + result, err := _i.Repo.SummaryStats(user.ID) + if err != nil { + return nil, err + } + return result, nil +} + func getFileExtension(filename string) string { // split file name parts := strings.Split(filename, ".") diff --git a/docs/swagger/docs.go b/docs/swagger/docs.go index ddb2b6f..57d94ff 100644 --- a/docs/swagger/docs.go +++ b/docs/swagger/docs.go @@ -2339,6 +2339,56 @@ const docTemplate = `{ } } }, + "/articles/statistic/summary": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Summary Stats of Article", + "tags": [ + "Articles" + ], + "summary": "SummaryStats Articles", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, "/articles/thumbnail/viewer/{thumbnailName}": { "get": { "security": [ diff --git a/docs/swagger/swagger.json b/docs/swagger/swagger.json index 482c16f..931df61 100644 --- a/docs/swagger/swagger.json +++ b/docs/swagger/swagger.json @@ -2328,6 +2328,56 @@ } } }, + "/articles/statistic/summary": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Summary Stats of Article", + "tags": [ + "Articles" + ], + "summary": "SummaryStats Articles", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, "/articles/thumbnail/viewer/{thumbnailName}": { "get": { "security": [ diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml index f03fb06..b752561 100644 --- a/docs/swagger/swagger.yaml +++ b/docs/swagger/swagger.yaml @@ -2354,6 +2354,38 @@ paths: summary: Update Articles tags: - Articles + /articles/statistic/summary: + get: + description: API for Summary Stats of Article + parameters: + - default: Bearer + description: Insert your access token + in: header + name: Authorization + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: SummaryStats Articles + tags: + - Articles /articles/thumbnail/{id}: post: description: API for Save Thumbnail of Articles