initial commit
This commit is contained in:
commit
a88bd957e3
|
|
@ -0,0 +1,3 @@
|
||||||
|
/vendor
|
||||||
|
debug.log
|
||||||
|
/.ideadebug.log
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
stages:
|
||||||
|
- build-image
|
||||||
|
- deploy
|
||||||
|
|
||||||
|
#build-1:
|
||||||
|
# stage: build-app
|
||||||
|
# image: golang:alpine
|
||||||
|
# script:
|
||||||
|
# - go build -o main .
|
||||||
|
# artifacts:
|
||||||
|
# paths:
|
||||||
|
# - main
|
||||||
|
|
||||||
|
build-2:
|
||||||
|
stage: build-image
|
||||||
|
image: docker/compose:latest
|
||||||
|
services:
|
||||||
|
- name: docker:dind
|
||||||
|
command: [ "--insecure-registry=103.82.242.92:8900" ]
|
||||||
|
script:
|
||||||
|
- docker login -u $DEPLOY_USERNAME -p $DEPLOY_TOKEN 103.82.242.92:8900
|
||||||
|
- docker-compose build
|
||||||
|
- docker tag registry.gitlab.com/hanifsalafi/narasi-ahli-be:dev 103.82.242.92:8900/medols/narasi-ahli-be:dev
|
||||||
|
- docker push 103.82.242.92:8900/medols/narasi-ahli-be:dev
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
stage: deploy
|
||||||
|
when: on_success
|
||||||
|
image: curlimages/curl:latest
|
||||||
|
services:
|
||||||
|
- docker:dind
|
||||||
|
script:
|
||||||
|
- curl --user $JENKINS_USER:$JENKINS_PWD http://38.47.180.165:8080/job/autodeploy-medols-be/build?token=autodeploymedols
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/narasi-ahli-be.iml" filepath="$PROJECT_DIR$/.idea/narasi-ahli-be.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="Go" enabled="true" />
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
FROM golang:alpine
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN go mod download
|
||||||
|
RUN go get -v ./...
|
||||||
|
RUN go mod vendor
|
||||||
|
RUN go build -o main .
|
||||||
|
|
||||||
|
EXPOSE 8800
|
||||||
|
|
||||||
|
CMD ["sh", "-c", "go run main.go"]
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
type ActivityLogTypes 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,15 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ActivityLogs struct {
|
||||||
|
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||||
|
ActivityTypeId int `json:"activity_type_id" gorm:"type:int4"`
|
||||||
|
Url string `json:"url" gorm:"type:varchar"`
|
||||||
|
VisitorIp *string `json:"visitor_ip" gorm:"type:varchar"`
|
||||||
|
ArticleId *uint `json:"article_id" gorm:"type:int4"`
|
||||||
|
UserId *uint `json:"user_id" gorm:"type:int4"`
|
||||||
|
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||||
|
}
|
||||||
|
|
@ -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 (
|
||||||
|
"narasi-ahli-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()"`
|
||||||
|
Session *AIChatSessions `json:"session" gorm:"foreignKey:SessionID;references:ID"`
|
||||||
|
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 uint `json:"session_id" gorm:"type:int4;not null;index"`
|
||||||
|
MessageType string `json:"message_type" gorm:"type:varchar;not null"`
|
||||||
|
Content string `json:"content" gorm:"type:text;not null"`
|
||||||
|
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||||
|
Session *AIChatSessions `json:"session" gorm:"foreignKey:SessionID;references:ID"`
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"narasi-ahli-be/app/database/entity/users"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AIChatSessions struct {
|
||||||
|
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||||
|
AISessionID string `json:"ai_session_id" gorm:"type:varchar;not null;unique"`
|
||||||
|
UserID uint `json:"user_id" gorm:"type:int4;not null;index"`
|
||||||
|
AgentID *string `json:"agent_id" gorm:"type:varchar"`
|
||||||
|
Title string `json:"title" gorm:"type:varchar;not null"`
|
||||||
|
MessageCount int `json:"message_count" gorm:"type:int4;default:0"`
|
||||||
|
Status string `json:"status" gorm:"type:varchar;default:'active'"`
|
||||||
|
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 "narasi-ahli-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,19 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AuditTrails struct {
|
||||||
|
ID uint `gorm:"primaryKey"`
|
||||||
|
Method string
|
||||||
|
Path string
|
||||||
|
IP string
|
||||||
|
Status int
|
||||||
|
UserID *string
|
||||||
|
RequestHeaders string
|
||||||
|
RequestBody string
|
||||||
|
ResponseBody string
|
||||||
|
DurationMs int64
|
||||||
|
CreatedAt time.Time
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"narasi-ahli-be/app/database/entity/users"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ChatMessages struct {
|
||||||
|
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||||
|
ConversationID uint `json:"conversation_id" gorm:"type:int4;not null;index"`
|
||||||
|
SenderID uint `json:"sender_id" gorm:"type:int4;not null;index"`
|
||||||
|
MessageText *string `json:"message_text" gorm:"type:text"`
|
||||||
|
MessageType string `json:"message_type" gorm:"type:varchar;not null;default:'text'"`
|
||||||
|
FileURL *string `json:"file_url" gorm:"type:varchar"`
|
||||||
|
FileName *string `json:"file_name" gorm:"type:varchar"`
|
||||||
|
FileSize *int64 `json:"file_size" gorm:"type:bigint"`
|
||||||
|
IsRead bool `json:"is_read" gorm:"default:false"`
|
||||||
|
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||||
|
Conversation *Conversations `json:"conversation" gorm:"foreignKey:ConversationID;references:ID"`
|
||||||
|
Sender *users.Users `json:"sender" gorm:"foreignKey:SenderID;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 (
|
||||||
|
"narasi-ahli-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,13 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CsrfTokenRecords struct {
|
||||||
|
ID uint `gorm:"primaryKey"`
|
||||||
|
Token string `gorm:"uniqueIndex;size:255"`
|
||||||
|
Value []byte `gorm:"value"`
|
||||||
|
ExpireAt time.Time `gorm:"index"`
|
||||||
|
CreatedAt time.Time
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CustomStaticPages struct {
|
||||||
|
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||||
|
Title string `json:"title" gorm:"type:varchar"`
|
||||||
|
Description string `json:"description" gorm:"type:varchar"`
|
||||||
|
Slug string `json:"slug" gorm:"type:varchar"`
|
||||||
|
HtmlBody string `json:"html_body" gorm:"type:text"`
|
||||||
|
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,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,19 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"narasi-ahli-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,14 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ForgotPasswords struct {
|
||||||
|
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||||
|
KeycloakID string `json:"keycloak_id" gorm:"type:varchar"`
|
||||||
|
CodeRequest string `json:"code_request" gorm:"type:varchar"`
|
||||||
|
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,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,7 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
type MasterApprovalStatuses 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,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,16 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OneTimePasswords struct {
|
||||||
|
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||||
|
Email string `json:"email" gorm:"type:varchar"`
|
||||||
|
Name *string `json:"name" gorm:"type:varchar"`
|
||||||
|
Identity *string `json:"identity" gorm:"type:varchar"`
|
||||||
|
OtpCode string `json:"otp_code" gorm:"type:varchar"`
|
||||||
|
ValidUntil time.Time `json:"valid_until" gorm:"default:(NOW() + INTERVAL '10 minutes')"`
|
||||||
|
IsActive bool `json:"is_active" gorm:"type:bool"`
|
||||||
|
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||||
|
}
|
||||||
|
|
@ -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 (
|
||||||
|
"narasi-ahli-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,17 @@
|
||||||
|
package user_levels
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type UserLevels struct {
|
||||||
|
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||||
|
Name string `json:"name" gorm:"type:varchar"`
|
||||||
|
AliasName string `json:"alias_name" gorm:"type:varchar"`
|
||||||
|
LevelNumber int `json:"level_number" gorm:"type:int4"`
|
||||||
|
ParentLevelId *int `json:"parent_level_id" gorm:"type:int4"`
|
||||||
|
ProvinceId *int `json:"province_id" gorm:"type:int4"`
|
||||||
|
Group *string `json:"group" gorm:"type:varchar"`
|
||||||
|
IsApprovalActive *bool `json:"is_approval_active" gorm:"type:bool;default:false"`
|
||||||
|
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,17 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type UserLevels struct {
|
||||||
|
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||||
|
Name string `json:"name" gorm:"type:varchar"`
|
||||||
|
AliasName string `json:"alias_name" gorm:"type:varchar"`
|
||||||
|
LevelNumber int `json:"level_number" gorm:"type:int4"`
|
||||||
|
ParentLevelId *int `json:"parent_level_id" gorm:"type:int4"`
|
||||||
|
ProvinceId *int `json:"province_id" gorm:"type:int4"`
|
||||||
|
Group *string `json:"group" gorm:"type:varchar"`
|
||||||
|
IsApprovalActive *bool `json:"is_approval_active" gorm:"type:bool;default:false"`
|
||||||
|
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 UserRoleAccesses struct {
|
||||||
|
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||||
|
UserRoleId uint `json:"user_role_id" gorm:"type:int4"`
|
||||||
|
MenuId int `json:"menu_id" gorm:"type:int4"`
|
||||||
|
IsViewEnabled bool `json:"is_view_enabled" gorm:"type:bool"`
|
||||||
|
IsInsertEnabled bool `json:"is_insert_enabled" gorm:"type:bool"`
|
||||||
|
IsUpdateEnabled bool `json:"is_update_enabled" gorm:"type:bool"`
|
||||||
|
IsDeleteEnabled bool `json:"is_delete_enabled" gorm:"type:bool"`
|
||||||
|
IsApprovalEnabled bool `json:"is_approval_enabled" gorm:"type:bool"`
|
||||||
|
IsAdminEnabled bool `json:"is_admin_enabled" 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,15 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserRoleLevelDetails struct {
|
||||||
|
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||||
|
UserRoleId uint `json:"user_role_id" gorm:"type:int4"`
|
||||||
|
UserLevelId uint `json:"user_level_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,19 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserRoles struct {
|
||||||
|
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||||
|
Name string `json:"name" gorm:"type:varchar"`
|
||||||
|
Description string `json:"description" gorm:"type:varchar"`
|
||||||
|
Code string `json:"code" gorm:"type:varchar"`
|
||||||
|
StatusId int `json:"status_id" gorm:"type:int4;default:1"`
|
||||||
|
CreatedById *uint `json:"created_by_id" gorm:"type:int4"`
|
||||||
|
UserLevelId uint `json:"user_level_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,38 @@
|
||||||
|
package users
|
||||||
|
|
||||||
|
import (
|
||||||
|
userLevels "narasi-ahli-be/app/database/entity/user_levels"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Users struct {
|
||||||
|
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||||
|
Username string `json:"username" gorm:"type:varchar"`
|
||||||
|
Email string `json:"email" gorm:"type:varchar"`
|
||||||
|
Fullname string `json:"fullname" gorm:"type:varchar"`
|
||||||
|
Address *string `json:"address" gorm:"type:varchar"`
|
||||||
|
PhoneNumber *string `json:"phone_number" gorm:"type:varchar"`
|
||||||
|
WorkType *string `json:"work_type" gorm:"type:varchar"`
|
||||||
|
GenderType *string `json:"gender_type" gorm:"type:varchar"`
|
||||||
|
IdentityType *string `json:"identity_type" gorm:"type:varchar"`
|
||||||
|
IdentityGroup *string `json:"identity_group" gorm:"type:varchar"`
|
||||||
|
IdentityGroupNumber *string `json:"identity_group_number" gorm:"type:varchar"`
|
||||||
|
IdentityNumber *string `json:"identity_number" gorm:"type:varchar"`
|
||||||
|
DateOfBirth *string `json:"date_of_birth" gorm:"type:varchar"`
|
||||||
|
LastEducation *string `json:"last_education" gorm:"type:varchar"`
|
||||||
|
Degree *string `json:"degree" gorm:"type:varchar"`
|
||||||
|
WhatsappNumber *string `json:"whatsapp_number" gorm:"type:varchar"`
|
||||||
|
LastJobTitle *string `json:"last_job_title" gorm:"type:varchar"`
|
||||||
|
UserRoleId uint `json:"user_role_id" gorm:"type:int4"`
|
||||||
|
UserLevelId uint `json:"user_level_id" gorm:"type:int4"`
|
||||||
|
UserLevel *userLevels.UserLevels `json:"user_levels" gorm:"foreignKey:UserLevelId;references:ID"`
|
||||||
|
KeycloakId *string `json:"keycloak_id" gorm:"type:varchar"`
|
||||||
|
StatusId *int `json:"status_id" gorm:"type:int4;default:1"`
|
||||||
|
CreatedById *uint `json:"created_by_id" gorm:"type:int4"`
|
||||||
|
ProfilePicturePath *string `json:"profile_picture_path" gorm:"type:varchar"`
|
||||||
|
TempPassword *string `json:"temp_password" gorm:"type:varchar"`
|
||||||
|
IsEmailUpdated *bool `json:"is_email_updated" gorm:"type:bool;default:false"`
|
||||||
|
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,38 @@
|
||||||
|
package users
|
||||||
|
|
||||||
|
import (
|
||||||
|
userLevels "narasi-ahli-be/app/database/entity/user_levels"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Users struct {
|
||||||
|
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||||
|
Username string `json:"username" gorm:"type:varchar"`
|
||||||
|
Email string `json:"email" gorm:"type:varchar"`
|
||||||
|
Fullname string `json:"fullname" gorm:"type:varchar"`
|
||||||
|
Address *string `json:"address" gorm:"type:varchar"`
|
||||||
|
PhoneNumber *string `json:"phone_number" gorm:"type:varchar"`
|
||||||
|
WorkType *string `json:"work_type" gorm:"type:varchar"`
|
||||||
|
GenderType *string `json:"gender_type" gorm:"type:varchar"`
|
||||||
|
IdentityType *string `json:"identity_type" gorm:"type:varchar"`
|
||||||
|
IdentityGroup *string `json:"identity_group" gorm:"type:varchar"`
|
||||||
|
IdentityGroupNumber *string `json:"identity_group_number" gorm:"type:varchar"`
|
||||||
|
IdentityNumber *string `json:"identity_number" gorm:"type:varchar"`
|
||||||
|
DateOfBirth *string `json:"date_of_birth" gorm:"type:varchar"`
|
||||||
|
LastEducation *string `json:"last_education" gorm:"type:varchar"`
|
||||||
|
Degree *string `json:"degree" gorm:"type:varchar"`
|
||||||
|
WhatsappNumber *string `json:"whatsapp_number" gorm:"type:varchar"`
|
||||||
|
LastJobTitle *string `json:"last_job_title" gorm:"type:varchar"`
|
||||||
|
UserRoleId uint `json:"user_role_id" gorm:"type:int4"`
|
||||||
|
UserLevelId uint `json:"user_level_id" gorm:"type:int4"`
|
||||||
|
UserLevel *userLevels.UserLevels `json:"user_levels" gorm:"foreignKey:UserLevelId;references:ID"`
|
||||||
|
KeycloakId *string `json:"keycloak_id" gorm:"type:varchar"`
|
||||||
|
StatusId *int `json:"status_id" gorm:"type:int4;default:1"`
|
||||||
|
CreatedById *uint `json:"created_by_id" gorm:"type:int4"`
|
||||||
|
ProfilePicturePath *string `json:"profile_picture_path" gorm:"type:varchar"`
|
||||||
|
TempPassword *string `json:"temp_password" gorm:"type:varchar"`
|
||||||
|
IsEmailUpdated *bool `json:"is_email_updated" gorm:"type:bool;default:false"`
|
||||||
|
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 (
|
||||||
|
"narasi-ahli-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"`
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,151 @@
|
||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"narasi-ahli-be/app/database/entity"
|
||||||
|
"narasi-ahli-be/app/database/entity/article_category_details"
|
||||||
|
"narasi-ahli-be/app/database/entity/user_levels"
|
||||||
|
"narasi-ahli-be/app/database/entity/users"
|
||||||
|
"narasi-ahli-be/config/config"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"gorm.io/driver/postgres"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Database setup database with gorm
|
||||||
|
type Database struct {
|
||||||
|
DB *gorm.DB
|
||||||
|
Log zerolog.Logger
|
||||||
|
Cfg *config.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
type Seeder interface {
|
||||||
|
Seed(*gorm.DB) error
|
||||||
|
Count(*gorm.DB) (int, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDatabase(cfg *config.Config, log zerolog.Logger) *Database {
|
||||||
|
db := &Database{
|
||||||
|
Cfg: cfg,
|
||||||
|
Log: log,
|
||||||
|
}
|
||||||
|
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnectDatabase connect database
|
||||||
|
func (_db *Database) ConnectDatabase() {
|
||||||
|
logMode := _db.Cfg.DB.Postgres.LogMode
|
||||||
|
var logLevel logger.LogLevel
|
||||||
|
if logMode == "INFO" {
|
||||||
|
logLevel = logger.Info
|
||||||
|
} else if logMode == "WARN" {
|
||||||
|
logLevel = logger.Warn
|
||||||
|
} else if logMode == "ERROR" {
|
||||||
|
logLevel = logger.Error
|
||||||
|
} else if logMode == "NONE" {
|
||||||
|
logLevel = logger.Silent
|
||||||
|
}
|
||||||
|
conn, err := gorm.Open(postgres.Open(_db.Cfg.DB.Postgres.DSN), &gorm.Config{
|
||||||
|
Logger: logger.Default.LogMode(logLevel),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
_db.Log.Error().Err(err).Msg("An unknown error occurred when to connect the database!")
|
||||||
|
} else {
|
||||||
|
_db.Log.Info().Msg("Connected the database succesfully!")
|
||||||
|
}
|
||||||
|
|
||||||
|
_db.DB = conn
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShutdownDatabase shutdown database
|
||||||
|
func (_db *Database) ShutdownDatabase() {
|
||||||
|
sqlDB, err := _db.DB.DB()
|
||||||
|
if err != nil {
|
||||||
|
_db.Log.Error().Err(err).Msg("An unknown error occurred when to shutdown the database!")
|
||||||
|
} else {
|
||||||
|
_db.Log.Info().Msg("Shutdown the database succesfully!")
|
||||||
|
}
|
||||||
|
sqlDB.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MigrateModels migrate models
|
||||||
|
func (_db *Database) MigrateModels() {
|
||||||
|
err := _db.DB.AutoMigrate(
|
||||||
|
Models()...,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
_db.Log.Error().Err(err).Msg("An unknown error occurred when to migrate the database!")
|
||||||
|
} else {
|
||||||
|
_db.Log.Info().Msg("Migrate the database entity succesfully!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Models list of models for migration
|
||||||
|
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.AIChatSessions{},
|
||||||
|
entity.AIChatMessages{},
|
||||||
|
entity.AIChatLogs{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SeedModels seed data
|
||||||
|
func (_db *Database) SeedModels(seeder []Seeder) {
|
||||||
|
for _, seed := range seeder {
|
||||||
|
count, err := seed.Count(_db.DB)
|
||||||
|
if err != nil {
|
||||||
|
_db.Log.Error().Err(err).Msg("An unknown error occurred when to seed the database!")
|
||||||
|
}
|
||||||
|
|
||||||
|
if count == 0 {
|
||||||
|
if err := seed.Seed(_db.DB); err != nil {
|
||||||
|
_db.Log.Error().Err(err).Msg("An unknown error occurred when to seed the database!")
|
||||||
|
}
|
||||||
|
|
||||||
|
_db.Log.Info().Msg("Seeded the database successfully!")
|
||||||
|
} else {
|
||||||
|
_db.Log.Info().Msg("Database is already seeded!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_db.Log.Info().Msg("Seeded the database succesfully!")
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
package seeds
|
||||||
|
|
||||||
|
import (
|
||||||
|
"narasi-ahli-be/app/database/entity"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ActivityLogsSeeder struct{}
|
||||||
|
|
||||||
|
var activityLogTypes = []entity.ActivityLogTypes{
|
||||||
|
{
|
||||||
|
ID: 1,
|
||||||
|
Name: "Login",
|
||||||
|
IsActive: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 2,
|
||||||
|
Name: "View",
|
||||||
|
IsActive: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 3,
|
||||||
|
Name: "Share",
|
||||||
|
IsActive: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 4,
|
||||||
|
Name: "Comment",
|
||||||
|
IsActive: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ActivityLogsSeeder) Seed(conn *gorm.DB) error {
|
||||||
|
for _, row := range activityLogTypes {
|
||||||
|
if err := conn.Create(&row).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ActivityLogsSeeder) Count(conn *gorm.DB) (int, error) {
|
||||||
|
var count int64
|
||||||
|
if err := conn.Model(&entity.ActivityLogTypes{}).Count(&count).Error; err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(count), nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
package seeds
|
||||||
|
|
||||||
|
import (
|
||||||
|
"narasi-ahli-be/app/database/entity"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MasterApprovalStatusesSeeder struct{}
|
||||||
|
|
||||||
|
var masterApprovalStatuses = []entity.MasterApprovalStatuses{
|
||||||
|
{
|
||||||
|
ID: 1,
|
||||||
|
Name: "Accepted",
|
||||||
|
IsActive: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 2,
|
||||||
|
Name: "Need Update",
|
||||||
|
IsActive: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 3,
|
||||||
|
Name: "Rejected",
|
||||||
|
IsActive: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func (MasterApprovalStatusesSeeder) Seed(conn *gorm.DB) error {
|
||||||
|
for _, row := range masterApprovalStatuses {
|
||||||
|
if err := conn.Create(&row).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (MasterApprovalStatusesSeeder) Count(conn *gorm.DB) (int, error) {
|
||||||
|
var count int64
|
||||||
|
if err := conn.Model(&entity.MasterApprovalStatuses{}).Count(&count).Error; err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(count), nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
package seeds
|
||||||
|
|
||||||
|
import (
|
||||||
|
"narasi-ahli-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
|
||||||
|
}
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,69 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"narasi-ahli-be/app/database/entity"
|
||||||
|
utilSvc "narasi-ahli-be/utils/service"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AuditTrailsMiddleware(db *gorm.DB) fiber.Handler {
|
||||||
|
return func(c *fiber.Ctx) error {
|
||||||
|
start := time.Now()
|
||||||
|
requestBody := c.Body()
|
||||||
|
|
||||||
|
headersMap := c.GetReqHeaders()
|
||||||
|
headersJSON, _ := json.Marshal(headersMap)
|
||||||
|
|
||||||
|
authHeader := c.Get("Authorization")
|
||||||
|
userId := utilSvc.GetUserId(authHeader)
|
||||||
|
|
||||||
|
err := c.Next()
|
||||||
|
|
||||||
|
audit := entity.AuditTrails{
|
||||||
|
Method: c.Method(),
|
||||||
|
Path: c.OriginalURL(),
|
||||||
|
IP: getIP(c),
|
||||||
|
Status: c.Response().StatusCode(),
|
||||||
|
UserID: userId,
|
||||||
|
RequestHeaders: string(headersJSON),
|
||||||
|
RequestBody: string(requestBody),
|
||||||
|
ResponseBody: string(c.Response().Body()),
|
||||||
|
DurationMs: time.Since(start).Milliseconds(),
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
go db.Create(&audit)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func StartAuditTrailCleanup(db *gorm.DB, retention int) {
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
time.Sleep(24 * time.Hour)
|
||||||
|
|
||||||
|
cutoff := time.Now().AddDate(0, 0, retention)
|
||||||
|
db.Where("created_at < ?", cutoff).Delete(&entity.AuditTrails{})
|
||||||
|
|
||||||
|
log.Printf(" at: %s", cutoff)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getIP(c *fiber.Ctx) string {
|
||||||
|
ip := c.Get("X-Forwarded-For")
|
||||||
|
if ip == "" {
|
||||||
|
ip = c.IP()
|
||||||
|
}
|
||||||
|
if strings.Contains(ip, ":") {
|
||||||
|
ip = strings.Split(ip, ":")[0]
|
||||||
|
}
|
||||||
|
return ip
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"narasi-ahli-be/app/database/entity"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PostgresStorage struct {
|
||||||
|
DB *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PostgresStorage) Get(key string) ([]byte, error) {
|
||||||
|
//log.Printf("CSRF Storage: Get token %s", key)
|
||||||
|
|
||||||
|
var record entity.CsrfTokenRecords
|
||||||
|
result := s.DB.Where("token = ?", key).First(&record)
|
||||||
|
|
||||||
|
if result.Error != nil {
|
||||||
|
//log.Printf("CSRF Storage Get error: %v for token: %s", result.Error, key)
|
||||||
|
return nil, result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
if record.ExpireAt.Before(time.Now()) {
|
||||||
|
//log.Printf("CSRF token %s is expired", key)
|
||||||
|
return nil, fmt.Errorf("CSRF token is expired")
|
||||||
|
}
|
||||||
|
|
||||||
|
return record.Value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PostgresStorage) Set(key string, value []byte, exp time.Duration) error {
|
||||||
|
//log.Printf("CSRF Storage: Setting token %s with expiration %v", key, exp)
|
||||||
|
|
||||||
|
// Calculate expiration time
|
||||||
|
expireAt := time.Now().Add(exp)
|
||||||
|
|
||||||
|
// Try to update existing record first
|
||||||
|
result := s.DB.Model(&entity.CsrfTokenRecords{}).
|
||||||
|
Where("token = ?", key).
|
||||||
|
Updates(map[string]interface{}{
|
||||||
|
"expire_at": expireAt,
|
||||||
|
})
|
||||||
|
|
||||||
|
// If no rows were affected (not found), create a new record
|
||||||
|
if result.RowsAffected == 0 {
|
||||||
|
record := entity.CsrfTokenRecords{
|
||||||
|
Token: key,
|
||||||
|
Value: value,
|
||||||
|
ExpireAt: expireAt,
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.DB.Create(&record).Error; err != nil {
|
||||||
|
//log.Printf("CSRF Storage: Error saving token: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if result.Error != nil {
|
||||||
|
//log.Printf("CSRF Storage: Error updating token: %v", result.Error)
|
||||||
|
return result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
//log.Printf("CSRF Storage: Successfully saved/updated token")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PostgresStorage) Delete(key string) error {
|
||||||
|
return s.DB.Where("token = ?", key).Delete(&entity.CsrfTokenRecords{}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PostgresStorage) Reset() error {
|
||||||
|
return s.DB.Where("expire_at < ?", time.Now()).Delete(&entity.CsrfTokenRecords{}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PostgresStorage) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,166 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"narasi-ahli-be/app/database"
|
||||||
|
"narasi-ahli-be/config/config"
|
||||||
|
utilsSvc "narasi-ahli-be/utils"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2/middleware/csrf"
|
||||||
|
"github.com/gofiber/fiber/v2/middleware/session"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/gofiber/fiber/v2/middleware/compress"
|
||||||
|
"github.com/gofiber/fiber/v2/middleware/cors"
|
||||||
|
"github.com/gofiber/fiber/v2/middleware/limiter"
|
||||||
|
"github.com/gofiber/fiber/v2/middleware/monitor"
|
||||||
|
"github.com/gofiber/fiber/v2/middleware/pprof"
|
||||||
|
"github.com/gofiber/fiber/v2/middleware/recover"
|
||||||
|
"github.com/gofiber/fiber/v2/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Middleware is a struct that contains all the middleware functions
|
||||||
|
type Middleware struct {
|
||||||
|
App *fiber.App
|
||||||
|
Cfg *config.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMiddleware(app *fiber.App, cfg *config.Config) *Middleware {
|
||||||
|
return &Middleware{
|
||||||
|
App: app,
|
||||||
|
Cfg: cfg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register registers all the middleware functions
|
||||||
|
func (m *Middleware) Register(db *database.Database) {
|
||||||
|
// Add Extra Middlewares
|
||||||
|
|
||||||
|
m.App.Use(limiter.New(limiter.Config{
|
||||||
|
Next: utilsSvc.IsEnabled(m.Cfg.Middleware.Limiter.Enable),
|
||||||
|
Max: m.Cfg.Middleware.Limiter.Max,
|
||||||
|
Expiration: m.Cfg.Middleware.Limiter.Expiration * time.Second,
|
||||||
|
}))
|
||||||
|
|
||||||
|
m.App.Use(compress.New(compress.Config{
|
||||||
|
Next: utilsSvc.IsEnabled(m.Cfg.Middleware.Compress.Enable),
|
||||||
|
Level: m.Cfg.Middleware.Compress.Level,
|
||||||
|
}))
|
||||||
|
|
||||||
|
m.App.Use(recover.New(recover.Config{
|
||||||
|
Next: utilsSvc.IsEnabled(m.Cfg.Middleware.Recover.Enable),
|
||||||
|
}))
|
||||||
|
|
||||||
|
m.App.Use(pprof.New(pprof.Config{
|
||||||
|
Next: utilsSvc.IsEnabled(m.Cfg.Middleware.Pprof.Enable),
|
||||||
|
}))
|
||||||
|
|
||||||
|
m.App.Use(cors.New(cors.Config{
|
||||||
|
Next: utilsSvc.IsEnabled(m.Cfg.Middleware.Cors.Enable),
|
||||||
|
AllowOrigins: "http://localhost:3000, http://localhost:4000, https://dev.mikulnews.com, https://n8n.qudoco.com, https://narasiahli.com",
|
||||||
|
AllowMethods: "HEAD, GET, POST, PUT, DELETE, OPTION, PATCH",
|
||||||
|
AllowHeaders: "Origin, Content-Type, Accept, Accept-Language, Authorization, X-Requested-With, Access-Control-Request-Method, Access-Control-Request-Headers, Access-Control-Allow-Origin, Access-Control-Allow-Credentials, X-Csrf-Token, Cookie, Set-Cookie, X-Client-Key",
|
||||||
|
ExposeHeaders: "Content-Length, Content-Type",
|
||||||
|
AllowCredentials: true,
|
||||||
|
MaxAge: 12,
|
||||||
|
}))
|
||||||
|
|
||||||
|
//===============================
|
||||||
|
// CSRF CONFIG
|
||||||
|
//===============================
|
||||||
|
|
||||||
|
// Custom storage for CSRF
|
||||||
|
csrfSessionStorage := &PostgresStorage{
|
||||||
|
DB: db.DB,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store initialization for session
|
||||||
|
store := session.New(session.Config{
|
||||||
|
CookieSameSite: m.Cfg.Middleware.Csrf.CookieSameSite,
|
||||||
|
CookieSecure: m.Cfg.Middleware.Csrf.CookieSecure,
|
||||||
|
CookieSessionOnly: m.Cfg.Middleware.Csrf.CookieSessionOnly,
|
||||||
|
CookieHTTPOnly: m.Cfg.Middleware.Csrf.CookieHttpOnly,
|
||||||
|
Storage: csrfSessionStorage,
|
||||||
|
})
|
||||||
|
|
||||||
|
m.App.Use(func(c *fiber.Ctx) error {
|
||||||
|
sess, err := store.Get(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.Locals("session", sess)
|
||||||
|
return c.Next()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Cleanup the expired token
|
||||||
|
go func() {
|
||||||
|
ticker := time.NewTicker(1 * time.Hour)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for range ticker.C {
|
||||||
|
if err := csrfSessionStorage.Reset(); err != nil {
|
||||||
|
log.Printf("Error cleaning up expired CSRF tokens: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
m.App.Use(csrf.New(csrf.Config{
|
||||||
|
Next: utilsSvc.IsEnabled(m.Cfg.Middleware.Csrf.Enable),
|
||||||
|
KeyLookup: "header:" + csrf.HeaderName,
|
||||||
|
CookieName: m.Cfg.Middleware.Csrf.CookieName,
|
||||||
|
CookieSameSite: m.Cfg.Middleware.Csrf.CookieSameSite,
|
||||||
|
CookieSecure: m.Cfg.Middleware.Csrf.CookieSecure,
|
||||||
|
CookieSessionOnly: m.Cfg.Middleware.Csrf.CookieSessionOnly,
|
||||||
|
CookieHTTPOnly: m.Cfg.Middleware.Csrf.CookieHttpOnly,
|
||||||
|
Expiration: 1 * time.Hour,
|
||||||
|
KeyGenerator: utils.UUIDv4,
|
||||||
|
ContextKey: "csrf",
|
||||||
|
ErrorHandler: func(c *fiber.Ctx, err error) error {
|
||||||
|
return utilsSvc.CsrfErrorHandler(c, err)
|
||||||
|
},
|
||||||
|
Extractor: csrf.CsrfFromHeader(csrf.HeaderName),
|
||||||
|
Session: store,
|
||||||
|
SessionKey: "fiber.csrf.token",
|
||||||
|
}))
|
||||||
|
|
||||||
|
//===============================
|
||||||
|
m.App.Use(AuditTrailsMiddleware(db.DB))
|
||||||
|
StartAuditTrailCleanup(db.DB, m.Cfg.Middleware.AuditTrails.Retention)
|
||||||
|
|
||||||
|
//m.App.Use(filesystem.New(filesystem.Config{
|
||||||
|
// Next: utils.IsEnabled(m.Cfg.Middleware.FileSystem.Enable),
|
||||||
|
// Root: http.Dir(m.Cfg.Middleware.FileSystem.Root),
|
||||||
|
// Browse: m.Cfg.Middleware.FileSystem.Browse,
|
||||||
|
// MaxAge: m.Cfg.Middleware.FileSystem.MaxAge,
|
||||||
|
//}))
|
||||||
|
|
||||||
|
// ==================================================
|
||||||
|
|
||||||
|
m.App.Get(m.Cfg.Middleware.Monitor.Path, monitor.New(monitor.Config{
|
||||||
|
Next: utilsSvc.IsEnabled(m.Cfg.Middleware.Monitor.Enable),
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Route for generate CSRF token
|
||||||
|
m.App.Get("/csrf-token", func(c *fiber.Ctx) error {
|
||||||
|
// Retrieve CSRF token from Fiber's middleware context
|
||||||
|
token, ok := c.Locals("csrf").(string)
|
||||||
|
|
||||||
|
//c.Context().VisitUserValues(func(key []byte, value interface{}) {
|
||||||
|
// log.Printf("Local Key: %s, Value: %v", key, value)
|
||||||
|
//})
|
||||||
|
|
||||||
|
if !ok || token == "" {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"success": false,
|
||||||
|
"code": 500,
|
||||||
|
"messages": []string{"Failed to retrieve CSRF token"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(fiber.Map{
|
||||||
|
"success": true,
|
||||||
|
"csrf_token": token,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
package activity_logs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"narasi-ahli-be/app/module/activity_logs/controller"
|
||||||
|
"narasi-ahli-be/app/module/activity_logs/repository"
|
||||||
|
"narasi-ahli-be/app/module/activity_logs/service"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"go.uber.org/fx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// struct of ActivityLogsRouter
|
||||||
|
type ActivityLogsRouter struct {
|
||||||
|
App fiber.Router
|
||||||
|
Controller *controller.Controller
|
||||||
|
}
|
||||||
|
|
||||||
|
// register bulky of ActivityLogs module
|
||||||
|
var NewActivityLogsModule = fx.Options(
|
||||||
|
// register repository of ActivityLogs module
|
||||||
|
fx.Provide(repository.NewActivityLogsRepository),
|
||||||
|
|
||||||
|
// register service of ActivityLogs module
|
||||||
|
fx.Provide(service.NewActivityLogsService),
|
||||||
|
|
||||||
|
// register controller of ActivityLogs module
|
||||||
|
fx.Provide(controller.NewController),
|
||||||
|
|
||||||
|
// register router of ActivityLogs module
|
||||||
|
fx.Provide(NewActivityLogsRouter),
|
||||||
|
)
|
||||||
|
|
||||||
|
// init ActivityLogsRouter
|
||||||
|
func NewActivityLogsRouter(fiber *fiber.App, controller *controller.Controller) *ActivityLogsRouter {
|
||||||
|
return &ActivityLogsRouter{
|
||||||
|
App: fiber,
|
||||||
|
Controller: controller,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// register routes of ActivityLogs module
|
||||||
|
func (_i *ActivityLogsRouter) RegisterActivityLogsRoutes() {
|
||||||
|
// define controllers
|
||||||
|
activityLogsController := _i.Controller.ActivityLogs
|
||||||
|
|
||||||
|
// define routes
|
||||||
|
_i.App.Route("/activity-logs", func(router fiber.Router) {
|
||||||
|
router.Get("/", activityLogsController.All)
|
||||||
|
router.Get("/statistics", activityLogsController.GetActivityStats)
|
||||||
|
router.Get("/detail/:id", activityLogsController.Show)
|
||||||
|
router.Post("/", activityLogsController.Save)
|
||||||
|
router.Put("/:id", activityLogsController.Update)
|
||||||
|
router.Delete("/:id", activityLogsController.Delete)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,250 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"narasi-ahli-be/app/module/activity_logs/request"
|
||||||
|
"narasi-ahli-be/app/module/activity_logs/service"
|
||||||
|
"narasi-ahli-be/utils/paginator"
|
||||||
|
utilRes "narasi-ahli-be/utils/response"
|
||||||
|
utilVal "narasi-ahli-be/utils/validator"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
)
|
||||||
|
|
||||||
|
type activityLogsController struct {
|
||||||
|
activityLogsService service.ActivityLogsService
|
||||||
|
Log zerolog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
type ActivityLogsController 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
|
||||||
|
GetActivityStats(c *fiber.Ctx) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewActivityLogsController(activityLogsService service.ActivityLogsService, log zerolog.Logger) ActivityLogsController {
|
||||||
|
return &activityLogsController{
|
||||||
|
activityLogsService: activityLogsService,
|
||||||
|
Log: log,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// All get all ActivityLogs
|
||||||
|
// @Summary Get all ActivityLogs
|
||||||
|
// @Description API for getting all ActivityLogs
|
||||||
|
// @Tags ActivityLogs
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Client-Key header string false "Insert the X-Client-Key"
|
||||||
|
// @Param req query request.ActivityLogsQueryRequest 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 /activity-logs [get]
|
||||||
|
func (_i *activityLogsController) All(c *fiber.Ctx) error {
|
||||||
|
paginate, err := paginator.Paginate(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
reqContext := request.ActivityLogsQueryRequestContext{
|
||||||
|
ActivityTypeId: c.Query("activityTypeId"),
|
||||||
|
Url: c.Query("url"),
|
||||||
|
ArticleId: c.Query("articleId"),
|
||||||
|
UserId: c.Query("userId"),
|
||||||
|
}
|
||||||
|
req := reqContext.ToParamRequest()
|
||||||
|
req.Pagination = paginate
|
||||||
|
|
||||||
|
activityLogsData, paging, err := _i.activityLogsService.All(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"ActivityLogs list successfully retrieved"},
|
||||||
|
Data: activityLogsData,
|
||||||
|
Meta: paging,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show get one ActivityLogs
|
||||||
|
// @Summary Get one ActivityLogs
|
||||||
|
// @Description API for getting one ActivityLogs
|
||||||
|
// @Tags ActivityLogs
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Client-Key header string false "Insert the X-Client-Key"
|
||||||
|
// @Param id path int true "ActivityLogs ID"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /activity-logs/detail/{id} [get]
|
||||||
|
func (_i *activityLogsController) Show(c *fiber.Ctx) error {
|
||||||
|
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
activityLogsData, err := _i.activityLogsService.Show(uint(id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"ActivityLogs successfully retrieved"},
|
||||||
|
Data: activityLogsData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save create ActivityLogs
|
||||||
|
// @Summary Create ActivityLogs
|
||||||
|
// @Description API for create ActivityLogs
|
||||||
|
// @Tags ActivityLogs
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Client-Key header string false "Insert the X-Client-Key"
|
||||||
|
// @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.ActivityLogsCreateRequest true "Required payload"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /activity-logs [post]
|
||||||
|
func (_i *activityLogsController) Save(c *fiber.Ctx) error {
|
||||||
|
req := new(request.ActivityLogsCreateRequest)
|
||||||
|
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var authToken *string
|
||||||
|
getTokenFromHeader := c.Get("Authorization")
|
||||||
|
if getTokenFromHeader == "" {
|
||||||
|
authToken = nil
|
||||||
|
} else {
|
||||||
|
authToken = &getTokenFromHeader
|
||||||
|
}
|
||||||
|
visitorIp := GetVisitorIP(c)
|
||||||
|
req.VisitorIp = &visitorIp
|
||||||
|
dataResult, err := _i.activityLogsService.Save(*req, authToken)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"ActivityLogs successfully created"},
|
||||||
|
Data: dataResult,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update update ActivityLogs
|
||||||
|
// @Summary update ActivityLogs
|
||||||
|
// @Description API for update ActivityLogs
|
||||||
|
// @Tags ActivityLogs
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Client-Key header string false "Insert the X-Client-Key"
|
||||||
|
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||||
|
// @Param payload body request.ActivityLogsUpdateRequest true "Required payload"
|
||||||
|
// @Param id path int true "ActivityLogs ID"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /activity-logs/{id} [put]
|
||||||
|
func (_i *activityLogsController) Update(c *fiber.Ctx) error {
|
||||||
|
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req := new(request.ActivityLogsUpdateRequest)
|
||||||
|
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = _i.activityLogsService.Update(uint(id), *req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"ActivityLogs successfully updated"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete delete ActivityLogs
|
||||||
|
// @Summary delete ActivityLogs
|
||||||
|
// @Description API for delete ActivityLogs
|
||||||
|
// @Tags ActivityLogs
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Client-Key header string false "Insert the X-Client-Key"
|
||||||
|
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||||
|
// @Param id path int true "ActivityLogs ID"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /activity-logs/{id} [delete]
|
||||||
|
func (_i *activityLogsController) Delete(c *fiber.Ctx) error {
|
||||||
|
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = _i.activityLogsService.Delete(uint(id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"ActivityLogs successfully deleted"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetActivityStats get activity stats ActivityLogs
|
||||||
|
// @Summary Get activity stats ActivityLogs
|
||||||
|
// @Description API for get activity stats ActivityLogs
|
||||||
|
// @Tags ActivityLogs
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Client-Key header string false "Insert the X-Client-Key"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /activity-logs/statistics [get]
|
||||||
|
func (_i *activityLogsController) GetActivityStats(c *fiber.Ctx) error {
|
||||||
|
_i.Log.Info().Interface("GetActivityStats", "checker controller").Msg("")
|
||||||
|
|
||||||
|
activityStatsData, err := _i.activityLogsService.GetActivityStats()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"ActivityLogs Stats successfully retrieved"},
|
||||||
|
Data: activityStatsData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetVisitorIP(c *fiber.Ctx) string {
|
||||||
|
ip := c.Get("X-Forwarded-For")
|
||||||
|
if ip == "" {
|
||||||
|
ip = c.IP()
|
||||||
|
}
|
||||||
|
if strings.Contains(ip, ":") {
|
||||||
|
ip = strings.Split(ip, ":")[0]
|
||||||
|
}
|
||||||
|
return ip
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"narasi-ahli-be/app/module/activity_logs/service"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Controller struct {
|
||||||
|
ActivityLogs ActivityLogsController
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewController(ActivityLogsService service.ActivityLogsService, log zerolog.Logger) *Controller {
|
||||||
|
return &Controller{
|
||||||
|
ActivityLogs: NewActivityLogsController(ActivityLogsService, log),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
package mapper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"narasi-ahli-be/app/database/entity"
|
||||||
|
res "narasi-ahli-be/app/module/activity_logs/response"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ActivityLogsResponseMapper(activityLogsReq *entity.ActivityLogs) (activityLogsRes *res.ActivityLogsResponse) {
|
||||||
|
if activityLogsReq != nil {
|
||||||
|
activityLogsRes = &res.ActivityLogsResponse{
|
||||||
|
ID: activityLogsReq.ID,
|
||||||
|
ActivityTypeId: activityLogsReq.ActivityTypeId,
|
||||||
|
Url: activityLogsReq.Url,
|
||||||
|
ArticleId: activityLogsReq.ArticleId,
|
||||||
|
UserId: activityLogsReq.UserId,
|
||||||
|
CreatedAt: activityLogsReq.CreatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return activityLogsRes
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,124 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"narasi-ahli-be/app/database"
|
||||||
|
"narasi-ahli-be/app/database/entity"
|
||||||
|
"narasi-ahli-be/app/module/activity_logs/request"
|
||||||
|
"narasi-ahli-be/utils/paginator"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
)
|
||||||
|
|
||||||
|
type activityLogsRepository struct {
|
||||||
|
DB *database.Database
|
||||||
|
Log zerolog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActivityLogsRepository define interface of IActivityLogsRepository
|
||||||
|
type ActivityLogsRepository interface {
|
||||||
|
GetAll(req request.ActivityLogsQueryRequest) (activityLogss []*entity.ActivityLogs, paging paginator.Pagination, err error)
|
||||||
|
FindOne(id uint) (activityLogs *entity.ActivityLogs, err error)
|
||||||
|
Create(activityLogs *entity.ActivityLogs) (activityLogsReturn *entity.ActivityLogs, err error)
|
||||||
|
Update(id uint, activityLogs *entity.ActivityLogs) (err error)
|
||||||
|
Delete(id uint) (err error)
|
||||||
|
CountUniqueVisitorAllTime() (count int64, err error)
|
||||||
|
CountUniqueVisitorToday() (count int64, err error)
|
||||||
|
CountTotalViewAllTime() (count int64, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewActivityLogsRepository(db *database.Database, logger zerolog.Logger) ActivityLogsRepository {
|
||||||
|
return &activityLogsRepository{
|
||||||
|
DB: db,
|
||||||
|
Log: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// implement interface of IActivityLogsRepository
|
||||||
|
func (_i *activityLogsRepository) GetAll(req request.ActivityLogsQueryRequest) (activityLogss []*entity.ActivityLogs, paging paginator.Pagination, err error) {
|
||||||
|
var count int64
|
||||||
|
|
||||||
|
query := _i.DB.DB.Model(&entity.ActivityLogs{})
|
||||||
|
|
||||||
|
if req.ActivityTypeId != nil {
|
||||||
|
query = query.Where("activity_type_id = ?", req.ActivityTypeId)
|
||||||
|
}
|
||||||
|
if req.Url != nil && *req.Url != "" {
|
||||||
|
url := strings.ToLower(*req.Url)
|
||||||
|
query = query.Where("LOWER(url) LIKE ?", "%"+strings.ToLower(url)+"%")
|
||||||
|
}
|
||||||
|
if req.ArticleId != nil {
|
||||||
|
query = query.Where("article_id = ?", req.ArticleId)
|
||||||
|
}
|
||||||
|
if req.UserId != nil {
|
||||||
|
query = query.Where("user_id = ?", req.UserId)
|
||||||
|
}
|
||||||
|
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(&activityLogss).Error
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
paging = *req.Pagination
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *activityLogsRepository) FindOne(id uint) (activityLogs *entity.ActivityLogs, err error) {
|
||||||
|
query := _i.DB.DB.Where("id = ?", id)
|
||||||
|
|
||||||
|
if err := query.First(&activityLogs).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return activityLogs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *activityLogsRepository) Create(activityLogs *entity.ActivityLogs) (activityLogsReturn *entity.ActivityLogs, err error) {
|
||||||
|
result := _i.DB.DB.Create(activityLogs)
|
||||||
|
return activityLogs, result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *activityLogsRepository) Update(id uint, activityLogs *entity.ActivityLogs) (err error) {
|
||||||
|
query := _i.DB.DB.Model(&entity.ActivityLogs{}).Where(&entity.ActivityLogs{ID: id})
|
||||||
|
return query.Updates(activityLogs).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *activityLogsRepository) Delete(id uint) error {
|
||||||
|
query := _i.DB.DB.Model(&entity.ActivityLogs{}).Where("id = ?", id)
|
||||||
|
return query.Delete(&entity.ActivityLogs{}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *activityLogsRepository) CountUniqueVisitorAllTime() (count int64, err error) {
|
||||||
|
query := _i.DB.DB.Model(&entity.ActivityLogs{})
|
||||||
|
err = query.Distinct("visitor_ip").Count(&count).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *activityLogsRepository) CountTotalViewAllTime() (count int64, err error) {
|
||||||
|
query := _i.DB.DB.Model(&entity.ActivityLogs{}).Where("activity_type_id = ?", 2)
|
||||||
|
err = query.Count(&count).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *activityLogsRepository) CountUniqueVisitorToday() (count int64, err error) {
|
||||||
|
tenMinutesAgo := time.Now().Add(-10 * time.Minute)
|
||||||
|
|
||||||
|
query := _i.DB.DB.Model(&entity.AuditTrails{}).Where("created_at >= ?", tenMinutesAgo)
|
||||||
|
err = query.Select("ip").Group("ip").Count(&count).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
package request
|
||||||
|
|
||||||
|
import (
|
||||||
|
"narasi-ahli-be/app/database/entity"
|
||||||
|
"narasi-ahli-be/utils/paginator"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ActivityLogsGeneric interface {
|
||||||
|
ToEntity()
|
||||||
|
}
|
||||||
|
|
||||||
|
type ActivityLogsQueryRequest struct {
|
||||||
|
ActivityTypeId *int `json:"activityTypeId"`
|
||||||
|
Url *string `json:"url"`
|
||||||
|
ArticleId *int `json:"articleId"`
|
||||||
|
UserId *int `json:"userId"`
|
||||||
|
Pagination *paginator.Pagination `json:"pagination"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ActivityLogsCreateRequest struct {
|
||||||
|
ActivityTypeId int `json:"activityTypeId" validate:"required"`
|
||||||
|
Url string `json:"url" validate:"required"`
|
||||||
|
ArticleId *uint `json:"articleId"`
|
||||||
|
UserId *uint `json:"userId"`
|
||||||
|
VisitorIp *string `json:"visitorIp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req ActivityLogsCreateRequest) ToEntity() *entity.ActivityLogs {
|
||||||
|
return &entity.ActivityLogs{
|
||||||
|
ActivityTypeId: req.ActivityTypeId,
|
||||||
|
Url: req.Url,
|
||||||
|
ArticleId: req.ArticleId,
|
||||||
|
UserId: req.UserId,
|
||||||
|
VisitorIp: req.VisitorIp,
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ActivityLogsUpdateRequest struct {
|
||||||
|
ID uint `json:"id" validate:"required"`
|
||||||
|
ActivityTypeId int `json:"activityTypeId" validate:"required"`
|
||||||
|
Url string `json:"url" validate:"required"`
|
||||||
|
ArticleId *uint `json:"articleId"`
|
||||||
|
UserId *uint `json:"userId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req ActivityLogsUpdateRequest) ToEntity() *entity.ActivityLogs {
|
||||||
|
return &entity.ActivityLogs{
|
||||||
|
ID: req.ID,
|
||||||
|
ActivityTypeId: req.ActivityTypeId,
|
||||||
|
Url: req.Url,
|
||||||
|
ArticleId: req.ArticleId,
|
||||||
|
UserId: req.UserId,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ActivityLogsQueryRequestContext struct {
|
||||||
|
ActivityTypeId string `json:"activityTypeId"`
|
||||||
|
Url string `json:"url"`
|
||||||
|
ArticleId string `json:"articleId"`
|
||||||
|
UserId string `json:"userId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req ActivityLogsQueryRequestContext) ToParamRequest() ActivityLogsQueryRequest {
|
||||||
|
var request ActivityLogsQueryRequest
|
||||||
|
|
||||||
|
if activityTypeIdStr := req.ActivityTypeId; activityTypeIdStr != "" {
|
||||||
|
activityTypeId, err := strconv.Atoi(activityTypeIdStr)
|
||||||
|
if err == nil {
|
||||||
|
request.ActivityTypeId = &activityTypeId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if url := req.Url; url != "" {
|
||||||
|
request.Url = &url
|
||||||
|
}
|
||||||
|
if articleIdStr := req.ArticleId; articleIdStr != "" {
|
||||||
|
articleId, err := strconv.Atoi(articleIdStr)
|
||||||
|
if err == nil {
|
||||||
|
request.ArticleId = &articleId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if userIdStr := req.UserId; userIdStr != "" {
|
||||||
|
userId, err := strconv.Atoi(userIdStr)
|
||||||
|
if err == nil {
|
||||||
|
request.UserId = &userId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return request
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
package response
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type ActivityLogsResponse struct {
|
||||||
|
ID uint `json:"id"`
|
||||||
|
ActivityTypeId int `json:"activityTypeId"`
|
||||||
|
Url string `json:"url"`
|
||||||
|
ArticleId *uint `json:"articleId"`
|
||||||
|
UserId *uint `json:"userId"`
|
||||||
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ActivityStatsResponse struct {
|
||||||
|
TotalVisitorAllTime int64 `json:"totalVisitorAllTime"`
|
||||||
|
TotalVisitorToday int64 `json:"totalVisitorToday"`
|
||||||
|
TotalViewAllTime int64 `json:"totalViewAllTime"`
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,124 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"narasi-ahli-be/app/database/entity"
|
||||||
|
"narasi-ahli-be/app/module/activity_logs/mapper"
|
||||||
|
"narasi-ahli-be/app/module/activity_logs/repository"
|
||||||
|
"narasi-ahli-be/app/module/activity_logs/request"
|
||||||
|
"narasi-ahli-be/app/module/activity_logs/response"
|
||||||
|
"narasi-ahli-be/app/module/articles/service"
|
||||||
|
usersRepository "narasi-ahli-be/app/module/users/repository"
|
||||||
|
"narasi-ahli-be/utils/paginator"
|
||||||
|
utilSvc "narasi-ahli-be/utils/service"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ActivityLogsService
|
||||||
|
type activityLogsService struct {
|
||||||
|
Repo repository.ActivityLogsRepository
|
||||||
|
UsersRepo usersRepository.UsersRepository
|
||||||
|
ArticleService service.ArticlesService
|
||||||
|
Log zerolog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActivityLogsService define interface of IActivityLogsService
|
||||||
|
type ActivityLogsService interface {
|
||||||
|
All(req request.ActivityLogsQueryRequest) (activityLogs []*response.ActivityLogsResponse, paging paginator.Pagination, err error)
|
||||||
|
Show(id uint) (activityLogs *response.ActivityLogsResponse, err error)
|
||||||
|
Save(req request.ActivityLogsCreateRequest, authToken *string) (activityLogs *entity.ActivityLogs, err error)
|
||||||
|
Update(id uint, req request.ActivityLogsUpdateRequest) (err error)
|
||||||
|
Delete(id uint) error
|
||||||
|
GetActivityStats() (activityStats *response.ActivityStatsResponse, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewActivityLogsService init ActivityLogsService
|
||||||
|
func NewActivityLogsService(repo repository.ActivityLogsRepository, log zerolog.Logger, usersRepo usersRepository.UsersRepository, articleService service.ArticlesService) ActivityLogsService {
|
||||||
|
|
||||||
|
return &activityLogsService{
|
||||||
|
Repo: repo,
|
||||||
|
Log: log,
|
||||||
|
UsersRepo: usersRepo,
|
||||||
|
ArticleService: articleService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// All implement interface of ActivityLogsService
|
||||||
|
func (_i *activityLogsService) All(req request.ActivityLogsQueryRequest) (activityLogss []*response.ActivityLogsResponse, paging paginator.Pagination, err error) {
|
||||||
|
results, paging, err := _i.Repo.GetAll(req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, result := range results {
|
||||||
|
activityLogss = append(activityLogss, mapper.ActivityLogsResponseMapper(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *activityLogsService) Show(id uint) (activityLogs *response.ActivityLogsResponse, err error) {
|
||||||
|
result, err := _i.Repo.FindOne(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapper.ActivityLogsResponseMapper(result), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *activityLogsService) Save(req request.ActivityLogsCreateRequest, authToken *string) (activityLogs *entity.ActivityLogs, err error) {
|
||||||
|
_i.Log.Info().Interface("data", req).Msg("")
|
||||||
|
|
||||||
|
newReq := req.ToEntity()
|
||||||
|
|
||||||
|
if authToken != nil {
|
||||||
|
createdBy := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, *authToken)
|
||||||
|
newReq.UserId = &createdBy.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := _i.Repo.Create(newReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update article
|
||||||
|
err = _i.ArticleService.UpdateActivityCount(*req.ArticleId, req.ActivityTypeId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *activityLogsService) Update(id uint, req request.ActivityLogsUpdateRequest) (err error) {
|
||||||
|
_i.Log.Info().Interface("data", req).Msg("")
|
||||||
|
newReq := req.ToEntity()
|
||||||
|
return _i.Repo.Update(id, newReq)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *activityLogsService) Delete(id uint) error {
|
||||||
|
return _i.Repo.Delete(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *activityLogsService) GetActivityStats() (activityStats *response.ActivityStatsResponse, err error) {
|
||||||
|
_i.Log.Info().Interface("GetActivityStats", "checker").Msg("")
|
||||||
|
countUniqueVisitorAllTime, err := _i.Repo.CountUniqueVisitorAllTime()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
countUniqueVisitorToday, err := _i.Repo.CountUniqueVisitorToday()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
countTotalViewAllTime, err := _i.Repo.CountTotalViewAllTime()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
getActivityStats := &response.ActivityStatsResponse{
|
||||||
|
TotalVisitorAllTime: countUniqueVisitorAllTime,
|
||||||
|
TotalVisitorToday: countUniqueVisitorToday,
|
||||||
|
TotalViewAllTime: countTotalViewAllTime,
|
||||||
|
}
|
||||||
|
return getActivityStats, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
package advertisement
|
||||||
|
|
||||||
|
import (
|
||||||
|
"narasi-ahli-be/app/module/advertisement/controller"
|
||||||
|
"narasi-ahli-be/app/module/advertisement/repository"
|
||||||
|
"narasi-ahli-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,292 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"narasi-ahli-be/app/module/advertisement/request"
|
||||||
|
"narasi-ahli-be/app/module/advertisement/service"
|
||||||
|
"narasi-ahli-be/utils/paginator"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
|
||||||
|
utilRes "narasi-ahli-be/utils/response"
|
||||||
|
utilVal "narasi-ahli-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 X-Client-Key header string false "Insert the X-Client-Key"
|
||||||
|
// @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 X-Client-Key header string false "Insert the X-Client-Key"
|
||||||
|
// @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-Client-Key header string false "Insert the X-Client-Key"
|
||||||
|
// @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-Client-Key header string false "Insert the X-Client-Key"
|
||||||
|
// @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-Client-Key header string false "Insert the X-Client-Key"
|
||||||
|
// @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-Client-Key header string false "Insert the X-Client-Key"
|
||||||
|
// @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-Client-Key header string false "Insert the X-Client-Key"
|
||||||
|
// @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 X-Client-Key header string false "Insert the X-Client-Key"
|
||||||
|
// @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 (
|
||||||
|
"narasi-ahli-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 (
|
||||||
|
"narasi-ahli-be/app/database/entity"
|
||||||
|
res "narasi-ahli-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 (
|
||||||
|
"fmt"
|
||||||
|
"narasi-ahli-be/app/database"
|
||||||
|
"narasi-ahli-be/app/database/entity"
|
||||||
|
"narasi-ahli-be/app/module/advertisement/request"
|
||||||
|
"narasi-ahli-be/utils/paginator"
|
||||||
|
utilSvc "narasi-ahli-be/utils/service"
|
||||||
|
"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 (
|
||||||
|
"narasi-ahli-be/app/database/entity"
|
||||||
|
"narasi-ahli-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 (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"math/rand"
|
||||||
|
"mime"
|
||||||
|
"narasi-ahli-be/app/database/entity"
|
||||||
|
"narasi-ahli-be/app/module/advertisement/mapper"
|
||||||
|
"narasi-ahli-be/app/module/advertisement/repository"
|
||||||
|
"narasi-ahli-be/app/module/advertisement/request"
|
||||||
|
"narasi-ahli-be/app/module/advertisement/response"
|
||||||
|
usersRepository "narasi-ahli-be/app/module/users/repository"
|
||||||
|
config "narasi-ahli-be/config/config"
|
||||||
|
minioStorage "narasi-ahli-be/config/config"
|
||||||
|
"narasi-ahli-be/utils/paginator"
|
||||||
|
"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,27 @@
|
||||||
|
package ai_chat
|
||||||
|
|
||||||
|
import (
|
||||||
|
"narasi-ahli-be/app/module/ai_chat/controller"
|
||||||
|
"narasi-ahli-be/app/module/ai_chat/repository"
|
||||||
|
"narasi-ahli-be/app/module/ai_chat/service"
|
||||||
|
usersRepository "narasi-ahli-be/app/module/users/repository"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"go.uber.org/fx"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Module = fx.Options(
|
||||||
|
fx.Provide(
|
||||||
|
repository.NewAIChatRepository,
|
||||||
|
service.NewAIChatService,
|
||||||
|
controller.NewAIChatController,
|
||||||
|
),
|
||||||
|
fx.Invoke(func(
|
||||||
|
aiChatController controller.AIChatController,
|
||||||
|
usersRepo usersRepository.UsersRepository,
|
||||||
|
log zerolog.Logger,
|
||||||
|
) {
|
||||||
|
log.Info().Msg("AI Chat module initialized successfully")
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
@ -0,0 +1,459 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"narasi-ahli-be/app/module/ai_chat/request"
|
||||||
|
"narasi-ahli-be/app/module/ai_chat/service"
|
||||||
|
"narasi-ahli-be/utils/paginator"
|
||||||
|
utilRes "narasi-ahli-be/utils/response"
|
||||||
|
utilVal "narasi-ahli-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 X-Client-Key header string false "Insert the X-Client-Key"
|
||||||
|
// @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{
|
||||||
|
Status: c.Query("status"),
|
||||||
|
}
|
||||||
|
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 X-Client-Key header string false "Insert the X-Client-Key"
|
||||||
|
// @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-Client-Key header string false "Insert the X-Client-Key"
|
||||||
|
// @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.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 X-Client-Key header string false "Insert the X-Client-Key"
|
||||||
|
// @Param X-Csrf-Token header string true "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 X-Client-Key header string false "Insert the X-Client-Key"
|
||||||
|
// @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 X-Client-Key header string false "Insert the X-Client-Key"
|
||||||
|
// @Param sessionId 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/{sessionId}/messages [get]
|
||||||
|
func (_i *aiChatController) GetSessionMessages(c *fiber.Ctx) error {
|
||||||
|
sessionId, err := strconv.ParseUint(c.Params("sessionId"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
paginate, err := paginator.Paginate(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
authHeader := c.Get("Authorization")
|
||||||
|
|
||||||
|
reqContext := request.AIChatMessagesQueryRequestContext{
|
||||||
|
SessionID: c.Query("sessionId"),
|
||||||
|
}
|
||||||
|
req := reqContext.ToParamRequest()
|
||||||
|
req.Pagination = paginate
|
||||||
|
|
||||||
|
messagesData, paging, err := _i.aiChatService.GetSessionMessages(authHeader, uint(sessionId), 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 X-Client-Key header string false "Insert the X-Client-Key"
|
||||||
|
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||||
|
// @Param sessionId path int true "Session ID"
|
||||||
|
// @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/{sessionId}/messages [post]
|
||||||
|
func (_i *aiChatController) SendMessage(c *fiber.Ctx) error {
|
||||||
|
sessionId, err := strconv.ParseUint(c.Params("sessionId"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req := new(request.AIChatMessagesCreateRequest)
|
||||||
|
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
authHeader := c.Get("Authorization")
|
||||||
|
|
||||||
|
dataResult, err := _i.aiChatService.SendMessage(authHeader, uint(sessionId), *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 X-Client-Key header string false "Insert the X-Client-Key"
|
||||||
|
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||||
|
// @Param sessionId path int true "Session ID"
|
||||||
|
// @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/{sessionId}/messages/{messageId} [put]
|
||||||
|
func (_i *aiChatController) UpdateMessage(c *fiber.Ctx) error {
|
||||||
|
sessionId, err := strconv.ParseUint(c.Params("sessionId"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
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(sessionId), 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 X-Client-Key header string false "Insert the X-Client-Key"
|
||||||
|
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||||
|
// @Param sessionId path int true "Session ID"
|
||||||
|
// @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/{sessionId}/messages/{messageId} [delete]
|
||||||
|
func (_i *aiChatController) DeleteMessage(c *fiber.Ctx) error {
|
||||||
|
sessionId, err := strconv.ParseUint(c.Params("sessionId"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
messageId, err := strconv.ParseUint(c.Params("messageId"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
authHeader := c.Get("Authorization")
|
||||||
|
|
||||||
|
err = _i.aiChatService.DeleteMessage(authHeader, uint(sessionId), 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 X-Client-Key header string false "Insert the X-Client-Key"
|
||||||
|
// @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 X-Client-Key header string false "Insert the X-Client-Key"
|
||||||
|
// @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,70 @@
|
||||||
|
package mapper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"narasi-ahli-be/app/database/entity"
|
||||||
|
"narasi-ahli-be/app/module/ai_chat/response"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AIChatSessionsResponseMapper(session *entity.AIChatSessions) *response.AIChatSessionsResponse {
|
||||||
|
result := &response.AIChatSessionsResponse{
|
||||||
|
ID: session.ID,
|
||||||
|
AISessionID: session.AISessionID,
|
||||||
|
UserID: session.UserID,
|
||||||
|
AgentID: session.AgentID,
|
||||||
|
Title: session.Title,
|
||||||
|
MessageCount: session.MessageCount,
|
||||||
|
Status: session.Status,
|
||||||
|
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,
|
||||||
|
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 (
|
||||||
|
"narasi-ahli-be/app/database"
|
||||||
|
"narasi-ahli-be/app/database/entity"
|
||||||
|
"narasi-ahli-be/app/module/ai_chat/request"
|
||||||
|
"narasi-ahli-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)
|
||||||
|
FindSessionByAISessionId(aiSessionId 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 uint, 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 uint) (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.Where("user_id = ?", userId)
|
||||||
|
|
||||||
|
// Apply filters
|
||||||
|
if req.Status != nil {
|
||||||
|
query = query.Where("status = ?", *req.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.Where("user_id = ? AND id = ?", userId, sessionId).Preload("User").First(&session).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *aiChatRepository) FindSessionByAISessionId(aiSessionId string) (session *entity.AIChatSessions, err error) {
|
||||||
|
err = _i.DB.DB.Where("ai_session_id = ?", aiSessionId).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.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.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.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 uint, req request.AIChatMessagesQueryRequest) (messages []*entity.AIChatMessages, paging paginator.Pagination, err error) {
|
||||||
|
query := _i.DB.DB.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.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.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.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.Model(&entity.AIChatLogs{}).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.Where("user_id = ? AND id = ?", userId, logId)
|
||||||
|
|
||||||
|
if err := query.First(&log).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *aiChatRepository) GetLastMessage(sessionId uint) (message *entity.AIChatMessages, err error) {
|
||||||
|
err = _i.DB.DB.Where("session_id = ?", sessionId).Order("created_at DESC").First(&message).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,118 @@
|
||||||
|
package request
|
||||||
|
|
||||||
|
import (
|
||||||
|
"narasi-ahli-be/app/database/entity"
|
||||||
|
"narasi-ahli-be/utils/paginator"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AI Chat Sessions Request DTOs
|
||||||
|
type AIChatSessionsQueryRequest struct {
|
||||||
|
Status *string `json:"status"`
|
||||||
|
Pagination *paginator.Pagination `json:"pagination"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AIChatSessionsCreateRequest struct {
|
||||||
|
Title string `json:"title" validate:"required,min=2,max=255"`
|
||||||
|
AgentID *string `json:"agentId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req AIChatSessionsCreateRequest) ToEntity() *entity.AIChatSessions {
|
||||||
|
return &entity.AIChatSessions{
|
||||||
|
AISessionID: "", // Will be generated in service layer
|
||||||
|
AgentID: req.AgentID,
|
||||||
|
Title: req.Title,
|
||||||
|
MessageCount: 0,
|
||||||
|
Status: "active",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type AIChatSessionsUpdateRequest struct {
|
||||||
|
Title string `json:"title" validate:"required,min=2,max=255"`
|
||||||
|
Status string `json:"status" validate:"required,oneof=active archived deleted"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req AIChatSessionsUpdateRequest) ToEntity() *entity.AIChatSessions {
|
||||||
|
return &entity.AIChatSessions{
|
||||||
|
Title: req.Title,
|
||||||
|
Status: req.Status,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AI Chat Messages Request DTOs
|
||||||
|
type AIChatMessagesQueryRequest struct {
|
||||||
|
SessionID uint `json:"sessionId" validate:"required"`
|
||||||
|
Pagination *paginator.Pagination `json:"pagination"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AIChatMessagesCreateRequest struct {
|
||||||
|
SessionID uint `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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
Status string `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req AIChatSessionsQueryRequestContext) ToParamRequest() AIChatSessionsQueryRequest {
|
||||||
|
var request AIChatSessionsQueryRequest
|
||||||
|
|
||||||
|
if status := req.Status; status != "" {
|
||||||
|
request.Status = &status
|
||||||
|
}
|
||||||
|
|
||||||
|
return request
|
||||||
|
}
|
||||||
|
|
||||||
|
type AIChatMessagesQueryRequestContext struct {
|
||||||
|
SessionID string `json:"sessionId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req AIChatMessagesQueryRequestContext) ToParamRequest() AIChatMessagesQueryRequest {
|
||||||
|
var request AIChatMessagesQueryRequest
|
||||||
|
|
||||||
|
if sessionIDStr := req.SessionID; sessionIDStr != "" {
|
||||||
|
// Parse session ID from string to uint
|
||||||
|
// This will be handled in the controller
|
||||||
|
}
|
||||||
|
|
||||||
|
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,62 @@
|
||||||
|
package response
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AI Chat Sessions Response DTOs
|
||||||
|
type AIChatSessionsResponse struct {
|
||||||
|
ID uint `json:"id"`
|
||||||
|
AISessionID string `json:"aiSessionId"`
|
||||||
|
UserID uint `json:"userId"`
|
||||||
|
AgentID *string `json:"agentId"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
MessageCount int `json:"messageCount"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
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 uint `json:"sessionId"`
|
||||||
|
MessageType string `json:"messageType"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
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,246 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"narasi-ahli-be/app/module/ai_chat/mapper"
|
||||||
|
"narasi-ahli-be/app/module/ai_chat/repository"
|
||||||
|
"narasi-ahli-be/app/module/ai_chat/request"
|
||||||
|
"narasi-ahli-be/app/module/ai_chat/response"
|
||||||
|
usersRepository "narasi-ahli-be/app/module/users/repository"
|
||||||
|
"narasi-ahli-be/utils/paginator"
|
||||||
|
utilSvc "narasi-ahli-be/utils/service"
|
||||||
|
|
||||||
|
"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, sessionId uint) (session *response.AIChatSessionsResponse, err error)
|
||||||
|
CreateSession(authToken string, req request.AIChatSessionsCreateRequest) (session *response.AIChatSessionsResponse, err error)
|
||||||
|
UpdateSession(authToken string, sessionId uint, req request.AIChatSessionsUpdateRequest) (err error)
|
||||||
|
DeleteSession(authToken string, sessionId uint) error
|
||||||
|
ArchiveSession(authToken string, sessionId uint) error
|
||||||
|
|
||||||
|
// Messages
|
||||||
|
GetSessionMessages(authToken string, sessionId uint, req request.AIChatMessagesQueryRequest) (messages []*response.AIChatMessagesResponse, paging paginator.Pagination, err error)
|
||||||
|
SendMessage(authToken string, sessionId uint, req request.AIChatMessagesCreateRequest) (message *response.AIChatMessagesResponse, err error)
|
||||||
|
UpdateMessage(authToken string, sessionId uint, messageId uint, req request.AIChatMessagesUpdateRequest) (err error)
|
||||||
|
DeleteMessage(authToken string, sessionId uint, 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, sessionId uint) (session *response.AIChatSessionsResponse, err error) {
|
||||||
|
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||||
|
|
||||||
|
result, err := _i.Repo.FindSessionByUserAndId(userInfo.ID, sessionId)
|
||||||
|
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
|
||||||
|
|
||||||
|
// Generate unique AI session ID
|
||||||
|
entity.AISessionID = fmt.Sprintf("ai_session_%d_%d", userInfo.ID, entity.ID)
|
||||||
|
|
||||||
|
result, err := _i.Repo.CreateSession(entity)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapper.AIChatSessionsResponseMapper(result), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *aiChatService) UpdateSession(authToken string, sessionId 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, sessionId)
|
||||||
|
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, sessionId, entity)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *aiChatService) DeleteSession(authToken string, sessionId uint) error {
|
||||||
|
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||||
|
_i.Log.Info().Uint("userId", userInfo.ID).Uint("sessionId", sessionId).Msg("Deleting AI chat session")
|
||||||
|
|
||||||
|
// Check if session exists and belongs to user
|
||||||
|
existing, err := _i.Repo.FindSessionByUserAndId(userInfo.ID, sessionId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if existing == nil {
|
||||||
|
return errors.New("AI chat session not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return _i.Repo.DeleteSession(userInfo.ID, sessionId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *aiChatService) ArchiveSession(authToken string, sessionId uint) error {
|
||||||
|
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||||
|
_i.Log.Info().Uint("userId", userInfo.ID).Uint("sessionId", sessionId).Msg("Archiving AI chat session")
|
||||||
|
|
||||||
|
// Check if session exists and belongs to user
|
||||||
|
existing, err := _i.Repo.FindSessionByUserAndId(userInfo.ID, sessionId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if existing == nil {
|
||||||
|
return errors.New("AI chat session not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update status to archived
|
||||||
|
existing.Status = "archived"
|
||||||
|
return _i.Repo.UpdateSession(userInfo.ID, sessionId, 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
|
||||||
|
_, err = _i.Repo.FindSessionByUserAndId(userInfo.ID, sessionId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, paginator.Pagination{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
results, paging, err := _i.Repo.GetSessionMessages(sessionId, req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, result := range results {
|
||||||
|
messages = append(messages, mapper.AIChatMessagesResponseMapper(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *aiChatService) SendMessage(authToken string, sessionId uint, req request.AIChatMessagesCreateRequest) (message *response.AIChatMessagesResponse, err error) {
|
||||||
|
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||||
|
_i.Log.Info().Interface("data", req).Msg("Sending AI chat message")
|
||||||
|
|
||||||
|
// Verify session belongs to user
|
||||||
|
_, err = _i.Repo.FindSessionByUserAndId(userInfo.ID, 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(req.SessionID)
|
||||||
|
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, sessionId uint, messageId uint, req request.AIChatMessagesUpdateRequest) (err error) {
|
||||||
|
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||||
|
|
||||||
|
// Verify session belongs to user
|
||||||
|
_, err = _i.Repo.FindSessionByUserAndId(userInfo.ID, sessionId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
entity := req.ToEntity()
|
||||||
|
return _i.Repo.UpdateMessage(messageId, entity)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete Message
|
||||||
|
func (_i *aiChatService) DeleteMessage(authToken string, sessionId uint, messageId uint) error {
|
||||||
|
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||||
|
|
||||||
|
// Verify session belongs to user
|
||||||
|
_, err := _i.Repo.FindSessionByUserAndId(userInfo.ID, sessionId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
"narasi-ahli-be/app/module/article_approvals/controller"
|
||||||
|
"narasi-ahli-be/app/module/article_approvals/repository"
|
||||||
|
"narasi-ahli-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,203 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"narasi-ahli-be/app/module/article_approvals/request"
|
||||||
|
"narasi-ahli-be/app/module/article_approvals/service"
|
||||||
|
"narasi-ahli-be/utils/paginator"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
|
||||||
|
utilRes "narasi-ahli-be/utils/response"
|
||||||
|
utilVal "narasi-ahli-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-Client-Key header string false "Insert the X-Client-Key"
|
||||||
|
// @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 (
|
||||||
|
"narasi-ahli-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 (
|
||||||
|
"narasi-ahli-be/app/database/entity"
|
||||||
|
res "narasi-ahli-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 (
|
||||||
|
"fmt"
|
||||||
|
"narasi-ahli-be/app/database"
|
||||||
|
"narasi-ahli-be/app/database/entity"
|
||||||
|
"narasi-ahli-be/app/module/article_approvals/request"
|
||||||
|
"narasi-ahli-be/utils/paginator"
|
||||||
|
utilSvc "narasi-ahli-be/utils/service"
|
||||||
|
"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 (
|
||||||
|
"narasi-ahli-be/app/database/entity"
|
||||||
|
"narasi-ahli-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 (
|
||||||
|
"narasi-ahli-be/app/database/entity"
|
||||||
|
"narasi-ahli-be/app/module/article_approvals/mapper"
|
||||||
|
"narasi-ahli-be/app/module/article_approvals/repository"
|
||||||
|
"narasi-ahli-be/app/module/article_approvals/request"
|
||||||
|
"narasi-ahli-be/app/module/article_approvals/response"
|
||||||
|
articlesService "narasi-ahli-be/app/module/articles/service"
|
||||||
|
usersRepository "narasi-ahli-be/app/module/users/repository"
|
||||||
|
"narasi-ahli-be/utils/paginator"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
|
||||||
|
utilSvc "narasi-ahli-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 (
|
||||||
|
"narasi-ahli-be/app/module/article_categories/controller"
|
||||||
|
"narasi-ahli-be/app/module/article_categories/repository"
|
||||||
|
"narasi-ahli-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,307 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"narasi-ahli-be/app/module/article_categories/request"
|
||||||
|
"narasi-ahli-be/app/module/article_categories/service"
|
||||||
|
"narasi-ahli-be/utils/paginator"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
|
||||||
|
utilRes "narasi-ahli-be/utils/response"
|
||||||
|
utilVal "narasi-ahli-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 X-Client-Key header string false "Insert the X-Client-Key"
|
||||||
|
// @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 X-Client-Key header string false "Insert the X-Client-Key"
|
||||||
|
// @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 X-Client-Key header string false "Insert the X-Client-Key"
|
||||||
|
// @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 X-Client-Key header string false "Insert the X-Client-Key"
|
||||||
|
// @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-Client-Key header string false "Insert the X-Client-Key"
|
||||||
|
// @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-Client-Key header string false "Insert the X-Client-Key"
|
||||||
|
// @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-Client-Key header string false "Insert the X-Client-Key"
|
||||||
|
// @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-Client-Key header string false "Insert the X-Client-Key"
|
||||||
|
// @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 X-Client-Key header string false "Insert the X-Client-Key"
|
||||||
|
// @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 "narasi-ahli-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 (
|
||||||
|
"narasi-ahli-be/app/database/entity"
|
||||||
|
res "narasi-ahli-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 (
|
||||||
|
"fmt"
|
||||||
|
"narasi-ahli-be/app/database"
|
||||||
|
"narasi-ahli-be/app/database/entity"
|
||||||
|
"narasi-ahli-be/app/module/article_categories/request"
|
||||||
|
"narasi-ahli-be/utils/paginator"
|
||||||
|
utilSvc "narasi-ahli-be/utils/service"
|
||||||
|
"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 (
|
||||||
|
"narasi-ahli-be/app/database/entity"
|
||||||
|
"narasi-ahli-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 (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"math/rand"
|
||||||
|
"mime"
|
||||||
|
"narasi-ahli-be/app/database/entity"
|
||||||
|
"narasi-ahli-be/app/module/article_categories/mapper"
|
||||||
|
"narasi-ahli-be/app/module/article_categories/repository"
|
||||||
|
"narasi-ahli-be/app/module/article_categories/request"
|
||||||
|
"narasi-ahli-be/app/module/article_categories/response"
|
||||||
|
usersRepository "narasi-ahli-be/app/module/users/repository"
|
||||||
|
config "narasi-ahli-be/config/config"
|
||||||
|
minioStorage "narasi-ahli-be/config/config"
|
||||||
|
"narasi-ahli-be/utils/paginator"
|
||||||
|
utilSvc "narasi-ahli-be/utils/service"
|
||||||
|
"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 (
|
||||||
|
"narasi-ahli-be/app/module/article_category_details/controller"
|
||||||
|
"narasi-ahli-be/app/module/article_category_details/repository"
|
||||||
|
"narasi-ahli-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 (
|
||||||
|
"narasi-ahli-be/app/module/article_category_details/request"
|
||||||
|
"narasi-ahli-be/app/module/article_category_details/service"
|
||||||
|
"narasi-ahli-be/utils/paginator"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
|
||||||
|
utilRes "narasi-ahli-be/utils/response"
|
||||||
|
utilVal "narasi-ahli-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 "narasi-ahli-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 (
|
||||||
|
"narasi-ahli-be/app/database/entity/article_category_details"
|
||||||
|
res "narasi-ahli-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 (
|
||||||
|
"narasi-ahli-be/app/database"
|
||||||
|
"narasi-ahli-be/app/database/entity/article_category_details"
|
||||||
|
"narasi-ahli-be/app/module/article_category_details/request"
|
||||||
|
"narasi-ahli-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 (
|
||||||
|
"narasi-ahli-be/app/database/entity/article_category_details"
|
||||||
|
"narasi-ahli-be/utils/paginator"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ArticleCategoryDetailsGeneric interface {
|
||||||
|
ToEntity()
|
||||||
|
}
|
||||||
|
|
||||||
|
type ArticleCategoryDetailsQueryRequest struct {
|
||||||
|
ArticleId int `json:"article_id" validate:"required"`
|
||||||
|
CategoryId int `json:"category_id" validate:"required"`
|
||||||
|
IsActive bool `json:"is_active" validate:"required"`
|
||||||
|
Pagination *paginator.Pagination `json:"pagination"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ArticleCategoryDetailsCreateRequest struct {
|
||||||
|
ArticleId uint `json:"article_id" validate:"required"`
|
||||||
|
CategoryId int `json:"category_id" validate:"required"`
|
||||||
|
IsActive bool `json:"is_active" 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:"article_id" validate:"required"`
|
||||||
|
CategoryId int `json:"category_id" validate:"required"`
|
||||||
|
IsActive bool `json:"is_active" validate:"required"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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:"article_id"`
|
||||||
|
CategoryId int `json:"category_id"`
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue