Compare commits

...

4 Commits

Author SHA1 Message Date
ericprd
24f0fe6efa fix: filter active in news 2025-03-20 13:13:28 +08:00
ericprd
8d7500610e fix: get userAgent and IP from custom header 2025-03-20 11:52:12 +08:00
ericprd
0c8133c802 fix: don't record log if staff 2025-03-20 11:19:41 +08:00
ericprd
d8f6968f64 fix: improvement logs and record news access 2025-03-20 11:11:08 +08:00
17 changed files with 111 additions and 42 deletions

View File

@ -2,12 +2,13 @@ package database
import "time" import "time"
type LogAds struct { type ContentLog struct {
ID string `gorm:"primaryKey;not null" json:"id"` ID string `gorm:"primaryKey;not null" json:"id"`
ContentID string `gorm:"not null" json:"content_id"` ContentID string `gorm:"not null" json:"content_id"`
UserID string `gorm:"default:null" json:"user_id"` UserID string `gorm:"default:null" json:"user_id"`
IP string `gorm:"default:null" json:"ip"` IP string `gorm:"default:null" json:"ip"`
UserAgent string `gorm:"default:null" json:"user_agent"` UserAgent string `gorm:"default:null" json:"user_agent"`
Category string `gorm:"not null" json:"category"`
CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"created_at"` CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"created_at"`
UpdatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"updated_at"` UpdatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"updated_at"`
} }

View File

@ -1,11 +0,0 @@
package database
import "time"
type LogNews struct {
ID string `gorm:"primaryKey;not null" json:"id"`
NewsID string `gorm:"not null" json:"news_id"`
UserID string `gorm:"not null" json:"user_id"`
CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"created_at"`
UpdatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"updated_at"`
}

View File

@ -55,7 +55,6 @@ func (db *DB) Migrate() error {
&Tag{}, &Tag{},
&Category{}, &Category{},
&Ads{}, &Ads{},
&LogAds{}, &ContentLog{},
&LogNews{},
) )
} }

View File

