feat: add article stats

This commit is contained in:
hanif salafi 2025-02-15 17:23:39 +07:00
parent f8a6136b95
commit a4a86d5fb6
19 changed files with 664 additions and 64 deletions

View File

@ -16,9 +16,9 @@ type Articles struct {
PageUrl *string `json:"page_url" gorm:"type:varchar"` PageUrl *string `json:"page_url" gorm:"type:varchar"`
CreatedById *uint `json:"created_by_id" gorm:"type:int4"` CreatedById *uint `json:"created_by_id" gorm:"type:int4"`
AiArticleId *int `json:"ai_article_id" gorm:"type:int4"` AiArticleId *int `json:"ai_article_id" gorm:"type:int4"`
CommentCount *int `json:"comment_count" gorm:"type:int4"` CommentCount *int `json:"comment_count" gorm:"type:int4;default:0"`
ShareCount *int `json:"share_count" gorm:"type:int4"` ShareCount *int `json:"share_count" gorm:"type:int4;default:0"`
ViewCount *int `json:"view_count" gorm:"type:int4"` ViewCount *int `json:"view_count" gorm:"type:int4;default:0"`
StatusId *int `json:"status_id" gorm:"type:int4"` StatusId *int `json:"status_id" gorm:"type:int4"`
OldId *uint `json:"old_id" gorm:"type:int4"` OldId *uint `json:"old_id" gorm:"type:int4"`
IsPublish *bool `json:"is_publish" gorm:"type:bool;default:false"` IsPublish *bool `json:"is_publish" gorm:"type:bool;default:false"`

View File

@ -1,30 +0,0 @@
package entity
import "time"
type Users struct {
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
Username string `json:"username" gorm:"type:varchar"`
Email string `json:"email" gorm:"type:varchar"`
Fullname string `json:"fullname" gorm:"type:varchar"`
Address *string `json:"address" gorm:"type:varchar"`
PhoneNumber *string `json:"phone_number" gorm:"type:varchar"`
WorkType *string `json:"work_type" gorm:"type:varchar"`
GenderType *string `json:"gender_type" gorm:"type:varchar"`
IdentityType *string `json:"identity_type" gorm:"type:varchar"`
IdentityGroup *string `json:"identity_group" gorm:"type:varchar"`
IdentityGroupNumber *string `json:"identity_group_number" gorm:"type:varchar"`
IdentityNumber *string `json:"identity_number" gorm:"type:varchar"`
DateOfBirth *string `json:"date_of_birth" gorm:"type:varchar"`
LastEducation *string `json:"last_education" gorm:"type:varchar"`
UserRoleId uint `json:"user_role_id" gorm:"type:int4"`
UserLevelId uint `json:"user_level_id" gorm:"type:int4"`
KeycloakId *string `json:"keycloak_id" gorm:"type:varchar"`
StatusId *int `json:"status_id" gorm:"type:int4;default:1"`
CreatedById *uint `json:"created_by_id" gorm:"type:int4"`
ProfilePicturePath *string `json:"profile_picture_path" gorm:"type:varchar"`
TempPassword *string `json:"temp_password" gorm:"type:varchar"`
IsActive *bool `json:"is_active" gorm:"type:bool;default:true"`
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
}

View File

@ -0,0 +1,34 @@
package users
import (
"go-humas-be/app/database/entity"
"time"
)
type Users struct {
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
Username string `json:"username" gorm:"type:varchar"`
Email string `json:"email" gorm:"type:varchar"`
Fullname string `json:"fullname" gorm:"type:varchar"`
Address *string `json:"address" gorm:"type:varchar"`
PhoneNumber *string `json:"phone_number" gorm:"type:varchar"`
WorkType *string `json:"work_type" gorm:"type:varchar"`
GenderType *string `json:"gender_type" gorm:"type:varchar"`
IdentityType *string `json:"identity_type" gorm:"type:varchar"`
IdentityGroup *string `json:"identity_group" gorm:"type:varchar"`
IdentityGroupNumber *string `json:"identity_group_number" gorm:"type:varchar"`
IdentityNumber *string `json:"identity_number" gorm:"type:varchar"`
DateOfBirth *string `json:"date_of_birth" gorm:"type:varchar"`
LastEducation *string `json:"last_education" gorm:"type:varchar"`
UserRoleId uint `json:"user_role_id" gorm:"type:int4"`
UserLevelId uint `json:"user_level_id" gorm:"type:int4"`
UserLevel *entity.UserLevels `json:"user_levels" gorm:"foreignKey:UserLevelId;references:ID"`
KeycloakId *string `json:"keycloak_id" gorm:"type:varchar"`
StatusId *int `json:"status_id" gorm:"type:int4;default:1"`
CreatedById *uint `json:"created_by_id" gorm:"type:int4"`
ProfilePicturePath *string `json:"profile_picture_path" gorm:"type:varchar"`
TempPassword *string `json:"temp_password" gorm:"type:varchar"`
IsActive *bool `json:"is_active" gorm:"type:bool;default:true"`
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
}

View File

@ -4,9 +4,11 @@ import (
"github.com/rs/zerolog" "github.com/rs/zerolog"
"go-humas-be/app/database/entity" "go-humas-be/app/database/entity"
"go-humas-be/app/database/entity/article_category_details" "go-humas-be/app/database/entity/article_category_details"
"go-humas-be/app/database/entity/users"
"go-humas-be/config/config" "go-humas-be/config/config"
"gorm.io/driver/postgres" "gorm.io/driver/postgres"
"gorm.io/gorm" "gorm.io/gorm"
"gorm.io/gorm/logger"
) )
// Database setup database with gorm // Database setup database with gorm
@ -32,7 +34,20 @@ func NewDatabase(cfg *config.Config, log zerolog.Logger) *Database {
// ConnectDatabase connect database // ConnectDatabase connect database
func (_db *Database) ConnectDatabase() { func (_db *Database) ConnectDatabase() {
conn, err := gorm.Open(postgres.Open(_db.Cfg.DB.Postgres.DSN), &gorm.Config{}) logMode := _db.Cfg.DB.Postgres.LogMode
var logLevel logger.LogLevel
if logMode == "INFO" {
logLevel = logger.Info
} else if logMode == "WARN" {
logLevel = logger.Warn
} else if logMode == "ERROR" {
logLevel = logger.Error
} else if logMode == "NONE" {
logLevel = logger.Silent
}
conn, err := gorm.Open(postgres.Open(_db.Cfg.DB.Postgres.DSN), &gorm.Config{
Logger: logger.Default.LogMode(logLevel),
})
if err != nil { if err != nil {
_db.Log.Error().Err(err).Msg("An unknown error occurred when to connect the database!") _db.Log.Error().Err(err).Msg("An unknown error occurred when to connect the database!")
} else { } else {
@ -91,7 +106,7 @@ func Models() []interface{} {
entity.UserLevels{}, entity.UserLevels{},
entity.UserRoles{}, entity.UserRoles{},
entity.UserRoleAccesses{}, entity.UserRoleAccesses{},
entity.Users{}, users.Users{},
entity.UserRoleLevelDetails{}, entity.UserRoleLevelDetails{},
} }
} }

View File

@ -52,5 +52,7 @@ func (_i *ArticlesRouter) RegisterArticlesRoutes() {
router.Get("/thumbnail/viewer/:thumbnailName", articlesController.Viewer) router.Get("/thumbnail/viewer/:thumbnailName", articlesController.Viewer)
router.Delete("/:id", articlesController.Delete) router.Delete("/:id", articlesController.Delete)
router.Get("/statistic/summary", articlesController.SummaryStats) router.Get("/statistic/summary", articlesController.SummaryStats)
router.Get("/statistic/user-levels", articlesController.ArticlePerUserLevelStats)
router.Get("/statistic/monthly", articlesController.ArticleMonthlyStats)
}) })
} }

View File

@ -24,6 +24,8 @@ type ArticlesController interface {
Delete(c *fiber.Ctx) error Delete(c *fiber.Ctx) error
Viewer(c *fiber.Ctx) error Viewer(c *fiber.Ctx) error
SummaryStats(c *fiber.Ctx) error SummaryStats(c *fiber.Ctx) error
ArticlePerUserLevelStats(c *fiber.Ctx) error
ArticleMonthlyStats(c *fiber.Ctx) error
} }
func NewArticlesController(articlesService service.ArticlesService) ArticlesController { func NewArticlesController(articlesService service.ArticlesService) ArticlesController {
@ -264,3 +266,65 @@ func (_i *articlesController) SummaryStats(c *fiber.Ctx) error {
Data: response, Data: response,
}) })
} }
// ArticlePerUserLevelStats Articles
// @Summary ArticlePerUserLevelStats Articles
// @Description API for ArticlePerUserLevelStats of Article
// @Tags Articles
// @Security Bearer
// @Param Authorization header string true "Insert your access token" default(Bearer <Add access token here>)
// @Param startDate query string false "start date"
// @Param endDate query string false "start date"
// @Success 200 {object} response.Response
// @Failure 400 {object} response.BadRequestError
// @Failure 401 {object} response.UnauthorizedError
// @Failure 500 {object} response.InternalServerError
// @Router /articles/statistic/user-levels [get]
func (_i *articlesController) ArticlePerUserLevelStats(c *fiber.Ctx) error {
authToken := c.Get("Authorization")
startDate := c.Query("startDate")
endDate := c.Query("endDate")
response, err := _i.articlesService.ArticlePerUserLevelStats(authToken, &startDate, &endDate)
if err != nil {
return err
}
return utilRes.Resp(c, utilRes.Response{
Success: true,
Messages: utilRes.Messages{"ArticlePerUserLevelStats of Articles successfully retrieved"},
Data: response,
})
}
// ArticleMonthlyStats Articles
// @Summary ArticleMonthlyStats Articles
// @Description API for ArticleMonthlyStats of Article
// @Tags Articles
// @Security Bearer
// @Param Authorization header string true "Insert your access token" default(Bearer <Add access token here>)
// @Param year query int false "year"
// @Success 200 {object} response.Response
// @Failure 400 {object} response.BadRequestError
// @Failure 401 {object} response.UnauthorizedError
// @Failure 500 {object} response.InternalServerError
// @Router /articles/statistic/monthly [get]
func (_i *articlesController) ArticleMonthlyStats(c *fiber.Ctx) error {
authToken := c.Get("Authorization")
year := c.Query("year")
yearInt, err := strconv.Atoi(year)
if err != nil {
return err
}
response, err := _i.articlesService.ArticleMonthlyStats(authToken, &yearInt)
if err != nil {
return err
}
return utilRes.Resp(c, utilRes.Response{
Success: true,
Messages: utilRes.Messages{"ArticleMonthlyStats of Articles successfully retrieved"},
Data: response,
})
}

