feat: update article and stats

This commit is contained in:
hanif salafi 2025-02-15 08:56:13 +07:00
parent 62223041c1
commit f8a6136b95
15 changed files with 268 additions and 21 deletions

View File

@ -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()"`
}

View File

@ -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"`

View File

@ -25,11 +25,6 @@ var activityLogTypes = []entity.ActivityLogTypes{
},
{
ID: 4,
Name: "Like",
IsActive: true,
},
{
ID: 5,
Name: "Comment",
IsActive: true,
},

View File

@ -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"`
}

View File

@ -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"`
}

View File

@ -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"
@ -16,6 +17,7 @@ import (
type activityLogsService struct {
Repo repository.ActivityLogsRepository
UsersRepo usersRepository.UsersRepository
ArticleService service.ArticlesService
Log zerolog.Logger
}
@ -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,
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) {

View File

@ -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)
})
}

View File

@ -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 <Add access token here>)
// @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,
})
}

View File

@ -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,

View File

@ -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
}

View File

@ -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"`
}

View File

@ -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, ".")

View File

@ -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": [

View File

@ -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": [

View File

@ -2354,6 +2354,38 @@ paths:
summary: Update Articles
tags:
- Articles
/articles/statistic/summary:
get:
description: API for Summary Stats of Article
parameters:
- default: Bearer <Add access token here>
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