@ -9,8 +9,8 @@ func (a *accessor) GetAll() ([]adsdomain.AdsResponse, error) {
var ads []adsdomain.AdsResponse var ads []adsdomain.AdsResponse
if err := a.db.Table("ads"). if err := a.db.Table("ads").
Select("ads.*, COUNT(log_ads.content_id) as clicked"). Select("ads.*, COUNT(content_logs.content_id) as clicked").
Joins("LEFT JOIN log_ads ON log_ads.content_id = ads.id"). Joins("LEFT JOIN content_logs ON content_logs.content_id = ads.id").
Group("ads.id"). Group("ads.id").
Scan(&ads).Error; err != nil { Scan(&ads).Error; err != nil {
return ads, fmt.Errorf("failed to get all ads: %v", err) return ads, fmt.Errorf("failed to get all ads: %v", err)

View File

@ -7,10 +7,11 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
) )
func (a *accessor) CreateLogAds(spec logsdomain.LogsSpec) error { func (a *accessor) CreateLog(spec logsdomain.LogsSpec) error {
newSpec := database.LogAds{ newSpec := database.ContentLog{
ID: uuid.NewString(), ID: uuid.NewString(),
ContentID: spec.ContentID, ContentID: spec.ContentID,
Category: spec.Category,
} }
if spec.UserID != nil { if spec.UserID != nil {

View File

@ -11,7 +11,7 @@ type accessor struct {
} }
type Log interface { type Log interface {
CreateLogAds(logsdomain.LogsSpec) error CreateLog(logsdomain.LogsSpec) error
GetAllLogAds(string) ([]adsdomain.Ads, error) GetAllLogAds(string) ([]adsdomain.Ads, error)
} }

View File

@ -1,13 +1,24 @@
package newsrepository package newsrepository
import newsdomain "legalgo-BE-go/internal/domain/news" import (
newsdomain "legalgo-BE-go/internal/domain/news"
timeutils "legalgo-BE-go/internal/utilities/time_utils"
)
type is_active string
const (
active is_active = "true"
notActive is_active = "false"
)
func (a *accessor) GetAll(filter newsdomain.NewsFilter) ([]newsdomain.News, error) { func (a *accessor) GetAll(filter newsdomain.NewsFilter) ([]newsdomain.News, error) {
var news []newsdomain.News var news []newsdomain.News
query := a.db. query := a.db.
Preload("Tags"). Preload("Tags").
Preload("Categories"). Preload("Categories").
Preload("Author") Preload("Author").
Joins("LEFT JOIN content_logs ON content_logs.content_id = news.id")
if len(filter.Category) > 0 { if len(filter.Category) > 0 {
query = query.Joins("JOIN news_categories nc ON nc.news_id = news.id"). query = query.Joins("JOIN news_categories nc ON nc.news_id = news.id").
@ -19,6 +30,15 @@ func (a *accessor) GetAll(filter newsdomain.NewsFilter) ([]newsdomain.News, erro
Where("nt.tag_id IN (?)", filter.Tags) Where("nt.tag_id IN (?)", filter.Tags)
} }
if filter.Active == string(active) {
query = query.Where("news.live_at <= ?", timeutils.Now())
}
query.
Select("news.*, COUNT(content_logs.content_id) as clicked").
Group("news.id").
Order("news.created_at DESC")
if err := query. if err := query.
Find(&news).Error; err != nil { Find(&news).Error; err != nil {
return nil, err return nil, err

View File

@ -7,7 +7,6 @@ import (
"legalgo-BE-go/internal/utilities/response" "legalgo-BE-go/internal/utilities/response"
"legalgo-BE-go/internal/utilities/utils" "legalgo-BE-go/internal/utilities/utils"
"net/http" "net/http"
"strings"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
@ -58,17 +57,11 @@ func CreateLogAds(
specReq.UserID = &userDetail.ID specReq.UserID = &userDetail.ID
} }
ip := r.RemoteAddr if ip := r.Header.Get("X-Ip-Address"); ip != "" {
if forwarded := r.Header.Get("X-Forwarded-For"); forwarded != "" {
ip = strings.Split(forwarded, ",")[0]
}
if ip != "" {
specReq.IP = &ip specReq.IP = &ip
} }
if userAgent := r.UserAgent(); userAgent != "" { if userAgent := r.Header.Get("X-User-Agent"); userAgent != "" {
specReq.UserAgent = &userAgent specReq.UserAgent = &userAgent
} }

View File

@ -23,8 +23,13 @@ func GetAll(
category := query.Get("categories") category := query.Get("categories")
tags := query.Get("tags") tags := query.Get("tags")
activeOnly := query.Get("active")
news, err = newsSvc.GetAll(category, tags) news, err = newsSvc.GetAll(newsdomain.Filter{
Category: category,
Tags: tags,
Active: activeOnly,
})
if err != nil { if err != nil {
response.ResponseWithErrorCode( response.ResponseWithErrorCode(

View File

@ -1,8 +1,11 @@
package newshttp package newshttp
import ( import (
logsdomain "legalgo-BE-go/internal/domain/logs"
logssvc "legalgo-BE-go/internal/services/logs"
newssvc "legalgo-BE-go/internal/services/news" newssvc "legalgo-BE-go/internal/services/news"
"legalgo-BE-go/internal/utilities/response" "legalgo-BE-go/internal/utilities/response"
"legalgo-BE-go/internal/utilities/utils"
"net/http" "net/http"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
@ -11,6 +14,7 @@ import (
func GetBySlug( func GetBySlug(
router chi.Router, router chi.Router,
newsSvc newssvc.News, newsSvc newssvc.News,
logSvc logssvc.Log,
) { ) {
router.Get("/news/{slug}", func(w http.ResponseWriter, r *http.Request) { router.Get("/news/{slug}", func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
@ -29,6 +33,38 @@ func GetBySlug(
return return
} }
userDetail, _ := utils.GetTokenDetail(r)
if userDetail.Role != "staff" {
var specReq logsdomain.LogsSpec
specReq.ContentID = news.ID
if userDetail.ID != "" {
specReq.UserID = &userDetail.ID
}
if ip := r.Header.Get("X-Ip-Address"); ip != "" {
specReq.IP = &ip
}
if userAgent := r.Header.Get("X-User-Agent"); userAgent != "" {
specReq.UserAgent = &userAgent
}
if err := logSvc.CreateLogNews(specReq); err != nil {
response.RespondJsonErrorWithCode(
ctx,
w,
err,
response.ErrBadRequest.Code,
response.ErrBadRequest.HttpCode,
err.Error(),
)
return
}
}
response.RespondJsonSuccess(ctx, w, news) response.RespondJsonSuccess(ctx, w, news)
}) })
} }

View File

@ -9,10 +9,11 @@ type LogsRequest struct {
} }
type LogsSpec struct { type LogsSpec struct {
ContentID string `json:"content_id" validate:"required"` ContentID string `json:"content_id" validate:"required"`
IP *string `json:"ip"` IP *string
UserID *string `json:"user_id"` UserID *string
UserAgent *string `json:"user_agent"` UserAgent *string
Category string
} }
type LogResponse struct { type LogResponse struct {

View File

@ -35,6 +35,7 @@ type News struct {
LiveAt time.Time `json:"live_at"` LiveAt time.Time `json:"live_at"`
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"` UpdatedAt time.Time `json:"updated_at"`
Clicked int64 `json:"clicked"`
Author Staff `json:"author"` Author Staff `json:"author"`
} }
@ -50,7 +51,12 @@ type NewsUpdate struct {
LiveAt time.Time `json:"live_at"` LiveAt time.Time `json:"live_at"`
} }
type Filter struct {
Category, Tags, Active string
}
type NewsFilter struct { type NewsFilter struct {
Tags []string Tags []string
Category []string Category []string
Active string
} }

View File

@ -6,7 +6,8 @@ import (
) )
func (i *impl) CreateLogAds(spec logsdomain.LogsSpec) error { func (i *impl) CreateLogAds(spec logsdomain.LogsSpec) error {
if err := i.logsRepo.CreateLogAds(spec); err != nil { spec.Category = "ads"
if err := i.logsRepo.CreateLog(spec); err != nil {
return fmt.Errorf("failed to create ads log: %v", err) return fmt.Errorf("failed to create ads log: %v", err)
} }

View File

@ -0,0 +1,15 @@
package logssvc
import (
"fmt"
logsdomain "legalgo-BE-go/internal/domain/logs"
)
func (i *impl) CreateLogNews(spec logsdomain.LogsSpec) error {
spec.Category = "news"
if err := i.logsRepo.CreateLog(spec); err != nil {
return fmt.Errorf("failed to create news log: %v", err)
}
return nil
}

View File

@ -12,6 +12,7 @@ type impl struct {
type Log interface { type Log interface {
CreateLogAds(logsdomain.LogsSpec) error CreateLogAds(logsdomain.LogsSpec) error
CreateLogNews(logsdomain.LogsSpec) error
GetAllLogAds(string) ([]adsdomain.Ads, error) GetAllLogAds(string) ([]adsdomain.Ads, error)
} }

View File

@ -5,15 +5,15 @@ import (
"strings" "strings"
) )
func (i *impl) GetAll(categoriesCode, tagCodes string) ([]newsdomain.News, error) { func (i *impl) GetAll(filter newsdomain.Filter) ([]newsdomain.News, error) {
var err error var err error
categories := []string{} categories := []string{}
tags := []string{} tags := []string{}
news := []newsdomain.News{} news := []newsdomain.News{}
tagCodeArr := strings.Split(tagCodes, " ") tagCodeArr := strings.Split(filter.Tags, " ")
categoryCodeArr := strings.Split(categoriesCode, " ") categoryCodeArr := strings.Split(filter.Category, " ")
if len(tagCodeArr) > 0 && tagCodeArr[0] != "" { if len(tagCodeArr) > 0 && tagCodeArr[0] != "" {
tags, err = i.tagRepo.GetIDsByCodes(tagCodeArr) tags, err = i.tagRepo.GetIDsByCodes(tagCodeArr)
@ -37,9 +37,10 @@ func (i *impl) GetAll(categoriesCode, tagCodes string) ([]newsdomain.News, error
} }
} }
filter := newsdomain.NewsFilter{ filterSpec := newsdomain.NewsFilter{
Tags: tags, Tags: tags,
Category: categories, Category: categories,
Active: filter.Active,
} }
return i.newsRepo.GetAll(filter) return i.newsRepo.GetAll(filterSpec)
} }

View File

@ -16,7 +16,7 @@ type impl struct {
} }
type News interface { type News interface {
GetAll(string, string) ([]newsdomain.News, error) GetAll(filter newsdomain.Filter) ([]newsdomain.News, error)
GetBySlug(string) (*newsdomain.News, error) GetBySlug(string) (*newsdomain.News, error)
Create(newsdomain.NewsReq, string) error Create(newsdomain.NewsReq, string) error
Update(string, newsdomain.NewsUpdate) error Update(string, newsdomain.NewsUpdate) error