View File

@ -26,6 +26,8 @@ type ArticlesRepository interface {
Update(id uint, articles *entity.Articles) (err error) Update(id uint, articles *entity.Articles) (err error)
Delete(id uint) (err error) Delete(id uint) (err error)
SummaryStats(userID uint) (articleSummaryStats *response.ArticleSummaryStats, err error) SummaryStats(userID uint) (articleSummaryStats *response.ArticleSummaryStats, err error)
ArticlePerUserLevelStats(userLevelId *uint, levelNumber *int, startDate *time.Time, endDate *time.Time) (articlePerUserLevelStats []*response.ArticlePerUserLevelStats, err error)
ArticleMonthlyStats(userLevelId *uint, levelNumber *int, year int) (articleMontlyStats []*response.ArticleMonthlyStats, err error)
} }
func NewArticlesRepository(db *database.Database, log zerolog.Logger) ArticlesRepository { func NewArticlesRepository(db *database.Database, log zerolog.Logger) ArticlesRepository {
@ -153,3 +155,134 @@ func (_i *articlesRepository) SummaryStats(userID uint) (articleSummaryStats *re
return articleSummaryStats, err return articleSummaryStats, err
} }
func (_i *articlesRepository) ArticlePerUserLevelStats(userLevelId *uint, levelNumber *int, startDate *time.Time, endDate *time.Time) (articlePerUserLevelStats []*response.ArticlePerUserLevelStats, err error) {
levelNumberTop := 1
query := _i.DB.DB.Model(&entity.Articles{}).
Select("user_levels.id as user_level_id", "user_levels.name as user_level_name", "COUNT(articles.id) as total_article").
Joins("LEFT JOIN users ON articles.created_by_id = users.id").
Joins("LEFT JOIN user_levels ON users.user_level_id = user_levels.id").
Where("articles.is_active = true")
if userLevelId != nil && *levelNumber != levelNumberTop {
query = query.Where("user_levels.id = ? or user_levels.parent_level_id = ?", *userLevelId, *userLevelId)
} else {
query = _i.DB.DB.Raw(`
WITH LevelHierarchy AS (
SELECT
id,
name,
level_number,
parent_level_id,
CASE
WHEN level_number = 2 THEN id
WHEN level_number = 3 THEN parent_level_id
END AS level_2_id,
CASE
WHEN level_number = 2 THEN name
WHEN level_number = 3 THEN (SELECT name FROM user_levels ul2 WHERE ul2.id = user_levels.parent_level_id)
END AS level_2_name
FROM user_levels
)
SELECT
lh.level_2_id AS user_level_id,
lh.level_2_name AS user_level_name,
COUNT(articles.id) AS total_article
FROM articles
JOIN users ON articles.created_by_id = users.id
JOIN LevelHierarchy lh ON users.user_level_id = lh.id
WHERE articles.is_active = true AND lh.level_2_id > 0
GROUP BY
lh.level_2_id,
lh.level_2_name
ORDER BY
total_article DESC`)
}
// Apply date filters if provided
if startDate != nil {
query = query.Where("articles.created_at >= ?", *startDate)
}
if endDate != nil {
query = query.Where("articles.created_at <= ?", *endDate)
}
// Group by all non-aggregated columns
err = query.Group("user_levels.id, user_levels.name").
Order("total_article DESC").
Scan(&articlePerUserLevelStats).Error
return articlePerUserLevelStats, err
}
func (_i *articlesRepository) ArticleMonthlyStats(userLevelId *uint, levelNumber *int, year int) (articleMontlyStats []*response.ArticleMonthlyStats, err error) {
levelNumberTop := 1
if year < 1900 || year > 2100 {
return nil, fmt.Errorf("invalid year")
}
var results []struct {
Month int
Day int
TotalView int
TotalComment int
TotalShare int
}
query := _i.DB.DB.Model(&entity.Articles{}).
Select("EXTRACT(MONTH FROM created_at) as month, EXTRACT(DAY FROM created_at) as day, "+
"SUM(view_count) as total_view, "+
"SUM(comment_count) as total_comment, "+
"SUM(share_count) as total_share").
Where("EXTRACT(YEAR FROM created_at) = ?", year)
if userLevelId != nil && *levelNumber != levelNumberTop {
query = _i.DB.DB.Model(&entity.Articles{}).
Select("EXTRACT(MONTH FROM articles.created_at) as month, EXTRACT(DAY FROM articles.created_at) as day, "+
"SUM(articles.view_count) as total_view, "+
"SUM(articles.comment_count) as total_comment, "+
"SUM(articles.share_count) as total_share").
Joins("LEFT JOIN users ON articles.created_by_id = users.id").
Joins("LEFT JOIN user_levels ON users.user_level_id = user_levels.id").
Where("articles.is_active = true").
Where("EXTRACT(YEAR FROM articles.created_at) = ?", year).
Where("(user_levels.id = ? OR user_levels.parent_level_id = ?)", *userLevelId, *userLevelId)
}
err = query.Group("month, day").Scan(&results).Error
if err != nil {
return nil, err
}
// Siapkan struktur untuk menyimpan data bulanan
monthlyAnalytics := make([]*response.ArticleMonthlyStats, 12)
for i := 0; i < 12; i++ {
daysInMonth := time.Date(year, time.Month(i+1), 0, 0, 0, 0, 0, time.UTC).Day()
monthlyAnalytics[i] = &response.ArticleMonthlyStats{
Year: year,
Month: i + 1,
View: make([]int, daysInMonth),
Comment: make([]int, daysInMonth),
Share: make([]int, daysInMonth),
}
}
// Isi data dari hasil agregasi
for _, result := range results {
monthIndex := result.Month - 1
dayIndex := result.Day - 1
if monthIndex >= 0 && monthIndex < 12 {
if dayIndex >= 0 && dayIndex < len(monthlyAnalytics[monthIndex].View) {
monthlyAnalytics[monthIndex].View[dayIndex] = result.TotalView
monthlyAnalytics[monthIndex].Comment[dayIndex] = result.TotalComment
monthlyAnalytics[monthIndex].Share[dayIndex] = result.TotalShare
}
}
}
return monthlyAnalytics, nil
}

View File

@ -43,3 +43,17 @@ type ArticleSummaryStats struct {
TotalShares int `json:"totalShares"` TotalShares int `json:"totalShares"`
TotalComments int `json:"totalComments"` TotalComments int `json:"totalComments"`
} }
type ArticlePerUserLevelStats struct {
UserLevelID uint `json:"userLevelId"`
UserLevelName string `json:"userLevelName"`
TotalArticle int64 `json:"totalArticle"`
}
type ArticleMonthlyStats struct {
Year int `json:"year"`
Month int `json:"month"`
View []int `json:"view"`
Comment []int `json:"comment"`
Share []int `json:"share"`
}

