Compare commits
No commits in common. "main" and "old-all-module" have entirely different histories.
main
...
old-all-mo
43
.drone.yml
43
.drone.yml
|
|
@ -1,43 +0,0 @@
|
|||
kind: pipeline
|
||||
type: ssh
|
||||
name: campaign-pool-be-build-deploy
|
||||
|
||||
server:
|
||||
host:
|
||||
from_secret: ssh_host
|
||||
user:
|
||||
from_secret: ssh_user
|
||||
ssh_key:
|
||||
from_secret: ssh_key
|
||||
|
||||
steps:
|
||||
- name: prepare repo
|
||||
when:
|
||||
branch:
|
||||
- main
|
||||
commands:
|
||||
- rm -rf /opt/build/campaignpool-be
|
||||
- mkdir -p /opt/build/campaignpool-be
|
||||
- cd /opt/build/campaignpool-be
|
||||
- git clone http://38.47.180.165:3000/campaignpool/campaignpool-be.git
|
||||
|
||||
- name: build image
|
||||
when:
|
||||
branch:
|
||||
- main
|
||||
commands:
|
||||
- docker login 38.47.180.165:3000 -u administrator -p HarborDockerImageRep0
|
||||
- cd /opt/build/campaignpool-be/campaignpool-be
|
||||
- docker build -t 38.47.180.165:3000/campaignpool/campaignpool-be:main .
|
||||
- docker push 38.47.180.165:3000/campaignpool/campaignpool-be:main
|
||||
|
||||
- name: deploy
|
||||
when:
|
||||
branch:
|
||||
- main
|
||||
commands:
|
||||
- docker login 38.47.180.165:3000 -u administrator -p HarborDockerImageRep0
|
||||
- docker pull 38.47.180.165:3000/campaignpool/campaignpool-be:main
|
||||
- docker stop campaignpool-be || true
|
||||
- docker rm campaignpool-be || true
|
||||
- docker run -dt -p 8807:8800 --name campaignpool-be 38.47.180.165:3000/campaignpool/campaignpool-be:main
|
||||
|
|
@ -16,12 +16,12 @@ build-2:
|
|||
image: docker/compose:latest
|
||||
services:
|
||||
- name: docker:dind
|
||||
command: ["--insecure-registry=38.47.185.86:8900"]
|
||||
command: [ "--insecure-registry=103.82.242.92:8900" ]
|
||||
script:
|
||||
- docker login -u $DEPLOY_USERNAME -p $DEPLOY_TOKEN 38.47.185.86:8900
|
||||
- docker login -u $DEPLOY_USERNAME -p $DEPLOY_TOKEN 103.82.242.92:8900
|
||||
- docker-compose build
|
||||
- docker tag registry.gitlab.com/hanifsalafi/campaign-pool-be:dev 38.47.185.86:8900/medols/campaign-pool-be:dev
|
||||
- docker push 38.47.185.86:8900/medols/campaign-pool-be:dev
|
||||
- docker tag registry.gitlab.com/hanifsalafi/campaign-pool-be:dev 103.82.242.92:8900/medols/campaign-pool-be:dev
|
||||
- docker push 103.82.242.92:8900/medols/campaign-pool-be:dev
|
||||
|
||||
deploy:
|
||||
stage: deploy
|
||||
|
|
@ -30,4 +30,4 @@ deploy:
|
|||
services:
|
||||
- docker:dind
|
||||
script:
|
||||
- curl --user $JENKINS_USER:$JENKINS_PWD http://38.47.185.86:8080/job/autodeploy-campaignpool-be/build?token=autodeploycampaignpool
|
||||
- curl --user $JENKINS_USER:$JENKINS_PWD http://38.47.180.165:8080/job/autodeploy-campaignpool-be/build?token=autodeploycampaignpool
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Advertisement struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||
Title string `json:"title" gorm:"type:varchar"`
|
||||
Description string `json:"description" gorm:"type:varchar"`
|
||||
RedirectLink string `json:"redirect_link" gorm:"type:varchar"`
|
||||
ContentFilePath *string `json:"content_file_path" gorm:"type:varchar"`
|
||||
ContentFileName *string `json:"content_file_name" gorm:"type:varchar"`
|
||||
Placement string `json:"placement" gorm:"type:varchar"`
|
||||
StatusId int `json:"status_id" gorm:"type:int4"`
|
||||
IsPublish bool `json:"is_publish" gorm:"type:bool"`
|
||||
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()"`
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/database/entity/users"
|
||||
"time"
|
||||
)
|
||||
|
||||
type AIChatLogs struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||
SessionID uint `json:"session_id" gorm:"type:int4;not null;index"`
|
||||
UserID uint `json:"user_id" gorm:"type:int4;not null;index"`
|
||||
StartDate time.Time `json:"start_date" gorm:"not null"`
|
||||
EndDate *time.Time `json:"end_date"`
|
||||
TotalDuration int64 `json:"total_duration" gorm:"type:bigint;default:0"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
|
||||
User *users.Users `json:"user" gorm:"foreignKey:UserID;references:ID"`
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type AIChatMessages struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||
SessionID string `json:"session_id" gorm:"type:varchar;not null;index"`
|
||||
MessageType string `json:"message_type" gorm:"type:varchar;not null"`
|
||||
Content string `json:"content" gorm:"type:text;not null"`
|
||||
IsActive bool `json:"is_active" gorm:"type:bool;default:true"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/database/entity/users"
|
||||
"time"
|
||||
)
|
||||
|
||||
type AIChatSessions struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||
SessionID string `json:"session_id" gorm:"type:varchar;not null;unique;index"`
|
||||
UserID uint `json:"user_id" gorm:"type:int4;not null;index"`
|
||||
AgentID string `json:"agent_id" gorm:"type:varchar;not null"`
|
||||
Title string `json:"title" gorm:"type:varchar;not null"`
|
||||
MessageCount int `json:"message_count" gorm:"type:int4;default:0"`
|
||||
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()"`
|
||||
User *users.Users `json:"user" gorm:"foreignKey:UserID;references:ID"`
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type ArticleApprovals struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||
ArticleId uint `json:"article_id" gorm:"type:int4"`
|
||||
ApprovalBy uint `json:"approval_by" gorm:"type:int4"`
|
||||
StatusId int `json:"status_id" gorm:"type:int4"`
|
||||
Message string `json:"message" gorm:"type:varchar"`
|
||||
ApprovalAtLevel *int `json:"approval_at_level" gorm:"type:int4"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type ArticleCategories struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||
Title string `json:"title" gorm:"type:varchar"`
|
||||
Description string `json:"description" gorm:"type:varchar"`
|
||||
ThumbnailPath *string `json:"thumbnail_path" gorm:"type:varchar"`
|
||||
Slug *string `json:"slug" gorm:"type:varchar"`
|
||||
ParentId *int `json:"parent_id" gorm:"type:int4"`
|
||||
Tags *string `json:"tags" gorm:"type:varchar"`
|
||||
Position *int `json:"position" gorm:"type:int4"`
|
||||
CreatedById *uint `json:"created_by_id" gorm:"type:int4"`
|
||||
OldCategoryId *uint `json:"old_category_id" gorm:"type:int4"`
|
||||
StatusId int `json:"status_id" gorm:"type:int4;default:1"`
|
||||
IsPublish *bool `json:"is_publish" gorm:"type:bool;default:false"`
|
||||
PublishedAt *time.Time `json:"published_at" gorm:"type:timestamp"`
|
||||
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()"`
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package article_category_details
|
||||
|
||||
import (
|
||||
entity "campaign-pool-be/app/database/entity"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ArticleCategoryDetails struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||
ArticleId uint `json:"article_id" gorm:"type:int4"`
|
||||
CategoryId int `json:"category_id" gorm:"type:int4"`
|
||||
Category *entity.ArticleCategories `json:"category" gorm:"foreignKey:CategoryId;references:ID"`
|
||||
IsActive bool `json:"is_active" gorm:"type:bool"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type ArticleComments struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||
Message string `json:"message" gorm:"type:varchar"`
|
||||
ArticleId uint `json:"article_id" gorm:"type:int4"`
|
||||
CommentFrom *uint `json:"comment_from" gorm:"type:int4"`
|
||||
ParentId *int `json:"parent_id" gorm:"type:int4"`
|
||||
IsPublic bool `json:"is_public" gorm:"type:bool;default:false"`
|
||||
StatusId int `json:"status_id" gorm:"type:int4;default:0"`
|
||||
ApprovedAt *time.Time `json:"approved_at" gorm:"type:timestamp"`
|
||||
IsActive bool `json:"is_active" gorm:"type:bool"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
|
||||
}
|
||||
|
||||
// statusId => 0: waiting, 1: accepted, 2: replied, 3: rejected
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type ArticleFiles struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||
ArticleId uint `json:"article_id" gorm:"type:int4"`
|
||||
UploadID *string `json:"upload_id" gorm:"type:varchar"`
|
||||
FilePath *string `json:"file_path" gorm:"type:varchar"`
|
||||
FileUrl *string `json:"file_url" gorm:"type:varchar"`
|
||||
FileName *string `json:"file_name" gorm:"type:varchar"`
|
||||
FileThumbnail *string `json:"file_thumbnail" gorm:"type:varchar"`
|
||||
FileAlt *string `json:"file_alt" gorm:"type:varchar"`
|
||||
WidthPixel *string `json:"width_pixel" gorm:"type:varchar"`
|
||||
HeightPixel *string `json:"height_pixel" gorm:"type:varchar"`
|
||||
Size *string `json:"size" gorm:"type:varchar"`
|
||||
DownloadCount *int `json:"download_count" gorm:"type:int4;default:0"`
|
||||
CreatedById int `json:"created_by_id" gorm:"type:int4"`
|
||||
StatusId int `json:"status_id" gorm:"type:int4"`
|
||||
IsPublish *bool `json:"is_publish" gorm:"type:bool;default:false"`
|
||||
PublishedAt *time.Time `json:"published_at" gorm:"type:timestamp"`
|
||||
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()"`
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Articles struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||
Title string `json:"title" gorm:"type:varchar"`
|
||||
Slug string `json:"slug" gorm:"type:varchar"`
|
||||
Description string `json:"description" gorm:"type:varchar"`
|
||||
CategoryId int `json:"category_id" gorm:"type:int4"`
|
||||
HtmlDescription string `json:"html_description" gorm:"type:varchar"`
|
||||
TypeId int `json:"type_id" gorm:"type:int4"`
|
||||
Tags string `json:"tags" gorm:"type:varchar"`
|
||||
ThumbnailName *string `json:"thumbnail_name" gorm:"type:varchar"`
|
||||
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;default:0"`
|
||||
ShareCount *int `json:"share_count" gorm:"type:int4;default:0"`
|
||||
ViewCount *int `json:"view_count" gorm:"type:int4;default:0"`
|
||||
StatusId *int `json:"status_id" gorm:"type:int4"`
|
||||
OldId *uint `json:"old_id" gorm:"type:int4"`
|
||||
NeedApprovalFrom *int `json:"need_approval_from" gorm:"type:int4"`
|
||||
HasApprovedBy *string `json:"has_approved_by" gorm:"type:varchar"`
|
||||
IsPublish *bool `json:"is_publish" gorm:"type:bool;default:false"`
|
||||
IsBanner *bool `json:"is_banner" gorm:"type:bool;default:false"`
|
||||
PublishedAt *time.Time `json:"published_at" gorm:"type:timestamp"`
|
||||
IsDraft *bool `json:"is_draft" gorm:"type:bool;default:false"`
|
||||
DraftedAt *time.Time `json:"drafted_at" gorm:"type:timestamp"`
|
||||
PublishSchedule *string `json:"publish_schedule" 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()"`
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/database/entity/users"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ChatMessages struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||
ChatSessionID uint `json:"chat_session_id" gorm:"type:int4;not null;index"`
|
||||
SenderID uint `json:"sender_id" gorm:"type:int4;not null;index"`
|
||||
Message string `json:"message" gorm:"type:text;not null"`
|
||||
MessageType string `json:"message_type" gorm:"type:varchar(20);not null;default:'text';check:message_type IN ('text', 'image', 'file', 'user', 'assistant')"` // 'text', 'image', 'file', 'user', 'assistant'
|
||||
IsEdited bool `json:"is_edited" gorm:"default:false"`
|
||||
EditedAt *time.Time `json:"edited_at"`
|
||||
IsDeleted bool `json:"is_deleted" gorm:"default:false"`
|
||||
DeletedAt *time.Time `json:"deleted_at"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
|
||||
|
||||
// Relationships
|
||||
ChatSession *ChatSessions `json:"chat_session" gorm:"foreignKey:ChatSessionID;references:ID"`
|
||||
Sender *users.Users `json:"sender" gorm:"foreignKey:SenderID;references:ID"`
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/database/entity/users"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ChatParticipants struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||
ChatSessionID uint `json:"chat_session_id" gorm:"type:int4;not null;index"`
|
||||
UserID uint `json:"user_id" gorm:"type:int4;not null;index"`
|
||||
JoinedAt time.Time `json:"joined_at" gorm:"default:now()"`
|
||||
LeftAt *time.Time `json:"left_at"`
|
||||
IsActive bool `json:"is_active" gorm:"default:true"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
|
||||
|
||||
// Relationships
|
||||
ChatSession *ChatSessions `json:"chat_session" gorm:"foreignKey:ChatSessionID;references:ID"`
|
||||
User *users.Users `json:"user" gorm:"foreignKey:UserID;references:ID"`
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type ChatScheduleFiles struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||
ChatScheduleID uint `json:"chat_schedule_id" gorm:"type:int4;not null;index"`
|
||||
FileName string `json:"file_name" gorm:"type:varchar(255);not null"`
|
||||
OriginalName string `json:"original_name" gorm:"type:varchar(255);not null"`
|
||||
FilePath string `json:"file_path" gorm:"type:varchar(500);not null"`
|
||||
FileSize int64 `json:"file_size" gorm:"type:int8"`
|
||||
MimeType string `json:"mime_type" gorm:"type:varchar(100)"`
|
||||
FileType string `json:"file_type" gorm:"type:varchar(20);not null;check:file_type IN ('article', 'journal', 'video', 'audio', 'document', 'other')"`
|
||||
Description string `json:"description" gorm:"type:text"`
|
||||
IsRequired bool `json:"is_required" gorm:"default:false"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
|
||||
|
||||
// Relationships
|
||||
ChatSchedule *ChatSchedules `json:"chat_schedule" gorm:"foreignKey:ChatScheduleID;references:ID"`
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/database/entity/users"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ChatSchedules struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||
ChatSessionID uint `json:"chat_session_id" gorm:"type:int4;not null;index"`
|
||||
Title string `json:"title" gorm:"type:varchar(255);not null"`
|
||||
Description string `json:"description" gorm:"type:text"`
|
||||
Summary string `json:"summary" gorm:"type:text"`
|
||||
ScheduledAt time.Time `json:"scheduled_at" gorm:"not null"`
|
||||
Duration int `json:"duration" gorm:"type:int4;default:60"` // duration in minutes
|
||||
Status string `json:"status" gorm:"type:varchar(20);not null;default:'scheduled';check:status IN ('scheduled', 'ongoing', 'completed', 'cancelled')"`
|
||||
IsReminderSent bool `json:"is_reminder_sent" gorm:"default:false"`
|
||||
ReminderSentAt *time.Time `json:"reminder_sent_at"`
|
||||
CreatedBy uint `json:"created_by" gorm:"type:int4;not null;index"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
|
||||
|
||||
// Relationships
|
||||
ChatSession *ChatSessions `json:"chat_session" gorm:"foreignKey:ChatSessionID;references:ID"`
|
||||
Creator *users.Users `json:"creator" gorm:"foreignKey:CreatedBy;references:ID"`
|
||||
Files []*ChatScheduleFiles `json:"files" gorm:"foreignKey:ChatScheduleID;references:ID"`
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/database/entity/users"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ChatSessions struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||
Name *string `json:"name" gorm:"type:varchar(255)"` // null for personal chat, filled for group chat
|
||||
Type string `json:"type" gorm:"type:varchar(20);not null;default:'personal'"` // 'personal' or 'group'
|
||||
CreatedBy uint `json:"created_by" gorm:"type:int4;not null;index"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
|
||||
|
||||
// Relationships
|
||||
Creator *users.Users `json:"creator" gorm:"foreignKey:CreatedBy;references:ID"`
|
||||
Participants []*ChatParticipants `json:"participants" gorm:"foreignKey:ChatSessionID;references:ID"`
|
||||
Messages []*ChatMessages `json:"messages" gorm:"foreignKey:ChatSessionID;references:ID"`
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package entity
|
||||
|
||||
type Cities struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||
CityName string `json:"city_name" gorm:"type:varchar"`
|
||||
ProvId int `json:"prov_id" gorm:"type:int4"`
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/database/entity/users"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Conversations struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||
Participant1ID uint `json:"participant1_id" gorm:"type:int4;not null;index"`
|
||||
Participant2ID uint `json:"participant2_id" gorm:"type:int4;not null;index"`
|
||||
LastMessageAt *time.Time `json:"last_message_at"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
|
||||
Participant1 *users.Users `json:"participant1" gorm:"foreignKey:Participant1ID;references:ID"`
|
||||
Participant2 *users.Users `json:"participant2" gorm:"foreignKey:Participant2ID;references:ID"`
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package entity
|
||||
|
||||
type Districts struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||
DisName string `json:"dis_name" gorm:"type:varchar"`
|
||||
CityId int `json:"city_id" gorm:"type:int4"`
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
users "campaign-pool-be/app/database/entity/users"
|
||||
"time"
|
||||
)
|
||||
|
||||
type EbookPurchases struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||
BuyerId uint `json:"buyer_id" gorm:"type:int4"`
|
||||
Buyer *users.Users `json:"buyer" gorm:"foreignKey:BuyerId;references:ID"`
|
||||
EbookId uint `json:"ebook_id" gorm:"type:int4"`
|
||||
Ebook *Ebooks `json:"ebook" gorm:"foreignKey:EbookId;references:ID"`
|
||||
PurchasePrice float64 `json:"purchase_price" gorm:"type:decimal(10,2)"`
|
||||
PaymentMethod *string `json:"payment_method" gorm:"type:varchar"`
|
||||
PaymentStatus *string `json:"payment_status" gorm:"type:varchar;default:'pending'"`
|
||||
TransactionId *string `json:"transaction_id" gorm:"type:varchar"`
|
||||
PaymentProof *string `json:"payment_proof" gorm:"type:varchar"`
|
||||
PaymentDate *time.Time `json:"payment_date" gorm:"type:timestamp"`
|
||||
DownloadCount *int `json:"download_count" gorm:"type:int4;default:0"`
|
||||
LastDownloadAt *time.Time `json:"last_download_at" gorm:"type:timestamp"`
|
||||
StatusId *int `json:"status_id" gorm:"type:int4;default:1"`
|
||||
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()"`
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
users "campaign-pool-be/app/database/entity/users"
|
||||
"time"
|
||||
)
|
||||
|
||||
type EbookRatings struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||
UserId uint `json:"user_id" gorm:"type:int4"`
|
||||
User *users.Users `json:"user" gorm:"foreignKey:UserId;references:ID"`
|
||||
EbookId uint `json:"ebook_id" gorm:"type:int4"`
|
||||
Ebook *Ebooks `json:"ebook" gorm:"foreignKey:EbookId;references:ID"`
|
||||
PurchaseId uint `json:"purchase_id" gorm:"type:int4"`
|
||||
Purchase *EbookPurchases `json:"purchase" gorm:"foreignKey:PurchaseId;references:ID"`
|
||||
Rating int `json:"rating" gorm:"type:int4;check:rating >= 1 AND rating <= 5"`
|
||||
Review *string `json:"review" gorm:"type:text"`
|
||||
IsAnonymous *bool `json:"is_anonymous" gorm:"type:bool;default:false"`
|
||||
IsVerified *bool `json:"is_verified" gorm:"type:bool;default:true"`
|
||||
StatusId *int `json:"status_id" gorm:"type:int4;default:1"`
|
||||
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()"`
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
users "campaign-pool-be/app/database/entity/users"
|
||||
"time"
|
||||
)
|
||||
|
||||
type EbookWishlists struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||
UserId uint `json:"user_id" gorm:"type:int4"`
|
||||
User *users.Users `json:"user" gorm:"foreignKey:UserId;references:ID"`
|
||||
EbookId uint `json:"ebook_id" gorm:"type:int4"`
|
||||
Ebook *Ebooks `json:"ebook" gorm:"foreignKey:EbookId;references:ID"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
users "campaign-pool-be/app/database/entity/users"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Ebooks struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||
Title string `json:"title" gorm:"type:varchar"`
|
||||
Slug string `json:"slug" gorm:"type:varchar"`
|
||||
Description string `json:"description" gorm:"type:varchar"`
|
||||
Price float64 `json:"price" gorm:"type:decimal(10,2)"`
|
||||
PdfFilePath *string `json:"pdf_file_path" gorm:"type:varchar"`
|
||||
PdfFileName *string `json:"pdf_file_name" gorm:"type:varchar"`
|
||||
PdfFileSize *int64 `json:"pdf_file_size" gorm:"type:int8"`
|
||||
ThumbnailPath *string `json:"thumbnail_path" gorm:"type:varchar"`
|
||||
ThumbnailName *string `json:"thumbnail_name" gorm:"type:varchar"`
|
||||
AuthorId uint `json:"author_id" gorm:"type:int4"`
|
||||
Author *users.Users `json:"author" gorm:"foreignKey:AuthorId;references:ID"`
|
||||
Category *string `json:"category" gorm:"type:varchar"`
|
||||
Tags *string `json:"tags" gorm:"type:varchar"`
|
||||
PageCount *int `json:"page_count" gorm:"type:int4"`
|
||||
Language *string `json:"language" gorm:"type:varchar;default:'id'"`
|
||||
Isbn *string `json:"isbn" gorm:"type:varchar"`
|
||||
Publisher *string `json:"publisher" gorm:"type:varchar"`
|
||||
PublishedYear *int `json:"published_year" gorm:"type:int4"`
|
||||
DownloadCount *int `json:"download_count" gorm:"type:int4;default:0"`
|
||||
PurchaseCount *int `json:"purchase_count" gorm:"type:int4;default:0"`
|
||||
WishlistCount *int `json:"wishlist_count" gorm:"type:int4;default:0"`
|
||||
Rating *float64 `json:"rating" gorm:"type:decimal(3,2);default:0"`
|
||||
ReviewCount *int `json:"review_count" gorm:"type:int4;default:0"`
|
||||
StatusId *int `json:"status_id" gorm:"type:int4;default:1"`
|
||||
IsActive *bool `json:"is_active" gorm:"type:bool;default:true"`
|
||||
IsPublished *bool `json:"is_published" gorm:"type:bool;default:false"`
|
||||
PublishedAt *time.Time `json:"published_at" gorm:"type:timestamp"`
|
||||
CreatedById *uint `json:"created_by_id" gorm:"type:int4"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/database/entity/users"
|
||||
"time"
|
||||
)
|
||||
|
||||
type EducationHistory struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||
UserID uint `json:"user_id" gorm:"type:int4;not null;index"`
|
||||
SchoolName string `json:"school_name" gorm:"type:varchar;not null"`
|
||||
Major string `json:"major" gorm:"type:varchar;not null"`
|
||||
EducationLevel string `json:"education_level" gorm:"type:varchar;not null"`
|
||||
GraduationYear int `json:"graduation_year" gorm:"type:int4;not null"`
|
||||
CertificateImage *string `json:"certificate_image" gorm:"type:varchar"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
|
||||
User *users.Users `json:"user" gorm:"foreignKey:UserID;references:ID"`
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Feedbacks struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||
Message string `json:"message" gorm:"type:varchar"`
|
||||
CommentFromName string `json:"comment_from_name" gorm:"type:varchar"`
|
||||
CommentFromEmail string `json:"comment_from_email" gorm:"type:varchar"`
|
||||
StatusId int `json:"status_id" gorm:"type:int4;default:0"`
|
||||
ApprovedAt *time.Time `json:"approved_at" gorm:"type:timestamp"`
|
||||
ReplyMessage *string `json:"reply_message" 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()"`
|
||||
}
|
||||
|
||||
// statusId => 0: waiting, 1: accepted, 2: replied, 3: nothing
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package entity
|
||||
|
||||
import "time"
|
||||
|
||||
type MagazineFiles struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||
Title string `json:"title" gorm:"type:varchar"`
|
||||
Description string `json:"description" gorm:"type:varchar"`
|
||||
MagazineId uint `json:"magazine_id" gorm:"type:int4"`
|
||||
DownloadCount *int `json:"download_count" gorm:"type:int4"`
|
||||
StatusId int `json:"status_id" gorm:"type:int4"`
|
||||
IsPublish *bool `json:"is_publish" gorm:"type:bool"`
|
||||
FilePath *string `json:"file_path" gorm:"type:varchar"`
|
||||
FileUrl *string `json:"file_url" gorm:"type:varchar"`
|
||||
FileName *string `json:"file_name" gorm:"type:varchar"`
|
||||
FileAlt *string `json:"file_alt" gorm:"type:varchar"`
|
||||
WidthPixel *string `json:"width_pixel" gorm:"type:varchar"`
|
||||
HeightPixel *string `json:"height_pixel" gorm:"type:varchar"`
|
||||
Size *string `json:"size" gorm:"type:varchar"`
|
||||
PublishedAt *time.Time `json:"published_at" gorm:"type:timestamp"`
|
||||
IsActive bool `json:"is_active" gorm:"type:bool"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package entity
|
||||
|
||||
import "time"
|
||||
|
||||
type Magazines struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||
Title string `json:"title" gorm:"type:varchar"`
|
||||
Description string `json:"description" gorm:"type:varchar"`
|
||||
ThumbnailName *string `json:"thumbnail_name" gorm:"type:varchar"`
|
||||
ThumbnailPath *string `json:"thumbnail_path" gorm:"type:varchar"`
|
||||
ThumbnailUrl *string `json:"thumbnail_url" gorm:"type:varchar"`
|
||||
PageUrl *string `json:"page_url" gorm:"type:varchar"`
|
||||
CreatedById *uint `json:"created_by_id" gorm:"type:int4"`
|
||||
StatusId int `json:"status_id" gorm:"type:int4"`
|
||||
IsPublish *bool `json:"is_publish" gorm:"type:bool"`
|
||||
PublishedAt *time.Time `json:"published_at" gorm:"type:timestamp"`
|
||||
IsActive bool `json:"is_active" gorm:"type:bool"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package entity
|
||||
|
||||
import "time"
|
||||
|
||||
type MasterMenus struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||
Name string `json:"name" gorm:"type:varchar"`
|
||||
Description string `json:"description" gorm:"type:varchar"`
|
||||
ModuleId int `json:"module_id" gorm:"type:int4"`
|
||||
ParentMenuId *int `json:"parent_menu_id" gorm:"type:int4"`
|
||||
Icon *string `json:"icon" gorm:"type:varchar"`
|
||||
Group string `json:"group" gorm:"type:varchar"`
|
||||
Position *int `json:"position" gorm:"type:int4"`
|
||||
StatusId int `json:"status_id" gorm:"type:int4"`
|
||||
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()"`
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type MasterModules struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||
Name string `json:"name" gorm:"type:varchar"`
|
||||
Description string `json:"description" gorm:"type:varchar"`
|
||||
PathUrl string `json:"path_url" gorm:"type:varchar"`
|
||||
StatusId int `json:"status_id" gorm:"type:int4"`
|
||||
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()"`
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package entity
|
||||
|
||||
type MasterStatuses struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||
Name string `json:"name" gorm:"type:varchar"`
|
||||
IsActive bool `json:"is_active" gorm:"type:bool"`
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package entity
|
||||
|
||||
type Provinces struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||
ProvName string `json:"prov_name" gorm:"type:varchar"`
|
||||
LocationId int `json:"location_id" gorm:"type:int4"`
|
||||
Status int `json:"status" gorm:"type:int4"`
|
||||
Timezone string `json:"timezone" gorm:"type:varchar"`
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/database/entity/users"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ResearchJournals struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||
UserID uint `json:"user_id" gorm:"type:int4;not null;index"`
|
||||
JournalTitle string `json:"journal_title" gorm:"type:varchar;not null"`
|
||||
Publisher string `json:"publisher" gorm:"type:varchar;not null"`
|
||||
JournalURL string `json:"journal_url" gorm:"type:varchar;not null"`
|
||||
PublishedDate *time.Time `json:"published_date"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
|
||||
User *users.Users `json:"user" gorm:"foreignKey:UserID;references:ID"`
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
)
|
||||
|
||||
type Subscription struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||
Email string `json:"email" 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()"`
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/database/entity/users"
|
||||
"time"
|
||||
)
|
||||
|
||||
type WorkHistory struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||
UserID uint `json:"user_id" gorm:"type:int4;not null;index"`
|
||||
JobTitle string `json:"job_title" gorm:"type:varchar;not null"`
|
||||
CompanyName string `json:"company_name" gorm:"type:varchar;not null"`
|
||||
StartDate time.Time `json:"start_date" gorm:"not null"`
|
||||
EndDate *time.Time `json:"end_date"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
|
||||
User *users.Users `json:"user" gorm:"foreignKey:UserID;references:ID"`
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package database
|
|||
|
||||
import (
|
||||
"campaign-pool-be/app/database/entity"
|
||||
"campaign-pool-be/app/database/entity/article_category_details"
|
||||
"campaign-pool-be/app/database/entity/user_levels"
|
||||
"campaign-pool-be/app/database/entity/users"
|
||||
"campaign-pool-be/config/config"
|
||||
|
|
@ -86,18 +87,55 @@ func Models() []interface{} {
|
|||
return []interface{}{
|
||||
entity.ActivityLogs{},
|
||||
entity.ActivityLogTypes{},
|
||||
entity.Advertisement{},
|
||||
entity.Articles{},
|
||||
entity.ArticleCategories{},
|
||||
entity.ArticleApprovals{},
|
||||
article_category_details.ArticleCategoryDetails{},
|
||||
entity.ArticleFiles{},
|
||||
entity.ArticleComments{},
|
||||
entity.AuditTrails{},
|
||||
entity.Cities{},
|
||||
entity.CsrfTokenRecords{},
|
||||
entity.CustomStaticPages{},
|
||||
entity.Districts{},
|
||||
entity.Feedbacks{},
|
||||
entity.ForgotPasswords{},
|
||||
entity.Magazines{},
|
||||
entity.MagazineFiles{},
|
||||
entity.MasterMenus{},
|
||||
entity.MasterModules{},
|
||||
entity.MasterStatuses{},
|
||||
entity.MasterApprovalStatuses{},
|
||||
entity.Provinces{},
|
||||
entity.OneTimePasswords{},
|
||||
entity.Subscription{},
|
||||
user_levels.UserLevels{},
|
||||
entity.UserRoles{},
|
||||
entity.UserRoleAccesses{},
|
||||
users.Users{},
|
||||
entity.UserRoleLevelDetails{},
|
||||
|
||||
// Narasi Ahli entities
|
||||
entity.EducationHistory{},
|
||||
entity.WorkHistory{},
|
||||
entity.ResearchJournals{},
|
||||
entity.Conversations{},
|
||||
entity.ChatMessages{},
|
||||
entity.ChatParticipants{},
|
||||
entity.ChatSessions{},
|
||||
entity.ChatSchedules{},
|
||||
entity.ChatScheduleFiles{},
|
||||
entity.AIChatSessions{},
|
||||
entity.AIChatMessages{},
|
||||
entity.AIChatLogs{},
|
||||
|
||||
// Ebook entities
|
||||
entity.Ebooks{},
|
||||
entity.EbookWishlists{},
|
||||
entity.EbookPurchases{},
|
||||
entity.EbookRatings{},
|
||||
|
||||
// Campaign entities
|
||||
entity.CampaignTypes{},
|
||||
entity.CampaignDestinations{},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
package seeds
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/database/entity"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type MasterStatusesSeeder struct{}
|
||||
|
||||
var masterStatuses = []entity.MasterStatuses{
|
||||
{
|
||||
ID: 1,
|
||||
Name: "Waiting",
|
||||
IsActive: true,
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
Name: "Active",
|
||||
IsActive: true,
|
||||
},
|
||||
{
|
||||
ID: 3,
|
||||
Name: "Inactive",
|
||||
IsActive: true,
|
||||
},
|
||||
}
|
||||
|
||||
func (MasterStatusesSeeder) Seed(conn *gorm.DB) error {
|
||||
for _, row := range masterStatuses {
|
||||
if err := conn.Create(&row).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (MasterStatusesSeeder) Count(conn *gorm.DB) (int, error) {
|
||||
var count int64
|
||||
if err := conn.Model(&entity.MasterStatuses{}).Count(&count).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return int(count), nil
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"campaign-pool-be/app/module/activity_logs/repository"
|
||||
"campaign-pool-be/app/module/activity_logs/request"
|
||||
"campaign-pool-be/app/module/activity_logs/response"
|
||||
"campaign-pool-be/app/module/articles/service"
|
||||
usersRepository "campaign-pool-be/app/module/users/repository"
|
||||
"campaign-pool-be/utils/paginator"
|
||||
utilSvc "campaign-pool-be/utils/service"
|
||||
|
|
@ -15,9 +16,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
|
||||
|
|
@ -31,12 +33,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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -78,6 +81,12 @@ func (_i *activityLogsService) Save(req request.ActivityLogsCreateRequest, authT
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// update article
|
||||
err = _i.ArticleService.UpdateActivityCount(*req.ArticleId, req.ActivityTypeId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
package advertisement
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/module/advertisement/controller"
|
||||
"campaign-pool-be/app/module/advertisement/repository"
|
||||
"campaign-pool-be/app/module/advertisement/service"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"go.uber.org/fx"
|
||||
)
|
||||
|
||||
// struct of AdvertisementRouter
|
||||
type AdvertisementRouter struct {
|
||||
App fiber.Router
|
||||
Controller *controller.Controller
|
||||
}
|
||||
|
||||
// register bulky of Advertisement module
|
||||
var NewAdvertisementModule = fx.Options(
|
||||
// register repository of Advertisement module
|
||||
fx.Provide(repository.NewAdvertisementRepository),
|
||||
|
||||
// register service of Advertisement module
|
||||
fx.Provide(service.NewAdvertisementService),
|
||||
|
||||
// register controller of Advertisement module
|
||||
fx.Provide(controller.NewController),
|
||||
|
||||
// register router of Advertisement module
|
||||
fx.Provide(NewAdvertisementRouter),
|
||||
)
|
||||
|
||||
// init AdvertisementRouter
|
||||
func NewAdvertisementRouter(fiber *fiber.App, controller *controller.Controller) *AdvertisementRouter {
|
||||
return &AdvertisementRouter{
|
||||
App: fiber,
|
||||
Controller: controller,
|
||||
}
|
||||
}
|
||||
|
||||
// register routes of Advertisement module
|
||||
func (_i *AdvertisementRouter) RegisterAdvertisementRoutes() {
|
||||
// define controllers
|
||||
advertisementController := _i.Controller.Advertisement
|
||||
|
||||
// define routes
|
||||
_i.App.Route("/advertisement", func(router fiber.Router) {
|
||||
router.Get("/", advertisementController.All)
|
||||
router.Get("/:id", advertisementController.Show)
|
||||
router.Post("/", advertisementController.Save)
|
||||
router.Post("/upload/:id", advertisementController.Upload)
|
||||
router.Get("/viewer/:filename", advertisementController.Viewer)
|
||||
router.Put("/:id", advertisementController.Update)
|
||||
router.Put("/publish/:id", advertisementController.UpdatePublish)
|
||||
router.Delete("/:id", advertisementController.Delete)
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,284 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/module/advertisement/request"
|
||||
"campaign-pool-be/app/module/advertisement/service"
|
||||
"campaign-pool-be/utils/paginator"
|
||||
"strconv"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
utilRes "campaign-pool-be/utils/response"
|
||||
utilVal "campaign-pool-be/utils/validator"
|
||||
)
|
||||
|
||||
type advertisementController struct {
|
||||
advertisementService service.AdvertisementService
|
||||
Log zerolog.Logger
|
||||
}
|
||||
|
||||
type AdvertisementController interface {
|
||||
All(c *fiber.Ctx) error
|
||||
Show(c *fiber.Ctx) error
|
||||
Save(c *fiber.Ctx) error
|
||||
Upload(c *fiber.Ctx) error
|
||||
Update(c *fiber.Ctx) error
|
||||
UpdatePublish(c *fiber.Ctx) error
|
||||
Delete(c *fiber.Ctx) error
|
||||
Viewer(c *fiber.Ctx) error
|
||||
}
|
||||
|
||||
func NewAdvertisementController(advertisementService service.AdvertisementService, log zerolog.Logger) AdvertisementController {
|
||||
return &advertisementController{
|
||||
advertisementService: advertisementService,
|
||||
Log: log,
|
||||
}
|
||||
}
|
||||
|
||||
// All get all Advertisement
|
||||
// @Summary Get all Advertisement
|
||||
// @Description API for getting all Advertisement
|
||||
// @Tags Advertisement
|
||||
// @Security Bearer
|
||||
// @Param req query request.AdvertisementQueryRequest false "query parameters"
|
||||
// @Param req query paginator.Pagination false "pagination parameters"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /advertisement [get]
|
||||
func (_i *advertisementController) All(c *fiber.Ctx) error {
|
||||
paginate, err := paginator.Paginate(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reqContext := request.AdvertisementQueryRequestContext{
|
||||
Title: c.Query("title"),
|
||||
Description: c.Query("description"),
|
||||
RedirectLink: c.Query("redirectLink"),
|
||||
Placement: c.Query("placement"),
|
||||
StatusId: c.Query("statusId"),
|
||||
}
|
||||
req := reqContext.ToParamRequest()
|
||||
req.Pagination = paginate
|
||||
|
||||
advertisementData, paging, err := _i.advertisementService.All(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"Advertisement list successfully retrieved"},
|
||||
Data: advertisementData,
|
||||
Meta: paging,
|
||||
})
|
||||
}
|
||||
|
||||
// Show get one Advertisement
|
||||
// @Summary Get one Advertisement
|
||||
// @Description API for getting one Advertisement
|
||||
// @Tags Advertisement
|
||||
// @Security Bearer
|
||||
// @Param id path int true "Advertisement ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /advertisement/{id} [get]
|
||||
func (_i *advertisementController) Show(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
advertisementData, err := _i.advertisementService.Show(uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"Advertisement successfully retrieved"},
|
||||
Data: advertisementData,
|
||||
})
|
||||
}
|
||||
|
||||
// Save create Advertisement
|
||||
// @Summary Create Advertisement
|
||||
// @Description API for create Advertisement
|
||||
// @Tags Advertisement
|
||||
// @Security Bearer
|
||||
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
||||
// @Param payload body request.AdvertisementCreateRequest true "Required payload"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /advertisement [post]
|
||||
func (_i *advertisementController) Save(c *fiber.Ctx) error {
|
||||
req := new(request.AdvertisementCreateRequest)
|
||||
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dataResult, err := _i.advertisementService.Save(*req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"Advertisement successfully created"},
|
||||
Data: dataResult,
|
||||
})
|
||||
}
|
||||
|
||||
// Upload Advertisement
|
||||
// @Summary Upload Advertisement
|
||||
// @Description API for Upload File Advertisement
|
||||
// @Tags Advertisement
|
||||
// @Security Bearer
|
||||
// @Produce json
|
||||
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||
// @Param file formData file true "Upload file" multiple false
|
||||
// @Param id path int true "Advertisement ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /advertisement/upload/{id} [post]
|
||||
func (_i *advertisementController) Upload(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = _i.advertisementService.Upload(c, uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"Advertisement successfully upload"},
|
||||
})
|
||||
}
|
||||
|
||||
// Update update Advertisement
|
||||
// @Summary update Advertisement
|
||||
// @Description API for update Advertisement
|
||||
// @Tags Advertisement
|
||||
// @Security Bearer
|
||||
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||
// @Param payload body request.AdvertisementUpdateRequest true "Required payload"
|
||||
// @Param id path int true "Advertisement ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /advertisement/{id} [put]
|
||||
func (_i *advertisementController) Update(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req := new(request.AdvertisementUpdateRequest)
|
||||
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = _i.advertisementService.Update(uint(id), *req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"Advertisement successfully updated"},
|
||||
})
|
||||
}
|
||||
|
||||
// UpdatePublish Advertisement
|
||||
// @Summary Update Publish Advertisement
|
||||
// @Description API for Update Publish Advertisement
|
||||
// @Tags Advertisement
|
||||
// @Security Bearer
|
||||
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
||||
// @Param id path int true "Advertisement ID"
|
||||
// @Param isPublish query bool true "Advertisement Publish Status"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /advertisement/publish/{id} [put]
|
||||
func (_i *advertisementController) UpdatePublish(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
isPublish, err := strconv.ParseBool(c.Query("isPublish"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = _i.advertisementService.UpdatePublish(uint(id), isPublish)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"Advertisement successfully publish updated"},
|
||||
})
|
||||
}
|
||||
|
||||
// Delete delete Advertisement
|
||||
// @Summary delete Advertisement
|
||||
// @Description API for delete Advertisement
|
||||
// @Tags Advertisement
|
||||
// @Security Bearer
|
||||
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||
// @Param id path int true "Advertisement ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /advertisement/{id} [delete]
|
||||
func (_i *advertisementController) Delete(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = _i.advertisementService.Delete(uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"Advertisement successfully deleted"},
|
||||
})
|
||||
}
|
||||
|
||||
// Viewer Advertisement
|
||||
// @Summary Viewer Advertisement
|
||||
// @Description API for Viewer Advertisement
|
||||
// @Tags Advertisement
|
||||
// @Security Bearer
|
||||
// @Param filename path string true "Content File Name"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /advertisement/viewer/{filename} [get]
|
||||
func (_i *advertisementController) Viewer(c *fiber.Ctx) error {
|
||||
return _i.advertisementService.Viewer(c)
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/module/advertisement/service"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
Advertisement AdvertisementController
|
||||
}
|
||||
|
||||
func NewController(AdvertisementService service.AdvertisementService, log zerolog.Logger) *Controller {
|
||||
return &Controller{
|
||||
Advertisement: NewAdvertisementController(AdvertisementService, log),
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package mapper
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/database/entity"
|
||||
res "campaign-pool-be/app/module/advertisement/response"
|
||||
)
|
||||
|
||||
func AdvertisementResponseMapper(advertisementReq *entity.Advertisement, host string) (advertisementRes *res.AdvertisementResponse) {
|
||||
if advertisementReq != nil {
|
||||
advertisementRes = &res.AdvertisementResponse{
|
||||
ID: advertisementReq.ID,
|
||||
Title: advertisementReq.Title,
|
||||
Description: advertisementReq.Description,
|
||||
RedirectLink: advertisementReq.RedirectLink,
|
||||
Placement: advertisementReq.Placement,
|
||||
StatusId: advertisementReq.StatusId,
|
||||
IsActive: advertisementReq.IsActive,
|
||||
IsPublish: advertisementReq.IsPublish,
|
||||
CreatedAt: advertisementReq.CreatedAt,
|
||||
UpdatedAt: advertisementReq.UpdatedAt,
|
||||
}
|
||||
|
||||
if advertisementReq.ContentFilePath != nil {
|
||||
advertisementRes.ContentFileUrl = host + "/advertisement/viewer/" + *advertisementReq.ContentFileName
|
||||
}
|
||||
}
|
||||
return advertisementRes
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
package repository
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/database"
|
||||
"campaign-pool-be/app/database/entity"
|
||||
"campaign-pool-be/app/module/advertisement/request"
|
||||
"campaign-pool-be/utils/paginator"
|
||||
utilSvc "campaign-pool-be/utils/service"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
type advertisementRepository struct {
|
||||
DB *database.Database
|
||||
Log zerolog.Logger
|
||||
}
|
||||
|
||||
// AdvertisementRepository define interface of IAdvertisementRepository
|
||||
type AdvertisementRepository interface {
|
||||
GetAll(req request.AdvertisementQueryRequest) (advertisements []*entity.Advertisement, paging paginator.Pagination, err error)
|
||||
FindOne(id uint) (advertisement *entity.Advertisement, err error)
|
||||
FindByFilename(contentFilename string) (advertisement *entity.Advertisement, err error)
|
||||
Create(advertisement *entity.Advertisement) (advertisementReturn *entity.Advertisement, err error)
|
||||
Update(id uint, advertisement *entity.Advertisement) (err error)
|
||||
Delete(id uint) (err error)
|
||||
}
|
||||
|
||||
func NewAdvertisementRepository(db *database.Database, logger zerolog.Logger) AdvertisementRepository {
|
||||
return &advertisementRepository{
|
||||
DB: db,
|
||||
Log: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// implement interface of IAdvertisementRepository
|
||||
func (_i *advertisementRepository) GetAll(req request.AdvertisementQueryRequest) (advertisements []*entity.Advertisement, paging paginator.Pagination, err error) {
|
||||
var count int64
|
||||
|
||||
query := _i.DB.DB.Model(&entity.Advertisement{})
|
||||
|
||||
query = query.Where("is_active = ?", true)
|
||||
|
||||
if req.Title != nil && *req.Title != "" {
|
||||
title := strings.ToLower(*req.Title)
|
||||
query = query.Where("LOWER(title) LIKE ?", "%"+strings.ToLower(title)+"%")
|
||||
}
|
||||
if req.Description != nil && *req.Description != "" {
|
||||
description := strings.ToLower(*req.Description)
|
||||
query = query.Where("LOWER(description) LIKE ?", "%"+strings.ToLower(description)+"%")
|
||||
}
|
||||
if req.RedirectLink != nil && *req.RedirectLink != "" {
|
||||
redirectLink := strings.ToLower(*req.RedirectLink)
|
||||
query = query.Where("LOWER(redirect_link) LIKE ?", "%"+strings.ToLower(redirectLink)+"%")
|
||||
}
|
||||
if req.Placement != nil && *req.Placement != "" {
|
||||
placement := strings.ToLower(*req.Placement)
|
||||
query = query.Where("LOWER(placement) LIKE ?", "%"+strings.ToLower(placement)+"%")
|
||||
}
|
||||
if req.StatusId != nil {
|
||||
query = query.Where("status_id = ?", req.StatusId)
|
||||
}
|
||||
query.Count(&count)
|
||||
|
||||
if req.Pagination.SortBy != "" {
|
||||
direction := "ASC"
|
||||
if req.Pagination.Sort == "desc" {
|
||||
direction = "DESC"
|
||||
}
|
||||
query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction))
|
||||
}
|
||||
|
||||
req.Pagination.Count = count
|
||||
req.Pagination = paginator.Paging(req.Pagination)
|
||||
|
||||
err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&advertisements).Error
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
paging = *req.Pagination
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *advertisementRepository) FindOne(id uint) (advertisement *entity.Advertisement, err error) {
|
||||
query := _i.DB.DB.Where("id = ?", id)
|
||||
|
||||
if err := query.First(&advertisement).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return advertisement, nil
|
||||
}
|
||||
|
||||
func (_i *advertisementRepository) FindByFilename(contentFilename string) (advertisement *entity.Advertisement, err error) {
|
||||
query := _i.DB.DB.Where("content_file_name = ?", contentFilename)
|
||||
|
||||
if err := query.First(&advertisement).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return advertisement, nil
|
||||
}
|
||||
|
||||
func (_i *advertisementRepository) Create(advertisement *entity.Advertisement) (advertisementReturn *entity.Advertisement, err error) {
|
||||
result := _i.DB.DB.Create(advertisement)
|
||||
return advertisement, result.Error
|
||||
}
|
||||
|
||||
func (_i *advertisementRepository) Update(id uint, advertisement *entity.Advertisement) (err error) {
|
||||
advertisementMap, err := utilSvc.StructToMap(advertisement)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
query := _i.DB.DB.Model(&entity.Advertisement{}).Where(&entity.Advertisement{ID: id})
|
||||
return query.Updates(advertisementMap).Error
|
||||
}
|
||||
|
||||
func (_i *advertisementRepository) Delete(id uint) error {
|
||||
query := _i.DB.DB.Model(&entity.Advertisement{}).Where("id = ?", id)
|
||||
return query.Delete(&entity.Advertisement{}).Error
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
package request
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/database/entity"
|
||||
"campaign-pool-be/utils/paginator"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type AdvertisementGeneric interface {
|
||||
ToEntity()
|
||||
}
|
||||
|
||||
type AdvertisementQueryRequest struct {
|
||||
Title *string `json:"title"`
|
||||
Description *string `json:"description"`
|
||||
RedirectLink *string `json:"redirectLink"`
|
||||
Placement *string `json:"placement"`
|
||||
IsPublish *bool `json:"isPublish"`
|
||||
StatusId *int `json:"statusId"`
|
||||
Pagination *paginator.Pagination `json:"pagination"`
|
||||
}
|
||||
|
||||
type AdvertisementCreateRequest struct {
|
||||
Title string `json:"title" validate:"required"`
|
||||
Description string `json:"description" validate:"required"`
|
||||
RedirectLink string `json:"redirectLink" validate:"required"`
|
||||
Placement string `json:"placement" validate:"required"`
|
||||
}
|
||||
|
||||
func (req AdvertisementCreateRequest) ToEntity() *entity.Advertisement {
|
||||
return &entity.Advertisement{
|
||||
Title: req.Title,
|
||||
Description: req.Description,
|
||||
RedirectLink: req.RedirectLink,
|
||||
Placement: req.Placement,
|
||||
StatusId: 1,
|
||||
IsPublish: true,
|
||||
IsActive: true,
|
||||
}
|
||||
}
|
||||
|
||||
type AdvertisementUpdateRequest struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
Title string `json:"title" validate:"required"`
|
||||
Description string `json:"description" validate:"required"`
|
||||
RedirectLink string `json:"redirectLink" validate:"required"`
|
||||
Placement string `json:"placement" validate:"required"`
|
||||
}
|
||||
|
||||
func (req AdvertisementUpdateRequest) ToEntity() *entity.Advertisement {
|
||||
return &entity.Advertisement{
|
||||
ID: req.ID,
|
||||
Title: req.Title,
|
||||
Description: req.Description,
|
||||
RedirectLink: req.RedirectLink,
|
||||
Placement: req.Placement,
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
type AdvertisementQueryRequestContext struct {
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
RedirectLink string `json:"redirectLink"`
|
||||
Placement string `json:"placement"`
|
||||
StatusId string `json:"statusId"`
|
||||
IsPublish string `json:"isPublish"`
|
||||
}
|
||||
|
||||
func (req AdvertisementQueryRequestContext) ToParamRequest() AdvertisementQueryRequest {
|
||||
var request AdvertisementQueryRequest
|
||||
|
||||
if title := req.Title; title != "" {
|
||||
request.Title = &title
|
||||
}
|
||||
if description := req.Description; description != "" {
|
||||
request.Description = &description
|
||||
}
|
||||
if redirectLink := req.RedirectLink; redirectLink != "" {
|
||||
request.RedirectLink = &redirectLink
|
||||
}
|
||||
if placement := req.Placement; placement != "" {
|
||||
request.Placement = &placement
|
||||
}
|
||||
if isPublishStr := req.IsPublish; isPublishStr != "" {
|
||||
isPublish, err := strconv.ParseBool(isPublishStr)
|
||||
if err == nil {
|
||||
request.IsPublish = &isPublish
|
||||
}
|
||||
}
|
||||
if statusIdStr := req.StatusId; statusIdStr != "" {
|
||||
statusId, err := strconv.Atoi(statusIdStr)
|
||||
if err == nil {
|
||||
request.StatusId = &statusId
|
||||
}
|
||||
}
|
||||
|
||||
return request
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package response
|
||||
|
||||
import "time"
|
||||
|
||||
type AdvertisementResponse struct {
|
||||
ID uint `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
RedirectLink string `json:"redirectLink"`
|
||||
ContentFileUrl string `json:"contentFileUrl"`
|
||||
Placement string `json:"placement"`
|
||||
StatusId int `json:"statusId"`
|
||||
IsPublish bool `json:"isPublish"`
|
||||
IsActive bool `json:"isActive"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
|
@ -0,0 +1,264 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/database/entity"
|
||||
"campaign-pool-be/app/module/advertisement/mapper"
|
||||
"campaign-pool-be/app/module/advertisement/repository"
|
||||
"campaign-pool-be/app/module/advertisement/request"
|
||||
"campaign-pool-be/app/module/advertisement/response"
|
||||
usersRepository "campaign-pool-be/app/module/users/repository"
|
||||
config "campaign-pool-be/config/config"
|
||||
minioStorage "campaign-pool-be/config/config"
|
||||
"campaign-pool-be/utils/paginator"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math/rand"
|
||||
"mime"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
// AdvertisementService
|
||||
type advertisementService struct {
|
||||
Repo repository.AdvertisementRepository
|
||||
UsersRepo usersRepository.UsersRepository
|
||||
Log zerolog.Logger
|
||||
Cfg *config.Config
|
||||
MinioStorage *minioStorage.MinioStorage
|
||||
}
|
||||
|
||||
// AdvertisementService define interface of IAdvertisementService
|
||||
type AdvertisementService interface {
|
||||
All(req request.AdvertisementQueryRequest) (advertisement []*response.AdvertisementResponse, paging paginator.Pagination, err error)
|
||||
Show(id uint) (advertisement *response.AdvertisementResponse, err error)
|
||||
Save(req request.AdvertisementCreateRequest) (advertisement *entity.Advertisement, err error)
|
||||
Upload(c *fiber.Ctx, id uint) (err error)
|
||||
Update(id uint, req request.AdvertisementUpdateRequest) (err error)
|
||||
UpdatePublish(id uint, isPublish bool) (err error)
|
||||
Delete(id uint) error
|
||||
Viewer(c *fiber.Ctx) (err error)
|
||||
}
|
||||
|
||||
// NewAdvertisementService init AdvertisementService
|
||||
func NewAdvertisementService(repo repository.AdvertisementRepository, minioStorage *minioStorage.MinioStorage, usersRepo usersRepository.UsersRepository, log zerolog.Logger, cfg *config.Config) AdvertisementService {
|
||||
|
||||
return &advertisementService{
|
||||
Repo: repo,
|
||||
UsersRepo: usersRepo,
|
||||
MinioStorage: minioStorage,
|
||||
Log: log,
|
||||
Cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// All implement interface of AdvertisementService
|
||||
func (_i *advertisementService) All(req request.AdvertisementQueryRequest) (advertisements []*response.AdvertisementResponse, paging paginator.Pagination, err error) {
|
||||
results, paging, err := _i.Repo.GetAll(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
host := _i.Cfg.App.Domain
|
||||
for _, result := range results {
|
||||
advertisements = append(advertisements, mapper.AdvertisementResponseMapper(result, host))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *advertisementService) Show(id uint) (advertisement *response.AdvertisementResponse, err error) {
|
||||
result, err := _i.Repo.FindOne(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
host := _i.Cfg.App.Domain
|
||||
return mapper.AdvertisementResponseMapper(result, host), nil
|
||||
}
|
||||
|
||||
func (_i *advertisementService) Save(req request.AdvertisementCreateRequest) (advertisement *entity.Advertisement, err error) {
|
||||
_i.Log.Info().Interface("data", req).Msg("")
|
||||
newReq := req.ToEntity()
|
||||
return _i.Repo.Create(newReq)
|
||||
}
|
||||
|
||||
func (_i *advertisementService) Upload(c *fiber.Ctx, id uint) (err error) {
|
||||
bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName
|
||||
|
||||
form, err := c.MultipartForm()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//filess := form.File["files"]
|
||||
|
||||
// Create minio connection.
|
||||
minioClient, err := _i.MinioStorage.ConnectMinio()
|
||||
|
||||
result, err := _i.Repo.FindOne(id)
|
||||
|
||||
if result == nil {
|
||||
// Return status 400. Id not found.
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"error": true,
|
||||
"msg": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// Return status 500 and minio connection error.
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"error": true,
|
||||
"msg": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
for _, files := range form.File {
|
||||
|
||||
_i.Log.Info().Str("timestamp", time.Now().
|
||||
Format(time.RFC3339)).Str("Service:Resource", "Uploader:: top").
|
||||
Interface("files", files).Msg("")
|
||||
|
||||
for _, fileHeader := range files {
|
||||
_i.Log.Info().Str("timestamp", time.Now().
|
||||
Format(time.RFC3339)).Str("Service:Resource", "Uploader:: loop").
|
||||
Interface("data", fileHeader).Msg("")
|
||||
|
||||
src, err := fileHeader.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
filename := filepath.Base(fileHeader.Filename)
|
||||
filename = strings.ReplaceAll(filename, " ", "")
|
||||
filenameWithoutExt := filepath.Clean(filename[:len(filename)-len(filepath.Ext(filename))])
|
||||
extension := filepath.Ext(fileHeader.Filename)[1:]
|
||||
|
||||
now := time.Now()
|
||||
rand.New(rand.NewSource(now.UnixNano()))
|
||||
randUniqueId := rand.Intn(1000000)
|
||||
|
||||
newFilenameWithoutExt := filenameWithoutExt + "_" + strconv.Itoa(randUniqueId)
|
||||
newFilename := newFilenameWithoutExt + "." + extension
|
||||
|
||||
objectName := fmt.Sprintf("advertisement/upload/%d/%d/%s", now.Year(), now.Month(), newFilename)
|
||||
|
||||
result.ContentFileName = &newFilename
|
||||
result.ContentFilePath = &objectName
|
||||
|
||||
err = _i.Repo.Update(id, result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Upload file ke MinIO
|
||||
_, err = minioClient.PutObject(context.Background(), bucketName, objectName, src, fileHeader.Size, minio.PutObjectOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *advertisementService) Update(id uint, req request.AdvertisementUpdateRequest) (err error) {
|
||||
_i.Log.Info().Interface("data", req).Msg("")
|
||||
newReq := req.ToEntity()
|
||||
return _i.Repo.Update(id, newReq)
|
||||
}
|
||||
|
||||
func (_i *advertisementService) UpdatePublish(id uint, isPublish bool) (err error) {
|
||||
_i.Log.Info().Str("timestamp", time.Now().
|
||||
Format(time.RFC3339)).Str("Service:Resource", "UpdatePublish").
|
||||
Interface("ids", id).Msg("")
|
||||
_i.Log.Info().Str("timestamp", time.Now().
|
||||
Format(time.RFC3339)).Str("Service:Resource", "UpdatePublish").
|
||||
Interface("isPublish", isPublish).Msg("")
|
||||
|
||||
result, err := _i.Repo.FindOne(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result.IsPublish = isPublish
|
||||
|
||||
_i.Log.Info().Str("timestamp", time.Now().
|
||||
Format(time.RFC3339)).Str("Service:Resource", "UpdatePublish").
|
||||
Interface("result", result).Msg("")
|
||||
|
||||
return _i.Repo.Update(id, result)
|
||||
}
|
||||
|
||||
func (_i *advertisementService) Delete(id uint) error {
|
||||
result, err := _i.Repo.FindOne(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result.IsActive = false
|
||||
return _i.Repo.Update(id, result)
|
||||
}
|
||||
|
||||
func (_i *advertisementService) Viewer(c *fiber.Ctx) (err error) {
|
||||
filename := c.Params("filename")
|
||||
result, err := _i.Repo.FindByFilename(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName
|
||||
objectName := *result.ContentFilePath
|
||||
|
||||
// Create minio connection.
|
||||
minioClient, err := _i.MinioStorage.ConnectMinio()
|
||||
if err != nil {
|
||||
// Return status 500 and minio connection error.
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"error": true,
|
||||
"msg": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
fileContent, err := minioClient.GetObject(ctx, bucketName, objectName, minio.GetObjectOptions{})
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
defer fileContent.Close()
|
||||
|
||||
// Tentukan Content-Type berdasarkan ekstensi file
|
||||
contentType := mime.TypeByExtension("." + getFileExtension(objectName))
|
||||
if contentType == "" {
|
||||
contentType = "application/octet-stream" // fallback jika tidak ada tipe MIME yang cocok
|
||||
}
|
||||
|
||||
c.Set("Content-Type", contentType)
|
||||
|
||||
if _, err := io.Copy(c.Response().BodyWriter(), fileContent); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getFileExtension(filename string) string {
|
||||
// split file name
|
||||
parts := strings.Split(filename, ".")
|
||||
|
||||
// jika tidak ada ekstensi, kembalikan string kosong
|
||||
if len(parts) == 1 || (len(parts) == 2 && parts[0] == "") {
|
||||
return ""
|
||||
}
|
||||
|
||||
// ambil ekstensi terakhir
|
||||
return parts[len(parts)-1]
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
package ai_chat
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/module/ai_chat/controller"
|
||||
"campaign-pool-be/app/module/ai_chat/repository"
|
||||
"campaign-pool-be/app/module/ai_chat/service"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"go.uber.org/fx"
|
||||
)
|
||||
|
||||
// struct of AIChatRouter
|
||||
type AIChatRouter struct {
|
||||
App fiber.Router
|
||||
Controller controller.AIChatController
|
||||
}
|
||||
|
||||
// register bulky of AI Chat module
|
||||
var NewAIChatModule = fx.Options(
|
||||
// register repository of AI Chat module
|
||||
fx.Provide(repository.NewAIChatRepository),
|
||||
|
||||
// register service of AI Chat module
|
||||
fx.Provide(service.NewAIChatService),
|
||||
|
||||
// register controller of AI Chat module
|
||||
fx.Provide(controller.NewAIChatController),
|
||||
|
||||
// register router of AI Chat module
|
||||
fx.Provide(NewAIChatRouter),
|
||||
)
|
||||
|
||||
// init AIChatRouter
|
||||
func NewAIChatRouter(fiber *fiber.App, controller controller.AIChatController) *AIChatRouter {
|
||||
return &AIChatRouter{
|
||||
App: fiber,
|
||||
Controller: controller,
|
||||
}
|
||||
}
|
||||
|
||||
// register routes of AI Chat module
|
||||
func (_i *AIChatRouter) RegisterAIChatRoutes() {
|
||||
// define controllers
|
||||
aiChatController := _i.Controller
|
||||
|
||||
// define routes
|
||||
_i.App.Route("/ai-chat", func(router fiber.Router) {
|
||||
// Sessions routes
|
||||
router.Get("/sessions", aiChatController.GetUserSessions)
|
||||
router.Get("/sessions/:id", aiChatController.GetSession)
|
||||
router.Post("/sessions", aiChatController.CreateSession)
|
||||
router.Put("/sessions/:id", aiChatController.UpdateSession)
|
||||
router.Delete("/sessions/:id", aiChatController.DeleteSession)
|
||||
|
||||
// Messages routes
|
||||
router.Get("/sessions/:id/messages", aiChatController.GetSessionMessages)
|
||||
router.Post("/sessions/messages", aiChatController.SendMessage)
|
||||
router.Put("/sessions/messages/:messageId", aiChatController.UpdateMessage)
|
||||
router.Delete("/sessions/messages/:messageId", aiChatController.DeleteMessage)
|
||||
|
||||
// Logs routes
|
||||
router.Get("/logs", aiChatController.GetUserLogs)
|
||||
router.Get("/logs/:id", aiChatController.GetLog)
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,438 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/module/ai_chat/request"
|
||||
"campaign-pool-be/app/module/ai_chat/service"
|
||||
"campaign-pool-be/utils/paginator"
|
||||
utilRes "campaign-pool-be/utils/response"
|
||||
utilVal "campaign-pool-be/utils/validator"
|
||||
"strconv"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
type aiChatController struct {
|
||||
aiChatService service.AIChatService
|
||||
Log zerolog.Logger
|
||||
}
|
||||
|
||||
type AIChatController interface {
|
||||
// AI Chat Sessions
|
||||
GetUserSessions(c *fiber.Ctx) error
|
||||
GetSession(c *fiber.Ctx) error
|
||||
CreateSession(c *fiber.Ctx) error
|
||||
UpdateSession(c *fiber.Ctx) error
|
||||
DeleteSession(c *fiber.Ctx) error
|
||||
|
||||
// AI Chat Messages
|
||||
GetSessionMessages(c *fiber.Ctx) error
|
||||
SendMessage(c *fiber.Ctx) error
|
||||
UpdateMessage(c *fiber.Ctx) error
|
||||
DeleteMessage(c *fiber.Ctx) error
|
||||
|
||||
// AI Chat Logs
|
||||
GetUserLogs(c *fiber.Ctx) error
|
||||
GetLog(c *fiber.Ctx) error
|
||||
}
|
||||
|
||||
func NewAIChatController(aiChatService service.AIChatService, log zerolog.Logger) AIChatController {
|
||||
return &aiChatController{
|
||||
aiChatService: aiChatService,
|
||||
Log: log,
|
||||
}
|
||||
}
|
||||
|
||||
// Get User Sessions
|
||||
// @Summary Get user AI chat sessions
|
||||
// @Description API for getting all AI chat sessions for authenticated user
|
||||
// @Tags AI Chat
|
||||
// @Security Bearer
|
||||
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
||||
// @Param req query request.AIChatSessionsQueryRequest false "query parameters"
|
||||
// @Param req query paginator.Pagination false "pagination parameters"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /ai-chat/sessions [get]
|
||||
func (_i *aiChatController) GetUserSessions(c *fiber.Ctx) error {
|
||||
paginate, err := paginator.Paginate(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authHeader := c.Get("Authorization")
|
||||
|
||||
reqContext := request.AIChatSessionsQueryRequestContext{
|
||||
IsActive: c.Query("isActive"),
|
||||
}
|
||||
req := reqContext.ToParamRequest()
|
||||
req.Pagination = paginate
|
||||
|
||||
sessionsData, paging, err := _i.aiChatService.GetUserSessions(authHeader, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"AI chat sessions successfully retrieved"},
|
||||
Data: sessionsData,
|
||||
Meta: paging,
|
||||
})
|
||||
}
|
||||
|
||||
// Get Session
|
||||
// @Summary Get one AI chat session
|
||||
// @Description API for getting one AI chat session
|
||||
// @Tags AI Chat
|
||||
// @Security Bearer
|
||||
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
||||
// @Param id path int true "Session ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /ai-chat/sessions/{id} [get]
|
||||
func (_i *aiChatController) GetSession(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authHeader := c.Get("Authorization")
|
||||
|
||||
sessionData, err := _i.aiChatService.GetSession(authHeader, uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"AI chat session successfully retrieved"},
|
||||
Data: sessionData,
|
||||
})
|
||||
}
|
||||
|
||||
// Create Session
|
||||
// @Summary Create AI chat session
|
||||
// @Description API for create AI chat session
|
||||
// @Tags AI Chat
|
||||
// @Security Bearer
|
||||
// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token"
|
||||
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
||||
// @Param payload body request.AIChatSessionsCreateRequest true "Required payload"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /ai-chat/sessions [post]
|
||||
func (_i *aiChatController) CreateSession(c *fiber.Ctx) error {
|
||||
req := new(request.AIChatSessionsCreateRequest)
|
||||
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authHeader := c.Get("Authorization")
|
||||
|
||||
dataResult, err := _i.aiChatService.CreateSession(authHeader, *req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"AI chat session successfully created"},
|
||||
Data: dataResult,
|
||||
})
|
||||
}
|
||||
|
||||
// Update Session
|
||||
// @Summary Update AI chat session
|
||||
// @Description API for update AI chat session
|
||||
// @Tags AI Chat
|
||||
// @Security Bearer
|
||||
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
||||
// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token"
|
||||
// @Param id path int true "Session ID"
|
||||
// @Param payload body request.AIChatSessionsUpdateRequest true "Required payload"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /ai-chat/sessions/{id} [put]
|
||||
func (_i *aiChatController) UpdateSession(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req := new(request.AIChatSessionsUpdateRequest)
|
||||
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authHeader := c.Get("Authorization")
|
||||
|
||||
err = _i.aiChatService.UpdateSession(authHeader, uint(id), *req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"AI chat session successfully updated"},
|
||||
})
|
||||
}
|
||||
|
||||
// Delete Session
|
||||
// @Summary Delete AI chat session
|
||||
// @Description API for delete AI chat session
|
||||
// @Tags AI Chat
|
||||
// @Security Bearer
|
||||
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
||||
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||
// @Param id path int true "Session ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /ai-chat/sessions/{id} [delete]
|
||||
func (_i *aiChatController) DeleteSession(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authHeader := c.Get("Authorization")
|
||||
|
||||
err = _i.aiChatService.DeleteSession(authHeader, uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"AI chat session successfully deleted"},
|
||||
})
|
||||
}
|
||||
|
||||
// Get Session Messages
|
||||
// @Summary Get AI chat session messages
|
||||
// @Description API for getting all messages in an AI chat session
|
||||
// @Tags AI Chat
|
||||
// @Security Bearer
|
||||
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
||||
// @Param id path int true "Session ID"
|
||||
// @Param req query request.AIChatMessagesQueryRequest false "query parameters"
|
||||
// @Param req query paginator.Pagination false "pagination parameters"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /ai-chat/sessions/{id}/messages [get]
|
||||
func (_i *aiChatController) GetSessionMessages(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
paginate, err := paginator.Paginate(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authHeader := c.Get("Authorization")
|
||||
|
||||
reqContext := request.AIChatMessagesQueryRequestContext{}
|
||||
req := reqContext.ToParamRequest()
|
||||
req.Pagination = paginate
|
||||
|
||||
messagesData, paging, err := _i.aiChatService.GetSessionMessages(authHeader, uint(id), req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"AI chat messages successfully retrieved"},
|
||||
Data: messagesData,
|
||||
Meta: paging,
|
||||
})
|
||||
}
|
||||
|
||||
// Send Message
|
||||
// @Summary Send message to AI chat session
|
||||
// @Description API for sending a message to an AI chat session
|
||||
// @Tags AI Chat
|
||||
// @Security Bearer
|
||||
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
||||
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||
// @Param payload body request.AIChatMessagesCreateRequest true "Required payload"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /ai-chat/sessions/messages [post]
|
||||
func (_i *aiChatController) SendMessage(c *fiber.Ctx) error {
|
||||
req := new(request.AIChatMessagesCreateRequest)
|
||||
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authHeader := c.Get("Authorization")
|
||||
|
||||
dataResult, err := _i.aiChatService.SendMessage(authHeader, *req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"Message successfully sent"},
|
||||
Data: dataResult,
|
||||
})
|
||||
}
|
||||
|
||||
// Update Message
|
||||
// @Summary Update AI chat message
|
||||
// @Description API for update AI chat message
|
||||
// @Tags AI Chat
|
||||
// @Security Bearer
|
||||
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
||||
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||
// @Param messageId path int true "Message ID"
|
||||
// @Param payload body request.AIChatMessagesUpdateRequest true "Required payload"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /ai-chat/sessions/messages/{messageId} [put]
|
||||
func (_i *aiChatController) UpdateMessage(c *fiber.Ctx) error {
|
||||
messageId, err := strconv.ParseUint(c.Params("messageId"), 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req := new(request.AIChatMessagesUpdateRequest)
|
||||
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authHeader := c.Get("Authorization")
|
||||
|
||||
err = _i.aiChatService.UpdateMessage(authHeader, uint(messageId), *req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"AI chat message successfully updated"},
|
||||
})
|
||||
}
|
||||
|
||||
// Delete Message
|
||||
// @Summary Delete AI chat message
|
||||
// @Description API for delete AI chat message
|
||||
// @Tags AI Chat
|
||||
// @Security Bearer
|
||||
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
||||
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||
// @Param messageId path int true "Message ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /ai-chat/sessions/messages/{messageId} [delete]
|
||||
func (_i *aiChatController) DeleteMessage(c *fiber.Ctx) error {
|
||||
messageId, err := strconv.ParseUint(c.Params("messageId"), 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authHeader := c.Get("Authorization")
|
||||
|
||||
err = _i.aiChatService.DeleteMessage(authHeader, uint(messageId))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"AI chat message successfully deleted"},
|
||||
})
|
||||
}
|
||||
|
||||
// Get User Logs
|
||||
// @Summary Get user AI chat logs
|
||||
// @Description API for getting all AI chat logs for authenticated user
|
||||
// @Tags AI Chat
|
||||
// @Security Bearer
|
||||
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
||||
// @Param req query request.AIChatLogsQueryRequest false "query parameters"
|
||||
// @Param req query paginator.Pagination false "pagination parameters"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /ai-chat/logs [get]
|
||||
func (_i *aiChatController) GetUserLogs(c *fiber.Ctx) error {
|
||||
paginate, err := paginator.Paginate(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authHeader := c.Get("Authorization")
|
||||
|
||||
reqContext := request.AIChatLogsQueryRequestContext{
|
||||
LogType: c.Query("logType"),
|
||||
}
|
||||
req := reqContext.ToParamRequest()
|
||||
req.Pagination = paginate
|
||||
|
||||
logsData, paging, err := _i.aiChatService.GetUserLogs(authHeader, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"AI chat logs successfully retrieved"},
|
||||
Data: logsData,
|
||||
Meta: paging,
|
||||
})
|
||||
}
|
||||
|
||||
// Get Log
|
||||
// @Summary Get one AI chat log
|
||||
// @Description API for getting one AI chat log
|
||||
// @Tags AI Chat
|
||||
// @Security Bearer
|
||||
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
||||
// @Param id path int true "Log ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /ai-chat/logs/{id} [get]
|
||||
func (_i *aiChatController) GetLog(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authHeader := c.Get("Authorization")
|
||||
|
||||
logData, err := _i.aiChatService.GetLog(authHeader, uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"AI chat log successfully retrieved"},
|
||||
Data: logData,
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
package mapper
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/database/entity"
|
||||
"campaign-pool-be/app/module/ai_chat/response"
|
||||
)
|
||||
|
||||
func AIChatSessionsResponseMapper(session *entity.AIChatSessions) *response.AIChatSessionsResponse {
|
||||
result := &response.AIChatSessionsResponse{
|
||||
ID: session.ID,
|
||||
SessionID: session.SessionID,
|
||||
UserID: session.UserID,
|
||||
AgentID: session.AgentID,
|
||||
Title: session.Title,
|
||||
MessageCount: session.MessageCount,
|
||||
IsActive: session.IsActive,
|
||||
CreatedAt: session.CreatedAt,
|
||||
UpdatedAt: session.UpdatedAt,
|
||||
}
|
||||
|
||||
if session.User != nil {
|
||||
result.User = &response.UserBasicInfo{
|
||||
ID: session.User.ID,
|
||||
Username: session.User.Username,
|
||||
Fullname: session.User.Fullname,
|
||||
Email: session.User.Email,
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func AIChatMessagesResponseMapper(message *entity.AIChatMessages) *response.AIChatMessagesResponse {
|
||||
return &response.AIChatMessagesResponse{
|
||||
ID: message.ID,
|
||||
SessionID: message.SessionID,
|
||||
MessageType: message.MessageType,
|
||||
Content: message.Content,
|
||||
IsActive: message.IsActive,
|
||||
CreatedAt: message.CreatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func AIChatSessionWithMessagesResponseMapper(session *entity.AIChatSessions, messages []*entity.AIChatMessages) *response.AIChatSessionWithMessagesResponse {
|
||||
sessionResponse := AIChatSessionsResponseMapper(session)
|
||||
|
||||
var messagesResponse []*response.AIChatMessagesResponse
|
||||
for _, message := range messages {
|
||||
messagesResponse = append(messagesResponse, AIChatMessagesResponseMapper(message))
|
||||
}
|
||||
|
||||
return &response.AIChatSessionWithMessagesResponse{
|
||||
Session: sessionResponse,
|
||||
Messages: messagesResponse,
|
||||
}
|
||||
}
|
||||
|
||||
func AIChatLogsResponseMapper(log *entity.AIChatLogs) *response.AIChatLogsResponse {
|
||||
result := &response.AIChatLogsResponse{
|
||||
ID: log.ID,
|
||||
UserID: log.UserID,
|
||||
SessionID: log.SessionID,
|
||||
StartDate: log.StartDate,
|
||||
EndDate: log.EndDate,
|
||||
TotalDuration: log.TotalDuration,
|
||||
CreatedAt: log.CreatedAt,
|
||||
UpdatedAt: log.UpdatedAt,
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
|
@ -0,0 +1,198 @@
|
|||
package repository
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/database"
|
||||
"campaign-pool-be/app/database/entity"
|
||||
"campaign-pool-be/app/module/ai_chat/request"
|
||||
"campaign-pool-be/utils/paginator"
|
||||
)
|
||||
|
||||
type aiChatRepository struct {
|
||||
DB *database.Database
|
||||
}
|
||||
|
||||
type AIChatRepository interface {
|
||||
// AI Chat Sessions
|
||||
GetUserSessions(userId uint, req request.AIChatSessionsQueryRequest) (sessions []*entity.AIChatSessions, paging paginator.Pagination, err error)
|
||||
FindSessionByUserAndId(userId uint, sessionId uint) (session *entity.AIChatSessions, err error)
|
||||
FindSessionBySessionId(sessionId string) (session *entity.AIChatSessions, err error)
|
||||
CreateSession(session *entity.AIChatSessions) (result *entity.AIChatSessions, err error)
|
||||
UpdateSession(userId uint, sessionId uint, session *entity.AIChatSessions) (err error)
|
||||
DeleteSession(userId uint, sessionId uint) (err error)
|
||||
IncrementMessageCount(sessionId uint) (err error)
|
||||
|
||||
// AI Chat Messages
|
||||
GetSessionMessages(sessionId string, req request.AIChatMessagesQueryRequest) (messages []*entity.AIChatMessages, paging paginator.Pagination, err error)
|
||||
CreateMessage(message *entity.AIChatMessages) (result *entity.AIChatMessages, err error)
|
||||
UpdateMessage(messageId uint, message *entity.AIChatMessages) (err error)
|
||||
DeleteMessage(messageId uint) (err error)
|
||||
GetLastMessage(sessionId string) (message *entity.AIChatMessages, err error)
|
||||
|
||||
// AI Chat Logs
|
||||
GetUserLogs(userId uint, req request.AIChatLogsQueryRequest) (logs []*entity.AIChatLogs, paging paginator.Pagination, err error)
|
||||
FindLogByUserAndId(userId uint, logId uint) (log *entity.AIChatLogs, err error)
|
||||
}
|
||||
|
||||
func NewAIChatRepository(db *database.Database) AIChatRepository {
|
||||
return &aiChatRepository{
|
||||
DB: db,
|
||||
}
|
||||
}
|
||||
|
||||
// AI Chat Sessions methods
|
||||
func (_i *aiChatRepository) GetUserSessions(userId uint, req request.AIChatSessionsQueryRequest) (sessions []*entity.AIChatSessions, paging paginator.Pagination, err error) {
|
||||
query := _i.DB.DB.Model(&entity.AIChatSessions{}).Where("user_id = ?", userId)
|
||||
|
||||
// Apply filters
|
||||
if req.IsActive != nil {
|
||||
query = query.Where("is_active = ?", *req.IsActive)
|
||||
}
|
||||
|
||||
// Include user relationship
|
||||
// query = query.Preload("User")
|
||||
|
||||
// Order by updated_at desc (most recent first)
|
||||
query = query.Order("updated_at DESC")
|
||||
|
||||
// Apply pagination
|
||||
var count int64
|
||||
query.Count(&count)
|
||||
req.Pagination.Count = count
|
||||
req.Pagination = paginator.Paging(req.Pagination)
|
||||
|
||||
err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&sessions).Error
|
||||
paging = *req.Pagination
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *aiChatRepository) FindSessionByUserAndId(userId uint, sessionId uint) (session *entity.AIChatSessions, err error) {
|
||||
err = _i.DB.DB.Model(&entity.AIChatSessions{}).Where("user_id = ? AND id = ?", userId, sessionId).Preload("User").First(&session).Error
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *aiChatRepository) FindSessionBySessionId(sessionId string) (session *entity.AIChatSessions, err error) {
|
||||
err = _i.DB.DB.Model(&entity.AIChatSessions{}).Where("session_id = ?", sessionId).Preload("User").First(&session).Error
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *aiChatRepository) CreateSession(session *entity.AIChatSessions) (result *entity.AIChatSessions, err error) {
|
||||
err = _i.DB.DB.Create(session).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Reload with relationships
|
||||
err = _i.DB.DB.Model(&entity.AIChatSessions{}).Preload("User").First(&result, session.ID).Error
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *aiChatRepository) UpdateSession(userId uint, sessionId uint, session *entity.AIChatSessions) (err error) {
|
||||
err = _i.DB.DB.Model(&entity.AIChatSessions{}).Where("user_id = ? AND id = ?", userId, sessionId).Updates(session).Error
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *aiChatRepository) DeleteSession(userId uint, sessionId uint) (err error) {
|
||||
err = _i.DB.DB.Model(&entity.AIChatSessions{}).Where("user_id = ? AND id = ?", userId, sessionId).Delete(&entity.AIChatSessions{}).Error
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *aiChatRepository) IncrementMessageCount(sessionId uint) (err error) {
|
||||
err = _i.DB.DB.Exec("UPDATE ai_chat_sessions SET message_count = message_count + 1 WHERE id = ?", sessionId).Error
|
||||
return
|
||||
}
|
||||
|
||||
// AI Chat Messages methods
|
||||
func (_i *aiChatRepository) GetSessionMessages(sessionId string, req request.AIChatMessagesQueryRequest) (messages []*entity.AIChatMessages, paging paginator.Pagination, err error) {
|
||||
query := _i.DB.DB.Model(&entity.AIChatMessages{}).Where("session_id = ?", sessionId)
|
||||
|
||||
// Order by created_at asc (oldest first for chat)
|
||||
query = query.Order("created_at ASC")
|
||||
|
||||
// Apply pagination
|
||||
var count int64
|
||||
query.Count(&count)
|
||||
req.Pagination.Count = count
|
||||
req.Pagination = paginator.Paging(req.Pagination)
|
||||
|
||||
err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&messages).Error
|
||||
paging = *req.Pagination
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *aiChatRepository) CreateMessage(message *entity.AIChatMessages) (result *entity.AIChatMessages, err error) {
|
||||
err = _i.DB.DB.Create(message).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Reload
|
||||
err = _i.DB.DB.Model(&entity.AIChatMessages{}).First(&result, message.ID).Error
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *aiChatRepository) UpdateMessage(messageId uint, message *entity.AIChatMessages) (err error) {
|
||||
err = _i.DB.DB.Model(&entity.AIChatMessages{}).Where("id = ?", messageId).Updates(message).Error
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *aiChatRepository) DeleteMessage(messageId uint) (err error) {
|
||||
err = _i.DB.DB.Model(&entity.AIChatMessages{}).Where("id = ?", messageId).Delete(&entity.AIChatMessages{}).Error
|
||||
return
|
||||
}
|
||||
|
||||
// AI Chat Logs methods
|
||||
func (_i *aiChatRepository) GetUserLogs(userId uint, req request.AIChatLogsQueryRequest) (logs []*entity.AIChatLogs, paging paginator.Pagination, err error) {
|
||||
var count int64
|
||||
|
||||
query := _i.DB.DB.Model(&entity.AIChatLogs{}).Where("user_id = ?", userId)
|
||||
|
||||
// Note: AIChatLogs entity doesn't have LogType field, so we skip this filter
|
||||
// if req.LogType != nil && *req.LogType != "" {
|
||||
// query = query.Where("log_type = ?", *req.LogType)
|
||||
// }
|
||||
|
||||
// Count total records
|
||||
err = query.Count(&count).Error
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Apply pagination
|
||||
if req.Pagination != nil {
|
||||
query = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit)
|
||||
}
|
||||
|
||||
// Order by created_at desc (newest first)
|
||||
query = query.Order("created_at DESC")
|
||||
|
||||
err = query.Find(&logs).Error
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Set pagination info
|
||||
if req.Pagination != nil {
|
||||
paging = *req.Pagination
|
||||
paging.Count = count
|
||||
paging.TotalPage = int((count + int64(req.Pagination.Limit) - 1) / int64(req.Pagination.Limit))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *aiChatRepository) FindLogByUserAndId(userId uint, logId uint) (log *entity.AIChatLogs, err error) {
|
||||
query := _i.DB.DB.Model(&entity.AIChatLogs{}).Where("user_id = ? AND id = ?", userId, logId)
|
||||
|
||||
if err := query.First(&log).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *aiChatRepository) GetLastMessage(sessionId string) (message *entity.AIChatMessages, err error) {
|
||||
err = _i.DB.DB.Model(&entity.AIChatMessages{}).Where("session_id = ?", sessionId).Order("created_at DESC").First(&message).Error
|
||||
return
|
||||
}
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
package request
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/database/entity"
|
||||
"campaign-pool-be/utils/paginator"
|
||||
)
|
||||
|
||||
// AI Chat Sessions Request DTOs
|
||||
type AIChatSessionsQueryRequest struct {
|
||||
IsActive *bool `json:"isActive"`
|
||||
Pagination *paginator.Pagination `json:"pagination"`
|
||||
}
|
||||
|
||||
type AIChatSessionsCreateRequest struct {
|
||||
SessionID string `json:"sessionId" validate:"required"`
|
||||
Title string `json:"title" validate:"required,min=2,max=255"`
|
||||
AgentID string `json:"agentId" validate:"required"`
|
||||
}
|
||||
|
||||
func (req AIChatSessionsCreateRequest) ToEntity() *entity.AIChatSessions {
|
||||
return &entity.AIChatSessions{
|
||||
SessionID: req.SessionID,
|
||||
AgentID: req.AgentID,
|
||||
Title: req.Title,
|
||||
MessageCount: 0,
|
||||
IsActive: true,
|
||||
}
|
||||
}
|
||||
|
||||
type AIChatSessionsUpdateRequest struct {
|
||||
Title string `json:"title" validate:"required,min=2,max=255"`
|
||||
IsActive bool `json:"isActive"`
|
||||
}
|
||||
|
||||
func (req AIChatSessionsUpdateRequest) ToEntity() *entity.AIChatSessions {
|
||||
return &entity.AIChatSessions{
|
||||
Title: req.Title,
|
||||
IsActive: req.IsActive,
|
||||
}
|
||||
}
|
||||
|
||||
// AI Chat Messages Request DTOs
|
||||
type AIChatMessagesQueryRequest struct {
|
||||
Pagination *paginator.Pagination `json:"pagination"`
|
||||
}
|
||||
|
||||
type AIChatMessagesCreateRequest struct {
|
||||
SessionID string `json:"sessionId" validate:"required"`
|
||||
MessageType string `json:"messageType" validate:"required,oneof=user assistant"`
|
||||
Content string `json:"content" validate:"required,min=1"`
|
||||
}
|
||||
|
||||
func (req AIChatMessagesCreateRequest) ToEntity() *entity.AIChatMessages {
|
||||
return &entity.AIChatMessages{
|
||||
SessionID: req.SessionID,
|
||||
MessageType: req.MessageType,
|
||||
Content: req.Content,
|
||||
IsActive: true,
|
||||
}
|
||||
}
|
||||
|
||||
type AIChatMessagesUpdateRequest struct {
|
||||
Content string `json:"content" validate:"required,min=1"`
|
||||
}
|
||||
|
||||
func (req AIChatMessagesUpdateRequest) ToEntity() *entity.AIChatMessages {
|
||||
return &entity.AIChatMessages{
|
||||
Content: req.Content,
|
||||
}
|
||||
}
|
||||
|
||||
type AIChatSessionsQueryRequestContext struct {
|
||||
IsActive string `json:"isActive"`
|
||||
}
|
||||
|
||||
func (req AIChatSessionsQueryRequestContext) ToParamRequest() AIChatSessionsQueryRequest {
|
||||
var request AIChatSessionsQueryRequest
|
||||
|
||||
if isActiveStr := req.IsActive; isActiveStr != "" {
|
||||
isActive := isActiveStr == "true"
|
||||
request.IsActive = &isActive
|
||||
}
|
||||
|
||||
return request
|
||||
}
|
||||
|
||||
type AIChatMessagesQueryRequestContext struct {
|
||||
}
|
||||
|
||||
func (req AIChatMessagesQueryRequestContext) ToParamRequest() AIChatMessagesQueryRequest {
|
||||
var request AIChatMessagesQueryRequest
|
||||
|
||||
return request
|
||||
}
|
||||
|
||||
// AI Chat Logs Request DTOs
|
||||
type AIChatLogsQueryRequest struct {
|
||||
LogType *string `json:"logType"`
|
||||
Pagination *paginator.Pagination `json:"pagination"`
|
||||
}
|
||||
|
||||
type AIChatLogsQueryRequestContext struct {
|
||||
LogType string `json:"logType"`
|
||||
}
|
||||
|
||||
func (req AIChatLogsQueryRequestContext) ToParamRequest() AIChatLogsQueryRequest {
|
||||
var request AIChatLogsQueryRequest
|
||||
|
||||
if logType := req.LogType; logType != "" {
|
||||
request.LogType = &logType
|
||||
}
|
||||
|
||||
return request
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
package response
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// AI Chat Sessions Response DTOs
|
||||
type AIChatSessionsResponse struct {
|
||||
ID uint `json:"id"`
|
||||
SessionID string `json:"sessionId"`
|
||||
UserID uint `json:"userId"`
|
||||
AgentID string `json:"agentId"`
|
||||
Title string `json:"title"`
|
||||
MessageCount int `json:"messageCount"`
|
||||
IsActive bool `json:"isActive"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
|
||||
// Nested user info
|
||||
User *UserBasicInfo `json:"user,omitempty"`
|
||||
|
||||
// Last message preview
|
||||
LastMessage *AIChatMessagesResponse `json:"lastMessage,omitempty"`
|
||||
}
|
||||
|
||||
// AI Chat Messages Response DTOs
|
||||
type AIChatMessagesResponse struct {
|
||||
ID uint `json:"id"`
|
||||
SessionID string `json:"sessionId"`
|
||||
MessageType string `json:"messageType"`
|
||||
Content string `json:"content"`
|
||||
IsActive bool `json:"isActive"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
type UserBasicInfo struct {
|
||||
ID uint `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Fullname string `json:"fullname"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
// Session with messages
|
||||
type AIChatSessionWithMessagesResponse struct {
|
||||
Session *AIChatSessionsResponse `json:"session"`
|
||||
Messages []*AIChatMessagesResponse `json:"messages"`
|
||||
Pagination interface{} `json:"pagination"`
|
||||
}
|
||||
|
||||
// AI Chat Logs Response DTOs
|
||||
type AIChatLogsResponse struct {
|
||||
ID uint `json:"id"`
|
||||
UserID uint `json:"userId"`
|
||||
SessionID uint `json:"sessionId"`
|
||||
StartDate time.Time `json:"startDate"`
|
||||
EndDate *time.Time `json:"endDate"`
|
||||
TotalDuration int64 `json:"totalDuration"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
|
||||
// Nested user info
|
||||
User *UserBasicInfo `json:"user,omitempty"`
|
||||
}
|
||||
|
|
@ -0,0 +1,229 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/module/ai_chat/mapper"
|
||||
"campaign-pool-be/app/module/ai_chat/repository"
|
||||
"campaign-pool-be/app/module/ai_chat/request"
|
||||
"campaign-pool-be/app/module/ai_chat/response"
|
||||
usersRepository "campaign-pool-be/app/module/users/repository"
|
||||
"campaign-pool-be/utils/paginator"
|
||||
utilSvc "campaign-pool-be/utils/service"
|
||||
"errors"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
type aiChatService struct {
|
||||
Repo repository.AIChatRepository
|
||||
UsersRepo usersRepository.UsersRepository
|
||||
Log zerolog.Logger
|
||||
}
|
||||
|
||||
type AIChatService interface {
|
||||
// Sessions
|
||||
GetUserSessions(authToken string, req request.AIChatSessionsQueryRequest) (sessions []*response.AIChatSessionsResponse, paging paginator.Pagination, err error)
|
||||
GetSession(authToken string, id uint) (session *response.AIChatSessionsResponse, err error)
|
||||
CreateSession(authToken string, req request.AIChatSessionsCreateRequest) (session *response.AIChatSessionsResponse, err error)
|
||||
UpdateSession(authToken string, id uint, req request.AIChatSessionsUpdateRequest) (err error)
|
||||
DeleteSession(authToken string, id uint) error
|
||||
ArchiveSession(authToken string, id uint) error
|
||||
|
||||
// Messages
|
||||
GetSessionMessages(authToken string, sessionId uint, req request.AIChatMessagesQueryRequest) (messages []*response.AIChatMessagesResponse, paging paginator.Pagination, err error)
|
||||
SendMessage(authToken string, req request.AIChatMessagesCreateRequest) (message *response.AIChatMessagesResponse, err error)
|
||||
UpdateMessage(authToken string, messageId uint, req request.AIChatMessagesUpdateRequest) (err error)
|
||||
DeleteMessage(authToken string, messageId uint) error
|
||||
|
||||
// Logs
|
||||
GetUserLogs(authToken string, req request.AIChatLogsQueryRequest) (logs []*response.AIChatLogsResponse, paging paginator.Pagination, err error)
|
||||
GetLog(authToken string, logId uint) (log *response.AIChatLogsResponse, err error)
|
||||
}
|
||||
|
||||
func NewAIChatService(repo repository.AIChatRepository, usersRepo usersRepository.UsersRepository, log zerolog.Logger) AIChatService {
|
||||
return &aiChatService{
|
||||
Repo: repo,
|
||||
UsersRepo: usersRepo,
|
||||
Log: log,
|
||||
}
|
||||
}
|
||||
|
||||
// Sessions methods
|
||||
func (_i *aiChatService) GetUserSessions(authToken string, req request.AIChatSessionsQueryRequest) (sessions []*response.AIChatSessionsResponse, paging paginator.Pagination, err error) {
|
||||
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||
|
||||
results, paging, err := _i.Repo.GetUserSessions(userInfo.ID, req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, result := range results {
|
||||
sessions = append(sessions, mapper.AIChatSessionsResponseMapper(result))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *aiChatService) GetSession(authToken string, id uint) (session *response.AIChatSessionsResponse, err error) {
|
||||
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||
|
||||
result, err := _i.Repo.FindSessionByUserAndId(userInfo.ID, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mapper.AIChatSessionsResponseMapper(result), nil
|
||||
}
|
||||
|
||||
func (_i *aiChatService) CreateSession(authToken string, req request.AIChatSessionsCreateRequest) (session *response.AIChatSessionsResponse, err error) {
|
||||
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||
|
||||
entity := req.ToEntity()
|
||||
entity.UserID = userInfo.ID
|
||||
|
||||
_i.Log.Info().Interface("data", entity).Msg("Create AI chat session")
|
||||
|
||||
result, err := _i.Repo.CreateSession(entity)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mapper.AIChatSessionsResponseMapper(result), nil
|
||||
}
|
||||
|
||||
func (_i *aiChatService) UpdateSession(authToken string, id uint, req request.AIChatSessionsUpdateRequest) (err error) {
|
||||
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||
_i.Log.Info().Interface("data", req).Msg("Updating AI chat session")
|
||||
|
||||
// Check if session exists and belongs to user
|
||||
existing, err := _i.Repo.FindSessionByUserAndId(userInfo.ID, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if existing == nil {
|
||||
return errors.New("AI chat session not found")
|
||||
}
|
||||
|
||||
entity := req.ToEntity()
|
||||
return _i.Repo.UpdateSession(userInfo.ID, id, entity)
|
||||
}
|
||||
|
||||
func (_i *aiChatService) DeleteSession(authToken string, id uint) error {
|
||||
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||
|
||||
// Check if session exists and belongs to user
|
||||
existing, err := _i.Repo.FindSessionByUserAndId(userInfo.ID, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if existing == nil {
|
||||
return errors.New("AI chat session not found")
|
||||
}
|
||||
|
||||
return _i.Repo.DeleteSession(userInfo.ID, id)
|
||||
}
|
||||
|
||||
func (_i *aiChatService) ArchiveSession(authToken string, id uint) error {
|
||||
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||
|
||||
// Check if session exists and belongs to user
|
||||
existing, err := _i.Repo.FindSessionByUserAndId(userInfo.ID, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if existing == nil {
|
||||
return errors.New("AI chat session not found")
|
||||
}
|
||||
|
||||
// Update status to archived
|
||||
existing.IsActive = false
|
||||
return _i.Repo.UpdateSession(userInfo.ID, id, existing)
|
||||
}
|
||||
|
||||
// Messages methods
|
||||
func (_i *aiChatService) GetSessionMessages(authToken string, sessionId uint, req request.AIChatMessagesQueryRequest) (messages []*response.AIChatMessagesResponse, paging paginator.Pagination, err error) {
|
||||
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||
|
||||
// Verify session belongs to user
|
||||
session, err := _i.Repo.FindSessionByUserAndId(userInfo.ID, sessionId)
|
||||
if err != nil {
|
||||
return nil, paginator.Pagination{}, err
|
||||
}
|
||||
|
||||
results, paging, err := _i.Repo.GetSessionMessages(session.SessionID, req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, result := range results {
|
||||
messages = append(messages, mapper.AIChatMessagesResponseMapper(result))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *aiChatService) SendMessage(authToken string, req request.AIChatMessagesCreateRequest) (message *response.AIChatMessagesResponse, err error) {
|
||||
// userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||
|
||||
// Verify session exists
|
||||
session, err := _i.Repo.FindSessionBySessionId(req.SessionID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
entity := req.ToEntity()
|
||||
|
||||
result, err := _i.Repo.CreateMessage(entity)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Increment message count in session
|
||||
err = _i.Repo.IncrementMessageCount(session.ID)
|
||||
if err != nil {
|
||||
_i.Log.Error().Err(err).Msg("Failed to increment message count")
|
||||
}
|
||||
|
||||
return mapper.AIChatMessagesResponseMapper(result), nil
|
||||
}
|
||||
|
||||
// Update Message
|
||||
func (_i *aiChatService) UpdateMessage(authToken string, messageId uint, req request.AIChatMessagesUpdateRequest) (err error) {
|
||||
// userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||
|
||||
entity := req.ToEntity()
|
||||
return _i.Repo.UpdateMessage(messageId, entity)
|
||||
}
|
||||
|
||||
// Delete Message
|
||||
func (_i *aiChatService) DeleteMessage(authToken string, messageId uint) error {
|
||||
// userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||
|
||||
return _i.Repo.DeleteMessage(messageId)
|
||||
}
|
||||
|
||||
// Logs methods
|
||||
func (_i *aiChatService) GetUserLogs(authToken string, req request.AIChatLogsQueryRequest) (logs []*response.AIChatLogsResponse, paging paginator.Pagination, err error) {
|
||||
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||
|
||||
results, paging, err := _i.Repo.GetUserLogs(userInfo.ID, req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, result := range results {
|
||||
logs = append(logs, mapper.AIChatLogsResponseMapper(result))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *aiChatService) GetLog(authToken string, logId uint) (log *response.AIChatLogsResponse, err error) {
|
||||
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||
|
||||
result, err := _i.Repo.FindLogByUserAndId(userInfo.ID, logId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mapper.AIChatLogsResponseMapper(result), nil
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package article_approvals
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/module/article_approvals/controller"
|
||||
"campaign-pool-be/app/module/article_approvals/repository"
|
||||
"campaign-pool-be/app/module/article_approvals/service"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"go.uber.org/fx"
|
||||
)
|
||||
|
||||
// struct of ArticleApprovalsRouter
|
||||
type ArticleApprovalsRouter struct {
|
||||
App fiber.Router
|
||||
Controller *controller.Controller
|
||||
}
|
||||
|
||||
// register bulky of ArticleApprovals module
|
||||
var NewArticleApprovalsModule = fx.Options(
|
||||
// register repository of ArticleApprovals module
|
||||
fx.Provide(repository.NewArticleApprovalsRepository),
|
||||
|
||||
// register service of ArticleApprovals module
|
||||
fx.Provide(service.NewArticleApprovalsService),
|
||||
|
||||
// register controller of ArticleApprovals module
|
||||
fx.Provide(controller.NewController),
|
||||
|
||||
// register router of ArticleApprovals module
|
||||
fx.Provide(NewArticleApprovalsRouter),
|
||||
)
|
||||
|
||||
// init ArticleApprovalsRouter
|
||||
func NewArticleApprovalsRouter(fiber *fiber.App, controller *controller.Controller) *ArticleApprovalsRouter {
|
||||
return &ArticleApprovalsRouter{
|
||||
App: fiber,
|
||||
Controller: controller,
|
||||
}
|
||||
}
|
||||
|
||||
// register routes of ArticleApprovals module
|
||||
func (_i *ArticleApprovalsRouter) RegisterArticleApprovalsRoutes() {
|
||||
// define controllers
|
||||
articleApprovalsController := _i.Controller.ArticleApprovals
|
||||
|
||||
// define routes
|
||||
_i.App.Route("/article-approvals", func(router fiber.Router) {
|
||||
router.Get("/", articleApprovalsController.All)
|
||||
router.Get("/:id", articleApprovalsController.Show)
|
||||
router.Post("/", articleApprovalsController.Save)
|
||||
router.Put("/:id", articleApprovalsController.Update)
|
||||
router.Delete("/:id", articleApprovalsController.Delete)
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,202 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/module/article_approvals/request"
|
||||
"campaign-pool-be/app/module/article_approvals/service"
|
||||
"campaign-pool-be/utils/paginator"
|
||||
"strconv"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
utilRes "campaign-pool-be/utils/response"
|
||||
utilVal "campaign-pool-be/utils/validator"
|
||||
)
|
||||
|
||||
type articleApprovalsController struct {
|
||||
articleApprovalsService service.ArticleApprovalsService
|
||||
Log zerolog.Logger
|
||||
}
|
||||
|
||||
type ArticleApprovalsController interface {
|
||||
All(c *fiber.Ctx) error
|
||||
Show(c *fiber.Ctx) error
|
||||
Save(c *fiber.Ctx) error
|
||||
Update(c *fiber.Ctx) error
|
||||
Delete(c *fiber.Ctx) error
|
||||
}
|
||||
|
||||
func NewArticleApprovalsController(articleApprovalsService service.ArticleApprovalsService, log zerolog.Logger) ArticleApprovalsController {
|
||||
return &articleApprovalsController{
|
||||
articleApprovalsService: articleApprovalsService,
|
||||
Log: log,
|
||||
}
|
||||
}
|
||||
|
||||
// All get all ArticleApprovals
|
||||
// @Summary Get all ArticleApprovals
|
||||
// @Description API for getting all ArticleApprovals
|
||||
// @Tags ArticleApprovals
|
||||
// @Security Bearer
|
||||
// @Param req query request.ArticleApprovalsQueryRequest false "query parameters"
|
||||
// @Param req query paginator.Pagination false "pagination parameters"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /article-approvals [get]
|
||||
func (_i *articleApprovalsController) All(c *fiber.Ctx) error {
|
||||
paginate, err := paginator.Paginate(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reqContext := request.ArticleApprovalsQueryRequestContext{
|
||||
ArticleId: c.Query("articleId"),
|
||||
ApprovalBy: c.Query("approvalBy"),
|
||||
StatusId: c.Query("statusId"),
|
||||
Message: c.Query("message"),
|
||||
ApprovalAtLevel: c.Query("approvalAtLevel"),
|
||||
}
|
||||
req := reqContext.ToParamRequest()
|
||||
req.Pagination = paginate
|
||||
|
||||
articleApprovalsData, paging, err := _i.articleApprovalsService.All(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"ArticleApprovals list successfully retrieved"},
|
||||
Data: articleApprovalsData,
|
||||
Meta: paging,
|
||||
})
|
||||
}
|
||||
|
||||
// Show get one ArticleApprovals
|
||||
// @Summary Get one ArticleApprovals
|
||||
// @Description API for getting one ArticleApprovals
|
||||
// @Tags ArticleApprovals
|
||||
// @Security Bearer
|
||||
// @Param id path int true "ArticleApprovals ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /article-approvals/{id} [get]
|
||||
func (_i *articleApprovalsController) Show(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
articleApprovalsData, err := _i.articleApprovalsService.Show(uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"ArticleApprovals successfully retrieved"},
|
||||
Data: articleApprovalsData,
|
||||
})
|
||||
}
|
||||
|
||||
// Save create ArticleApprovals
|
||||
// @Summary Create ArticleApprovals
|
||||
// @Description API for create ArticleApprovals
|
||||
// @Tags ArticleApprovals
|
||||
// @Security Bearer
|
||||
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
||||
// @Param payload body request.ArticleApprovalsCreateRequest true "Required payload"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /article-approvals [post]
|
||||
func (_i *articleApprovalsController) Save(c *fiber.Ctx) error {
|
||||
req := new(request.ArticleApprovalsCreateRequest)
|
||||
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authToken := c.Get("Authorization")
|
||||
|
||||
// Get from context
|
||||
dataResult, err := _i.articleApprovalsService.Save(*req, authToken)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"ArticleApprovals successfully created"},
|
||||
Data: dataResult,
|
||||
})
|
||||
}
|
||||
|
||||
// Update update ArticleApprovals
|
||||
// @Summary update ArticleApprovals
|
||||
// @Description API for update ArticleApprovals
|
||||
// @Tags ArticleApprovals
|
||||
// @Security Bearer
|
||||
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||
// @Param payload body request.ArticleApprovalsUpdateRequest true "Required payload"
|
||||
// @Param id path int true "ArticleApprovals ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /article-approvals/{id} [put]
|
||||
func (_i *articleApprovalsController) Update(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req := new(request.ArticleApprovalsUpdateRequest)
|
||||
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = _i.articleApprovalsService.Update(uint(id), *req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"ArticleApprovals successfully updated"},
|
||||
})
|
||||
}
|
||||
|
||||
// Delete delete ArticleApprovals
|
||||
// @Summary delete ArticleApprovals
|
||||
// @Description API for delete ArticleApprovals
|
||||
// @Tags ArticleApprovals
|
||||
// @Security Bearer
|
||||
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||
// @Param id path int true "ArticleApprovals ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /article-approvals/{id} [delete]
|
||||
func (_i *articleApprovalsController) Delete(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = _i.articleApprovalsService.Delete(uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"ArticleApprovals successfully deleted"},
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/module/article_approvals/service"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
ArticleApprovals ArticleApprovalsController
|
||||
}
|
||||
|
||||
func NewController(ArticleApprovalsService service.ArticleApprovalsService, log zerolog.Logger) *Controller {
|
||||
return &Controller{
|
||||
ArticleApprovals: NewArticleApprovalsController(ArticleApprovalsService, log),
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package mapper
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/database/entity"
|
||||
res "campaign-pool-be/app/module/article_approvals/response"
|
||||
)
|
||||
|
||||
func ArticleApprovalsResponseMapper(articleApprovalsReq *entity.ArticleApprovals) (articleApprovalsRes *res.ArticleApprovalsResponse) {
|
||||
if articleApprovalsReq != nil {
|
||||
articleApprovalsRes = &res.ArticleApprovalsResponse{
|
||||
ID: articleApprovalsReq.ID,
|
||||
ArticleId: articleApprovalsReq.ArticleId,
|
||||
ApprovalBy: articleApprovalsReq.ApprovalBy,
|
||||
StatusId: articleApprovalsReq.StatusId,
|
||||
Message: articleApprovalsReq.Message,
|
||||
ApprovalAtLevel: articleApprovalsReq.ApprovalAtLevel,
|
||||
CreatedAt: articleApprovalsReq.CreatedAt,
|
||||
}
|
||||
}
|
||||
return articleApprovalsRes
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
package repository
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/database"
|
||||
"campaign-pool-be/app/database/entity"
|
||||
"campaign-pool-be/app/module/article_approvals/request"
|
||||
"campaign-pool-be/utils/paginator"
|
||||
utilSvc "campaign-pool-be/utils/service"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
type articleApprovalsRepository struct {
|
||||
DB *database.Database
|
||||
Log zerolog.Logger
|
||||
}
|
||||
|
||||
// ArticleApprovalsRepository define interface of IArticleApprovalsRepository
|
||||
type ArticleApprovalsRepository interface {
|
||||
GetAll(req request.ArticleApprovalsQueryRequest) (articleApprovalss []*entity.ArticleApprovals, paging paginator.Pagination, err error)
|
||||
FindOne(id uint) (articleApprovals *entity.ArticleApprovals, err error)
|
||||
Create(articleApprovals *entity.ArticleApprovals) (articleApprovalsReturn *entity.ArticleApprovals, err error)
|
||||
Update(id uint, articleApprovals *entity.ArticleApprovals) (err error)
|
||||
Delete(id uint) (err error)
|
||||
}
|
||||
|
||||
func NewArticleApprovalsRepository(db *database.Database, logger zerolog.Logger) ArticleApprovalsRepository {
|
||||
return &articleApprovalsRepository{
|
||||
DB: db,
|
||||
Log: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// implement interface of IArticleApprovalsRepository
|
||||
func (_i *articleApprovalsRepository) GetAll(req request.ArticleApprovalsQueryRequest) (articleApprovalss []*entity.ArticleApprovals, paging paginator.Pagination, err error) {
|
||||
var count int64
|
||||
|
||||
query := _i.DB.DB.Model(&entity.ArticleApprovals{})
|
||||
if req.ArticleId != nil {
|
||||
query = query.Where("article_id = ?", req.ArticleId)
|
||||
}
|
||||
if req.ApprovalBy != nil {
|
||||
query = query.Where("approval_by = ?", req.ApprovalBy)
|
||||
}
|
||||
if req.StatusId != nil {
|
||||
query = query.Where("status_id = ?", req.StatusId)
|
||||
}
|
||||
if req.Message != nil && *req.Message != "" {
|
||||
message := strings.ToLower(*req.Message)
|
||||
query = query.Where("LOWER(message) LIKE ?", "%"+strings.ToLower(message)+"%")
|
||||
}
|
||||
if req.ApprovalAtLevel != nil {
|
||||
query = query.Where("approval_at_level = ?", req.ApprovalAtLevel)
|
||||
}
|
||||
query.Count(&count)
|
||||
|
||||
if req.Pagination.SortBy != "" {
|
||||
direction := "ASC"
|
||||
if req.Pagination.Sort == "desc" {
|
||||
direction = "DESC"
|
||||
}
|
||||
query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction))
|
||||
}
|
||||
|
||||
req.Pagination.Count = count
|
||||
req.Pagination = paginator.Paging(req.Pagination)
|
||||
|
||||
err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&articleApprovalss).Error
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
paging = *req.Pagination
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *articleApprovalsRepository) FindOne(id uint) (articleApprovals *entity.ArticleApprovals, err error) {
|
||||
if err := _i.DB.DB.First(&articleApprovals, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return articleApprovals, nil
|
||||
}
|
||||
|
||||
func (_i *articleApprovalsRepository) Create(articleApprovals *entity.ArticleApprovals) (articleApprovalsReturn *entity.ArticleApprovals, err error) {
|
||||
result := _i.DB.DB.Create(articleApprovals)
|
||||
return articleApprovals, result.Error
|
||||
}
|
||||
|
||||
func (_i *articleApprovalsRepository) Update(id uint, articleApprovals *entity.ArticleApprovals) (err error) {
|
||||
articleApprovalsMap, err := utilSvc.StructToMap(articleApprovals)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return _i.DB.DB.Model(&entity.ArticleApprovals{}).
|
||||
Where(&entity.ArticleApprovals{ID: id}).
|
||||
Updates(articleApprovalsMap).Error
|
||||
}
|
||||
|
||||
func (_i *articleApprovalsRepository) Delete(id uint) error {
|
||||
return _i.DB.DB.Delete(&entity.ArticleApprovals{}, id).Error
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
package request
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/database/entity"
|
||||
"campaign-pool-be/utils/paginator"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type ArticleApprovalsGeneric interface {
|
||||
ToEntity()
|
||||
}
|
||||
|
||||
type ArticleApprovalsQueryRequest struct {
|
||||
ArticleId *int `json:"articleId"`
|
||||
ApprovalBy *int `json:"approvalBy"`
|
||||
StatusId *int `json:"statusId"`
|
||||
Message *string `json:"message"`
|
||||
ApprovalAtLevel *int `json:"approvalAtLevel"`
|
||||
Pagination *paginator.Pagination `json:"pagination"`
|
||||
}
|
||||
|
||||
type ArticleApprovalsCreateRequest struct {
|
||||
ArticleId uint `json:"articleId" validate:"required"`
|
||||
StatusId int `json:"statusId" validate:"required"`
|
||||
Message string `json:"message" validate:"required"`
|
||||
}
|
||||
|
||||
func (req ArticleApprovalsCreateRequest) ToEntity() *entity.ArticleApprovals {
|
||||
return &entity.ArticleApprovals{
|
||||
ArticleId: req.ArticleId,
|
||||
StatusId: req.StatusId,
|
||||
Message: req.Message,
|
||||
}
|
||||
}
|
||||
|
||||
type ArticleApprovalsUpdateRequest struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
ArticleId uint `json:"articleId" validate:"required"`
|
||||
StatusId int `json:"statusId" validate:"required"`
|
||||
Message string `json:"message" validate:"required"`
|
||||
}
|
||||
|
||||
func (req ArticleApprovalsUpdateRequest) ToEntity() *entity.ArticleApprovals {
|
||||
return &entity.ArticleApprovals{
|
||||
ID: req.ID,
|
||||
ArticleId: req.ArticleId,
|
||||
StatusId: req.StatusId,
|
||||
Message: req.Message,
|
||||
}
|
||||
}
|
||||
|
||||
type ArticleApprovalsQueryRequestContext struct {
|
||||
ArticleId string `json:"articleId"`
|
||||
ApprovalBy string `json:"approvalBy"`
|
||||
StatusId string `json:"statusId"`
|
||||
Message string `json:"message"`
|
||||
ApprovalAtLevel string `json:"approvalAtLevel"`
|
||||
}
|
||||
|
||||
func (req ArticleApprovalsQueryRequestContext) ToParamRequest() ArticleApprovalsQueryRequest {
|
||||
var request ArticleApprovalsQueryRequest
|
||||
|
||||
if articleIdStr := req.ArticleId; articleIdStr != "" {
|
||||
articleId, err := strconv.Atoi(articleIdStr)
|
||||
if err == nil {
|
||||
request.ArticleId = &articleId
|
||||
}
|
||||
}
|
||||
if approvalByStr := req.ApprovalBy; approvalByStr != "" {
|
||||
approvalBy, err := strconv.Atoi(approvalByStr)
|
||||
if err == nil {
|
||||
request.ApprovalBy = &approvalBy
|
||||
}
|
||||
}
|
||||
if statusIdStr := req.StatusId; statusIdStr != "" {
|
||||
statusId, err := strconv.Atoi(statusIdStr)
|
||||
if err == nil {
|
||||
request.StatusId = &statusId
|
||||
}
|
||||
}
|
||||
if message := req.Message; message != "" {
|
||||
request.Message = &message
|
||||
}
|
||||
if approvalAtLevelStr := req.ApprovalAtLevel; approvalAtLevelStr != "" {
|
||||
approvalAtLevel, err := strconv.Atoi(approvalAtLevelStr)
|
||||
if err == nil {
|
||||
request.ApprovalAtLevel = &approvalAtLevel
|
||||
}
|
||||
}
|
||||
|
||||
return request
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package response
|
||||
|
||||
import "time"
|
||||
|
||||
type ArticleApprovalsResponse struct {
|
||||
ID uint `json:"id"`
|
||||
ArticleId uint `json:"articleId"`
|
||||
ApprovalBy uint `json:"approvalBy"`
|
||||
StatusId int `json:"statusId"`
|
||||
Message string `json:"message"`
|
||||
ApprovalAtLevel *int `json:"approvalAtLevel"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/database/entity"
|
||||
"campaign-pool-be/app/module/article_approvals/mapper"
|
||||
"campaign-pool-be/app/module/article_approvals/repository"
|
||||
"campaign-pool-be/app/module/article_approvals/request"
|
||||
"campaign-pool-be/app/module/article_approvals/response"
|
||||
articlesService "campaign-pool-be/app/module/articles/service"
|
||||
usersRepository "campaign-pool-be/app/module/users/repository"
|
||||
"campaign-pool-be/utils/paginator"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
utilSvc "campaign-pool-be/utils/service"
|
||||
)
|
||||
|
||||
// ArticleApprovalsService
|
||||
type articleApprovalsService struct {
|
||||
Repo repository.ArticleApprovalsRepository
|
||||
UsersRepo usersRepository.UsersRepository
|
||||
ArticlesService articlesService.ArticlesService
|
||||
Log zerolog.Logger
|
||||
}
|
||||
|
||||
// ArticleApprovalsService define interface of IArticleApprovalsService
|
||||
type ArticleApprovalsService interface {
|
||||
All(req request.ArticleApprovalsQueryRequest) (articleApprovals []*response.ArticleApprovalsResponse, paging paginator.Pagination, err error)
|
||||
Show(id uint) (articleApprovals *response.ArticleApprovalsResponse, err error)
|
||||
Save(req request.ArticleApprovalsCreateRequest, authToken string) (articleApprovals *entity.ArticleApprovals, err error)
|
||||
Update(id uint, req request.ArticleApprovalsUpdateRequest) (err error)
|
||||
Delete(id uint) error
|
||||
}
|
||||
|
||||
// NewArticleApprovalsService init ArticleApprovalsService
|
||||
func NewArticleApprovalsService(repo repository.ArticleApprovalsRepository, log zerolog.Logger, usersRepo usersRepository.UsersRepository, articlesSvc articlesService.ArticlesService) ArticleApprovalsService {
|
||||
|
||||
return &articleApprovalsService{
|
||||
Repo: repo,
|
||||
Log: log,
|
||||
UsersRepo: usersRepo,
|
||||
ArticlesService: articlesSvc,
|
||||
}
|
||||
}
|
||||
|
||||
// All implement interface of ArticleApprovalsService
|
||||
func (_i *articleApprovalsService) All(req request.ArticleApprovalsQueryRequest) (articleApprovalss []*response.ArticleApprovalsResponse, paging paginator.Pagination, err error) {
|
||||
results, paging, err := _i.Repo.GetAll(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, result := range results {
|
||||
articleApprovalss = append(articleApprovalss, mapper.ArticleApprovalsResponseMapper(result))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *articleApprovalsService) Show(id uint) (articleApprovals *response.ArticleApprovalsResponse, err error) {
|
||||
result, err := _i.Repo.FindOne(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mapper.ArticleApprovalsResponseMapper(result), nil
|
||||
}
|
||||
|
||||
func (_i *articleApprovalsService) Save(req request.ArticleApprovalsCreateRequest, authToken string) (articleApprovals *entity.ArticleApprovals, err error) {
|
||||
_i.Log.Info().Interface("data", req).Msg("")
|
||||
|
||||
newReq := req.ToEntity()
|
||||
|
||||
createdBy := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||
newReq.ApprovalBy = createdBy.ID
|
||||
newReq.ApprovalAtLevel = &createdBy.UserLevel.LevelNumber
|
||||
|
||||
approvalByUserLevelId := createdBy.UserLevelId
|
||||
approvalParentLevelId := createdBy.UserLevel.ParentLevelId
|
||||
|
||||
err = _i.ArticlesService.UpdateApproval(newReq.ArticleId, newReq.StatusId, int(approvalByUserLevelId), *newReq.ApprovalAtLevel, *approvalParentLevelId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return _i.Repo.Create(newReq)
|
||||
}
|
||||
|
||||
func (_i *articleApprovalsService) Update(id uint, req request.ArticleApprovalsUpdateRequest) (err error) {
|
||||
_i.Log.Info().Interface("data", req).Msg("")
|
||||
return _i.Repo.Update(id, req.ToEntity())
|
||||
}
|
||||
|
||||
func (_i *articleApprovalsService) Delete(id uint) error {
|
||||
return _i.Repo.Delete(id)
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
package article_categories
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/module/article_categories/controller"
|
||||
"campaign-pool-be/app/module/article_categories/repository"
|
||||
"campaign-pool-be/app/module/article_categories/service"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"go.uber.org/fx"
|
||||
)
|
||||
|
||||
// struct of ArticleCategoriesRouter
|
||||
type ArticleCategoriesRouter struct {
|
||||
App fiber.Router
|
||||
Controller *controller.Controller
|
||||
}
|
||||
|
||||
// register bulky of ArticleCategories module
|
||||
var NewArticleCategoriesModule = fx.Options(
|
||||
// register repository of ArticleCategories module
|
||||
fx.Provide(repository.NewArticleCategoriesRepository),
|
||||
|
||||
// register service of ArticleCategories module
|
||||
fx.Provide(service.NewArticleCategoriesService),
|
||||
|
||||
// register controller of ArticleCategories module
|
||||
fx.Provide(controller.NewController),
|
||||
|
||||
// register router of ArticleCategories module
|
||||
fx.Provide(NewArticleCategoriesRouter),
|
||||
)
|
||||
|
||||
// init ArticleCategoriesRouter
|
||||
func NewArticleCategoriesRouter(fiber *fiber.App, controller *controller.Controller) *ArticleCategoriesRouter {
|
||||
return &ArticleCategoriesRouter{
|
||||
App: fiber,
|
||||
Controller: controller,
|
||||
}
|
||||
}
|
||||
|
||||
// register routes of ArticleCategories module
|
||||
func (_i *ArticleCategoriesRouter) RegisterArticleCategoriesRoutes() {
|
||||
// define controllers
|
||||
articleCategoriesController := _i.Controller.ArticleCategories
|
||||
|
||||
// define routes
|
||||
_i.App.Route("/article-categories", func(router fiber.Router) {
|
||||
router.Get("/", articleCategoriesController.All)
|
||||
router.Get("/:id", articleCategoriesController.Show)
|
||||
router.Get("/old/:id", articleCategoriesController.ShowByOldId)
|
||||
router.Get("/slug/:slug", articleCategoriesController.ShowBySlug)
|
||||
router.Post("/", articleCategoriesController.Save)
|
||||
router.Put("/:id", articleCategoriesController.Update)
|
||||
router.Post("/thumbnail/:id", articleCategoriesController.SaveThumbnail)
|
||||
router.Get("/thumbnail/viewer/:id", articleCategoriesController.Viewer)
|
||||
router.Delete("/:id", articleCategoriesController.Delete)
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,298 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/module/article_categories/request"
|
||||
"campaign-pool-be/app/module/article_categories/service"
|
||||
"campaign-pool-be/utils/paginator"
|
||||
"strconv"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
|
||||
utilRes "campaign-pool-be/utils/response"
|
||||
utilVal "campaign-pool-be/utils/validator"
|
||||
)
|
||||
|
||||
type articleCategoriesController struct {
|
||||
articleCategoriesService service.ArticleCategoriesService
|
||||
}
|
||||
|
||||
type ArticleCategoriesController interface {
|
||||
All(c *fiber.Ctx) error
|
||||
Show(c *fiber.Ctx) error
|
||||
ShowByOldId(c *fiber.Ctx) error
|
||||
ShowBySlug(c *fiber.Ctx) error
|
||||
Save(c *fiber.Ctx) error
|
||||
SaveThumbnail(c *fiber.Ctx) error
|
||||
Update(c *fiber.Ctx) error
|
||||
Delete(c *fiber.Ctx) error
|
||||
Viewer(c *fiber.Ctx) error
|
||||
}
|
||||
|
||||
func NewArticleCategoriesController(articleCategoriesService service.ArticleCategoriesService) ArticleCategoriesController {
|
||||
return &articleCategoriesController{
|
||||
articleCategoriesService: articleCategoriesService,
|
||||
}
|
||||
}
|
||||
|
||||
// All ArticleCategories
|
||||
// @Summary Get all ArticleCategories
|
||||
// @Description API for getting all ArticleCategories
|
||||
// @Tags Article Categories
|
||||
// @Security Bearer
|
||||
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
||||
// @Param req query request.ArticleCategoriesQueryRequest false "query parameters"
|
||||
// @Param req query paginator.Pagination false "pagination parameters"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /article-categories [get]
|
||||
func (_i *articleCategoriesController) All(c *fiber.Ctx) error {
|
||||
paginate, err := paginator.Paginate(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authToken := c.Get("Authorization")
|
||||
reqContext := request.ArticleCategoriesQueryRequestContext{
|
||||
Title: c.Query("title"),
|
||||
Description: c.Query("description"),
|
||||
ParentId: c.Query("parentId"),
|
||||
IsPublish: c.Query("isPublish"),
|
||||
StatusId: c.Query("statusId"),
|
||||
}
|
||||
req := reqContext.ToParamRequest()
|
||||
req.Pagination = paginate
|
||||
|
||||
articleCategoriesData, paging, err := _i.articleCategoriesService.All(req, authToken)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"ArticleCategories list successfully retrieved"},
|
||||
Data: articleCategoriesData,
|
||||
Meta: paging,
|
||||
})
|
||||
}
|
||||
|
||||
// Show ArticleCategories
|
||||
// @Summary Get one ArticleCategories
|
||||
// @Description API for getting one ArticleCategories
|
||||
// @Tags Article Categories
|
||||
// @Security Bearer
|
||||
// @Param id path int true "ArticleCategories ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /article-categories/{id} [get]
|
||||
func (_i *articleCategoriesController) Show(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
articleCategoriesData, err := _i.articleCategoriesService.Show(uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"ArticleCategories successfully retrieved"},
|
||||
Data: articleCategoriesData,
|
||||
})
|
||||
}
|
||||
|
||||
// ShowByOldId ArticleCategories
|
||||
// @Summary Get one ArticleCategories
|
||||
// @Description API for getting one ArticleCategories
|
||||
// @Tags Article Categories
|
||||
// @Security Bearer
|
||||
// @Param id path int true "ArticleCategories Old ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /article-categories/old/{id} [get]
|
||||
func (_i *articleCategoriesController) ShowByOldId(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
articleCategoriesData, err := _i.articleCategoriesService.ShowByOldId(uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"ArticleCategories successfully retrieved"},
|
||||
Data: articleCategoriesData,
|
||||
})
|
||||
}
|
||||
|
||||
// ShowBySlug ArticleCategories
|
||||
// @Summary Get one ArticleCategories
|
||||
// @Description API for getting one ArticleCategories
|
||||
// @Tags Article Categories
|
||||
// @Security Bearer
|
||||
// @Param slug path string true "ArticleCategories Slug"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /article-categories/slug/{slug} [get]
|
||||
func (_i *articleCategoriesController) ShowBySlug(c *fiber.Ctx) error {
|
||||
slug := c.Params("slug")
|
||||
articleCategoriesData, err := _i.articleCategoriesService.ShowBySlug(slug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"ArticleCategories successfully retrieved"},
|
||||
Data: articleCategoriesData,
|
||||
})
|
||||
}
|
||||
|
||||
// Save ArticleCategories
|
||||
// @Summary Create ArticleCategories
|
||||
// @Description API for create ArticleCategories
|
||||
// @Tags Article Categories
|
||||
// @Security Bearer
|
||||
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
||||
// @Param payload body request.ArticleCategoriesCreateRequest true "Required payload"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /article-categories [post]
|
||||
func (_i *articleCategoriesController) Save(c *fiber.Ctx) error {
|
||||
req := new(request.ArticleCategoriesCreateRequest)
|
||||
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authToken := c.Get("Authorization")
|
||||
dataResult, err := _i.articleCategoriesService.Save(*req, authToken)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"ArticleCategories successfully created"},
|
||||
Data: dataResult,
|
||||
})
|
||||
}
|
||||
|
||||
// SaveThumbnail ArticleCategories
|
||||
// @Summary Upload ArticleCategories Thumbnail
|
||||
// @Description API for Upload ArticleCategories Thumbnail
|
||||
// @Tags Article Categories
|
||||
// @Security Bearer
|
||||
// @Produce json
|
||||
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||
// @Param files formData file true "Upload thumbnail"
|
||||
// @Param id path int true "ArticleCategories ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /article-categories/thumbnail/{id} [post]
|
||||
func (_i *articleCategoriesController) SaveThumbnail(c *fiber.Ctx) error {
|
||||
err := _i.articleCategoriesService.SaveThumbnail(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"Thumbnail of ArticleCategories successfully created"},
|
||||
})
|
||||
}
|
||||
|
||||
// Update ArticleCategories
|
||||
// @Summary update ArticleCategories
|
||||
// @Description API for update ArticleCategories
|
||||
// @Tags Article Categories
|
||||
// @Security Bearer
|
||||
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||
// @Param payload body request.ArticleCategoriesUpdateRequest true "Required payload"
|
||||
// @Param id path int true "ArticleCategories ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /article-categories/{id} [put]
|
||||
func (_i *articleCategoriesController) Update(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req := new(request.ArticleCategoriesUpdateRequest)
|
||||
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = _i.articleCategoriesService.Update(uint(id), *req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"ArticleCategories successfully updated"},
|
||||
})
|
||||
}
|
||||
|
||||
// Delete ArticleCategories
|
||||
// @Summary delete ArticleCategories
|
||||
// @Description API for delete ArticleCategories
|
||||
// @Tags Article Categories
|
||||
// @Security Bearer
|
||||
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||
// @Param id path int true "ArticleCategories ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /article-categories/{id} [delete]
|
||||
func (_i *articleCategoriesController) Delete(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = _i.articleCategoriesService.Delete(uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"ArticleCategories successfully deleted"},
|
||||
})
|
||||
}
|
||||
|
||||
// Viewer ArticleCategories
|
||||
// @Summary Viewer ArticleCategories
|
||||
// @Description API for View Thumbnail of ArticleCategories
|
||||
// @Tags Article Categories
|
||||
// @Security Bearer
|
||||
// @Param id path string true "ArticleCategories ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /article-categories/thumbnail/viewer/{id} [get]
|
||||
func (_i *articleCategoriesController) Viewer(c *fiber.Ctx) error {
|
||||
return _i.articleCategoriesService.Viewer(c)
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package controller
|
||||
|
||||
import "campaign-pool-be/app/module/article_categories/service"
|
||||
|
||||
type Controller struct {
|
||||
ArticleCategories ArticleCategoriesController
|
||||
}
|
||||
|
||||
func NewController(ArticleCategoriesService service.ArticleCategoriesService) *Controller {
|
||||
return &Controller{
|
||||
ArticleCategories: NewArticleCategoriesController(ArticleCategoriesService),
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package mapper
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/database/entity"
|
||||
res "campaign-pool-be/app/module/article_categories/response"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func ArticleCategoriesResponseMapper(articleCategoriesReq *entity.ArticleCategories, host string) (articleCategoriesRes *res.ArticleCategoriesResponse) {
|
||||
if articleCategoriesReq != nil {
|
||||
articleCategoriesRes = &res.ArticleCategoriesResponse{
|
||||
ID: articleCategoriesReq.ID,
|
||||
Title: articleCategoriesReq.Title,
|
||||
Description: articleCategoriesReq.Description,
|
||||
Slug: articleCategoriesReq.Slug,
|
||||
ThumbnailPath: articleCategoriesReq.ThumbnailPath,
|
||||
ParentId: articleCategoriesReq.ParentId,
|
||||
OldCategoryId: articleCategoriesReq.OldCategoryId,
|
||||
CreatedById: articleCategoriesReq.CreatedById,
|
||||
StatusId: articleCategoriesReq.StatusId,
|
||||
IsPublish: articleCategoriesReq.IsPublish,
|
||||
PublishedAt: articleCategoriesReq.PublishedAt,
|
||||
IsActive: articleCategoriesReq.IsActive,
|
||||
CreatedAt: articleCategoriesReq.CreatedAt,
|
||||
UpdatedAt: articleCategoriesReq.UpdatedAt,
|
||||
}
|
||||
|
||||
if articleCategoriesReq.Tags != nil {
|
||||
tagsValue := *articleCategoriesReq.Tags
|
||||
articleCategoriesRes.Tags = strings.Split(tagsValue, ",")
|
||||
}
|
||||
|
||||
if articleCategoriesRes.ThumbnailPath != nil {
|
||||
articleCategoriesRes.ThumbnailUrl = host + "/article-categories/thumbnail/viewer/" + strconv.Itoa(int(articleCategoriesReq.ID))
|
||||
}
|
||||
}
|
||||
return articleCategoriesRes
|
||||
}
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
package repository
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/database"
|
||||
"campaign-pool-be/app/database/entity"
|
||||
"campaign-pool-be/app/module/article_categories/request"
|
||||
"campaign-pool-be/utils/paginator"
|
||||
utilSvc "campaign-pool-be/utils/service"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
type articleCategoriesRepository struct {
|
||||
DB *database.Database
|
||||
Log zerolog.Logger
|
||||
}
|
||||
|
||||
// ArticleCategoriesRepository define interface of IArticleCategoriesRepository
|
||||
type ArticleCategoriesRepository interface {
|
||||
GetAll(req request.ArticleCategoriesQueryRequest) (articleCategoriess []*entity.ArticleCategories, paging paginator.Pagination, err error)
|
||||
FindOne(id uint) (articleCategories *entity.ArticleCategories, err error)
|
||||
FindOneByOldId(id uint) (articleCategories *entity.ArticleCategories, err error)
|
||||
FindOneBySlug(slug string) (articleCategories *entity.ArticleCategories, err error)
|
||||
Create(articleCategories *entity.ArticleCategories) (articleCategoriesReturn *entity.ArticleCategories, err error)
|
||||
Update(id uint, articleCategories *entity.ArticleCategories) (err error)
|
||||
Delete(id uint) (err error)
|
||||
}
|
||||
|
||||
func NewArticleCategoriesRepository(db *database.Database, log zerolog.Logger) ArticleCategoriesRepository {
|
||||
return &articleCategoriesRepository{
|
||||
DB: db,
|
||||
Log: log,
|
||||
}
|
||||
}
|
||||
|
||||
// implement interface of IArticleCategoriesRepository
|
||||
func (_i *articleCategoriesRepository) GetAll(req request.ArticleCategoriesQueryRequest) (articleCategoriess []*entity.ArticleCategories, paging paginator.Pagination, err error) {
|
||||
var count int64
|
||||
|
||||
query := _i.DB.DB.Model(&entity.ArticleCategories{})
|
||||
|
||||
if req.UserLevelId != nil {
|
||||
query = _i.DB.DB.Model(&entity.ArticleCategories{}).
|
||||
Joins("LEFT JOIN users ON article_categories.created_by_id = users.id").
|
||||
Joins("LEFT JOIN user_levels ON users.user_level_id = user_levels.id").
|
||||
Where("user_levels.id = ? or user_levels.parent_level_id = ?", *req.UserLevelId, *req.UserLevelId)
|
||||
}
|
||||
|
||||
query = query.Where("article_categories.is_active = ?", true)
|
||||
|
||||
if req.Title != nil && *req.Title != "" {
|
||||
title := strings.ToLower(*req.Title)
|
||||
query = query.Where("LOWER(article_categories.title) LIKE ?", "%"+strings.ToLower(title)+"%")
|
||||
}
|
||||
if req.Description != nil && *req.Description != "" {
|
||||
description := strings.ToLower(*req.Description)
|
||||
query = query.Where("LOWER(article_categories.description) LIKE ?", "%"+strings.ToLower(description)+"%")
|
||||
}
|
||||
if req.ParentId != nil {
|
||||
query = query.Where("article_categories.parent_id = ?", req.ParentId)
|
||||
}
|
||||
if req.IsPublish != nil {
|
||||
query = query.Where("article_categories.is_publish = ?", req.IsPublish)
|
||||
}
|
||||
if req.StatusId != nil {
|
||||
query = query.Where("article_categories.status_id = ?", req.StatusId)
|
||||
}
|
||||
query.Count(&count)
|
||||
|
||||
if req.Pagination.SortBy != "" {
|
||||
direction := "ASC"
|
||||
if req.Pagination.Sort == "desc" {
|
||||
direction = "DESC"
|
||||
}
|
||||
query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction))
|
||||
}
|
||||
|
||||
req.Pagination.Count = count
|
||||
req.Pagination = paginator.Paging(req.Pagination)
|
||||
|
||||
err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&articleCategoriess).Error
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
paging = *req.Pagination
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *articleCategoriesRepository) FindOne(id uint) (articleCategories *entity.ArticleCategories, err error) {
|
||||
query := _i.DB.DB.Where("id = ?", id)
|
||||
|
||||
if err := query.First(&articleCategories).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return articleCategories, nil
|
||||
}
|
||||
|
||||
func (_i *articleCategoriesRepository) FindOneByOldId(id uint) (articleCategories *entity.ArticleCategories, err error) {
|
||||
query := _i.DB.DB.Where("old_category_id = ?", id)
|
||||
|
||||
if err := query.First(&articleCategories).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return articleCategories, nil
|
||||
}
|
||||
|
||||
func (_i *articleCategoriesRepository) FindOneBySlug(slug string) (articleCategories *entity.ArticleCategories, err error) {
|
||||
query := _i.DB.DB.Where("slug = ?", slug)
|
||||
|
||||
if err := query.First(&articleCategories).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return articleCategories, nil
|
||||
}
|
||||
|
||||
func (_i *articleCategoriesRepository) Create(articleCategories *entity.ArticleCategories) (articleCategoriesReturn *entity.ArticleCategories, err error) {
|
||||
result := _i.DB.DB.Create(articleCategories)
|
||||
return articleCategories, result.Error
|
||||
}
|
||||
|
||||
func (_i *articleCategoriesRepository) Update(id uint, articleCategories *entity.ArticleCategories) (err error) {
|
||||
articleCategoriesMap, err := utilSvc.StructToMap(articleCategories)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
query := _i.DB.DB.Model(&entity.ArticleCategories{}).Where(&entity.ArticleCategories{ID: id})
|
||||
return query.Updates(articleCategoriesMap).Error
|
||||
}
|
||||
|
||||
func (_i *articleCategoriesRepository) Delete(id uint) error {
|
||||
query := _i.DB.DB.Model(&entity.ArticleCategories{}).Where("id = ?", id)
|
||||
return query.Delete(&entity.ArticleCategories{}).Error
|
||||
}
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
package request
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/database/entity"
|
||||
"campaign-pool-be/utils/paginator"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ArticleCategoriesGeneric interface {
|
||||
ToEntity()
|
||||
}
|
||||
|
||||
type ArticleCategoriesQueryRequest struct {
|
||||
Title *string `json:"title"`
|
||||
Description *string `json:"description"`
|
||||
UserLevelId *uint `json:"UserLevelId"`
|
||||
UserLevelNumber *int `json:"UserLevelNumber"`
|
||||
ParentId *int `json:"parentId"`
|
||||
StatusId *int `json:"statusId"`
|
||||
IsPublish *bool `json:"isPublish"`
|
||||
Pagination *paginator.Pagination `json:"pagination"`
|
||||
}
|
||||
|
||||
type ArticleCategoriesCreateRequest struct {
|
||||
Title string `json:"title" validate:"required"`
|
||||
Description string `json:"description" validate:"required"`
|
||||
StatusId int `json:"statusId" validate:"required"`
|
||||
Tags *string `json:"tags"`
|
||||
Slug *string `json:"slug"`
|
||||
CreatedById *uint `json:"createdById"`
|
||||
ParentId *int `json:"parentId"`
|
||||
OldCategoryId *uint `json:"oldCategoryId"`
|
||||
}
|
||||
|
||||
func (req ArticleCategoriesCreateRequest) ToEntity() *entity.ArticleCategories {
|
||||
return &entity.ArticleCategories{
|
||||
Title: req.Title,
|
||||
Description: req.Description,
|
||||
Tags: req.Tags,
|
||||
ParentId: req.ParentId,
|
||||
Slug: req.Slug,
|
||||
OldCategoryId: req.OldCategoryId,
|
||||
StatusId: req.StatusId,
|
||||
}
|
||||
}
|
||||
|
||||
type ArticleCategoriesUpdateRequest struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
Title string `json:"title" validate:"required"`
|
||||
Description string `json:"description" validate:"required"`
|
||||
StatusId int `json:"statusId" validate:"required"`
|
||||
Tags *string `json:"tags"`
|
||||
Slug *string `json:"slug"`
|
||||
ParentId *int `json:"parentId"`
|
||||
CreatedById *uint `json:"createdById"`
|
||||
IsPublish *bool `json:"isPublish"`
|
||||
PublishedAt *time.Time `json:"publishedAt"`
|
||||
}
|
||||
|
||||
func (req ArticleCategoriesUpdateRequest) ToEntity() *entity.ArticleCategories {
|
||||
return &entity.ArticleCategories{
|
||||
ID: req.ID,
|
||||
Title: req.Title,
|
||||
Description: req.Description,
|
||||
ParentId: req.ParentId,
|
||||
Slug: req.Slug,
|
||||
Tags: req.Tags,
|
||||
StatusId: req.StatusId,
|
||||
IsPublish: req.IsPublish,
|
||||
PublishedAt: req.PublishedAt,
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
type ArticleCategoriesQueryRequestContext struct {
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
ParentId string `json:"parentId"`
|
||||
StatusId string `json:"statusId"`
|
||||
IsPublish string `json:"isPublish"`
|
||||
UserLevelId string `json:"UserLevelId"`
|
||||
UserLevelNumber string `json:"UserLevelNumber"`
|
||||
}
|
||||
|
||||
func (req ArticleCategoriesQueryRequestContext) ToParamRequest() ArticleCategoriesQueryRequest {
|
||||
var request ArticleCategoriesQueryRequest
|
||||
|
||||
if title := req.Title; title != "" {
|
||||
request.Title = &title
|
||||
}
|
||||
if description := req.Description; description != "" {
|
||||
request.Description = &description
|
||||
}
|
||||
if parentIdStr := req.ParentId; parentIdStr != "" {
|
||||
parentId, err := strconv.Atoi(parentIdStr)
|
||||
if err == nil {
|
||||
request.ParentId = &parentId
|
||||
}
|
||||
}
|
||||
if isPublishStr := req.IsPublish; isPublishStr != "" {
|
||||
isPublish, err := strconv.ParseBool(isPublishStr)
|
||||
if err == nil {
|
||||
request.IsPublish = &isPublish
|
||||
}
|
||||
}
|
||||
if statusIdStr := req.StatusId; statusIdStr != "" {
|
||||
statusId, err := strconv.Atoi(statusIdStr)
|
||||
if err == nil {
|
||||
request.StatusId = &statusId
|
||||
}
|
||||
}
|
||||
if userLevelIdStr := req.UserLevelId; userLevelIdStr != "" {
|
||||
userLevelId, err := strconv.Atoi(userLevelIdStr)
|
||||
if err == nil {
|
||||
userLevelIdUint := uint(userLevelId)
|
||||
request.UserLevelId = &userLevelIdUint
|
||||
}
|
||||
}
|
||||
if userLevelNumberStr := req.UserLevelNumber; userLevelNumberStr != "" {
|
||||
userLevelNumber, err := strconv.Atoi(userLevelNumberStr)
|
||||
if err == nil {
|
||||
request.UserLevelNumber = &userLevelNumber
|
||||
}
|
||||
}
|
||||
|
||||
return request
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package response
|
||||
|
||||
import "time"
|
||||
|
||||
type ArticleCategoriesResponse struct {
|
||||
ID uint `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
ThumbnailUrl string `json:"thumbnailUrl"`
|
||||
Slug *string `json:"slug"`
|
||||
Tags []string `json:"tags"`
|
||||
ThumbnailPath *string `json:"thumbnailPath"`
|
||||
ParentId *int `json:"parentId"`
|
||||
OldCategoryId *uint `json:"oldCategoryId"`
|
||||
CreatedById *uint `json:"createdById"`
|
||||
StatusId int `json:"statusId"`
|
||||
IsPublish *bool `json:"isPublish"`
|
||||
PublishedAt *time.Time `json:"publishedAt"`
|
||||
IsEnabled *bool `json:"isEnabled"`
|
||||
IsActive *bool `json:"isActive"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
|
@ -0,0 +1,281 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/database/entity"
|
||||
"campaign-pool-be/app/module/article_categories/mapper"
|
||||
"campaign-pool-be/app/module/article_categories/repository"
|
||||
"campaign-pool-be/app/module/article_categories/request"
|
||||
"campaign-pool-be/app/module/article_categories/response"
|
||||
usersRepository "campaign-pool-be/app/module/users/repository"
|
||||
config "campaign-pool-be/config/config"
|
||||
minioStorage "campaign-pool-be/config/config"
|
||||
"campaign-pool-be/utils/paginator"
|
||||
utilSvc "campaign-pool-be/utils/service"
|
||||
"context"
|
||||
"io"
|
||||
"log"
|
||||
"math/rand"
|
||||
"mime"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
// ArticleCategoriesService
|
||||
type articleCategoriesService struct {
|
||||
Repo repository.ArticleCategoriesRepository
|
||||
UsersRepo usersRepository.UsersRepository
|
||||
MinioStorage *minioStorage.MinioStorage
|
||||
Log zerolog.Logger
|
||||
Cfg *config.Config
|
||||
}
|
||||
|
||||
// ArticleCategoriesService define interface of IArticleCategoriesService
|
||||
type ArticleCategoriesService interface {
|
||||
All(req request.ArticleCategoriesQueryRequest, authToken string) (articleCategories []*response.ArticleCategoriesResponse, paging paginator.Pagination, err error)
|
||||
Show(id uint) (articleCategories *response.ArticleCategoriesResponse, err error)
|
||||
ShowByOldId(id uint) (articleCategories *response.ArticleCategoriesResponse, err error)
|
||||
ShowBySlug(slug string) (articleCategories *response.ArticleCategoriesResponse, err error)
|
||||
Save(req request.ArticleCategoriesCreateRequest, authToken string) (articleCategories *entity.ArticleCategories, err error)
|
||||
SaveThumbnail(c *fiber.Ctx) (err error)
|
||||
Update(id uint, req request.ArticleCategoriesUpdateRequest) (err error)
|
||||
Delete(id uint) error
|
||||
Viewer(c *fiber.Ctx) error
|
||||
}
|
||||
|
||||
// NewArticleCategoriesService init ArticleCategoriesService
|
||||
func NewArticleCategoriesService(repo repository.ArticleCategoriesRepository, usersRepo usersRepository.UsersRepository, minioStorage *minioStorage.MinioStorage, log zerolog.Logger, cfg *config.Config) ArticleCategoriesService {
|
||||
|
||||
return &articleCategoriesService{
|
||||
Repo: repo,
|
||||
UsersRepo: usersRepo,
|
||||
MinioStorage: minioStorage,
|
||||
Log: log,
|
||||
Cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// All implement interface of ArticleCategoriesService
|
||||
func (_i *articleCategoriesService) All(req request.ArticleCategoriesQueryRequest, authToken string) (articleCategoriess []*response.ArticleCategoriesResponse, paging paginator.Pagination, err error) {
|
||||
createdBy := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||
if createdBy != nil {
|
||||
if createdBy.UserLevel.LevelNumber > 1 {
|
||||
req.UserLevelId = &createdBy.UserLevelId
|
||||
}
|
||||
}
|
||||
|
||||
results, paging, err := _i.Repo.GetAll(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
host := _i.Cfg.App.Domain
|
||||
for _, result := range results {
|
||||
articleCategoriess = append(articleCategoriess, mapper.ArticleCategoriesResponseMapper(result, host))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *articleCategoriesService) Show(id uint) (articleCategories *response.ArticleCategoriesResponse, err error) {
|
||||
result, err := _i.Repo.FindOne(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
host := _i.Cfg.App.Domain
|
||||
return mapper.ArticleCategoriesResponseMapper(result, host), nil
|
||||
}
|
||||
|
||||
func (_i *articleCategoriesService) ShowByOldId(id uint) (articleCategories *response.ArticleCategoriesResponse, err error) {
|
||||
result, err := _i.Repo.FindOneByOldId(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
host := _i.Cfg.App.Domain
|
||||
return mapper.ArticleCategoriesResponseMapper(result, host), nil
|
||||
}
|
||||
|
||||
func (_i *articleCategoriesService) ShowBySlug(slug string) (articleCategories *response.ArticleCategoriesResponse, err error) {
|
||||
result, err := _i.Repo.FindOneBySlug(slug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
host := _i.Cfg.App.Domain
|
||||
return mapper.ArticleCategoriesResponseMapper(result, host), nil
|
||||
}
|
||||
|
||||
func (_i *articleCategoriesService) Save(req request.ArticleCategoriesCreateRequest, authToken string) (articleCategories *entity.ArticleCategories, err error) {
|
||||
_i.Log.Info().Interface("data", req).Msg("")
|
||||
newReq := req.ToEntity()
|
||||
|
||||
if req.CreatedById != nil {
|
||||
createdBy, err := _i.UsersRepo.FindOne(*req.CreatedById)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newReq.CreatedById = &createdBy.ID
|
||||
} else {
|
||||
createdBy := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||
newReq.CreatedById = &createdBy.ID
|
||||
}
|
||||
|
||||
return _i.Repo.Create(newReq)
|
||||
}
|
||||
|
||||
func (_i *articleCategoriesService) SaveThumbnail(c *fiber.Ctx) (err error) {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName
|
||||
|
||||
form, err := c.MultipartForm()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
files := form.File["files"]
|
||||
|
||||
// Create minio connection.
|
||||
minioClient, err := _i.MinioStorage.ConnectMinio()
|
||||
if err != nil {
|
||||
// Return status 500 and minio connection error.
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"error": true,
|
||||
"msg": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
// Iterasi semua file yang diunggah
|
||||
for _, file := range files {
|
||||
_i.Log.Info().Str("timestamp", time.Now().
|
||||
Format(time.RFC3339)).Str("Service:Resource", "Uploader:: loop1").
|
||||
Interface("data", file).Msg("")
|
||||
|
||||
src, err := file.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
filename := filepath.Base(file.Filename)
|
||||
filenameWithoutExt := filepath.Clean(filename[:len(filename)-len(filepath.Ext(filename))])
|
||||
extension := filepath.Ext(file.Filename)[1:]
|
||||
|
||||
rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
randUniqueId := rand.Intn(1000000)
|
||||
|
||||
newFilenameWithoutExt := filenameWithoutExt + "_" + strconv.Itoa(randUniqueId)
|
||||
newFilename := newFilenameWithoutExt + "." + extension
|
||||
objectName := "articles/category/thumbnail/" + newFilename
|
||||
|
||||
findCategory, err := _i.Repo.FindOne(uint(id))
|
||||
findCategory.ThumbnailPath = &objectName
|
||||
err = _i.Repo.Update(uint(id), findCategory)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Upload file ke MinIO
|
||||
_, err = minioClient.PutObject(context.Background(), bucketName, objectName, src, file.Size, minio.PutObjectOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *articleCategoriesService) Update(id uint, req request.ArticleCategoriesUpdateRequest) (err error) {
|
||||
_i.Log.Info().Interface("data", req).Msg("")
|
||||
|
||||
newReq := req.ToEntity()
|
||||
if req.CreatedById != nil {
|
||||
createdBy, err := _i.UsersRepo.FindOne(*req.CreatedById)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newReq.CreatedById = &createdBy.ID
|
||||
}
|
||||
|
||||
return _i.Repo.Update(id, newReq)
|
||||
}
|
||||
|
||||
func (_i *articleCategoriesService) Delete(id uint) error {
|
||||
result, err := _i.Repo.FindOne(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
isActive := false
|
||||
result.IsActive = &isActive
|
||||
|
||||
return _i.Repo.Update(id, result)
|
||||
}
|
||||
|
||||
func (_i *articleCategoriesService) Viewer(c *fiber.Ctx) (err error) {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||
result, err := _i.Repo.FindOne(uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result.ThumbnailPath == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName
|
||||
objectName := result.ThumbnailPath
|
||||
|
||||
_i.Log.Info().Str("timestamp", time.Now().
|
||||
Format(time.RFC3339)).Str("Service:Resource", "Article:Uploads").
|
||||
Interface("data", objectName).Msg("")
|
||||
|
||||
// Create minio connection.
|
||||
minioClient, err := _i.MinioStorage.ConnectMinio()
|
||||
if err != nil {
|
||||
// Return status 500 and minio connection error.
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"error": true,
|
||||
"msg": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
fileContent, err := minioClient.GetObject(ctx, bucketName, *objectName, minio.GetObjectOptions{})
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
defer fileContent.Close()
|
||||
|
||||
contentType := mime.TypeByExtension("." + getFileExtension(*objectName))
|
||||
if contentType == "" {
|
||||
contentType = "application/octet-stream"
|
||||
}
|
||||
|
||||
c.Set("Content-Type", contentType)
|
||||
|
||||
if _, err := io.Copy(c.Response().BodyWriter(), fileContent); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getFileExtension(filename string) string {
|
||||
// split file name
|
||||
parts := strings.Split(filename, ".")
|
||||
|
||||
// jika tidak ada ekstensi, kembalikan string kosong
|
||||
if len(parts) == 1 || (len(parts) == 2 && parts[0] == "") {
|
||||
return ""
|
||||
}
|
||||
|
||||
// ambil ekstensi terakhir
|
||||
return parts[len(parts)-1]
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package article_category_details
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/module/article_category_details/controller"
|
||||
"campaign-pool-be/app/module/article_category_details/repository"
|
||||
"campaign-pool-be/app/module/article_category_details/service"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"go.uber.org/fx"
|
||||
)
|
||||
|
||||
// struct of ArticleCategoryDetailsRouter
|
||||
type ArticleCategoryDetailsRouter struct {
|
||||
App fiber.Router
|
||||
Controller *controller.Controller
|
||||
}
|
||||
|
||||
// register bulky of ArticleCategoryDetails module
|
||||
var NewArticleCategoryDetailsModule = fx.Options(
|
||||
// register repository of ArticleCategoryDetails module
|
||||
fx.Provide(repository.NewArticleCategoryDetailsRepository),
|
||||
|
||||
// register service of ArticleCategoryDetails module
|
||||
fx.Provide(service.NewArticleCategoryDetailsService),
|
||||
|
||||
// register controller of ArticleCategoryDetails module
|
||||
fx.Provide(controller.NewController),
|
||||
|
||||
// register router of ArticleCategoryDetails module
|
||||
fx.Provide(NewArticleCategoryDetailsRouter),
|
||||
)
|
||||
|
||||
// init ArticleCategoryDetailsRouter
|
||||
func NewArticleCategoryDetailsRouter(fiber *fiber.App, controller *controller.Controller) *ArticleCategoryDetailsRouter {
|
||||
return &ArticleCategoryDetailsRouter{
|
||||
App: fiber,
|
||||
Controller: controller,
|
||||
}
|
||||
}
|
||||
|
||||
// register routes of ArticleCategoryDetails module
|
||||
func (_i *ArticleCategoryDetailsRouter) RegisterArticleCategoryDetailsRoutes() {
|
||||
// define controllers
|
||||
articleCategoryDetailsController := _i.Controller.ArticleCategoryDetails
|
||||
|
||||
// define routes
|
||||
_i.App.Route("/article-category-details", func(router fiber.Router) {
|
||||
router.Get("/", articleCategoryDetailsController.All)
|
||||
router.Get("/:id", articleCategoryDetailsController.Show)
|
||||
router.Post("/", articleCategoryDetailsController.Save)
|
||||
router.Put("/:id", articleCategoryDetailsController.Update)
|
||||
router.Delete("/:id", articleCategoryDetailsController.Delete)
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,185 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/module/article_category_details/request"
|
||||
"campaign-pool-be/app/module/article_category_details/service"
|
||||
"campaign-pool-be/utils/paginator"
|
||||
"strconv"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
|
||||
utilRes "campaign-pool-be/utils/response"
|
||||
utilVal "campaign-pool-be/utils/validator"
|
||||
)
|
||||
|
||||
type articleCategoryDetailsController struct {
|
||||
articleCategoryDetailsService service.ArticleCategoryDetailsService
|
||||
}
|
||||
|
||||
type ArticleCategoryDetailsController interface {
|
||||
All(c *fiber.Ctx) error
|
||||
Show(c *fiber.Ctx) error
|
||||
Save(c *fiber.Ctx) error
|
||||
Update(c *fiber.Ctx) error
|
||||
Delete(c *fiber.Ctx) error
|
||||
}
|
||||
|
||||
func NewArticleCategoryDetailsController(articleCategoryDetailsService service.ArticleCategoryDetailsService) ArticleCategoryDetailsController {
|
||||
return &articleCategoryDetailsController{
|
||||
articleCategoryDetailsService: articleCategoryDetailsService,
|
||||
}
|
||||
}
|
||||
|
||||
// All get all ArticleCategoryDetails
|
||||
// @Summary Get all ArticleCategoryDetails
|
||||
// @Description API for getting all ArticleCategoryDetails
|
||||
// @Tags Untags
|
||||
// @Security Bearer
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 401 {object} response.Response
|
||||
// @Failure 404 {object} response.Response
|
||||
// @Failure 422 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /article-category-details [get]
|
||||
func (_i *articleCategoryDetailsController) All(c *fiber.Ctx) error {
|
||||
paginate, err := paginator.Paginate(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var req request.ArticleCategoryDetailsQueryRequest
|
||||
req.Pagination = paginate
|
||||
|
||||
articleCategoryDetailsData, paging, err := _i.articleCategoryDetailsService.All(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Messages: utilRes.Messages{"ArticleCategoryDetails list successfully retrieved"},
|
||||
Data: articleCategoryDetailsData,
|
||||
Meta: paging,
|
||||
})
|
||||
}
|
||||
|
||||
// Show get one ArticleCategoryDetails
|
||||
// @Summary Get one ArticleCategoryDetails
|
||||
// @Description API for getting one ArticleCategoryDetails
|
||||
// @Tags Untags
|
||||
// @Security Bearer
|
||||
// @Param id path int true "ArticleCategoryDetails ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 401 {object} response.Response
|
||||
// @Failure 404 {object} response.Response
|
||||
// @Failure 422 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /article-category-details/{id} [get]
|
||||
func (_i *articleCategoryDetailsController) Show(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
articleCategoryDetailsData, err := _i.articleCategoryDetailsService.Show(uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Messages: utilRes.Messages{"ArticleCategoryDetails successfully retrieved"},
|
||||
Data: articleCategoryDetailsData,
|
||||
})
|
||||
}
|
||||
|
||||
// Save create ArticleCategoryDetails
|
||||
// @Summary Create ArticleCategoryDetails
|
||||
// @Description API for create ArticleCategoryDetails
|
||||
// @Tags Untags
|
||||
// @Security Bearer
|
||||
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||
// @Body request.ArticleCategoryDetailsCreateRequest
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 401 {object} response.Response
|
||||
// @Failure 404 {object} response.Response
|
||||
// @Failure 422 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /article-category-details [post]
|
||||
func (_i *articleCategoryDetailsController) Save(c *fiber.Ctx) error {
|
||||
req := new(request.ArticleCategoryDetailsCreateRequest)
|
||||
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err := _i.articleCategoryDetailsService.Save(*req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Messages: utilRes.Messages{"ArticleCategoryDetails successfully created"},
|
||||
})
|
||||
}
|
||||
|
||||
// Update update ArticleCategoryDetails
|
||||
// @Summary update ArticleCategoryDetails
|
||||
// @Description API for update ArticleCategoryDetails
|
||||
// @Tags Untags
|
||||
// @Security Bearer
|
||||
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||
// @Body request.ArticleCategoryDetailsUpdateRequest
|
||||
// @Param id path int true "ArticleCategoryDetails ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 401 {object} response.Response
|
||||
// @Failure 404 {object} response.Response
|
||||
// @Failure 422 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /article-category-details/{id} [put]
|
||||
func (_i *articleCategoryDetailsController) Update(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req := new(request.ArticleCategoryDetailsUpdateRequest)
|
||||
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = _i.articleCategoryDetailsService.Update(uint(id), *req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Messages: utilRes.Messages{"ArticleCategoryDetails successfully updated"},
|
||||
})
|
||||
}
|
||||
|
||||
// Delete delete ArticleCategoryDetails
|
||||
// @Summary delete ArticleCategoryDetails
|
||||
// @Description API for delete ArticleCategoryDetails
|
||||
// @Tags Untags
|
||||
// @Security Bearer
|
||||
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||
// @Param id path int true "ArticleCategoryDetails ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 401 {object} response.Response
|
||||
// @Failure 404 {object} response.Response
|
||||
// @Failure 422 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /article-category-details/{id} [delete]
|
||||
func (_i *articleCategoryDetailsController) Delete(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = _i.articleCategoryDetailsService.Delete(uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Messages: utilRes.Messages{"ArticleCategoryDetails successfully deleted"},
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package controller
|
||||
|
||||
import "campaign-pool-be/app/module/article_category_details/service"
|
||||
|
||||
type Controller struct {
|
||||
ArticleCategoryDetails ArticleCategoryDetailsController
|
||||
}
|
||||
|
||||
func NewController(ArticleCategoryDetailsService service.ArticleCategoryDetailsService) *Controller {
|
||||
return &Controller{
|
||||
ArticleCategoryDetails: NewArticleCategoryDetailsController(ArticleCategoryDetailsService),
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package mapper
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/database/entity/article_category_details"
|
||||
res "campaign-pool-be/app/module/article_category_details/response"
|
||||
)
|
||||
|
||||
func ArticleCategoryDetailsResponseMapper(articleCategoryDetailsReq *article_category_details.ArticleCategoryDetails) (articleCategoryDetailsRes *res.ArticleCategoryDetailsResponse) {
|
||||
if articleCategoryDetailsReq != nil {
|
||||
|
||||
articleCategoryDetailsRes = &res.ArticleCategoryDetailsResponse{
|
||||
ID: articleCategoryDetailsReq.ID,
|
||||
ArticleId: articleCategoryDetailsReq.ArticleId,
|
||||
CategoryId: articleCategoryDetailsReq.CategoryId,
|
||||
IsActive: articleCategoryDetailsReq.IsActive,
|
||||
CreatedAt: articleCategoryDetailsReq.CreatedAt,
|
||||
UpdatedAt: articleCategoryDetailsReq.UpdatedAt,
|
||||
}
|
||||
}
|
||||
return articleCategoryDetailsRes
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
package repository
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/database"
|
||||
"campaign-pool-be/app/database/entity/article_category_details"
|
||||
"campaign-pool-be/app/module/article_category_details/request"
|
||||
"campaign-pool-be/utils/paginator"
|
||||
)
|
||||
|
||||
type articleCategoryDetailsRepository struct {
|
||||
DB *database.Database
|
||||
}
|
||||
|
||||
// ArticleCategoryDetailsRepository define interface of IArticleCategoryDetailsRepository
|
||||
type ArticleCategoryDetailsRepository interface {
|
||||
GetAll(req request.ArticleCategoryDetailsQueryRequest) (articleCategoryDetailss []*article_category_details.ArticleCategoryDetails, paging paginator.Pagination, err error)
|
||||
FindOne(id uint) (articleCategoryDetails *article_category_details.ArticleCategoryDetails, err error)
|
||||
FindByArticleId(articleId uint) (articleCategoryDetailss []*article_category_details.ArticleCategoryDetails, err error)
|
||||
Create(articleCategoryDetails *article_category_details.ArticleCategoryDetails) (err error)
|
||||
Update(id uint, articleCategoryDetails *article_category_details.ArticleCategoryDetails) (err error)
|
||||
Delete(id uint) (err error)
|
||||
}
|
||||
|
||||
func NewArticleCategoryDetailsRepository(db *database.Database) ArticleCategoryDetailsRepository {
|
||||
return &articleCategoryDetailsRepository{
|
||||
DB: db,
|
||||
}
|
||||
}
|
||||
|
||||
// implement interface of IArticleCategoryDetailsRepository
|
||||
func (_i *articleCategoryDetailsRepository) GetAll(req request.ArticleCategoryDetailsQueryRequest) (articleCategoryDetailss []*article_category_details.ArticleCategoryDetails, paging paginator.Pagination, err error) {
|
||||
var count int64
|
||||
|
||||
query := _i.DB.DB.Model(&article_category_details.ArticleCategoryDetails{})
|
||||
query.Count(&count)
|
||||
|
||||
req.Pagination.Count = count
|
||||
req.Pagination = paginator.Paging(req.Pagination)
|
||||
|
||||
err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&articleCategoryDetailss).Error
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
paging = *req.Pagination
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *articleCategoryDetailsRepository) FindOne(id uint) (articleCategoryDetails *article_category_details.ArticleCategoryDetails, err error) {
|
||||
if err := _i.DB.DB.First(&articleCategoryDetails, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return articleCategoryDetails, nil
|
||||
}
|
||||
|
||||
func (_i *articleCategoryDetailsRepository) FindByArticleId(articleId uint) (articleCategoryDetailss []*article_category_details.ArticleCategoryDetails, err error) {
|
||||
if err := _i.DB.DB.Where("article_id = ?", articleId).Preload("Category").Find(&articleCategoryDetailss).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return articleCategoryDetailss, nil
|
||||
}
|
||||
|
||||
func (_i *articleCategoryDetailsRepository) Create(articleCategoryDetails *article_category_details.ArticleCategoryDetails) (err error) {
|
||||
return _i.DB.DB.Create(articleCategoryDetails).Error
|
||||
}
|
||||
|
||||
func (_i *articleCategoryDetailsRepository) Update(id uint, articleCategoryDetails *article_category_details.ArticleCategoryDetails) (err error) {
|
||||
return _i.DB.DB.Model(&article_category_details.ArticleCategoryDetails{}).
|
||||
Where(&article_category_details.ArticleCategoryDetails{ID: id}).
|
||||
Updates(articleCategoryDetails).Error
|
||||
}
|
||||
|
||||
func (_i *articleCategoryDetailsRepository) Delete(id uint) error {
|
||||
return _i.DB.DB.Delete(&article_category_details.ArticleCategoryDetails{}, id).Error
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package request
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/database/entity/article_category_details"
|
||||
"campaign-pool-be/utils/paginator"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ArticleCategoryDetailsGeneric interface {
|
||||
ToEntity()
|
||||
}
|
||||
|
||||
type ArticleCategoryDetailsQueryRequest struct {
|
||||
ArticleId int `json:"articleId" validate:"required"`
|
||||
CategoryId int `json:"categoryId" validate:"required"`
|
||||
IsActive bool `json:"isActive" validate:"required"`
|
||||
Pagination *paginator.Pagination `json:"pagination"`
|
||||
}
|
||||
|
||||
type ArticleCategoryDetailsCreateRequest struct {
|
||||
ArticleId uint `json:"articleId" validate:"required"`
|
||||
CategoryId int `json:"categoryId" validate:"required"`
|
||||
IsActive bool `json:"isActive" validate:"required"`
|
||||
}
|
||||
|
||||
func (req ArticleCategoryDetailsCreateRequest) ToEntity() *article_category_details.ArticleCategoryDetails {
|
||||
return &article_category_details.ArticleCategoryDetails{
|
||||
ArticleId: req.ArticleId,
|
||||
CategoryId: req.CategoryId,
|
||||
IsActive: req.IsActive,
|
||||
}
|
||||
}
|
||||
|
||||
type ArticleCategoryDetailsUpdateRequest struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
ArticleId uint `json:"articleId" validate:"required"`
|
||||
CategoryId int `json:"categoryId" validate:"required"`
|
||||
IsActive bool `json:"isActive" validate:"required"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
func (req ArticleCategoryDetailsUpdateRequest) ToEntity() *article_category_details.ArticleCategoryDetails {
|
||||
return &article_category_details.ArticleCategoryDetails{
|
||||
ID: req.ID,
|
||||
ArticleId: req.ArticleId,
|
||||
CategoryId: req.CategoryId,
|
||||
IsActive: req.IsActive,
|
||||
CreatedAt: req.CreatedAt,
|
||||
UpdatedAt: req.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package response
|
||||
|
||||
import "time"
|
||||
|
||||
type ArticleCategoryDetailsResponse struct {
|
||||
ID uint `json:"id"`
|
||||
ArticleId uint `json:"articleId"`
|
||||
CategoryId int `json:"categoryId"`
|
||||
IsActive bool `json:"isActive"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/module/article_category_details/mapper"
|
||||
"campaign-pool-be/app/module/article_category_details/repository"
|
||||
"campaign-pool-be/app/module/article_category_details/request"
|
||||
"campaign-pool-be/app/module/article_category_details/response"
|
||||
"campaign-pool-be/utils/paginator"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
// ArticleCategoryDetailsService
|
||||
type articleCategoryDetailsService struct {
|
||||
Repo repository.ArticleCategoryDetailsRepository
|
||||
Log zerolog.Logger
|
||||
}
|
||||
|
||||
// ArticleCategoryDetailsService define interface of IArticleCategoryDetailsService
|
||||
type ArticleCategoryDetailsService interface {
|
||||
All(req request.ArticleCategoryDetailsQueryRequest) (articleCategoryDetails []*response.ArticleCategoryDetailsResponse, paging paginator.Pagination, err error)
|
||||
Show(id uint) (articleCategoryDetails *response.ArticleCategoryDetailsResponse, err error)
|
||||
Save(req request.ArticleCategoryDetailsCreateRequest) (err error)
|
||||
Update(id uint, req request.ArticleCategoryDetailsUpdateRequest) (err error)
|
||||
Delete(id uint) error
|
||||
}
|
||||
|
||||
// NewArticleCategoryDetailsService init ArticleCategoryDetailsService
|
||||
func NewArticleCategoryDetailsService(repo repository.ArticleCategoryDetailsRepository, log zerolog.Logger) ArticleCategoryDetailsService {
|
||||
|
||||
return &articleCategoryDetailsService{
|
||||
Repo: repo,
|
||||
Log: log,
|
||||
}
|
||||
}
|
||||
|
||||
// All implement interface of ArticleCategoryDetailsService
|
||||
func (_i *articleCategoryDetailsService) All(req request.ArticleCategoryDetailsQueryRequest) (articleCategoryDetailss []*response.ArticleCategoryDetailsResponse, paging paginator.Pagination, err error) {
|
||||
results, paging, err := _i.Repo.GetAll(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, result := range results {
|
||||
articleCategoryDetailss = append(articleCategoryDetailss, mapper.ArticleCategoryDetailsResponseMapper(result))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *articleCategoryDetailsService) Show(id uint) (articleCategoryDetails *response.ArticleCategoryDetailsResponse, err error) {
|
||||
result, err := _i.Repo.FindOne(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mapper.ArticleCategoryDetailsResponseMapper(result), nil
|
||||
}
|
||||
|
||||
func (_i *articleCategoryDetailsService) Save(req request.ArticleCategoryDetailsCreateRequest) (err error) {
|
||||
_i.Log.Info().Interface("data", req).Msg("")
|
||||
|
||||
return _i.Repo.Create(req.ToEntity())
|
||||
}
|
||||
|
||||
func (_i *articleCategoryDetailsService) Update(id uint, req request.ArticleCategoryDetailsUpdateRequest) (err error) {
|
||||
_i.Log.Info().Interface("data", req).Msg("")
|
||||
return _i.Repo.Update(id, req.ToEntity())
|
||||
}
|
||||
|
||||
func (_i *articleCategoryDetailsService) Delete(id uint) error {
|
||||
return _i.Repo.Delete(id)
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package article_comments
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/module/article_comments/controller"
|
||||
"campaign-pool-be/app/module/article_comments/repository"
|
||||
"campaign-pool-be/app/module/article_comments/service"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"go.uber.org/fx"
|
||||
)
|
||||
|
||||
// struct of ArticleCommentsRouter
|
||||
type ArticleCommentsRouter struct {
|
||||
App fiber.Router
|
||||
Controller *controller.Controller
|
||||
}
|
||||
|
||||
// register bulky of ArticleComments module
|
||||
var NewArticleCommentsModule = fx.Options(
|
||||
// register repository of ArticleComments module
|
||||
fx.Provide(repository.NewArticleCommentsRepository),
|
||||
|
||||
// register service of ArticleComments module
|
||||
fx.Provide(service.NewArticleCommentsService),
|
||||
|
||||
// register controller of ArticleComments module
|
||||
fx.Provide(controller.NewController),
|
||||
|
||||
// register router of ArticleComments module
|
||||
fx.Provide(NewArticleCommentsRouter),
|
||||
)
|
||||
|
||||
// init ArticleCommentsRouter
|
||||
func NewArticleCommentsRouter(fiber *fiber.App, controller *controller.Controller) *ArticleCommentsRouter {
|
||||
return &ArticleCommentsRouter{
|
||||
App: fiber,
|
||||
Controller: controller,
|
||||
}
|
||||
}
|
||||
|
||||
// register routes of ArticleComments module
|
||||
func (_i *ArticleCommentsRouter) RegisterArticleCommentsRoutes() {
|
||||
// define controllers
|
||||
articleCommentsController := _i.Controller.ArticleComments
|
||||
|
||||
// define routes
|
||||
_i.App.Route("/article-comments", func(router fiber.Router) {
|
||||
router.Get("/", articleCommentsController.All)
|
||||
router.Get("/:id", articleCommentsController.Show)
|
||||
router.Post("/", articleCommentsController.Save)
|
||||
router.Put("/:id", articleCommentsController.Update)
|
||||
router.Delete("/:id", articleCommentsController.Delete)
|
||||
router.Post("/approval", articleCommentsController.Approval)
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,235 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/module/article_comments/request"
|
||||
"campaign-pool-be/app/module/article_comments/service"
|
||||
"campaign-pool-be/utils/paginator"
|
||||
utilRes "campaign-pool-be/utils/response"
|
||||
utilVal "campaign-pool-be/utils/validator"
|
||||
"strconv"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
type articleCommentsController struct {
|
||||
articleCommentsService service.ArticleCommentsService
|
||||
Log zerolog.Logger
|
||||
}
|
||||
|
||||
type ArticleCommentsController interface {
|
||||
All(c *fiber.Ctx) error
|
||||
Show(c *fiber.Ctx) error
|
||||
Save(c *fiber.Ctx) error
|
||||
Update(c *fiber.Ctx) error
|
||||
Delete(c *fiber.Ctx) error
|
||||
Approval(c *fiber.Ctx) error
|
||||
}
|
||||
|
||||
func NewArticleCommentsController(articleCommentsService service.ArticleCommentsService, log zerolog.Logger) ArticleCommentsController {
|
||||
return &articleCommentsController{
|
||||
articleCommentsService: articleCommentsService,
|
||||
Log: log,
|
||||
}
|
||||
}
|
||||
|
||||
// All get all ArticleComments
|
||||
// @Summary Get all ArticleComments
|
||||
// @Description API for getting all ArticleComments
|
||||
// @Tags ArticleComments
|
||||
// @Security Bearer
|
||||
// @Param req query request.ArticleCommentsQueryRequest false "query parameters"
|
||||
// @Param req query paginator.Pagination false "pagination parameters"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /article-comments [get]
|
||||
func (_i *articleCommentsController) All(c *fiber.Ctx) error {
|
||||
// Get from context
|
||||
paginate, err := paginator.Paginate(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reqContext := request.ArticleCommentsQueryRequestContext{
|
||||
Message: c.Query("message"),
|
||||
ArticleId: c.Query("articleId"),
|
||||
CommentFrom: c.Query("commentFrom"),
|
||||
ParentId: c.Query("parentId"),
|
||||
IsPublic: c.Query("isPublic"),
|
||||
}
|
||||
req := reqContext.ToParamRequest()
|
||||
req.Pagination = paginate
|
||||
|
||||
articleCommentsData, paging, err := _i.articleCommentsService.All(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"ArticleComments list successfully retrieved"},
|
||||
Data: articleCommentsData,
|
||||
Meta: paging,
|
||||
})
|
||||
}
|
||||
|
||||
// Show get one ArticleComments
|
||||
// @Summary Get one ArticleComments
|
||||
// @Description API for getting one ArticleComments
|
||||
// @Tags ArticleComments
|
||||
// @Security Bearer
|
||||
// @Param id path int true "ArticleComments ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /article-comments/{id} [get]
|
||||
func (_i *articleCommentsController) Show(c *fiber.Ctx) error {
|
||||
// Get from context
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
articleCommentsData, err := _i.articleCommentsService.Show(uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"ArticleComments successfully retrieved"},
|
||||
Data: articleCommentsData,
|
||||
})
|
||||
}
|
||||
|
||||
// Save create ArticleComments
|
||||
// @Summary Create ArticleComments
|
||||
// @Description API for create ArticleComments
|
||||
// @Tags ArticleComments
|
||||
// @Security Bearer
|
||||
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
||||
// @Param payload body request.ArticleCommentsCreateRequest true "Required payload"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /article-comments [post]
|
||||
func (_i *articleCommentsController) Save(c *fiber.Ctx) error {
|
||||
// Get from context
|
||||
req := new(request.ArticleCommentsCreateRequest)
|
||||
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authToken := c.Get("Authorization")
|
||||
dataResult, err := _i.articleCommentsService.Save(*req, authToken)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"ArticleComments successfully created"},
|
||||
Data: dataResult,
|
||||
})
|
||||
}
|
||||
|
||||
// Update update ArticleComments
|
||||
// @Summary update ArticleComments
|
||||
// @Description API for update ArticleComments
|
||||
// @Tags ArticleComments
|
||||
// @Security Bearer
|
||||
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||
// @Param payload body request.ArticleCommentsUpdateRequest true "Required payload"
|
||||
// @Param id path int true "ArticleComments ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /article-comments/{id} [put]
|
||||
func (_i *articleCommentsController) Update(c *fiber.Ctx) error {
|
||||
// Get from context
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req := new(request.ArticleCommentsUpdateRequest)
|
||||
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = _i.articleCommentsService.Update(uint(id), *req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"ArticleComments successfully updated"},
|
||||
})
|
||||
}
|
||||
|
||||
// Delete delete ArticleComments
|
||||
// @Summary delete ArticleComments
|
||||
// @Description API for delete ArticleComments
|
||||
// @Tags ArticleComments
|
||||
// @Security Bearer
|
||||
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||
// @Param id path int true "ArticleComments ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /article-comments/{id} [delete]
|
||||
func (_i *articleCommentsController) Delete(c *fiber.Ctx) error {
|
||||
// Get from context
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = _i.articleCommentsService.Delete(uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"ArticleComments successfully deleted"},
|
||||
})
|
||||
}
|
||||
|
||||
// Approval ArticleComments
|
||||
// @Summary Approval ArticleComments
|
||||
// @Description API for Approval ArticleComments
|
||||
// @Tags ArticleComments
|
||||
// @Security Bearer
|
||||
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||
// @Param payload body request.ArticleCommentsApprovalRequest true "Required payload"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /article-comments/approval [post]
|
||||
func (_i *articleCommentsController) Approval(c *fiber.Ctx) error {
|
||||
// Get from context
|
||||
req := new(request.ArticleCommentsApprovalRequest)
|
||||
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err := _i.articleCommentsService.Approval(req.ID, *req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"ArticleComments successfully reviewed"},
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/module/article_comments/service"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
ArticleComments ArticleCommentsController
|
||||
}
|
||||
|
||||
func NewController(ArticleCommentsService service.ArticleCommentsService, log zerolog.Logger) *Controller {
|
||||
return &Controller{
|
||||
ArticleComments: NewArticleCommentsController(ArticleCommentsService, log),
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package mapper
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/database/entity"
|
||||
res "campaign-pool-be/app/module/article_comments/response"
|
||||
usersRepository "campaign-pool-be/app/module/users/repository"
|
||||
)
|
||||
|
||||
func ArticleCommentsResponseMapper(articleCommentsReq *entity.ArticleComments, usersRepo usersRepository.UsersRepository) (articleCommentsRes *res.ArticleCommentsResponse) {
|
||||
if articleCommentsReq != nil {
|
||||
|
||||
commentFromName := ""
|
||||
if articleCommentsReq.CommentFrom != nil {
|
||||
findUser, _ := usersRepo.FindOne(*articleCommentsReq.CommentFrom)
|
||||
if findUser != nil {
|
||||
commentFromName = findUser.Fullname
|
||||
}
|
||||
}
|
||||
|
||||
articleCommentsRes = &res.ArticleCommentsResponse{
|
||||
ID: articleCommentsReq.ID,
|
||||
Message: articleCommentsReq.Message,
|
||||
ArticleId: articleCommentsReq.ArticleId,
|
||||
CommentFromId: articleCommentsReq.CommentFrom,
|
||||
CommentFromName: &commentFromName,
|
||||
ParentId: articleCommentsReq.ParentId,
|
||||
IsPublic: articleCommentsReq.IsPublic,
|
||||
StatusId: articleCommentsReq.StatusId,
|
||||
IsActive: articleCommentsReq.IsActive,
|
||||
CreatedAt: articleCommentsReq.CreatedAt,
|
||||
UpdatedAt: articleCommentsReq.UpdatedAt,
|
||||
}
|
||||
}
|
||||
return articleCommentsRes
|
||||
}
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
package repository
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/database"
|
||||
"campaign-pool-be/app/database/entity"
|
||||
"campaign-pool-be/app/module/article_comments/request"
|
||||
"campaign-pool-be/utils/paginator"
|
||||
utilSvc "campaign-pool-be/utils/service"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
type articleCommentsRepository struct {
|
||||
DB *database.Database
|
||||
Log zerolog.Logger
|
||||
}
|
||||
|
||||
// ArticleCommentsRepository define interface of IArticleCommentsRepository
|
||||
type ArticleCommentsRepository interface {
|
||||
GetAll(req request.ArticleCommentsQueryRequest) (articleCommentss []*entity.ArticleComments, paging paginator.Pagination, err error)
|
||||
FindOne(id uint) (articleComments *entity.ArticleComments, err error)
|
||||
Create(articleComments *entity.ArticleComments) (articleCommentsReturn *entity.ArticleComments, err error)
|
||||
Update(id uint, articleComments *entity.ArticleComments) (err error)
|
||||
Delete(id uint) (err error)
|
||||
}
|
||||
|
||||
func NewArticleCommentsRepository(db *database.Database, logger zerolog.Logger) ArticleCommentsRepository {
|
||||
return &articleCommentsRepository{
|
||||
DB: db,
|
||||
Log: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// implement interface of IArticleCommentsRepository
|
||||
func (_i *articleCommentsRepository) GetAll(req request.ArticleCommentsQueryRequest) (articleCommentss []*entity.ArticleComments, paging paginator.Pagination, err error) {
|
||||
var count int64
|
||||
|
||||
query := _i.DB.DB.Model(&entity.ArticleComments{})
|
||||
query = query.Where("is_active = ?", true)
|
||||
|
||||
if req.Message != nil && *req.Message != "" {
|
||||
message := strings.ToLower(*req.Message)
|
||||
query = query.Where("LOWER(message) LIKE ?", "%"+strings.ToLower(message)+"%")
|
||||
}
|
||||
if req.ArticleId != nil {
|
||||
query = query.Where("article_id = ?", req.ArticleId)
|
||||
}
|
||||
if req.CommentFrom != nil {
|
||||
query = query.Where("comment_from = ?", req.CommentFrom)
|
||||
}
|
||||
if req.ParentId != nil {
|
||||
query = query.Where("parent_id = ?", req.ParentId)
|
||||
}
|
||||
if req.IsPublic != nil {
|
||||
query = query.Where("is_public = ?", req.IsPublic)
|
||||
}
|
||||
query.Count(&count)
|
||||
|
||||
if req.Pagination.SortBy != "" {
|
||||
direction := "ASC"
|
||||
if req.Pagination.Sort == "desc" {
|
||||
direction = "DESC"
|
||||
}
|
||||
query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction))
|
||||
}
|
||||
|
||||
req.Pagination.Count = count
|
||||
req.Pagination = paginator.Paging(req.Pagination)
|
||||
|
||||
err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&articleCommentss).Error
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
paging = *req.Pagination
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *articleCommentsRepository) FindOne(id uint) (articleComments *entity.ArticleComments, err error) {
|
||||
query := _i.DB.DB
|
||||
if err := query.First(&articleComments, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return articleComments, nil
|
||||
}
|
||||
|
||||
func (_i *articleCommentsRepository) Create(articleComments *entity.ArticleComments) (articleCommentsReturn *entity.ArticleComments, err error) {
|
||||
result := _i.DB.DB.Create(articleComments)
|
||||
return articleComments, result.Error
|
||||
}
|
||||
|
||||
func (_i *articleCommentsRepository) Update(id uint, articleComments *entity.ArticleComments) (err error) {
|
||||
articleCommentsMap, err := utilSvc.StructToMap(articleComments)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return _i.DB.DB.Model(&entity.ArticleComments{}).
|
||||
Where(&entity.ArticleComments{ID: id}).
|
||||
Updates(articleCommentsMap).Error
|
||||
}
|
||||
|
||||
func (_i *articleCommentsRepository) Delete(id uint) error {
|
||||
return _i.DB.DB.Delete(&entity.ArticleComments{}, id).Error
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
package request
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/database/entity"
|
||||
"campaign-pool-be/utils/paginator"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ArticleCommentsGeneric interface {
|
||||
ToEntity()
|
||||
}
|
||||
|
||||
type ArticleCommentsQueryRequest struct {
|
||||
Message *string `json:"message"`
|
||||
ArticleId *int `json:"articleId"`
|
||||
CommentFrom *int `json:"commentFrom"`
|
||||
ParentId *int `json:"parentId"`
|
||||
IsPublic *bool `json:"isPublic"`
|
||||
Pagination *paginator.Pagination `json:"pagination"`
|
||||
}
|
||||
|
||||
type ArticleCommentsCreateRequest struct {
|
||||
Message string `json:"message" validate:"required"`
|
||||
ArticleId uint `json:"articleId" validate:"required"`
|
||||
ParentId *int `json:"parentId"`
|
||||
IsPublic *bool `json:"isPublic"`
|
||||
}
|
||||
|
||||
func (req ArticleCommentsCreateRequest) ToEntity() *entity.ArticleComments {
|
||||
return &entity.ArticleComments{
|
||||
Message: req.Message,
|
||||
ArticleId: req.ArticleId,
|
||||
ParentId: req.ParentId,
|
||||
StatusId: 0,
|
||||
}
|
||||
}
|
||||
|
||||
type ArticleCommentsUpdateRequest struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
Message string `json:"message" validate:"required"`
|
||||
ArticleId uint `json:"articleId" validate:"required"`
|
||||
ParentId *int `json:"parentId"`
|
||||
IsPublic *bool `json:"isPublic"`
|
||||
}
|
||||
|
||||
func (req ArticleCommentsUpdateRequest) ToEntity() *entity.ArticleComments {
|
||||
return &entity.ArticleComments{
|
||||
ID: req.ID,
|
||||
Message: req.Message,
|
||||
ArticleId: req.ArticleId,
|
||||
ParentId: req.ParentId,
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
type ArticleCommentsApprovalRequest struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
StatusId int `json:"statusId" validate:"required"`
|
||||
}
|
||||
|
||||
func (req ArticleCommentsApprovalRequest) ToEntity() *entity.ArticleComments {
|
||||
approvedNow := time.Now()
|
||||
return &entity.ArticleComments{
|
||||
ID: req.ID,
|
||||
StatusId: req.StatusId,
|
||||
ApprovedAt: &approvedNow,
|
||||
}
|
||||
}
|
||||
|
||||
type ArticleCommentsQueryRequestContext struct {
|
||||
Message string `json:"message"`
|
||||
ArticleId string `json:"articleId"`
|
||||
CommentFrom string `json:"commentFrom"`
|
||||
ParentId string `json:"parentId"`
|
||||
IsPublic string `json:"isPublic"`
|
||||
}
|
||||
|
||||
func (req ArticleCommentsQueryRequestContext) ToParamRequest() ArticleCommentsQueryRequest {
|
||||
var request ArticleCommentsQueryRequest
|
||||
|
||||
if message := req.Message; message != "" {
|
||||
request.Message = &message
|
||||
}
|
||||
if articleIdStr := req.ArticleId; articleIdStr != "" {
|
||||
articleId, err := strconv.Atoi(articleIdStr)
|
||||
if err == nil {
|
||||
request.ArticleId = &articleId
|
||||
}
|
||||
}
|
||||
if commentFromStr := req.CommentFrom; commentFromStr != "" {
|
||||
commentFrom, err := strconv.Atoi(commentFromStr)
|
||||
if err == nil {
|
||||
request.CommentFrom = &commentFrom
|
||||
}
|
||||
}
|
||||
if parentIdStr := req.ParentId; parentIdStr != "" {
|
||||
parentId, err := strconv.Atoi(parentIdStr)
|
||||
if err == nil {
|
||||
request.ParentId = &parentId
|
||||
}
|
||||
}
|
||||
if isPublicStr := req.IsPublic; isPublicStr != "" {
|
||||
isPublic, err := strconv.ParseBool(isPublicStr)
|
||||
if err == nil {
|
||||
request.IsPublic = &isPublic
|
||||
}
|
||||
}
|
||||
|
||||
return request
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package response
|
||||
|
||||
import "time"
|
||||
|
||||
type ArticleCommentsResponse struct {
|
||||
ID uint `json:"id"`
|
||||
Message string `json:"message"`
|
||||
ArticleId uint `json:"articleId"`
|
||||
CommentFromId *uint `json:"commentFromId"`
|
||||
CommentFromName *string `json:"commentFromName"`
|
||||
ParentId *int `json:"parentId"`
|
||||
StatusId int `json:"statusId"`
|
||||
IsPublic bool `json:"isPublic"`
|
||||
IsActive bool `json:"isActive"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/database/entity"
|
||||
"campaign-pool-be/app/module/article_comments/mapper"
|
||||
"campaign-pool-be/app/module/article_comments/repository"
|
||||
"campaign-pool-be/app/module/article_comments/request"
|
||||
"campaign-pool-be/app/module/article_comments/response"
|
||||
usersRepository "campaign-pool-be/app/module/users/repository"
|
||||
"campaign-pool-be/utils/paginator"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
utilSvc "campaign-pool-be/utils/service"
|
||||
)
|
||||
|
||||
// ArticleCommentsService
|
||||
type articleCommentsService struct {
|
||||
Repo repository.ArticleCommentsRepository
|
||||
UsersRepo usersRepository.UsersRepository
|
||||
Log zerolog.Logger
|
||||
}
|
||||
|
||||
// ArticleCommentsService define interface of IArticleCommentsService
|
||||
type ArticleCommentsService interface {
|
||||
All(req request.ArticleCommentsQueryRequest) (articleComments []*response.ArticleCommentsResponse, paging paginator.Pagination, err error)
|
||||
Show(id uint) (articleComments *response.ArticleCommentsResponse, err error)
|
||||
Save(req request.ArticleCommentsCreateRequest, authToken string) (articleComments *entity.ArticleComments, err error)
|
||||
Update(id uint, req request.ArticleCommentsUpdateRequest) (err error)
|
||||
Delete(id uint) error
|
||||
Approval(id uint, req request.ArticleCommentsApprovalRequest) (err error)
|
||||
}
|
||||
|
||||
// NewArticleCommentsService init ArticleCommentsService
|
||||
func NewArticleCommentsService(repo repository.ArticleCommentsRepository, log zerolog.Logger, usersRepo usersRepository.UsersRepository) ArticleCommentsService {
|
||||
|
||||
return &articleCommentsService{
|
||||
Repo: repo,
|
||||
Log: log,
|
||||
UsersRepo: usersRepo,
|
||||
}
|
||||
}
|
||||
|
||||
// All implement interface of ArticleCommentsService
|
||||
func (_i *articleCommentsService) All(req request.ArticleCommentsQueryRequest) (articleCommentss []*response.ArticleCommentsResponse, paging paginator.Pagination, err error) {
|
||||
results, paging, err := _i.Repo.GetAll(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, result := range results {
|
||||
articleCommentss = append(articleCommentss, mapper.ArticleCommentsResponseMapper(result, _i.UsersRepo))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *articleCommentsService) Show(id uint) (articleComments *response.ArticleCommentsResponse, err error) {
|
||||
result, err := _i.Repo.FindOne(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mapper.ArticleCommentsResponseMapper(result, _i.UsersRepo), nil
|
||||
}
|
||||
|
||||
func (_i *articleCommentsService) Save(req request.ArticleCommentsCreateRequest, authToken string) (articleComments *entity.ArticleComments, err error) {
|
||||
_i.Log.Info().Interface("data", req).Msg("")
|
||||
|
||||
newReq := req.ToEntity()
|
||||
|
||||
createdBy := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||
newReq.CommentFrom = &createdBy.ID
|
||||
newReq.IsActive = true
|
||||
|
||||
return _i.Repo.Create(newReq)
|
||||
}
|
||||
|
||||
func (_i *articleCommentsService) Update(id uint, req request.ArticleCommentsUpdateRequest) (err error) {
|
||||
_i.Log.Info().Interface("data", req).Msg("")
|
||||
return _i.Repo.Update(id, req.ToEntity())
|
||||
}
|
||||
|
||||
func (_i *articleCommentsService) Delete(id uint) error {
|
||||
result, err := _i.Repo.FindOne(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result.IsActive = false
|
||||
return _i.Repo.Update(id, result)
|
||||
}
|
||||
|
||||
func (_i *articleCommentsService) Approval(id uint, req request.ArticleCommentsApprovalRequest) (err error) {
|
||||
_i.Log.Info().Interface("data", req).Msg("")
|
||||
newReq := req.ToEntity()
|
||||
|
||||
if newReq.StatusId == 1 || newReq.StatusId == 2 {
|
||||
newReq.IsPublic = true
|
||||
} else {
|
||||
newReq.IsPublic = false
|
||||
}
|
||||
|
||||
return _i.Repo.Update(id, newReq)
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
package article_files
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/module/article_files/controller"
|
||||
"campaign-pool-be/app/module/article_files/repository"
|
||||
"campaign-pool-be/app/module/article_files/service"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"go.uber.org/fx"
|
||||
)
|
||||
|
||||
// struct of ArticleFilesRouter
|
||||
type ArticleFilesRouter struct {
|
||||
App fiber.Router
|
||||
Controller *controller.Controller
|
||||
}
|
||||
|
||||
// register bulky of ArticleFiles module
|
||||
var NewArticleFilesModule = fx.Options(
|
||||
// register repository of ArticleFiles module
|
||||
fx.Provide(repository.NewArticleFilesRepository),
|
||||
|
||||
// register service of ArticleFiles module
|
||||
fx.Provide(service.NewArticleFilesService),
|
||||
fx.Provide(service.NewUploadService),
|
||||
fx.Provide(service.NewUploadManager),
|
||||
|
||||
// register controller of ArticleFiles module
|
||||
fx.Provide(controller.NewController),
|
||||
|
||||
// register router of ArticleFiles module
|
||||
fx.Provide(NewArticleFilesRouter),
|
||||
)
|
||||
|
||||
// init ArticleFilesRouter
|
||||
func NewArticleFilesRouter(fiber *fiber.App, controller *controller.Controller) *ArticleFilesRouter {
|
||||
return &ArticleFilesRouter{
|
||||
App: fiber,
|
||||
Controller: controller,
|
||||
}
|
||||
}
|
||||
|
||||
// register routes of ArticleFiles module
|
||||
func (_i *ArticleFilesRouter) RegisterArticleFilesRoutes() {
|
||||
// define controllers
|
||||
articleFilesController := _i.Controller.ArticleFiles
|
||||
|
||||
// define routes
|
||||
_i.App.Route("/article-files", func(router fiber.Router) {
|
||||
router.Get("/", articleFilesController.All)
|
||||
router.Get("/:id", articleFilesController.Show)
|
||||
router.Post("/:articleId", articleFilesController.Save)
|
||||
router.Put("/:id", articleFilesController.Update)
|
||||
router.Delete("/:id", articleFilesController.Delete)
|
||||
router.Get("/viewer/:filename", articleFilesController.Viewer)
|
||||
router.Get("/upload-status/:uploadId", articleFilesController.GetUploadStatus)
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,240 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/module/article_files/request"
|
||||
"campaign-pool-be/app/module/article_files/service"
|
||||
"campaign-pool-be/utils/paginator"
|
||||
utilRes "campaign-pool-be/utils/response"
|
||||
utilVal "campaign-pool-be/utils/validator"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
type articleFilesController struct {
|
||||
articleFilesService service.ArticleFilesService
|
||||
}
|
||||
|
||||
type ArticleFilesController interface {
|
||||
All(c *fiber.Ctx) error
|
||||
Show(c *fiber.Ctx) error
|
||||
Save(c *fiber.Ctx) error
|
||||
Update(c *fiber.Ctx) error
|
||||
Delete(c *fiber.Ctx) error
|
||||
Viewer(c *fiber.Ctx) error
|
||||
GetUploadStatus(c *fiber.Ctx) error
|
||||
}
|
||||
|
||||
func NewArticleFilesController(articleFilesService service.ArticleFilesService) ArticleFilesController {
|
||||
return &articleFilesController{
|
||||
articleFilesService: articleFilesService,
|
||||
}
|
||||
}
|
||||
|
||||
// All ArticleFiles
|
||||
// @Summary Get all ArticleFiles
|
||||
// @Description API for getting all ArticleFiles
|
||||
// @Tags Article Files
|
||||
// @Security Bearer
|
||||
// @Param req query request.ArticleFilesQueryRequest false "query parameters"
|
||||
// @Param req query paginator.Pagination false "pagination parameters"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /article-files [get]
|
||||
func (_i *articleFilesController) All(c *fiber.Ctx) error {
|
||||
// Get from context
|
||||
paginate, err := paginator.Paginate(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reqContext := request.ArticleFilesQueryRequestContext{
|
||||
ArticleId: c.Query("articleId"),
|
||||
FileName: c.Query("fileName"),
|
||||
StatusId: c.Query("statusId"),
|
||||
IsPublish: c.Query("isPublish"),
|
||||
}
|
||||
req := reqContext.ToParamRequest()
|
||||
req.Pagination = paginate
|
||||
|
||||
articleFilesData, paging, err := _i.articleFilesService.All(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"ArticleFiles list successfully retrieved"},
|
||||
Data: articleFilesData,
|
||||
Meta: paging,
|
||||
})
|
||||
}
|
||||
|
||||
// Show ArticleFiles
|
||||
// @Summary Get one ArticleFiles
|
||||
// @Description API for getting one ArticleFiles
|
||||
// @Tags Article Files
|
||||
// @Security Bearer
|
||||
// @Param id path int true "ArticleFiles ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /article-files/{id} [get]
|
||||
func (_i *articleFilesController) Show(c *fiber.Ctx) error {
|
||||
// Get from context
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
articleFilesData, err := _i.articleFilesService.Show(uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"ArticleFiles successfully retrieved"},
|
||||
Data: articleFilesData,
|
||||
})
|
||||
}
|
||||
|
||||
// Save ArticleFiles
|
||||
// @Summary Upload ArticleFiles
|
||||
// @Description API for create ArticleFiles
|
||||
// @Tags Article Files
|
||||
// @Security Bearer
|
||||
// @Produce json
|
||||
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||
// @Param files formData file true "Upload file" multiple true
|
||||
// @Param articleId path int true "Article ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /article-files/{articleId} [post]
|
||||
func (_i *articleFilesController) Save(c *fiber.Ctx) error {
|
||||
// Get from context
|
||||
id, err := strconv.ParseUint(c.Params("articleId"), 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = _i.articleFilesService.Save(c, uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"ArticleFiles successfully upload"},
|
||||
})
|
||||
}
|
||||
|
||||
// Update ArticleFiles
|
||||
// @Summary Update ArticleFiles
|
||||
// @Description API for update ArticleFiles
|
||||
// @Tags Article Files
|
||||
// @Security Bearer
|
||||
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||
// @Param payload body request.ArticleFilesUpdateRequest true "Required payload"
|
||||
// @Param id path int true "ArticleFiles ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /article-files/{id} [put]
|
||||
func (_i *articleFilesController) Update(c *fiber.Ctx) error {
|
||||
// Get from context
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req := new(request.ArticleFilesUpdateRequest)
|
||||
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = _i.articleFilesService.Update(uint(id), *req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"ArticleFiles successfully updated"},
|
||||
})
|
||||
}
|
||||
|
||||
// Delete ArticleFiles
|
||||
// @Summary Delete ArticleFiles
|
||||
// @Description API for delete ArticleFiles
|
||||
// @Tags Article Files
|
||||
// @Security Bearer
|
||||
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||
// @Param id path int true "ArticleFiles ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /article-files/{id} [delete]
|
||||
func (_i *articleFilesController) Delete(c *fiber.Ctx) error {
|
||||
// Get from context
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = _i.articleFilesService.Delete(uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"ArticleFiles successfully deleted"},
|
||||
})
|
||||
}
|
||||
|
||||
// Viewer ArticleFiles
|
||||
// @Summary Viewer ArticleFiles
|
||||
// @Description API for Viewer ArticleFiles
|
||||
// @Tags Article Files
|
||||
// @Security Bearer
|
||||
// @Param filename path string true "Article File Name"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /article-files/viewer/{filename} [get]
|
||||
func (_i *articleFilesController) Viewer(c *fiber.Ctx) error {
|
||||
// Get from context
|
||||
return _i.articleFilesService.Viewer(c)
|
||||
}
|
||||
|
||||
// GetUploadStatus ArticleFiles
|
||||
// @Summary GetUploadStatus ArticleFiles
|
||||
// @Description API for GetUploadStatus ArticleFiles
|
||||
// @Tags Article Files
|
||||
// @Security Bearer
|
||||
// @Param uploadId path string true "Upload ID of ArticleFiles"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /article-files/upload-status/{uploadId} [get]
|
||||
func (_i *articleFilesController) GetUploadStatus(c *fiber.Ctx) error {
|
||||
progress, _ := _i.articleFilesService.GetUploadStatus(c)
|
||||
progressMessage := fmt.Sprintf("Upload Progress: %d%%", progress)
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"Upload Status Retrieve"},
|
||||
Data: progressMessage,
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package controller
|
||||
|
||||
import "campaign-pool-be/app/module/article_files/service"
|
||||
|
||||
type Controller struct {
|
||||
ArticleFiles ArticleFilesController
|
||||
}
|
||||
|
||||
func NewController(ArticleFilesService service.ArticleFilesService) *Controller {
|
||||
return &Controller{
|
||||
ArticleFiles: NewArticleFilesController(ArticleFilesService),
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package mapper
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/database/entity"
|
||||
res "campaign-pool-be/app/module/article_files/response"
|
||||
)
|
||||
|
||||
func ArticleFilesResponseMapper(articleFilesReq *entity.ArticleFiles, host string) (articleFilesRes *res.ArticleFilesResponse) {
|
||||
fileUrl := host + "/article-files/viewer/"
|
||||
if articleFilesReq.FileName != nil {
|
||||
fileUrl += *articleFilesReq.FileName
|
||||
}
|
||||
|
||||
if articleFilesReq != nil {
|
||||
articleFilesRes = &res.ArticleFilesResponse{
|
||||
ID: articleFilesReq.ID,
|
||||
ArticleId: articleFilesReq.ArticleId,
|
||||
FilePath: articleFilesReq.FilePath,
|
||||
FileUrl: &fileUrl,
|
||||
FileName: articleFilesReq.FileName,
|
||||
FileThumbnail: articleFilesReq.FileThumbnail,
|
||||
FileAlt: articleFilesReq.FileAlt,
|
||||
WidthPixel: articleFilesReq.WidthPixel,
|
||||
HeightPixel: articleFilesReq.HeightPixel,
|
||||
Size: articleFilesReq.Size,
|
||||
DownloadCount: articleFilesReq.DownloadCount,
|
||||
CreatedById: articleFilesReq.CreatedById,
|
||||
StatusId: articleFilesReq.StatusId,
|
||||
IsPublish: articleFilesReq.IsPublish,
|
||||
PublishedAt: articleFilesReq.PublishedAt,
|
||||
IsActive: articleFilesReq.IsActive,
|
||||
CreatedAt: articleFilesReq.CreatedAt,
|
||||
UpdatedAt: articleFilesReq.UpdatedAt,
|
||||
}
|
||||
}
|
||||
return articleFilesRes
|
||||
}
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
package repository
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/database"
|
||||
"campaign-pool-be/app/database/entity"
|
||||
"campaign-pool-be/app/module/article_files/request"
|
||||
"campaign-pool-be/utils/paginator"
|
||||
utilSvc "campaign-pool-be/utils/service"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type articleFilesRepository struct {
|
||||
DB *database.Database
|
||||
}
|
||||
|
||||
// ArticleFilesRepository define interface of IArticleFilesRepository
|
||||
type ArticleFilesRepository interface {
|
||||
GetAll(req request.ArticleFilesQueryRequest) (articleFiless []*entity.ArticleFiles, paging paginator.Pagination, err error)
|
||||
FindOne(id uint) (articleFiles *entity.ArticleFiles, err error)
|
||||
FindByArticle(articleId uint) (articleFiles []*entity.ArticleFiles, err error)
|
||||
FindByFilename(filename string) (articleFiles *entity.ArticleFiles, err error)
|
||||
Create(articleFiles *entity.ArticleFiles) (err error)
|
||||
Update(id uint, articleFiles *entity.ArticleFiles) (err error)
|
||||
Delete(id uint) (err error)
|
||||
}
|
||||
|
||||
func NewArticleFilesRepository(db *database.Database) ArticleFilesRepository {
|
||||
return &articleFilesRepository{
|
||||
DB: db,
|
||||
}
|
||||
}
|
||||
|
||||
// implement interface of IArticleFilesRepository
|
||||
func (_i *articleFilesRepository) GetAll(req request.ArticleFilesQueryRequest) (articleFiless []*entity.ArticleFiles, paging paginator.Pagination, err error) {
|
||||
var count int64
|
||||
|
||||
query := _i.DB.DB.Model(&entity.ArticleFiles{})
|
||||
query = query.Where("is_active = ?", true)
|
||||
|
||||
if req.ArticleId != nil {
|
||||
query = query.Where("article_id = ?", req.ArticleId)
|
||||
}
|
||||
if req.FileName != nil && *req.FileName != "" {
|
||||
fileName := strings.ToLower(*req.FileName)
|
||||
query = query.Where("LOWER(file_name) LIKE ?", "%"+strings.ToLower(fileName)+"%")
|
||||
}
|
||||
if req.IsPublish != nil {
|
||||
query = query.Where("is_publish = ?", req.IsPublish)
|
||||
}
|
||||
if req.StatusId != nil {
|
||||
query = query.Where("status_id = ?", req.StatusId)
|
||||
}
|
||||
query.Count(&count)
|
||||
|
||||
if req.Pagination.SortBy != "" {
|
||||
direction := "ASC"
|
||||
if req.Pagination.Sort == "desc" {
|
||||
direction = "DESC"
|
||||
}
|
||||
query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction))
|
||||
}
|
||||
|
||||
req.Pagination.Count = count
|
||||
req.Pagination = paginator.Paging(req.Pagination)
|
||||
|
||||
err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&articleFiless).Error
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
paging = *req.Pagination
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *articleFilesRepository) FindOne(id uint) (articleFiles *entity.ArticleFiles, err error) {
|
||||
query := _i.DB.DB
|
||||
|
||||
if err := query.First(&articleFiles, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return articleFiles, nil
|
||||
}
|
||||
|
||||
func (_i *articleFilesRepository) FindByArticle(articleId uint) (articleFiles []*entity.ArticleFiles, err error) {
|
||||
query := _i.DB.DB.Where("article_id = ?", articleId)
|
||||
|
||||
if err := query.Find(&articleFiles).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return articleFiles, nil
|
||||
}
|
||||
|
||||
func (_i *articleFilesRepository) FindByFilename(articleFilename string) (articleFiles *entity.ArticleFiles, err error) {
|
||||
query := _i.DB.DB.Where("file_name = ?", articleFilename)
|
||||
|
||||
if err := query.First(&articleFiles).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return articleFiles, nil
|
||||
}
|
||||
|
||||
func (_i *articleFilesRepository) Create(articleFiles *entity.ArticleFiles) (err error) {
|
||||
return _i.DB.DB.Create(articleFiles).Error
|
||||
}
|
||||
|
||||
func (_i *articleFilesRepository) Update(id uint, articleFiles *entity.ArticleFiles) (err error) {
|
||||
articleFilesMap, err := utilSvc.StructToMap(articleFiles)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return _i.DB.DB.Model(&entity.ArticleFiles{}).
|
||||
Where(&entity.ArticleFiles{ID: id}).
|
||||
Updates(articleFilesMap).Error
|
||||
}
|
||||
|
||||
func (_i *articleFilesRepository) Delete(id uint) error {
|
||||
return _i.DB.DB.Delete(&entity.ArticleFiles{}, id).Error
|
||||
}
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
package request
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/database/entity"
|
||||
"campaign-pool-be/utils/paginator"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ArticleFilesGeneric interface {
|
||||
ToEntity()
|
||||
}
|
||||
|
||||
type ArticleFilesQueryRequest struct {
|
||||
ArticleId *int `json:"articleId"`
|
||||
FileName *string `json:"fileName"`
|
||||
StatusId *int `json:"statusId"`
|
||||
IsPublish *bool `json:"isPublish"`
|
||||
Pagination *paginator.Pagination `json:"pagination"`
|
||||
}
|
||||
|
||||
type ArticleFilesCreateRequest struct {
|
||||
ArticleId uint `json:"articleId" validate:"required"`
|
||||
StatusId int `json:"statusId" validate:"required"`
|
||||
UploadId *string `json:"uploadId"`
|
||||
FilePath *string `json:"filePath"`
|
||||
FileUrl *string `json:"fileUrl"`
|
||||
FileName *string `json:"fileName"`
|
||||
FileThumbnail *string `json:"fileThumbnail"`
|
||||
FileAlt *string `json:"fileAlt"`
|
||||
WidthPixel *string `json:"widthPixel"`
|
||||
HeightPixel *string `json:"heightPixel"`
|
||||
Size *string `json:"size"`
|
||||
}
|
||||
|
||||
func (req ArticleFilesCreateRequest) ToEntity() *entity.ArticleFiles {
|
||||
return &entity.ArticleFiles{
|
||||
ArticleId: req.ArticleId,
|
||||
UploadID: req.UploadId,
|
||||
FilePath: req.FilePath,
|
||||
FileUrl: req.FileUrl,
|
||||
FileName: req.FileName,
|
||||
FileThumbnail: req.FileThumbnail,
|
||||
FileAlt: req.FileAlt,
|
||||
WidthPixel: req.WidthPixel,
|
||||
HeightPixel: req.HeightPixel,
|
||||
Size: req.Size,
|
||||
StatusId: req.StatusId,
|
||||
}
|
||||
}
|
||||
|
||||
type ArticleFilesUpdateRequest struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
ArticleId uint `json:"articleId" validate:"required"`
|
||||
StatusId int `json:"statusId" validate:"required"`
|
||||
FilePath *string `json:"filePath"`
|
||||
FileUrl *string `json:"fileUrl"`
|
||||
FileName *string `json:"fileName"`
|
||||
FileThumbnail *string `json:"fileThumbnail"`
|
||||
FileAlt *string `json:"fileAlt"`
|
||||
WidthPixel *string `json:"widthPixel"`
|
||||
HeightPixel *string `json:"heightPixel"`
|
||||
Size *string `json:"size"`
|
||||
IsPublish *bool `json:"isPublish" validate:"required"`
|
||||
PublishedAt *time.Time `json:"publishedAt" validate:"required"`
|
||||
}
|
||||
|
||||
func (req ArticleFilesUpdateRequest) ToEntity() *entity.ArticleFiles {
|
||||
return &entity.ArticleFiles{
|
||||
ID: req.ID,
|
||||
ArticleId: req.ArticleId,
|
||||
FilePath: req.FilePath,
|
||||
FileUrl: req.FileUrl,
|
||||
FileName: req.FileName,
|
||||
FileThumbnail: req.FileThumbnail,
|
||||
FileAlt: req.FileAlt,
|
||||
WidthPixel: req.WidthPixel,
|
||||
HeightPixel: req.HeightPixel,
|
||||
Size: req.Size,
|
||||
StatusId: req.StatusId,
|
||||
IsPublish: req.IsPublish,
|
||||
PublishedAt: req.PublishedAt,
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
type ArticleFilesQueryRequestContext struct {
|
||||
ArticleId string `json:"articleId"`
|
||||
FileName string `json:"fileName"`
|
||||
StatusId string `json:"statusId"`
|
||||
IsPublish string `json:"isPublish"`
|
||||
}
|
||||
|
||||
func (req ArticleFilesQueryRequestContext) ToParamRequest() ArticleFilesQueryRequest {
|
||||
var request ArticleFilesQueryRequest
|
||||
|
||||
if articleIdStr := req.ArticleId; articleIdStr != "" {
|
||||
articleId, err := strconv.Atoi(articleIdStr)
|
||||
if err == nil {
|
||||
request.ArticleId = &articleId
|
||||
}
|
||||
}
|
||||
if fileName := req.FileName; fileName != "" {
|
||||
request.FileName = &fileName
|
||||
}
|
||||
if statusIdStr := req.StatusId; statusIdStr != "" {
|
||||
statusId, err := strconv.Atoi(statusIdStr)
|
||||
if err == nil {
|
||||
request.StatusId = &statusId
|
||||
}
|
||||
}
|
||||
if isPublishStr := req.IsPublish; isPublishStr != "" {
|
||||
isPublish, err := strconv.ParseBool(isPublishStr)
|
||||
if err == nil {
|
||||
request.IsPublish = &isPublish
|
||||
}
|
||||
}
|
||||
|
||||
return request
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package response
|
||||
|
||||
import "time"
|
||||
|
||||
type ArticleFilesResponse struct {
|
||||
ID uint `json:"id"`
|
||||
ArticleId uint `json:"articleId"`
|
||||
FilePath *string `json:"filePath"`
|
||||
FileUrl *string `json:"fileUrl"`
|
||||
FileName *string `json:"fileName"`
|
||||
FileThumbnail *string `json:"fileThumbnail"`
|
||||
FileAlt *string `json:"fileAlt"`
|
||||
WidthPixel *string `json:"widthPixel"`
|
||||
HeightPixel *string `json:"heightPixel"`
|
||||
Size *string `json:"size"`
|
||||
DownloadCount *int `json:"downloadCount"`
|
||||
CreatedById int `json:"createdById"`
|
||||
StatusId int `json:"statusId"`
|
||||
IsPublish *bool `json:"isPublish"`
|
||||
PublishedAt *time.Time `json:"publishedAt"`
|
||||
IsActive bool `json:"isActive"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
|
@ -0,0 +1,446 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/module/article_files/mapper"
|
||||
"campaign-pool-be/app/module/article_files/repository"
|
||||
"campaign-pool-be/app/module/article_files/request"
|
||||
"campaign-pool-be/app/module/article_files/response"
|
||||
config "campaign-pool-be/config/config"
|
||||
minioStorage "campaign-pool-be/config/config"
|
||||
"campaign-pool-be/utils/paginator"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math/rand"
|
||||
"mime"
|
||||
"mime/multipart"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
// ArticleFilesService
|
||||
type articleFilesService struct {
|
||||
Repo repository.ArticleFilesRepository
|
||||
Log zerolog.Logger
|
||||
Cfg *config.Config
|
||||
MinioStorage *minioStorage.MinioStorage
|
||||
}
|
||||
|
||||
// ArticleFilesService define interface of IArticleFilesService
|
||||
type ArticleFilesService interface {
|
||||
All(req request.ArticleFilesQueryRequest) (articleFiles []*response.ArticleFilesResponse, paging paginator.Pagination, err error)
|
||||
Show(id uint) (articleFiles *response.ArticleFilesResponse, err error)
|
||||
Save(c *fiber.Ctx, id uint) error
|
||||
SaveAsync(c *fiber.Ctx, id uint) error
|
||||
Update(id uint, req request.ArticleFilesUpdateRequest) (err error)
|
||||
GetUploadStatus(c *fiber.Ctx) (progress int, err error)
|
||||
Delete(id uint) error
|
||||
Viewer(c *fiber.Ctx) error
|
||||
}
|
||||
|
||||
// NewArticleFilesService init ArticleFilesService
|
||||
func NewArticleFilesService(repo repository.ArticleFilesRepository, log zerolog.Logger, cfg *config.Config, minioStorage *minioStorage.MinioStorage) ArticleFilesService {
|
||||
|
||||
return &articleFilesService{
|
||||
Repo: repo,
|
||||
Log: log,
|
||||
Cfg: cfg,
|
||||
MinioStorage: minioStorage,
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
progressMap = make(map[string]int) // Menyimpan progress upload per UploadID
|
||||
progressLock = sync.Mutex{}
|
||||
)
|
||||
|
||||
type progressWriter struct {
|
||||
uploadID string
|
||||
totalSize int64
|
||||
uploadedSize *int64
|
||||
}
|
||||
|
||||
// All implement interface of ArticleFilesService
|
||||
func (_i *articleFilesService) All(req request.ArticleFilesQueryRequest) (articleFiless []*response.ArticleFilesResponse, paging paginator.Pagination, err error) {
|
||||
results, paging, err := _i.Repo.GetAll(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
host := _i.Cfg.App.Domain
|
||||
|
||||
for _, result := range results {
|
||||
articleFiless = append(articleFiless, mapper.ArticleFilesResponseMapper(result, host))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *articleFilesService) Show(id uint) (articleFiles *response.ArticleFilesResponse, err error) {
|
||||
result, err := _i.Repo.FindOne(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
host := _i.Cfg.App.Domain
|
||||
|
||||
return mapper.ArticleFilesResponseMapper(result, host), nil
|
||||
}
|
||||
|
||||
func (_i *articleFilesService) SaveAsync(c *fiber.Ctx, id uint) (err error) {
|
||||
bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
form, err := c.MultipartForm()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//filess := form.File["files"]
|
||||
|
||||
// Create minio connection.
|
||||
minioClient, err := _i.MinioStorage.ConnectMinio()
|
||||
if err != nil {
|
||||
// Return status 500 and minio connection error.
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"error": true,
|
||||
"msg": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
for _, files := range form.File {
|
||||
|
||||
_i.Log.Info().Str("timestamp", time.Now().
|
||||
Format(time.RFC3339)).Str("Service:Resource", "Uploader:: top").
|
||||
Interface("files", files).Msg("")
|
||||
|
||||
for _, fileHeader := range files {
|
||||
_i.Log.Info().Str("timestamp", time.Now().
|
||||
Format(time.RFC3339)).Str("Service:Resource", "Uploader:: loop").
|
||||
Interface("data", fileHeader).Msg("")
|
||||
|
||||
filename := filepath.Base(fileHeader.Filename)
|
||||
filenameAlt := filepath.Clean(filename[:len(filename)-len(filepath.Ext(filename))])
|
||||
filename = strings.ReplaceAll(filename, " ", "")
|
||||
filenameWithoutExt := filepath.Clean(filename[:len(filename)-len(filepath.Ext(filename))])
|
||||
extension := filepath.Ext(fileHeader.Filename)[1:]
|
||||
|
||||
now := time.Now()
|
||||
rand.New(rand.NewSource(now.UnixNano()))
|
||||
randUniqueId := rand.Intn(1000000)
|
||||
uploadID := strconv.Itoa(randUniqueId)
|
||||
|
||||
newFilenameWithoutExt := filenameWithoutExt + "_" + strconv.Itoa(randUniqueId)
|
||||
newFilename := newFilenameWithoutExt + "." + extension
|
||||
|
||||
objectName := fmt.Sprintf("articles/upload/%d/%d/%s", now.Year(), now.Month(), newFilename)
|
||||
fileSize := strconv.FormatInt(fileHeader.Size, 10)
|
||||
fileSizeInt := fileHeader.Size
|
||||
|
||||
_i.Log.Info().Str("timestamp", time.Now().
|
||||
Format(time.RFC3339)).Str("Service:Resource", "Uploader:: top").
|
||||
Interface("Start upload", uploadID).Msg("")
|
||||
|
||||
req := request.ArticleFilesCreateRequest{
|
||||
ArticleId: id,
|
||||
UploadId: &uploadID,
|
||||
FilePath: &objectName,
|
||||
FileName: &newFilename,
|
||||
FileAlt: &filenameAlt,
|
||||
Size: &fileSize,
|
||||
}
|
||||
|
||||
err = _i.Repo.Create(req.ToEntity())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
src, err := fileHeader.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
tempFilePath := fmt.Sprintf("/tmp/%s", newFilename)
|
||||
tempFile, err := os.Create(tempFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tempFile.Close()
|
||||
|
||||
// Copy file ke direktori sementara
|
||||
_, err = io.Copy(tempFile, src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go uploadToMinIO(ctx, _i.Log, minioClient, uploadID, tempFilePath, bucketName, objectName, fileSizeInt)
|
||||
}
|
||||
}
|
||||
|
||||
_i.Log.Info().Str("timestamp", time.Now().
|
||||
Format(time.RFC3339)).Str("Service:Resource", "User:All").
|
||||
Interface("data", "Successfully uploaded").Msg("")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *articleFilesService) Save(c *fiber.Ctx, id uint) (err error) {
|
||||
bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName
|
||||
|
||||
form, err := c.MultipartForm()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//filess := form.File["files"]
|
||||
|
||||
// Create minio connection.
|
||||
minioClient, err := _i.MinioStorage.ConnectMinio()
|
||||
|
||||
if err != nil {
|
||||
// Return status 500 and minio connection error.
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"error": true,
|
||||
"msg": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
for _, files := range form.File {
|
||||
|
||||
_i.Log.Info().Str("timestamp", time.Now().
|
||||
Format(time.RFC3339)).Str("Service:Resource", "Uploader:: top").
|
||||
Interface("files", files).Msg("")
|
||||
|
||||
for _, fileHeader := range files {
|
||||
_i.Log.Info().Str("timestamp", time.Now().
|
||||
Format(time.RFC3339)).Str("Service:Resource", "Uploader:: loop").
|
||||
Interface("data", fileHeader).Msg("")
|
||||
|
||||
src, err := fileHeader.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
filename := filepath.Base(fileHeader.Filename)
|
||||
filenameAlt := filepath.Clean(filename[:len(filename)-len(filepath.Ext(filename))])
|
||||
filename = strings.ReplaceAll(filename, " ", "")
|
||||
filenameWithoutExt := filepath.Clean(filename[:len(filename)-len(filepath.Ext(filename))])
|
||||
extension := filepath.Ext(fileHeader.Filename)[1:]
|
||||
|
||||
now := time.Now()
|
||||
rand.New(rand.NewSource(now.UnixNano()))
|
||||
randUniqueId := rand.Intn(1000000)
|
||||
|
||||
newFilenameWithoutExt := filenameWithoutExt + "_" + strconv.Itoa(randUniqueId)
|
||||
newFilename := newFilenameWithoutExt + "." + extension
|
||||
|
||||
objectName := fmt.Sprintf("articles/upload/%d/%d/%s", now.Year(), now.Month(), newFilename)
|
||||
fileSize := strconv.FormatInt(fileHeader.Size, 10)
|
||||
|
||||
req := request.ArticleFilesCreateRequest{
|
||||
ArticleId: id,
|
||||
FilePath: &objectName,
|
||||
FileName: &newFilename,
|
||||
FileAlt: &filenameAlt,
|
||||
Size: &fileSize,
|
||||
}
|
||||
|
||||
err = _i.Repo.Create(req.ToEntity())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Upload file ke MinIO
|
||||
_, err = minioClient.PutObject(context.Background(), bucketName, objectName, src, fileHeader.Size, minio.PutObjectOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_i.Log.Info().Str("timestamp", time.Now().
|
||||
Format(time.RFC3339)).Str("Service:Resource", "User:All").
|
||||
Interface("data", "Successfully uploaded").Msg("")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *articleFilesService) Update(id uint, req request.ArticleFilesUpdateRequest) (err error) {
|
||||
_i.Log.Info().Interface("data", req).Msg("")
|
||||
return _i.Repo.Update(id, req.ToEntity())
|
||||
}
|
||||
|
||||
func (_i *articleFilesService) Delete(id uint) error {
|
||||
result, err := _i.Repo.FindOne(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result.IsActive = false
|
||||
return _i.Repo.Update(id, result)
|
||||
}
|
||||
|
||||
func (_i *articleFilesService) Viewer(c *fiber.Ctx) (err error) {
|
||||
filename := c.Params("filename")
|
||||
result, err := _i.Repo.FindByFilename(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName
|
||||
objectName := *result.FilePath
|
||||
|
||||
_i.Log.Info().Str("timestamp", time.Now().
|
||||
Format(time.RFC3339)).Str("Service:Resource", "Article:Uploads").
|
||||
Interface("data", objectName).Msg("")
|
||||
|
||||
// Create minio connection.
|
||||
minioClient, err := _i.MinioStorage.ConnectMinio()
|
||||
if err != nil {
|
||||
// Return status 500 and minio connection error.
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"error": true,
|
||||
"msg": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
fileContent, err := minioClient.GetObject(ctx, bucketName, objectName, minio.GetObjectOptions{})
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
defer fileContent.Close()
|
||||
|
||||
// Tentukan Content-Type berdasarkan ekstensi file
|
||||
contentType := mime.TypeByExtension("." + getFileExtension(objectName))
|
||||
if contentType == "" {
|
||||
contentType = "application/octet-stream" // fallback jika tidak ada tipe MIME yang cocok
|
||||
}
|
||||
|
||||
c.Set("Content-Type", contentType)
|
||||
|
||||
if _, err := io.Copy(c.Response().BodyWriter(), fileContent); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getFileExtension(filename string) string {
|
||||
// split file name
|
||||
parts := strings.Split(filename, ".")
|
||||
|
||||
// jika tidak ada ekstensi, kembalikan string kosong
|
||||
if len(parts) == 1 || (len(parts) == 2 && parts[0] == "") {
|
||||
return ""
|
||||
}
|
||||
|
||||
// ambil ekstensi terakhir
|
||||
return parts[len(parts)-1]
|
||||
}
|
||||
|
||||
func uploadTempFile(log zerolog.Logger, fileHeader *multipart.FileHeader, filePath string) {
|
||||
src, err := fileHeader.Open()
|
||||
if err != nil {
|
||||
log.Info().Str("timestamp", time.Now().
|
||||
Format(time.RFC3339)).Str("Service:Resource", "Article:uploadToMinIO-0").
|
||||
Interface("err", err).Msg("")
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
tempFile, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
log.Info().Str("timestamp", time.Now().
|
||||
Format(time.RFC3339)).Str("Service:Resource", "Article:uploadToMinIO-1").
|
||||
Interface("err", err).Msg("")
|
||||
}
|
||||
defer tempFile.Close()
|
||||
|
||||
// Copy file ke direktori sementara
|
||||
_, err = io.Copy(tempFile, src)
|
||||
if err != nil {
|
||||
log.Info().Str("timestamp", time.Now().
|
||||
Format(time.RFC3339)).Str("Service:Resource", "Article:uploadToMinIO-2").
|
||||
Interface("err", err).Msg("")
|
||||
}
|
||||
}
|
||||
|
||||
func uploadToMinIO(ctx context.Context, log zerolog.Logger, minioClient *minio.Client, uploadID, filePath, bucketName string, objectName string, fileSize int64) {
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
log.Info().Str("timestamp", time.Now().
|
||||
Format(time.RFC3339)).Str("Service:Resource", "Article:uploadToMinIO-3").
|
||||
Interface("err", err).Msg("")
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Upload file ke MinIO dengan progress tracking
|
||||
uploadProgress := int64(0)
|
||||
reader := io.TeeReader(file, &progressWriter{uploadID: uploadID, totalSize: fileSize, uploadedSize: &uploadProgress})
|
||||
|
||||
_, err = minioClient.PutObject(ctx, bucketName, objectName, reader, fileSize, minio.PutObjectOptions{})
|
||||
if err != nil {
|
||||
|
||||
log.Info().Str("timestamp", time.Now().
|
||||
Format(time.RFC3339)).Str("Service:Resource", "Article:uploadToMinIO-4").
|
||||
Interface("err", err).Msg("")
|
||||
return
|
||||
}
|
||||
|
||||
// Upload selesai, update progress menjadi 100
|
||||
progressLock.Lock()
|
||||
progressMap[uploadID] = 100
|
||||
progressLock.Unlock()
|
||||
|
||||
go removeFileTemp(log, filePath)
|
||||
}
|
||||
|
||||
func removeFileTemp(log zerolog.Logger, filePath string) {
|
||||
err := os.Remove(filePath)
|
||||
if err != nil {
|
||||
log.Info().Str("timestamp", time.Now().
|
||||
Format(time.RFC3339)).Str("Service:Resource", "Article:uploadToMinIO-5").
|
||||
Interface("Failed to remove temporary file", err).Msg("")
|
||||
} else {
|
||||
log.Info().Str("timestamp", time.Now().
|
||||
Format(time.RFC3339)).Str("Service:Resource", "Article:uploadToMinIO-6").
|
||||
Interface("err", "Temporary file removed").Msg("")
|
||||
}
|
||||
}
|
||||
|
||||
func (p *progressWriter) Write(data []byte) (int, error) {
|
||||
n := len(data)
|
||||
progressLock.Lock()
|
||||
defer progressLock.Unlock()
|
||||
|
||||
*p.uploadedSize += int64(n)
|
||||
progress := int(float64(*p.uploadedSize) / float64(p.totalSize) * 100)
|
||||
|
||||
// Update progress di map
|
||||
progressMap[p.uploadID] = progress
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (_i *articleFilesService) GetUploadStatus(c *fiber.Ctx) (progress int, err error) {
|
||||
uploadID := c.Params("uploadId")
|
||||
|
||||
// Ambil progress dari map
|
||||
progressLock.Lock()
|
||||
progress, _ = progressMap[uploadID]
|
||||
progressLock.Unlock()
|
||||
|
||||
return progress, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/rs/zerolog"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
// AsyncUploader menangani proses upload secara asynchronous
|
||||
type UploadService interface {
|
||||
UploadFile(ctx context.Context, minioClient *minio.Client, uploadID string, reader io.Reader, bucketName string, objectName string, size int64, contentType string) error
|
||||
}
|
||||
|
||||
type uploadService struct {
|
||||
uploadManager UploadManager
|
||||
Log zerolog.Logger
|
||||
}
|
||||
|
||||
func NewUploadService(uploadManager UploadManager, log zerolog.Logger) UploadService {
|
||||
return &uploadService{
|
||||
uploadManager: uploadManager,
|
||||
Log: log,
|
||||
}
|
||||
}
|
||||
|
||||
func (u *uploadService) UploadFile(ctx context.Context, minioClient *minio.Client, uploadID string, reader io.Reader, bucketName string, objectName string, size int64, contentType string) error {
|
||||
status := &UploadStatus{
|
||||
FileName: objectName,
|
||||
Size: size,
|
||||
Progress: 0,
|
||||
Status: "uploading",
|
||||
ObjectName: objectName,
|
||||
BucketName: bucketName,
|
||||
StartTime: time.Now(),
|
||||
}
|
||||
|
||||
u.uploadManager.Add(uploadID, status)
|
||||
|
||||
u.Log.Info().Str("timestamp", time.Now().
|
||||
Format(time.RFC3339)).Str("Service:Resource", "UploadService::UploadFile").
|
||||
Interface("add status", status).Msg("")
|
||||
|
||||
// Upload ke Minio
|
||||
_, err := minioClient.PutObject(
|
||||
ctx,
|
||||
bucketName,
|
||||
objectName,
|
||||
reader,
|
||||
size,
|
||||
minio.PutObjectOptions{
|
||||
ContentType: contentType,
|
||||
PartSize: 10 * 1024 * 1024, // 10MB part size
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
u.uploadManager.UpdateStatus(uploadID, "error", err)
|
||||
|
||||
u.Log.Info().Str("timestamp", time.Now().
|
||||
Format(time.RFC3339)).Str("Service:Resource", "UploadService::UploadFile").
|
||||
Interface("error when upload", err).Msg("")
|
||||
}
|
||||
|
||||
u.uploadManager.UpdateStatus(uploadID, "completed", nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
//func (au *UploadService) UploadFile() {
|
||||
// // Buat context dengan timeout
|
||||
// ctx, cancel := context.WithTimeout(au.ctx, 30*time.Minute)
|
||||
// defer cancel()
|
||||
//
|
||||
// // Buat reader dari byte slice
|
||||
// reader := bytes.NewReader(au.fileData)
|
||||
// pipeReader, pipeWriter := io.Pipe()
|
||||
//
|
||||
// au.progressMap.Store(au.uploadID, 0.0)
|
||||
//
|
||||
// // Start goroutine to read from reader and write to pipe
|
||||
// go func() {
|
||||
// defer pipeWriter.Close()
|
||||
// buf := make([]byte, au.partSize)
|
||||
//
|
||||
// totalParts := int(reader.Size() / au.partSize)
|
||||
// if reader.Size()%au.partSize != 0 {
|
||||
// totalParts++
|
||||
// }
|
||||
//
|
||||
// for i := 0; i < totalParts; i++ {
|
||||
// n, err := reader.Read(buf)
|
||||
// if err != nil && err != io.EOF {
|
||||
// log.Println("Error reading file:", err)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// if _, err := pipeWriter.Write(buf[:n]); err != nil {
|
||||
// log.Println("Error writing to pipe:", err)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// progress := float64(i+1) / float64(totalParts) * 100
|
||||
// au.progressMap.Store(au.uploadID, progress)
|
||||
// au.uploadManager.UpdateProgress(au.uploadID, int(progress))
|
||||
// }
|
||||
// }()
|
||||
//
|
||||
// // Upload ke Minio
|
||||
// _, err := au.minioClient.PutObject(
|
||||
// ctx,
|
||||
// au.bucketName,
|
||||
// au.objectName,
|
||||
// pipeReader,
|
||||
// reader.Size(),
|
||||
// minio.PutObjectOptions{
|
||||
// ContentType: au.contentType,
|
||||
// PartSize: 10 * 1024 * 1024, // 10MB part size
|
||||
// },
|
||||
// )
|
||||
//
|
||||
// if err != nil {
|
||||
// log.Println("Error uploading file:", err)
|
||||
// au.progressMap.Store(au.uploadID, "error")
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// fmt.Printf("Uploading process for %s", au.objectName)
|
||||
//
|
||||
// if err != nil {
|
||||
// uploadManager.UpdateStatus(au.uploadID, "error", err)
|
||||
// fmt.Printf("Upload error for %s: %v\n", au.objectName, err)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// au.progressMap.Store(au.uploadID, 100)
|
||||
// au.uploadManager.UpdateProgress(au.uploadID, 100)
|
||||
// au.uploadManager.UpdateStatus(au.uploadID, "completed", nil)
|
||||
//}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type UploadStatus struct {
|
||||
FileName string `json:"fileName"`
|
||||
Size int64 `json:"size"`
|
||||
Progress int `json:"progress"`
|
||||
Status string `json:"status"`
|
||||
ObjectName string `json:"objectName"`
|
||||
BucketName string `json:"bucketName"`
|
||||
StartTime time.Time `json:"startTime"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
type UploadManager interface {
|
||||
Add(uploadID string, status *UploadStatus)
|
||||
UpdateProgress(uploadID string, progress int)
|
||||
UpdateStatus(uploadID string, status string, err error)
|
||||
Get(uploadID string) (*UploadStatus, bool)
|
||||
}
|
||||
|
||||
type uploadManager struct {
|
||||
uploads map[string]*UploadStatus
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
func NewUploadManager() UploadManager {
|
||||
return &uploadManager{
|
||||
uploads: make(map[string]*UploadStatus),
|
||||
}
|
||||
}
|
||||
|
||||
// Add menambahkan status upload baru
|
||||
func (um *uploadManager) Add(uploadID string, status *UploadStatus) {
|
||||
um.mutex.Lock()
|
||||
defer um.mutex.Unlock()
|
||||
um.uploads[uploadID] = status
|
||||
}
|
||||
|
||||
// UpdateProgress memperbarui progress upload
|
||||
func (um *uploadManager) UpdateProgress(uploadID string, progress int) {
|
||||
um.mutex.Lock()
|
||||
defer um.mutex.Unlock()
|
||||
if status, exists := um.uploads[uploadID]; exists {
|
||||
status.Progress = progress
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateStatus memperbarui status upload
|
||||
func (um *uploadManager) UpdateStatus(uploadID string, status string, err error) {
|
||||
um.mutex.Lock()
|
||||
defer um.mutex.Unlock()
|
||||
if upload, exists := um.uploads[uploadID]; exists {
|
||||
upload.Status = status
|
||||
if err != nil {
|
||||
upload.Error = err.Error()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get mendapatkan status upload berdasarkan ID
|
||||
func (um *uploadManager) Get(uploadID string) (*UploadStatus, bool) {
|
||||
um.mutex.RLock()
|
||||
defer um.mutex.RUnlock()
|
||||
status, exists := um.uploads[uploadID]
|
||||
return status, exists
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
package articles
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/module/articles/controller"
|
||||
"campaign-pool-be/app/module/articles/repository"
|
||||
"campaign-pool-be/app/module/articles/service"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"go.uber.org/fx"
|
||||
)
|
||||
|
||||
// ArticlesRouter struct of ArticlesRouter
|
||||
type ArticlesRouter struct {
|
||||
App fiber.Router
|
||||
Controller *controller.Controller
|
||||
}
|
||||
|
||||
// NewArticlesModule register bulky of Articles module
|
||||
var NewArticlesModule = fx.Options(
|
||||
// register repository of Articles module
|
||||
fx.Provide(repository.NewArticlesRepository),
|
||||
|
||||
// register service of Articles module
|
||||
fx.Provide(service.NewArticlesService),
|
||||
|
||||
// register controller of Articles module
|
||||
fx.Provide(controller.NewController),
|
||||
|
||||
// register router of Articles module
|
||||
fx.Provide(NewArticlesRouter),
|
||||
)
|
||||
|
||||
// NewArticlesRouter init ArticlesRouter
|
||||
func NewArticlesRouter(fiber *fiber.App, controller *controller.Controller) *ArticlesRouter {
|
||||
return &ArticlesRouter{
|
||||
App: fiber,
|
||||
Controller: controller,
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterArticlesRoutes register routes of Articles module
|
||||
func (_i *ArticlesRouter) RegisterArticlesRoutes() {
|
||||
// define controllers
|
||||
articlesController := _i.Controller.Articles
|
||||
|
||||
// define routes
|
||||
_i.App.Route("/articles", func(router fiber.Router) {
|
||||
router.Get("/", articlesController.All)
|
||||
router.Get("/old-id/:id", articlesController.ShowByOldId)
|
||||
router.Get("/:id", articlesController.Show)
|
||||
router.Post("/", articlesController.Save)
|
||||
router.Put("/:id", articlesController.Update)
|
||||
router.Put("/banner/:id", articlesController.UpdateBanner)
|
||||
router.Post("/thumbnail/:id", articlesController.SaveThumbnail)
|
||||
router.Get("/thumbnail/viewer/:thumbnailName", articlesController.Viewer)
|
||||
router.Post("/publish-scheduling", articlesController.PublishScheduling)
|
||||
router.Delete("/:id", articlesController.Delete)
|
||||
router.Get("/statistic/summary", articlesController.SummaryStats)
|
||||
router.Get("/statistic/user-levels", articlesController.ArticlePerUserLevelStats)
|
||||
router.Get("/statistic/monthly", articlesController.ArticleMonthlyStats)
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,452 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/module/articles/request"
|
||||
"campaign-pool-be/app/module/articles/service"
|
||||
"campaign-pool-be/utils/paginator"
|
||||
"strconv"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
utilRes "campaign-pool-be/utils/response"
|
||||
utilVal "campaign-pool-be/utils/validator"
|
||||
)
|
||||
|
||||
type articlesController struct {
|
||||
articlesService service.ArticlesService
|
||||
Log zerolog.Logger
|
||||
}
|
||||
|
||||
type ArticlesController interface {
|
||||
All(c *fiber.Ctx) error
|
||||
Show(c *fiber.Ctx) error
|
||||
ShowByOldId(c *fiber.Ctx) error
|
||||
Save(c *fiber.Ctx) error
|
||||
SaveThumbnail(c *fiber.Ctx) error
|
||||
Update(c *fiber.Ctx) error
|
||||
UpdateBanner(c *fiber.Ctx) error
|
||||
Delete(c *fiber.Ctx) error
|
||||
Viewer(c *fiber.Ctx) error
|
||||
SummaryStats(c *fiber.Ctx) error
|
||||
ArticlePerUserLevelStats(c *fiber.Ctx) error
|
||||
ArticleMonthlyStats(c *fiber.Ctx) error
|
||||
PublishScheduling(c *fiber.Ctx) error
|
||||
}
|
||||
|
||||
func NewArticlesController(articlesService service.ArticlesService, log zerolog.Logger) ArticlesController {
|
||||
return &articlesController{
|
||||
articlesService: articlesService,
|
||||
Log: log,
|
||||
}
|
||||
}
|
||||
|
||||
// All Articles
|
||||
// @Summary Get all Articles
|
||||
// @Description API for getting all Articles
|
||||
// @Tags Articles
|
||||
// @Security Bearer
|
||||
// @Param X-Client-Key header string true "Insert the X-Client-Key"
|
||||
// @Param req query request.ArticlesQueryRequest false "query parameters"
|
||||
// @Param req query paginator.Pagination false "pagination parameters"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /articles [get]
|
||||
func (_i *articlesController) All(c *fiber.Ctx) error {
|
||||
paginate, err := paginator.Paginate(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reqContext := request.ArticlesQueryRequestContext{
|
||||
Title: c.Query("title"),
|
||||
Description: c.Query("description"),
|
||||
Tags: c.Query("tags"),
|
||||
Category: c.Query("category"),
|
||||
CategoryId: c.Query("categoryId"),
|
||||
TypeId: c.Query("typeId"),
|
||||
StatusId: c.Query("statusId"),
|
||||
IsPublish: c.Query("isPublish"),
|
||||
IsDraft: c.Query("isDraft"),
|
||||
IsBanner: c.Query("isBanner"),
|
||||
}
|
||||
req := reqContext.ToParamRequest()
|
||||
req.Pagination = paginate
|
||||
|
||||
articlesData, paging, err := _i.articlesService.All(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"Articles list successfully retrieved"},
|
||||
Data: articlesData,
|
||||
Meta: paging,
|
||||
})
|
||||
}
|
||||
|
||||
// Show Articles
|
||||
// @Summary Get one Articles
|
||||
// @Description API for getting one Articles
|
||||
// @Tags Articles
|
||||
// @Security Bearer
|
||||
// @Param id path int true "Articles ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /articles/{id} [get]
|
||||
func (_i *articlesController) Show(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get from context
|
||||
articlesData, err := _i.articlesService.Show(uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"Articles successfully retrieved"},
|
||||
Data: articlesData,
|
||||
})
|
||||
}
|
||||
|
||||
// ShowByOldId Articles
|
||||
// @Summary Get one Articles
|
||||
// @Description API for getting one Articles
|
||||
// @Tags Articles
|
||||
// @Security Bearer
|
||||
// @Param id path int true "Articles Old ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /articles/old-id/{id} [get]
|
||||
func (_i *articlesController) ShowByOldId(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get from context
|
||||
articlesData, err := _i.articlesService.ShowByOldId(uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"Articles successfully retrieved"},
|
||||
Data: articlesData,
|
||||
})
|
||||
}
|
||||
|
||||
// Save Articles
|
||||
// @Summary Create Articles
|
||||
// @Description API for create Articles
|
||||
// @Tags Articles
|
||||
// @Security Bearer
|
||||
// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token"
|
||||
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
||||
// @Param payload body request.ArticlesCreateRequest true "Required payload"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /articles [post]
|
||||
func (_i *articlesController) Save(c *fiber.Ctx) error {
|
||||
req := new(request.ArticlesCreateRequest)
|
||||
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authToken := c.Get("Authorization")
|
||||
|
||||
// Get from context
|
||||
dataResult, err := _i.articlesService.Save(*req, authToken)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"Articles successfully created"},
|
||||
Data: dataResult,
|
||||
})
|
||||
}
|
||||
|
||||
// SaveThumbnail Articles
|
||||
// @Summary Save Thumbnail Articles
|
||||
// @Description API for Save Thumbnail of Articles
|
||||
// @Tags Articles
|
||||
// @Security Bearer
|
||||
// @Produce json
|
||||
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||
// @Param files formData file true "Upload thumbnail"
|
||||
// @Param id path int true "Articles ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /articles/thumbnail/{id} [post]
|
||||
func (_i *articlesController) SaveThumbnail(c *fiber.Ctx) error {
|
||||
// Get from context
|
||||
err := _i.articlesService.SaveThumbnail(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"Thumbnail of Articles successfully created"},
|
||||
})
|
||||
}
|
||||
|
||||
// Update Articles
|
||||
// @Summary Update Articles
|
||||
// @Description API for update Articles
|
||||
// @Tags Articles
|
||||
// @Security Bearer
|
||||
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||
// @Param payload body request.ArticlesUpdateRequest true "Required payload"
|
||||
// @Param id path int true "Articles ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /articles/{id} [put]
|
||||
func (_i *articlesController) Update(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req := new(request.ArticlesUpdateRequest)
|
||||
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get from context
|
||||
err = _i.articlesService.Update(uint(id), *req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"Articles successfully updated"},
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateBanner Articles
|
||||
// @Summary Update Banner Articles
|
||||
// @Description API for Update Banner Articles
|
||||
// @Tags Articles
|
||||
// @Security Bearer
|
||||
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||
// @Param id path int true "Articles ID"
|
||||
// @Param isBanner query bool true "Articles Banner Status"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /articles/banner/{id} [put]
|
||||
func (_i *articlesController) UpdateBanner(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
isBanner, err := strconv.ParseBool(c.Query("isBanner"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get from context
|
||||
err = _i.articlesService.UpdateBanner(uint(id), isBanner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"Articles successfully banner updated"},
|
||||
})
|
||||
}
|
||||
|
||||
// Delete Articles
|
||||
// @Summary Delete Articles
|
||||
// @Description API for delete Articles
|
||||
// @Tags Articles
|
||||
// @Security Bearer
|
||||
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||
// @Param id path int true "Articles ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /articles/{id} [delete]
|
||||
func (_i *articlesController) Delete(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get from context
|
||||
err = _i.articlesService.Delete(uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"Articles successfully deleted"},
|
||||
})
|
||||
}
|
||||
|
||||
// Viewer Articles
|
||||
// @Summary Viewer Articles Thumbnail
|
||||
// @Description API for View Thumbnail of Article
|
||||
// @Tags Articles
|
||||
// @Security Bearer
|
||||
// @Param thumbnailName path string true "Articles Thumbnail Name"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /articles/thumbnail/viewer/{thumbnailName} [get]
|
||||
func (_i *articlesController) Viewer(c *fiber.Ctx) error {
|
||||
// Get from context
|
||||
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 false "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")
|
||||
|
||||
// Get from context
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
// ArticlePerUserLevelStats Articles
|
||||
// @Summary ArticlePerUserLevelStats Articles
|
||||
// @Description API for ArticlePerUserLevelStats of Article
|
||||
// @Tags Articles
|
||||
// @Security Bearer
|
||||
// @Param Authorization header string false "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")
|
||||
|
||||
// Get from context
|
||||
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 false "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
|
||||
}
|
||||
|
||||
// Get from context
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
// PublishScheduling Articles
|
||||
// @Summary PublishScheduling Articles
|
||||
// @Description API for Publish Schedule of Article
|
||||
// @Tags Articles
|
||||
// @Security Bearer
|
||||
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
||||
// @Param id query int false "article id"
|
||||
// @Param date query string false "publish date"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /articles/publish-scheduling [post]
|
||||
func (_i *articlesController) PublishScheduling(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Query("id"), 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
date := c.Query("date")
|
||||
|
||||
// Get from context
|
||||
err = _i.articlesService.PublishScheduling(uint(id), date)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"Publish Scheduling of Articles successfully saved"},
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/module/articles/service"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
Articles ArticlesController
|
||||
}
|
||||
|
||||
func NewController(ArticlesService service.ArticlesService, log zerolog.Logger) *Controller {
|
||||
return &Controller{
|
||||
Articles: NewArticlesController(ArticlesService, log),
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
package mapper
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/database/entity"
|
||||
articleCategoriesMapper "campaign-pool-be/app/module/article_categories/mapper"
|
||||
articleCategoriesRepository "campaign-pool-be/app/module/article_categories/repository"
|
||||
articleCategoriesResponse "campaign-pool-be/app/module/article_categories/response"
|
||||
articleCategoryDetailsRepository "campaign-pool-be/app/module/article_category_details/repository"
|
||||
articleFilesMapper "campaign-pool-be/app/module/article_files/mapper"
|
||||
articleFilesRepository "campaign-pool-be/app/module/article_files/repository"
|
||||
articleFilesResponse "campaign-pool-be/app/module/article_files/response"
|
||||
res "campaign-pool-be/app/module/articles/response"
|
||||
usersRepository "campaign-pool-be/app/module/users/repository"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
func ArticlesResponseMapper(
|
||||
log zerolog.Logger,
|
||||
host string,
|
||||
articlesReq *entity.Articles,
|
||||
articleCategoriesRepo articleCategoriesRepository.ArticleCategoriesRepository,
|
||||
articleCategoryDetailsRepo articleCategoryDetailsRepository.ArticleCategoryDetailsRepository,
|
||||
articleFilesRepo articleFilesRepository.ArticleFilesRepository,
|
||||
usersRepo usersRepository.UsersRepository,
|
||||
) (articlesRes *res.ArticlesResponse) {
|
||||
|
||||
createdByName := ""
|
||||
if articlesReq.CreatedById != nil {
|
||||
findUser, _ := usersRepo.FindOne(*articlesReq.CreatedById)
|
||||
if findUser != nil {
|
||||
createdByName = findUser.Fullname
|
||||
}
|
||||
}
|
||||
|
||||
categoryName := ""
|
||||
articleCategories, _ := articleCategoryDetailsRepo.FindByArticleId(articlesReq.ID)
|
||||
var articleCategoriesArr []*articleCategoriesResponse.ArticleCategoriesResponse
|
||||
if articleCategories != nil && len(articleCategories) > 0 {
|
||||
for _, result := range articleCategories {
|
||||
articleCategoriesArr = append(articleCategoriesArr, articleCategoriesMapper.ArticleCategoriesResponseMapper(result.Category, host))
|
||||
}
|
||||
log.Info().Interface("articleCategoriesArr", articleCategoriesArr).Msg("")
|
||||
}
|
||||
|
||||
articleFiles, _ := articleFilesRepo.FindByArticle(articlesReq.ID)
|
||||
var articleFilesArr []*articleFilesResponse.ArticleFilesResponse
|
||||
if articleFiles != nil && len(articleFiles) > 0 {
|
||||
for _, result := range articleFiles {
|
||||
articleFilesArr = append(articleFilesArr, articleFilesMapper.ArticleFilesResponseMapper(result, host))
|
||||
}
|
||||
}
|
||||
|
||||
if articlesReq != nil {
|
||||
articlesRes = &res.ArticlesResponse{
|
||||
ID: articlesReq.ID,
|
||||
Title: articlesReq.Title,
|
||||
Slug: articlesReq.Slug,
|
||||
Description: articlesReq.Description,
|
||||
HtmlDescription: articlesReq.HtmlDescription,
|
||||
TypeId: articlesReq.TypeId,
|
||||
Tags: articlesReq.Tags,
|
||||
CategoryId: articlesReq.CategoryId,
|
||||
AiArticleId: articlesReq.AiArticleId,
|
||||
CategoryName: categoryName,
|
||||
PageUrl: articlesReq.PageUrl,
|
||||
CreatedById: articlesReq.CreatedById,
|
||||
CreatedByName: &createdByName,
|
||||
ShareCount: articlesReq.ShareCount,
|
||||
ViewCount: articlesReq.ViewCount,
|
||||
CommentCount: articlesReq.CommentCount,
|
||||
OldId: articlesReq.OldId,
|
||||
StatusId: articlesReq.StatusId,
|
||||
IsBanner: articlesReq.IsBanner,
|
||||
IsPublish: articlesReq.IsPublish,
|
||||
PublishedAt: articlesReq.PublishedAt,
|
||||
IsActive: articlesReq.IsActive,
|
||||
CreatedAt: articlesReq.CreatedAt,
|
||||
UpdatedAt: articlesReq.UpdatedAt,
|
||||
ArticleFiles: articleFilesArr,
|
||||
ArticleCategories: articleCategoriesArr,
|
||||
}
|
||||
|
||||
if articlesReq.ThumbnailName != nil {
|
||||
articlesRes.ThumbnailUrl = host + "/articles/thumbnail/viewer/" + *articlesReq.ThumbnailName
|
||||
}
|
||||
}
|
||||
|
||||
return articlesRes
|
||||
}
|
||||
|
|
@ -0,0 +1,330 @@
|
|||
package repository
|
||||
|
||||
import (
|
||||
"campaign-pool-be/app/database"
|
||||
"campaign-pool-be/app/database/entity"
|
||||
"campaign-pool-be/app/module/articles/request"
|
||||
"campaign-pool-be/app/module/articles/response"
|
||||
"campaign-pool-be/utils/paginator"
|
||||
utilSvc "campaign-pool-be/utils/service"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
type articlesRepository struct {
|
||||
DB *database.Database
|
||||
Log zerolog.Logger
|
||||
}
|
||||
|
||||
// ArticlesRepository define interface of IArticlesRepository
|
||||
type ArticlesRepository interface {
|
||||
GetAll(req request.ArticlesQueryRequest) (articless []*entity.Articles, paging paginator.Pagination, err error)
|
||||
GetAllPublishSchedule() (articless []*entity.Articles, err error)
|
||||
FindOne(id uint) (articles *entity.Articles, err error)
|
||||
FindByFilename(thumbnailName string) (articleReturn *entity.Articles, err error)
|
||||
FindByOldId(oldId uint) (articles *entity.Articles, err error)
|
||||
Create(articles *entity.Articles) (articleReturn *entity.Articles, err error)
|
||||
Update(id uint, articles *entity.Articles) (err error)
|
||||
UpdateSkipNull(id uint, articles *entity.Articles) (err error)
|
||||
Delete(id uint) (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 {
|
||||
return &articlesRepository{
|
||||
DB: db,
|
||||
Log: log,
|
||||
}
|
||||
}
|
||||
|
||||
// implement interface of IArticlesRepository
|
||||
func (_i *articlesRepository) GetAll(req request.ArticlesQueryRequest) (articless []*entity.Articles, paging paginator.Pagination, err error) {
|
||||
var count int64
|
||||
|
||||
query := _i.DB.DB.Model(&entity.Articles{})
|
||||
|
||||
if req.CategoryId != nil {
|
||||
query = query.Joins("JOIN article_category_details acd ON acd.article_id = articles.id").
|
||||
Where("acd.category_id = ?", req.CategoryId)
|
||||
}
|
||||
query = query.Where("articles.is_active = ?", true)
|
||||
|
||||
if req.Title != nil && *req.Title != "" {
|
||||
title := strings.ToLower(*req.Title)
|
||||
query = query.Where("LOWER(articles.title) LIKE ?", "%"+strings.ToLower(title)+"%")
|
||||
}
|
||||
if req.Description != nil && *req.Description != "" {
|
||||
description := strings.ToLower(*req.Description)
|
||||
query = query.Where("LOWER(articles.description) LIKE ?", "%"+strings.ToLower(description)+"%")
|
||||
}
|
||||
if req.Tags != nil && *req.Tags != "" {
|
||||
tags := strings.ToLower(*req.Tags)
|
||||
query = query.Where("LOWER(articles.tags) LIKE ?", "%"+strings.ToLower(tags)+"%")
|
||||
}
|
||||
if req.TypeId != nil {
|
||||
query = query.Where("articles.type_id = ?", req.TypeId)
|
||||
}
|
||||
if req.IsPublish != nil {
|
||||
query = query.Where("articles.is_publish = ?", req.IsPublish)
|
||||
}
|
||||
if req.IsBanner != nil {
|
||||
query = query.Where("articles.is_banner = ?", req.IsBanner)
|
||||
}
|
||||
if req.IsDraft != nil {
|
||||
query = query.Where("articles.is_draft = ?", req.IsDraft)
|
||||
}
|
||||
if req.StatusId != nil {
|
||||
query = query.Where("articles.status_id = ?", req.StatusId)
|
||||
}
|
||||
if req.CreatedById != nil {
|
||||
query = query.Where("articles.created_by_id = ?", req.CreatedById)
|
||||
}
|
||||
query.Count(&count)
|
||||
|
||||
if req.Pagination.SortBy != "" {
|
||||
direction := "ASC"
|
||||
if req.Pagination.Sort == "desc" {
|
||||
direction = "DESC"
|
||||
}
|
||||
query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction))
|
||||
} else {
|
||||
direction := "DESC"
|
||||
sortBy := "articles.created_at"
|
||||
query.Order(fmt.Sprintf("%s %s", sortBy, direction))
|
||||
}
|
||||
|
||||
req.Pagination.Count = count
|
||||
req.Pagination = paginator.Paging(req.Pagination)
|
||||
|
||||
err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&articless).Error
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
paging = *req.Pagination
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *articlesRepository) GetAllPublishSchedule() (articles []*entity.Articles, err error) {
|
||||
query := _i.DB.DB.Where("publish_schedule IS NOT NULL")
|
||||
err = query.Find(&articles).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return articles, nil
|
||||
}
|
||||
|
||||
func (_i *articlesRepository) FindOne(id uint) (articles *entity.Articles, err error) {
|
||||
query := _i.DB.DB
|
||||
if err := query.First(&articles, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return articles, nil
|
||||
}
|
||||
|
||||
func (_i *articlesRepository) FindByFilename(thumbnailName string) (articles *entity.Articles, err error) {
|
||||
query := _i.DB.DB.Where("thumbnail_name = ?", thumbnailName)
|
||||
|
||||
if err := query.First(&articles).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return articles, nil
|
||||
}
|
||||
|
||||
func (_i *articlesRepository) FindByOldId(oldId uint) (articles *entity.Articles, err error) {
|
||||
query := _i.DB.DB.Where("old_id = ?", oldId)
|
||||
|
||||
if err := query.First(&articles).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return articles, nil
|
||||
}
|
||||
|
||||
func (_i *articlesRepository) Create(articles *entity.Articles) (articleReturn *entity.Articles, err error) {
|
||||
result := _i.DB.DB.Create(articles)
|
||||
return articles, result.Error
|
||||
}
|
||||
|
||||
func (_i *articlesRepository) Update(id uint, articles *entity.Articles) (err error) {
|
||||
articlesMap, err := utilSvc.StructToMap(articles)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return _i.DB.DB.Model(&entity.Articles{}).
|
||||
Where(&entity.Articles{ID: id}).
|
||||
Updates(articlesMap).Error
|
||||
}
|
||||
|
||||
func (_i *articlesRepository) UpdateSkipNull(id uint, articles *entity.Articles) (err error) {
|
||||
return _i.DB.DB.Model(&entity.Articles{}).
|
||||
Where(&entity.Articles{ID: id}).
|
||||
Updates(articles).Error
|
||||
}
|
||||
|
||||
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
|
||||
query := _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)
|
||||
|
||||
err = query.Scan(&articleSummaryStats).Error
|
||||
|
||||
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 = 1 THEN id
|
||||
WHEN level_number = 2 and name ILIKE '%polda%' THEN id
|
||||
WHEN level_number = 2 and name NOT ILIKE '%polda%' THEN parent_level_id
|
||||
WHEN level_number = 3 THEN parent_level_id
|
||||
END AS level_2_id,
|
||||
CASE
|
||||
WHEN level_number = 1 THEN name
|
||||
WHEN level_number = 2 and name ILIKE '%polda%' THEN name
|
||||
WHEN level_number = 2 and name NOT ILIKE '%polda%' THEN (SELECT name FROM user_levels ul2 WHERE ul2.id = user_levels.parent_level_id)
|
||||
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,
|
||||
UPPER(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`)
|
||||
|
||||
query = query.Group("lh.level_2_id, lh.level_2_name").Order("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
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue