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