View File

@ -54,6 +54,8 @@ type ArticlesService interface {
UpdateActivityCount(id uint, activityTypeId int) (err error) UpdateActivityCount(id uint, activityTypeId int) (err error)
Viewer(c *fiber.Ctx) error Viewer(c *fiber.Ctx) error
SummaryStats(authToken string) (summaryStats *response.ArticleSummaryStats, err error) SummaryStats(authToken string) (summaryStats *response.ArticleSummaryStats, err error)
ArticlePerUserLevelStats(authToken string, startDate *string, endDate *string) (articlePerUserLevelStats []*response.ArticlePerUserLevelStats, err error)
ArticleMonthlyStats(authToken string, year *int) (articleMonthlyStats []*response.ArticleMonthlyStats, err error)
} }
// NewArticlesService init ArticlesService // NewArticlesService init ArticlesService
@ -400,6 +402,56 @@ func (_i *articlesService) SummaryStats(authToken string) (summaryStats *respons
return result, nil return result, nil
} }
func (_i *articlesService) ArticlePerUserLevelStats(authToken string, startDate *string, endDate *string) (articlePerUserLevelStats []*response.ArticlePerUserLevelStats, err error) {
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
_i.Log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "articlesService:ArticlePerUserLevelStats").
Interface("startDate", startDate).Msg("")
_i.Log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "articlesService:ArticlePerUserLevelStats").
Interface("endDate", endDate).Msg("")
var userLevelId *uint
var userLevelNumber *int
if user != nil {
userLevelId = &user.UserLevelId
userLevelNumber = &user.UserLevel.LevelNumber
}
_i.Log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "articlesService:ArticlePerUserLevelStats").
Interface("userLevelId", userLevelId).Msg("")
_i.Log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "articlesService:ArticlePerUserLevelStats").
Interface("userLevelNumber", userLevelNumber).Msg("")
result, err := _i.Repo.ArticlePerUserLevelStats(userLevelId, userLevelNumber, nil, nil)
if err != nil {
return nil, err
}
return result, nil
}
func (_i *articlesService) ArticleMonthlyStats(authToken string, year *int) (articleMonthlyStats []*response.ArticleMonthlyStats, err error) {
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
var userLevelId *uint
var userLevelNumber *int
if user != nil {
userLevelId = &user.UserLevelId
userLevelNumber = &user.UserLevel.LevelNumber
}
result, err := _i.Repo.ArticleMonthlyStats(userLevelId, userLevelNumber, *year)
if err != nil {
return nil, err
}
return result, nil
}
func getFileExtension(filename string) string { func getFileExtension(filename string) string {
// split file name // split file name
parts := strings.Split(filename, ".") parts := strings.Split(filename, ".")

View File

@ -1,12 +1,12 @@
package mapper package mapper
import ( import (
"go-humas-be/app/database/entity" "go-humas-be/app/database/entity/users"
userLevelsRepository "go-humas-be/app/module/user_levels/repository" userLevelsRepository "go-humas-be/app/module/user_levels/repository"
res "go-humas-be/app/module/users/response" res "go-humas-be/app/module/users/response"
) )
func UsersResponseMapper(usersReq *entity.Users, userLevelsRepo userLevelsRepository.UserLevelsRepository) (usersRes *res.UsersResponse) { func UsersResponseMapper(usersReq *users.Users, userLevelsRepo userLevelsRepository.UserLevelsRepository) (usersRes *res.UsersResponse) {
if usersReq != nil { if usersReq != nil {
findUserLevel, _ := userLevelsRepo.FindOne(usersReq.UserLevelId) findUserLevel, _ := userLevelsRepo.FindOne(usersReq.UserLevelId)
userLevelGroup := "" userLevelGroup := ""

View File

@ -5,6 +5,7 @@ import (
"github.com/rs/zerolog" "github.com/rs/zerolog"
"go-humas-be/app/database" "go-humas-be/app/database"
"go-humas-be/app/database/entity" "go-humas-be/app/database/entity"
"go-humas-be/app/database/entity/users"
"go-humas-be/app/module/users/request" "go-humas-be/app/module/users/request"
"go-humas-be/utils/paginator" "go-humas-be/utils/paginator"
"strings" "strings"
@ -17,12 +18,12 @@ type usersRepository struct {
// UsersRepository define interface of IUsersRepository // UsersRepository define interface of IUsersRepository
type UsersRepository interface { type UsersRepository interface {
GetAll(req request.UsersQueryRequest) (userss []*entity.Users, paging paginator.Pagination, err error) GetAll(req request.UsersQueryRequest) (userss []*users.Users, paging paginator.Pagination, err error)
FindOne(id uint) (users *entity.Users, err error) FindOne(id uint) (users *users.Users, err error)
FindByKeycloakId(keycloakId string) (users *entity.Users, err error) FindByKeycloakId(keycloakId string) (users *users.Users, err error)
FindByUsername(username string) (users *entity.Users, err error) FindByUsername(username string) (users *users.Users, err error)
Create(users *entity.Users) (userReturn *entity.Users, err error) Create(users *users.Users) (userReturn *users.Users, err error)
Update(id uint, users *entity.Users) (err error) Update(id uint, users *users.Users) (err error)
Delete(id uint) (err error) Delete(id uint) (err error)
CreateForgotPassword(forgotPasswords *entity.ForgotPasswords) (err error) CreateForgotPassword(forgotPasswords *entity.ForgotPasswords) (err error)
UpdateForgotPassword(id uint, forgotPasswords *entity.ForgotPasswords) (err error) UpdateForgotPassword(id uint, forgotPasswords *entity.ForgotPasswords) (err error)
@ -39,10 +40,10 @@ func NewUsersRepository(db *database.Database, log zerolog.Logger) UsersReposito
} }
// implement interface of IUsersRepository // implement interface of IUsersRepository
func (_i *usersRepository) GetAll(req request.UsersQueryRequest) (userss []*entity.Users, paging paginator.Pagination, err error) { func (_i *usersRepository) GetAll(req request.UsersQueryRequest) (userss []*users.Users, paging paginator.Pagination, err error) {
var count int64 var count int64
query := _i.DB.DB.Model(&entity.Users{}) query := _i.DB.DB.Model(&users.Users{})
query = query.Where("is_active = ?", true) query = query.Where("is_active = ?", true)
if req.Username != nil && *req.Username != "" { if req.Username != nil && *req.Username != "" {
@ -106,7 +107,7 @@ func (_i *usersRepository) GetAll(req request.UsersQueryRequest) (userss []*enti
return return
} }
func (_i *usersRepository) FindOne(id uint) (users *entity.Users, err error) { func (_i *usersRepository) FindOne(id uint) (users *users.Users, err error) {
if err := _i.DB.DB.First(&users, id).Error; err != nil { if err := _i.DB.DB.First(&users, id).Error; err != nil {
return nil, err return nil, err
} }
@ -114,15 +115,15 @@ func (_i *usersRepository) FindOne(id uint) (users *entity.Users, err error) {
return users, nil return users, nil
} }
func (_i *usersRepository) FindByKeycloakId(keycloakId string) (users *entity.Users, err error) { func (_i *usersRepository) FindByKeycloakId(keycloakId string) (users *users.Users, err error) {
if err := _i.DB.DB.Where("keycloak_id = ?", keycloakId).First(&users).Error; err != nil { if err := _i.DB.DB.Where("keycloak_id = ?", keycloakId).Preload("UserLevel").First(&users).Error; err != nil {
return nil, err return nil, err
} }
return users, nil return users, nil
} }
func (_i *usersRepository) FindByUsername(username string) (users *entity.Users, err error) { func (_i *usersRepository) FindByUsername(username string) (users *users.Users, err error) {
if err := _i.DB.DB.Where("username = ?", username).First(&users).Error; err != nil { if err := _i.DB.DB.Where("username = ?", username).First(&users).Error; err != nil {
return nil, err return nil, err
} }
@ -130,19 +131,19 @@ func (_i *usersRepository) FindByUsername(username string) (users *entity.Users,
return users, nil return users, nil
} }
func (_i *usersRepository) Create(users *entity.Users) (userReturn *entity.Users, err error) { func (_i *usersRepository) Create(users *users.Users) (userReturn *users.Users, err error) {
result := _i.DB.DB.Create(users) result := _i.DB.DB.Create(users)
return users, result.Error return users, result.Error
} }
func (_i *usersRepository) Update(id uint, users *entity.Users) (err error) { func (_i *usersRepository) Update(id uint, usersReturn *users.Users) (err error) {
return _i.DB.DB.Model(&entity.Users{}). return _i.DB.DB.Model(&users.Users{}).
Where(&entity.Users{ID: id}). Where(&users.Users{ID: id}).
Updates(users).Error Updates(usersReturn).Error
} }
func (_i *usersRepository) Delete(id uint) error { func (_i *usersRepository) Delete(id uint) error {
return _i.DB.DB.Delete(&entity.Users{}, id).Error return _i.DB.DB.Delete(&users.Users{}, id).Error
} }
func (_i *usersRepository) CreateForgotPassword(forgotPasswords *entity.ForgotPasswords) (err error) { func (_i *usersRepository) CreateForgotPassword(forgotPasswords *entity.ForgotPasswords) (err error) {

View File

@ -1,7 +1,7 @@
package request package request
import ( import (
"go-humas-be/app/database/entity" "go-humas-be/app/database/entity/users"
"go-humas-be/utils/paginator" "go-humas-be/utils/paginator"
"strconv" "strconv"
"time" "time"
@ -46,8 +46,8 @@ type UsersCreateRequest struct {
LastEducation *string `json:"lastEducation"` LastEducation *string `json:"lastEducation"`
} }
func (req UsersCreateRequest) ToEntity() *entity.Users { func (req UsersCreateRequest) ToEntity() *users.Users {
return &entity.Users{ return &users.Users{
Username: req.Username, Username: req.Username,
Email: req.Email, Email: req.Email,
Fullname: req.Fullname, Fullname: req.Fullname,
@ -85,8 +85,8 @@ type UsersUpdateRequest struct {
StatusId *int `json:"statusId"` StatusId *int `json:"statusId"`
} }
func (req UsersUpdateRequest) ToEntity() *entity.Users { func (req UsersUpdateRequest) ToEntity() *users.Users {
return &entity.Users{ return &users.Users{
Username: req.Username, Username: req.Username,
Email: req.Email, Email: req.Email,
Fullname: req.Fullname, Fullname: req.Fullname,

View File

@ -8,6 +8,7 @@ import (
"github.com/Nerzal/gocloak/v13" "github.com/Nerzal/gocloak/v13"
"github.com/rs/zerolog" "github.com/rs/zerolog"
"go-humas-be/app/database/entity" "go-humas-be/app/database/entity"
"go-humas-be/app/database/entity/users"
userLevelsRepository "go-humas-be/app/module/user_levels/repository" userLevelsRepository "go-humas-be/app/module/user_levels/repository"
"go-humas-be/app/module/users/mapper" "go-humas-be/app/module/users/mapper"
"go-humas-be/app/module/users/repository" "go-humas-be/app/module/users/repository"
@ -35,7 +36,7 @@ type UsersService interface {
Show(id uint) (users *response.UsersResponse, err error) Show(id uint) (users *response.UsersResponse, err error)
ShowByUsername(username string) (users *response.UsersResponse, err error) ShowByUsername(username string) (users *response.UsersResponse, err error)
ShowUserInfo(authToken string) (users *response.UsersResponse, err error) ShowUserInfo(authToken string) (users *response.UsersResponse, err error)
Save(req request.UsersCreateRequest, authToken string) (userReturn *entity.Users, err error) Save(req request.UsersCreateRequest, authToken string) (userReturn *users.Users, err error)
Login(req request.UserLogin) (res *gocloak.JWT, err error) Login(req request.UserLogin) (res *gocloak.JWT, err error)
ParetoLogin(req request.UserLogin) (res *response.ParetoLoginResponse, err error) ParetoLogin(req request.UserLogin) (res *response.ParetoLoginResponse, err error)
Update(id uint, req request.UsersUpdateRequest) (err error) Update(id uint, req request.UsersUpdateRequest) (err error)
@ -97,7 +98,7 @@ func (_i *usersService) ShowUserInfo(authToken string) (users *response.UsersRes
return mapper.UsersResponseMapper(userInfo, _i.UserLevelsRepo), nil return mapper.UsersResponseMapper(userInfo, _i.UserLevelsRepo), nil
} }
func (_i *usersService) Save(req request.UsersCreateRequest, authToken string) (userReturn *entity.Users, err error) { func (_i *usersService) Save(req request.UsersCreateRequest, authToken string) (userReturn *users.Users, err error) {
_i.Log.Info().Interface("data", req).Msg("") _i.Log.Info().Interface("data", req).Msg("")
newReq := req.ToEntity() newReq := req.ToEntity()

View File

@ -30,6 +30,7 @@ type app = struct {
type db = struct { type db = struct {
Postgres struct { Postgres struct {
DSN string `toml:"dsn"` DSN string `toml:"dsn"`
LogMode string `toml:"log-mode"`
Migrate bool `toml:"migrate"` Migrate bool `toml:"migrate"`
Seed bool `toml:"seed"` Seed bool `toml:"seed"`
} }

View File

@ -13,6 +13,7 @@ body-limit = 1048576000 # "100 * 1024 * 1024"
[db.postgres] [db.postgres]
dsn = "postgresql://humas_user:HumasDB@2024@38.47.180.165:5432/humas_db" # <driver>://<username>:<password>@<host>:<port>/<database> dsn = "postgresql://humas_user:HumasDB@2024@38.47.180.165:5432/humas_db" # <driver>://<username>:<password>@<host>:<port>/<database>
log-mode = "NONE"
migrate = false migrate = false
seed = false seed = false

View File

@ -2339,6 +2339,62 @@ const docTemplate = `{
} }
} }
}, },
"/articles/statistic/monthly": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "API for ArticleMonthlyStats of Article",
"tags": [
"Articles"
],
"summary": "ArticleMonthlyStats Articles",
"parameters": [
{
"type": "string",
"default": "Bearer \u003cAdd access token here\u003e",
"description": "Insert your access token",
"name": "Authorization",
"in": "header",
"required": true
},
{
"type": "integer",
"description": "year",
"name": "year",
"in": "query"
}
],
"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/statistic/summary": { "/articles/statistic/summary": {
"get": { "get": {
"security": [ "security": [
@ -2389,6 +2445,68 @@ const docTemplate = `{
} }
} }
}, },
"/articles/statistic/user-levels": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "API for ArticlePerUserLevelStats of Article",
"tags": [
"Articles"
],
"summary": "ArticlePerUserLevelStats Articles",
"parameters": [
{
"type": "string",
"default": "Bearer \u003cAdd access token here\u003e",
"description": "Insert your access token",
"name": "Authorization",
"in": "header",
"required": true
},
{
"type": "string",
"description": "start date",
"name": "startDate",
"in": "query"
},
{
"type": "string",
"description": "start date",
"name": "endDate",
"in": "query"
}
],
"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}": { "/articles/thumbnail/viewer/{thumbnailName}": {
"get": { "get": {
"security": [ "security": [

View File

@ -2328,6 +2328,62 @@
} }
} }
}, },
"/articles/statistic/monthly": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "API for ArticleMonthlyStats of Article",
"tags": [
"Articles"
],
"summary": "ArticleMonthlyStats Articles",
"parameters": [
{
"type": "string",
"default": "Bearer \u003cAdd access token here\u003e",
"description": "Insert your access token",
"name": "Authorization",
"in": "header",
"required": true
},
{
"type": "integer",
"description": "year",
"name": "year",
"in": "query"
}
],
"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/statistic/summary": { "/articles/statistic/summary": {
"get": { "get": {
"security": [ "security": [
@ -2378,6 +2434,68 @@
} }
} }
}, },
"/articles/statistic/user-levels": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "API for ArticlePerUserLevelStats of Article",
"tags": [
"Articles"
],
"summary": "ArticlePerUserLevelStats Articles",
"parameters": [
{
"type": "string",
"default": "Bearer \u003cAdd access token here\u003e",
"description": "Insert your access token",
"name": "Authorization",
"in": "header",
"required": true
},
{
"type": "string",
"description": "start date",
"name": "startDate",
"in": "query"
},
{
"type": "string",
"description": "start date",
"name": "endDate",
"in": "query"
}
],
"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}": { "/articles/thumbnail/viewer/{thumbnailName}": {
"get": { "get": {
"security": [ "security": [

View File

@ -2354,6 +2354,42 @@ paths:
summary: Update Articles summary: Update Articles
tags: tags:
- Articles - Articles
/articles/statistic/monthly:
get:
description: API for ArticleMonthlyStats of Article
parameters:
- default: Bearer <Add access token here>
description: Insert your access token
in: header
name: Authorization
required: true
type: string
- description: year
in: query
name: year
type: integer
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: ArticleMonthlyStats Articles
tags:
- Articles
/articles/statistic/summary: /articles/statistic/summary:
get: get:
description: API for Summary Stats of Article description: API for Summary Stats of Article
@ -2386,6 +2422,46 @@ paths:
summary: SummaryStats Articles summary: SummaryStats Articles
tags: tags:
- Articles - Articles
/articles/statistic/user-levels:
get:
description: API for ArticlePerUserLevelStats of Article
parameters:
- default: Bearer <Add access token here>
description: Insert your access token
in: header
name: Authorization
required: true
type: string
- description: start date
in: query
name: startDate
type: string
- description: start date
in: query
name: endDate
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: ArticlePerUserLevelStats Articles
tags:
- Articles
/articles/thumbnail/{id}: /articles/thumbnail/{id}:
post: post:
description: API for Save Thumbnail of Articles description: API for Save Thumbnail of Articles

View File

@ -3,13 +3,13 @@ package service
import ( import (
"github.com/golang-jwt/jwt/v5" "github.com/golang-jwt/jwt/v5"
"github.com/rs/zerolog" "github.com/rs/zerolog"
"go-humas-be/app/database/entity" "go-humas-be/app/database/entity/users"
"go-humas-be/app/module/users/repository" "go-humas-be/app/module/users/repository"
"strings" "strings"
"time" "time"
) )
func GetUserInfo(log zerolog.Logger, repo repository.UsersRepository, bearerToken string) *entity.Users { func GetUserInfo(log zerolog.Logger, repo repository.UsersRepository, bearerToken string) *users.Users {
tokenString := strings.TrimPrefix(bearerToken, "Bearer ") tokenString := strings.TrimPrefix(bearerToken, "Bearer ")
token, _, err := new(jwt.Parser).ParseUnverified(tokenString, jwt.MapClaims{}) token, _, err := new(jwt.Parser).ParseUnverified(tokenString, jwt.MapClaims{})
if err != nil { if err != nil {